Код Гольф: самый короткий код для поиска взвешенной медианы? - PullRequest
4 голосов
/ 09 июня 2009

Моя попытка игры в гольф.

Проблема нахождения минимального значения ∑W_i*|X-X_i| сводится к нахождению взвешенной медианы списка x[i] с весами w[i] (определение см. Ниже). Как вы будете делать это с самой короткой, простой и красивой программой?

Вот как первоначально выглядел мой код (объяснение в ответе на вопрос , а короткая версия опубликована как один из ответов ниже).

    #define zero(x) ( abs(x) < 1e-10 )  /* because == doesn't work for floats */

    float sum = 0;
    int i;

    for (i = 0; i < n; i++) 
         sum += w[i];
    while (sum > 0) 
         sum -= 2*w[--i];

    right = x[i]             // the rightmost minimum point
    left  = ( zero(sum) && zero(w[i]-w[i-1]) ) ? x[i-1] : right;
    answer = (left + right) / 2;

(На самом деле, он уже сильно оптимизирован, поскольку вы видите переменные i и sum, повторно использованные)

Правила

Плавающие и целые числа: разные языки имеют разные арифметические стандарты с плавающей точкой, поэтому я переформулирую проблему так, чтобы x[i] и w[i] были целыми числами , и вы можете вернуться дважды значение ответа (которое всегда целое), если вы предпочитаете. Вы можете вернуть, распечатать или присвоить ответ переменной.

Определение взвешенной медианы и уточнения:

  • Медиана отсортированного массива x[i] длины n имеет значение x[n/2] или (x[n/2-1/2]+x[n/2+1/2])/2 в зависимости от того, является ли n нечетным или четным
  • Медиана несортированного массива - это медиана массива после сортировки (true, но наш массив отсортирован)
  • Взвешенная медиана x[i] с целыми положительными весами w[i] определяется как медиана большего массива, где каждое вхождение x[i] заменено на w[i] вхождений x[i].

Что я надеюсь увидеть

Одна из причин, по которой я спрашиваю, состоит в том, что я предполагаю, что наиболее подходящий язык будет иметь тривиальное суммирование массива и итерацию с лямбдами. Я думал, что функциональный язык может быть разумным, но я не уверен в этом - так что это часть вопроса. Я надеюсь увидеть что-то вроде

    // standard function   add  :=  (a,b) :-> a + b 
    myreduce := w.reduce  
        with:  add  
        until: (value) :-> 2*value >= (w.reduce with:add)
    answer = x [myreduce  from:Begin] + x [myreduce  from:End]

Не знаю, есть ли язык, на котором это возможно, и на самом деле он короче.

Данные испытаний

static int n = 10;
for (int j = 0; j < n; j++) {
        w[j] = j + 1;
        x[j] = j;
}

Ответ: 6 или 12.

static int n = 9;
int w[n], x[n] ;
for (int j = 0; j < n; j++) {
    w[j] = j + ((j<6) ? 1 : 0);
    x[j] = j + 1;
}

Ответ: 6,5 или 13.

Ответы [ 7 ]

5 голосов
/ 09 июня 2009

J

Вперед и введите это прямо в переводчик. Подсказка состоит из трех пробелов, поэтому строки с отступом вводятся пользователем.

   m=:-:@+/@(((2*+/\)I.+/)"1@(,:(\:i.@#))@[{"0 1(,:(\:i.@#))@])

Данные теста, которые я использовал в другом ответе:

   1 1 1 1 m 1 2 3 4
2.5
   1 1 2 1 m 1 2 3 4
3
   1 2 2 5 m 1 2 3 4
3.5
   1 2 2 6 m 1 2 3 4
4

Данные теста добавлены к вопросу:

   (>:,:[)i.10
1 2 3 4 5 6 7 8 9 10
0 1 2 3 4 5 6 7 8  9
   (>:m[)i.10
6
   (([+<&6),:>:)i.9
1 2 3 4 5 6 6 7 8
1 2 3 4 5 6 7 8 9
   (([+<&6)m>:)i.9
6.5

   i =: (2 * +/\) I. +/

Первый индекс такой, что общая сумма больше или равна удвоенной сумме.

   j =: ,: (\: i.@#)

Список и его оборот.

   k =: i"1 @ j @ [

Первые индексы такие, что - см. Выше - левого аргумента и его обратного.

   l =: k {"(0 1) j @ ]

Эти индексы извлекаются из правильного аргумента и его обратного, соответственно.

   m =: -: @ +/ @ l

Половина суммы результирующего списка.

3 голосов
/ 09 июня 2009

Итак, вот как я мог бы сжать свое собственное решение: все еще оставляя некоторые пробелы:

    int s = 0, i = 0;
    for (; i < n; s += w[i++]) ;
    while ( (s -= 2*w[--i] ) > 0) ;
    a  =  x[i]  +  x[ !s && (w[i]==w[i-1]) ? i-1 : i ]; 
2 голосов
/ 19 июня 2009

коротко, и делает то, что вы ожидаете. Не особенно компактно.

def f(l,i):
   x,y=[],sum(i)
   map(x.extend,([m]*n for m,n in zip(l,i)))
   return (x[y/2]+x[(y-1)/2])/2.

вот версия с постоянным пробелом, использующая itertools. ему все равно приходится повторять сумму (i) / 2 раза, чтобы не превышать алгоритмы расчета индекса.

from itertools import *
def f(l,i):
   y=sum(i)-1
   return sum(islice(
       chain(*([m]*n for m,n in zip(l,i))),
       y/2,
       (y+1)/2+1
   ))/(y%2+1.)
2 голосов
/ 09 июня 2009

Код на Haskell, не в гольфе: попытка найти разумное функциональное решение.

import Data.List (zip4)
import Data.Maybe (listToMaybe)

mid :: (Num a, Ord a) => [a] -> (Int, Bool)
mid w = (i, total == part && maybe False (l ==) r) where
    (i, l, r, part):_ = dropWhile less . zip4 [0..] w v $ map (2*) sums
    _:sums = scanl (+) 0 w; total = last sums; less (_,_,_,x) = x < total
    v = map Just w ++ repeat Nothing

wmedian :: (Num a, Ord a) => [a] -> [a] -> (a, Maybe a)
wmedian w x = (left, if rem then listToMaybe rest else Nothing) where
    (i, rem) = mid w; left:rest = drop i x
> wmedian [1,1,1,1] [1,2,3,4]
(2,Just 3)
> wmedian [1,1,2,1] [1,2,3,4]
(3,Nothing)
> wmedian [1,2,2,5] [1,2,3,4]
(3,Just 4)
> wmedian [1,2,2,6] [1,2,3,4]
(4,Nothing)
> wmedian [1..10] [0..9]
(6,Nothing)
> wmedian ([1..6]++[6..8]) [1..9]
(6,Just 7)

Моим оригинальным решением J был простой перевод приведенного выше кода на Haskell.

Вот перевод на Haskell текущего кода J:

{-# LANGUAGE ParallelListComp #-}
import Data.List (find); import Data.Maybe (fromJust)
w&x=foldr((+).fst.fromJust.find((>=sum w).snd))0[f.g(+)0$map
    (2*)w|f<-[zip x.tail,reverse.zip x]|g<-[scanl,scanr]]/2

Да & hellip; пожалуйста, не пишите такой код.

> [1,1,1,1]&[1,2,3,4]
2.5
> [1,1,2,1]&[1,2,3,4]
3
> [1,2,2,5]&[1,2,3,4]
3.5
> [1,2,2,6]&[1,2,3,4]
4
> [1..10]&[0..9]
6
> ([1..6]++[6..8])&[1..9]
6.5
1 голос
/ 28 июня 2009

Python:

a=sum([[X]*W for X,W in zip(x,w)],[]);l=len(a);a[l/2]+a[(l-1)/2]
0 голосов
/ 09 июня 2009

Как то так? O (n) время работы.

for(int i = 0; i < x.length; i++)
{
sum += x[i] * w[i];
sums.push(sum);
}

median = sum/2;

for(int i = 0; i < array.length - 1; i++)
{
    if(median > sums[element] and median < sums[element+1]
         return x[i];
    if(median == sums[element])
         return (x[i] + x[i+1])/2
}

Не уверен, как вы можете получить два ответа для медианы, вы имеете в виду, если сумма / 2 точно равна границе?

РЕДАКТИРОВАТЬ: После просмотра вашего отформатированного кода мой код делает по существу то же самое, вы хотели более эффективный метод?

EDIT2: поисковая часть может быть выполнена с использованием модифицированного двоичного поиска, который сделает его немного быстрее.

index = sums.length /2;
finalIndex = binarySearch(index);

int binarySearch(i)
{
    if(median > sums[i+1])
    {
        i += i/2
        return binarySearch(i);
    }
    else if(median < sums[i])
    {
        i -= i/2
        return binarySearch(i);
    }
    return i;
}

Придется провести некоторую проверку, чтобы убедиться, что она не работает бесконечно в крайних случаях.

0 голосов
/ 09 июня 2009

Просто комментарий о вашем коде: я действительно надеюсь, что мне не придется его поддерживать, если вы не написали все модульные тесты, которые требуются здесь:

Конечно, это не связано с вашим вопросом, но обычно «кратчайший путь к кодированию» также является «самым сложным способом поддержки». Для научных приложений это, вероятно, не ограничитель показа. Но для ИТ-приложений это так.

Я думаю, что это должно быть сказано. Всего наилучшего.

...