tags:

views:

158

answers:

5

I have an array that reflects rebate percentages depending on the number of items ordered:

$rebates = array(
   1 => 0,
   3 => 10,
   5 => 25,
  10 => 35)

meaning that for one or two items, you get no rebate; for 3+ items you get 10%, for 5+ items 20%, for 10+ 35% and so on.

Is there an elegant, one-line way to get the correct rebate percentage for an arbitrary number of items, say 7?

Obviously, this can be solved using a simple loop: That's not what I'm looking for. I'm interested whether there is a core array or other function that I don't know of that can do this more elegantly.

I'm going to award the accepted answer a bounty of 200, but apparently, I have to wait 24 hours until I can do that. The question is solved.

+3  A: 

There is no such core function!

Thariama
+1  A: 

Best I can manage so far:

$testValue = 7;
array_walk( $rebates, function($value, $key, &$test) { if ($key > $test[0]) unset($test[1][$key]); } array($testValue,&$rebates) );

Uses a nasty little quirk of passing by reference, and strips off any entry in the $rebates array where the key is numerically greater than $testValue... unfortunately, it still leaves lower-keyed entries, so an array_pop() would be needed to get the correct value. Note that it actively reduces the entries in the original $rebates array.

Perhaps somebody can build on this to discard the lower entries in the array.

Don't have 5.3.3 available to hand at the moment, so not tested using an anonymous function, but works (as much as it works) when using a standard callback function.

EDIT

Building on my previous one-liner, adding a second line (so probably shouldn't count):

$testValue = 7;
array_walk( $rebates, function($value, $key, &$test) { if ($key > $test[0]) unset($test[1][$key]); } array($testValue,&$rebates) );
array_walk( array_reverse($rebates,true), function($value, $key, &$test) { if ($key < $test[0]) unset($test[1][$key]); } array(array_pop(array_keys($rebates)),&$rebates) );

Now results in the $rebates array containing only a single element, being the highest break point key from the original $rebates array that is a lower key than $testValue.

Mark Baker
+1 Interesting approach!
Pekka
+2  A: 
David Mårtensson
Very nice, thank you. I'm accepting @salathe's approach because it's a tiny bit shorter and works without a global / closure, but this will work fine as well.
Pekka
Of cause, his solution is more elegant :-)
David Mårtensson
+12  A: 

Here's another one, again not short at all.

$percent = $rebates[max(array_intersect(array_keys($rebates),range(0,$items)))];

The idea is basically to get the highest key (max) which is somewhere between 0 and $items.

salathe
+1 neat and practical
Mark Baker
I'm accepting this one because it works non-destructively, is the shortest approach, and doesn't need globals / closures. Thanks!
Pekka
+1 relatively short and functional
Thariama
+1 I like the idea, though I would never use that in production code.
nikic
+4  A: 

I think that the above one-line solutions aren't really elegant or readable. So why not use something that can really be understood by someone on first glance?

$items = NUM_OF_ITEMS;
$rabate = 0;
foreach ($rabates as $rItems => $rRabate) {
    if ($rItems > $items) break;
    $rabate = $rRabate;
}

This obviously needs a sorted array, but at least in your example this is given ;)

Okay, I know, you don't want the solution with the simple loop. But what about this:

while (!isset($rabates[$items])) {
    --$items;
}
$rabate = $rabates[$items];

Still quite straightforward, but a little shorter. Can we do even shorter?

for (; !isset($rabates[$items]); --$items);
$rabate = $rabates[$items];

We are already getting near one line. So let's do a little bit of cheating:

for (; !isset($rabates[$items]) || 0 > $rabate = $rabates[$items]; --$items);

This is shorter then all of the approaches in other answers. It has only one disadvantage: It changes the value of $items which you may still need later on. So we could do:

for ($i = $items; !isset($rabates[$i]) || 0 > $rabate = $rabates[$i]; --$i);

That's again one character less and we keep $items.

Though I think that the last two versions are already two hacky. Better stick with this one, as it is both short and understandable:

for ($i = $items; !isset($rabates[$i]); --$i);
$rabate = $rabates[$i];
nikic