views:

1081

answers:

7

My try at code golfing.

The problem of finding the minimum value of ∑W_i*|X-X_i| reduces to finding the weighted median of a list of x[i] with weights w[i] (see below for definition). How will you do that with a shortest, simplest and most beautiful program?

Here's how my code looked originally (explanation is in the answer to the question and short version is posted as one of the answers below).

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

(Actually, it's been already heavily optimized as you see variables i and sum reused)

Rules

Floats and integers: different languages have different floating point arithmetic standards, so I reformulate the problem to have x[i] and w[i] to be integers and you can return twice the value of the answer (which is always integer) if you prefer. You can return, print or assign the answer to variable.

Definition of weighted median and clarifications:

  • Median of sorted array x[i] of length n is either x[n/2] or (x[n/2-1/2]+x[n/2+1/2])/2 depending on whether n is odd or even
  • Median of unsorted array is the median of array after sort (true, but our array is sorted)
  • Weighted median of x[i] with integer positive weights w[i] is defined as the median of larger array where each occurrence of x[i] has been changed into w[i] occurrences of x[i].

What I hope to see

One of the reasons for asking is that I assume the most suitable language will have trivial array summation and iteration with lambdas. I thought a functional language could be reasonable, but I'm not sure about that - so it's part of the question. My hope is to see something like

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

Dunno if there's any language where this is possible and is actually shorter.

Test data

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

Answer: 6 or 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;
}

Answer: 6.5 or 13.

A: 

Just a comment about your code : I really hope I will not have to maintain it, unless you also wrote all the unit tests that are required here :-)

It is not related to your question of course, but usually, the "shortest way to code" is also the "hardest way to maintain". For scientific applications, it is probably not a show stopper. But for IT applications, it is.

I think it has to be said. All the best.

Sylvain
Sorry for misunderstanding. I wrote a long answer to somebody else's question (my answer is also linked now) and tried to write a very readable code. But now I'm thinking about code golfing, so I am also trying to produce a minimum version.
ilya n.
@Sylvain: but it's code golf!
Nosredna
@Sylvain - the point of code golf isn't to produce production-quality code. It's more like a brain-teaser - fun and challenging, but not to be used to real life projects.
Erik Forbes
You are right, misplaced answer. Sorry Ilya... :o)
Sylvain
A: 

Something like this? O(n) running time.

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
}

Not sure how you can get two answers for the median, do you mean if sum/2 is exactly equal to a boundary?

EDIT: After looking at your formatted code, my code does essentially the same thing, did you want a MORE efficient method?

EDIT2: The search part can be done using a modified binary search, that would make it slightly faster.

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

Will have to do some checking to make sure it doesn't go on infinitely on edge cases.

CookieOfFortune
In my understanding of the tradition of code golfing, I was thinking about the shortest program. That's why mine reuses variables suma nd i for two cycles :)
ilya n.
Are you looking for shortest or fastest?
CookieOfFortune
Shortest! Anyway, your code and mine appears to be identical as for performance, and in general it's not possible to compare performance in different languages beyond O(...)
ilya n.
yeah, it's going to have to be O(n) because of the sum, let me rethink this about writing it shorter, though... not really a practical reason for doing that.
CookieOfFortune
All of solutions, including yours, will be likely O(n).
ilya n.
Exactly, your algorithmic complexity is not helped by the binary search at all because the function as a whole is already O(n).
ephemient
+3  A: 

So, here's how I could squeeze my own solution:, still leaving some whitespaces:

    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 ];
ilya n.
+2  A: 
ephemient
Looks interesting, I'm parsing it... (I actually wrote I wanted to see some Haskell from the start, but was snubbed by functional languages crowd, so removed the idea...)
ilya n.
+5  A: 

J

Go ahead and type this directly into the interpreter. The prompt is three spaces, so the indented lines are user input.

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

The test data I used in my other answer:

   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

The test data added to the question:

   (>:,:[)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. +/

First index such that total sum is greater than or equal to double the accumulated sum.

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

List and its reverse.

   k =: i"1 @ j @ [

First indices such that -see above- of the left argument and its reverse.

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

Those indices extracted from the right argument and its reverse, respectively.

   m =: -: @ +/ @ l

Half the sum of the resulting list.

ephemient
good one, i've still a lot to learn about J :P
Andrea Ambu
Cool, very much what I wanted. There's something wrong with the Newton's method: the answer should always be half-integer.
ilya n.
Hmm, it appears that comparisons are quite long. If there's a simple way to flop the array, it could be easier to find j in the same way as i but from the other side. Then (x[i]+x[j])/2 is always an answer (actually, x[i]+x[j] is a good answer since I modified the rules to stay within integers).Also, I give up on this one: why is the function with two arguments defined with 4 and 0?
ilya n.
Never mind about m =:4 :0, I read how that's just a syntax for dyads.
ilya n.
Still, could the third line be made shorter, like second?
ilya n.
A change in strategy, so now there's only one line :)
ephemient
Coooooooooooool
ilya n.
+1  A: 

short, and does what you'd expect. Not particularly space-efficient.

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.

here's the constant-space version using itertools. it still has to iterate sum(i)/2 times so it won't beat the index-calculating algorithms.

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.)
Jimmy
Yes, using sum(i) amount of space might be an overkill... maybe in the future python arrays will be smart enough to not allocate the space?
ilya n.
+1  A: 

Python:

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