10

I have arrays:

$arr1 = array(5, 3, 9, 11, 6, 15);
$arr2 = array(11, 20, 1, 3, 8);

Now I need to loop through $arr1 and find the largest number that is less than X:

foreach($arr1 as $x) {
   //need element that is MAX in $arr2 but is less than $x
}

so for example for the first run when $x = 5, maximum in $arr2 is 3 that is less than 5.

Is it possible to do this without having nested loop? I do not want to loop through $arr2. I tried using array_filter but didnt really work. Maybe I used it wrong.

This is what I tried with array_filter:

$results = array();
foreach($arr1 as $x) {
   $max = max(array_filter($arr2, function ($x) { return $x < $y; }));

   $results[$x] = $max;
}

The result should be this:

5  => 3, 
3  => 1, 
9  => 8, 
11 => 8, 
6  => 3, 
15 => 11
GGio
  • 7,563
  • 11
  • 44
  • 81

3 Answers3

16

The problem is with the use of the lambda - PHP does not automatically capture variables in the enclosing scope. (The official Anonymous Functions is a bit sparse on the topic, so see In PHP 5.3.0, what is the function "use" identifier? as well.)

Compare the original, which returns an empty array as it is effectively $x < undefined

$arr2 = array(11, 20, 1, 3, 8);
$y = 5;
var_dump(array_filter($arr2, function ($x) { return $x < $y; }));

with the following that employs the use syntax to bind the variable in the function

$arr2 = array(11, 20, 1, 3, 8);
$y = 5;
var_dump(array_filter($arr2, function ($x) use ($y) { return $x < $y; }));

(Also, in the original code there was no $y variable at all, whoops!)

Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
  • 3
    +1 For not writing out the exact code needed but for providing the working logic that will solve the problem – Mark Miller Jun 12 '14 at 21:24
  • Thank you. this seems to be working. One question I am not sure what lambda means for PHP can you please explain how the `use` works and in which case this would not work? – GGio Jun 12 '14 at 21:24
  • @GGio [Lambda](http://en.wikipedia.org/wiki/Lambda_(programming)) is another name for "anonymous function", roughly speaking. – user2864740 Jun 12 '14 at 21:25
  • @user2864740 yea I got that part and read docs for it but still doesnt seem to get the `use` part. So `use($y)` the `$y` part if reference to the `$y = 5`? – GGio Jun 12 '14 at 21:26
  • @GGio If you want to access (the non-global) $y inside the function, yes. The documentation is a bit .. uhh, shallow there. [On PHP 5.3, Lambda Functions, and Closures](http://fabien.potencier.org/article/17/on-php-5-3-lambda-functions-and-closures) goes into it in a bit more depth. – user2864740 Jun 12 '14 at 21:27
4

Here's a method that uses array_map() to return the values that are lower than your maximum variable, then uses max() to return the highest (as in your example).

In this example I've used $var as a variable by reference (the &), so that it can be used by the get_highest() callback function which is accessing it as a global variable.

function get_highest($x) {
    global $var;
    return $x < $var ? $x : 0;
}

$results = array();    
foreach($arr1 as &$var) {
    $results[$var] = max(array_map('get_highest', $arr2));
}

Without using global variables

You can modify this if you don't want to use global variables by passing in an array of parameters to array_map(). It's a little strange the way it works, because the parameter array needs to be the same length as the original array, so I've used array_fill() to fill it up to the required length with the current value, so the callback function can use it to compare:

function get_highest($x, $y) {
    return $x < $y ? $x : 0;
}

$results = array();    
foreach($arr1 as $var) {
    $max = array_fill(0, count($arr2), $var);
    $results[$var] = max(array_map('get_highest', $arr2, $max));
}
scrowler
  • 24,273
  • 9
  • 60
  • 92
2

Any solution that reuses/resets arr2 will be of quadratic complexity, meaning that if you have an arr1 of size N and an arr2 of size M, you'll be doing around N*M operations.

Plus function calls and parameter passing in case of lambda functions.

A better strategy if N and M are very large is to sort both arrays (complexity is N log N + M log M), then use the same strategy as merge sort to loop through the arrays, saving their state. This will execute in at most N+M operations instead of N*M, making the overall complexity linearithmic.

The lambda solution is easier to understand, more robust and simpler to maintain, so unless there's a pressing reason (N and M in the tens of thousands), it is to be preferred. You are trading speed for ease of maintenance; but it is normally a sweet deal.

First you sort both arrays with PHP's array sort functions, and get

$arr1 = array(3, 5, 6, 9, 11, 15);
$arr2 = array(1, 3, 8, 11, 20);

$map = array();

$n = count($arr1);
$m = count($arr2);

for ($i = 0, $j = 0, $y = false; $i < $n;) {
    if ($j == $m) {
        $map[$arr1[$i++]] = $y;
    } else {
        if ($arr1[$i] <= $arr2[$j]) {
            $map[$arr1[$i++]] = $y;
        } else {
            $y = $arr2[$j++];
        }
    }
}
LSerni
  • 55,617
  • 10
  • 65
  • 107
  • this is using nested loop, is there a way without using inner loop? – GGio Jun 12 '14 at 21:12
  • 1
    this version only uses a single loop, still running in N+M (plus the linearithmic cost of sorting, not included in the code). – LSerni Jun 12 '14 at 22:01