【问题标题】:Code Golf: Shortest code to find a weighted median?Code Golf:找到加权中位数的最短代码?
【发布时间】:2009-06-08 20:39:42
【问题描述】:

我尝试打代码。

finding the minimum value of ∑W_i*|X-X_i| 的问题简化为找到权重为w[i]x[i] 列表的加权中位数(定义见下文)。你将如何用一个最短、最简单、最漂亮的程序来做到这一点?

这是我的代码最初的样子(解释在the answer to the question 中,简短版本作为以下答案之一发布)。

    #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;

(实际上,它已经被大量优化,因为您看到变量 isum 被重用)

规则

浮点数和整数:不同的语言有不同的浮点算术标准,所以我将问题重新表述为将x[i]w[i] 设为整数,你可以如果您愿意,返回两倍的答案值(始终为整数)。您可以返回、打印或将答案分配给变量。

加权中位数的定义和说明:

  • 长度为n 的排序数组x[i] 的中位数是x[n/2](x[n/2-1/2]+x[n/2+1/2])/2,具体取决于n 是奇数还是偶数
  • 未排序数组的中位数是排序后数组的中位数(正确,但我们的数组已排序)
  • x[i] 和整数正权重 w[i] 的加权中值被定义为较大数组的中值,其中 x[i] 的每次出现都已更改为 w[i] x[i] 的出现。

我希望看到什么

询问的原因之一是我假设最合适的语言将使用 lambda 进行简单的数组求和和迭代。我认为函数式语言可能是合理的,但我不确定——所以这是问题的一部分。我希望看到类似的东西

    // 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。

【问题讨论】:

  • [function-programming] Inigo Montoya 说“你一直在使用这个词。我不认为它意味着你认为它的意思。”
  • ...你能让你的代码更具可读性吗?
  • 代码高尔夫问题应该是CW。
  • 澄清 x[i] 已排序。顺便说一句,CW 是什么?
  • 您仍然可以将其更改为社区 wiki(我认为)。

标签: language-agnostic code-golf


【解决方案1】:

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

结果列表总和的一半。

【讨论】:

  • 酷,这正是我想要的。牛顿法有问题:答案应该总是半整数。
  • 嗯,比较的时间似乎很长。如果有一种简单的方法来翻转数组,那么以与 i 相同的方式但从另一边找到 j 可能会更容易。那么 (x[i]+x[j])/2 总是一个答案(实际上,x[i]+x[j] 是一个很好的答案,因为我修改了规则以保持在整数范围内)。另外,我放弃了这个:为什么有两个参数的函数定义为 4 和 0?
  • 别管 m =:4 :0,我读到这只是对偶语法。
  • 不过,第三行能否像第二行一样缩短?
  • 策略的改变,所以现在只有一行:)
【解决方案2】:

所以,这就是我如何压缩自己的解决方案:,仍然留下一些空格:

    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 ]; 

【讨论】:

    【解决方案3】:

    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
    
    > w 中位数 [1,1,1,1] [1,2,3,4] (2,就3) > w 中位数 [1,1,2,1] [1,2,3,4] (3,无) > w 中位数 [1,2,2,5] [1,2,3,4] (3,只有 4 个) > w 中位数 [1,2,2,6] [1,2,3,4] (4,无) > w 中位数 [1..10] [0..9] (6,无) > wmedian ([1..6]++[6..8]) [1..9] (6个,就7个)

    我最初的 J 解决方案是对上述 Haskell 代码的直接翻译。

    这是当前 J 代码的 Haskell 翻译:

    {-# 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
    

    是的……请不要写这样的代码。

    > [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

    【讨论】:

    • 看起来很有趣,我正在解析它......(实际上我写了我想从一开始就看到一些 Haskell,但被函数式语言人群冷落,所以删除了这个想法......)
    【解决方案4】:

    简短,并且可以满足您的期望。不是特别节省空间。

    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 的常量空间版本。它仍然必须迭代 sum(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.)
    

    【讨论】:

    • 是的,使用 sum(i) 的空间量可能有点矫枉过正......也许将来 python 数组会足够聪明,不会分配空间?
    【解决方案5】:

    Python:

    a=sum([[X]*W for X,W in zip(x,w)],[]);l=len(a);a[l/2]+a[(l-1)/2]
    

    【讨论】:

      【解决方案6】:

      这样的? 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
      }
      

      不确定如何获得中位数的两个答案,您的意思是 sum/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;
      }
      

      必须做一些检查以确保它不会在边缘情况下无限地继续下去。

      【讨论】:

      • 在我对代码高尔夫传统的理解中,我在考虑最短的程序。这就是为什么我在两个周期内重复使用变量 suma 和 i :)
      • 您在寻找最短还是最快?
      • 最短!无论如何,您的代码和我的代码似乎与性能相同,并且通常无法比较 O(...) 以外的不同语言的性能
      • 是的,由于总和,它必须是 O(n),让我重新考虑一下,把它写得更短,虽然……这并不是这样做的实际理由。
      • 所有解决方案,包括您的解决方案,都可能是 O(n)。
      【解决方案7】:

      只是对您的代码的评论:我真的希望我不必维护它,除非您还编写了此处所需的所有单元测试:-)

      这当然与您的问题无关,但通常,“最短的编码方式”也是“最难维护的方式”。对于科学应用来说,它可能不是一个炫耀的东西。但对于 IT 应用程序来说,确实如此。

      我觉得不得不说。一切顺利。

      【讨论】:

      • 抱歉造成误会。我为别人的问题写了一个很长的答案(我的答案现在也链接了)并尝试编写一个非常易读的代码。但现在我在考虑打代码打高尔夫球,所以我也在尝试制作一个最低版本。
      • @Sylvain - 代码高尔夫的重点不是产生生产质量的代码。它更像是一个脑筋急转弯 - 有趣且具有挑战性,但不适用于现实生活中的项目。
      猜你喜欢
      • 2010-12-18
      • 2016-06-25
      • 2011-02-11
      • 2014-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多