Basically, I want to be able to get the functionality of C++'s find_if(), Smalltalk's detect: etc.:

// would return the element or null
check_in_array($myArray, function($element) { return $elemnt->foo() > 10; });

But I don't know of any PHP function which does this. One "approximation" I came up with:

$check = array_filter($myArray, function($element) { ... });
if ($check) 
    //...

The downside of this is that the code's purpose is not immediately clear. Also, it won't stop iterating over the array even if the element was found, although this is more of a nitpick (if the data set is large enough to cause problems, linear search won't be an answer anyway)

Answer
    • Don't you have to do the exact same thing in C++/Smalltalk/etc anyway, on the condition it wasn't found? Or are you assuming there'll always be at least one result?
      • 1
    • Boolean return would be ok as well. @Izkata: Yes, you're right of course, you can only stop iterating if there is a result - so it's kind of a special case anyway. My bad.

To pull the first one from the array, or return false:

current(array_filter($myArray, function($element) { ... }))

More info on current() here.

  • 52
Reply Report
      • 1
    • @QuolonelQuestions All answers will be O(n) unless there's been some preprocessing (sorting, hashing, etc)
    • @Izkata That's patently false. The search should stop once the element has been found. Your crude code continues to process every element regardless of when a match is found.
      • 1
    • A straightforward implementation with foreach would have O(1) best case, O(n) worst case. This is O(n) in both cases. Also, O(n) space complexity instead of O(1) (ie. the whole array might get duplicated in memory).

Here's a basic solution

function array_find($xs, $f) {
  foreach ($xs as $x) {
    if (call_user_func($f, $x) === true)
      return $x;
  }
  return null;
}

array_find([1,2,3,4,5,6], function($x) { return $x > 4; });  // 5
array_find([1,2,3,4,5,6], function($x) { return $x > 10; }); // null

In the event $f($x) returns true, the loop short circuits and $x is immediately returned. Compared to array_filter, this is better for our use case because array_find does not have to continue iterating after the first positive match has been found.

In the event the callback never returns true, a value of null is returned.


Note, I used call_user_func($f, $x) instead of just calling $f($x). This is appropriate here because it allows you to use any compatible callable

Class Foo {
  static private $data = 'z';
  static public function match($x) {
    return $x === self::$data;
  }
}

array_find(['x', 'y', 'z', 1, 2, 3], ['Foo', 'match']); // 'z'

Of course it works for more complex data structures too

$data = [
  (object) ['id' => 1, 'value' => 'x'],
  (object) ['id' => 2, 'value' => 'y'],
  (object) ['id' => 3, 'value' => 'z']
];

array_find($data, function($x) { return $x->id === 3; });
// stdClass Object (
//     [id] => 3
//     [value] => z
// )

If you're using PHP 7, add some type hints

function array_find(array $xs, callable $f) { ...
  • 38
Reply Report

The original array_search returns the key of the matched value, and not the value itself (this might be useful if you're will to change the original array later).

try this function (it also works will associatives arrays)

function array_search_func(array $arr, $func)
{
    foreach ($arr as $key => $v)
        if ($func($v))
            return $key;

    return false;
}
  • 4
Reply Report

Use \iter\search() from nikic's iter library of primitive iteration functions. It has the added benefit that it operates on both arrays and Traversable collections.

$foundItem = \iter\search(function ($item) {
    return $item > 10;
}, range(1, 20));

if ($foundItem !== null) {
    echo $foundItem; // 11
}
  • 1
Reply Report

You can write such a function yourself, although it is little more than a loop.

For instance, this function allows you to pass a callback function. The callback can either return 0 or a value. The callback I specify returns the integer if it is > 10. The function stops when the callback returns a non null value.

function check_in_array(array $array, $callback)
{
  foreach($array as $item)
  {
    $value = call_user_func($callback, $item);
    if ($value !== null)
      return $value;
  }
}

$a = array(1, 2, 3, 6, 9, 11, 15);
echo check_in_array($a, function($i){ return ($i > 10?$i:null); });
  • 0
Reply Report
      • 1
    • I would prefer to do it in a way where I can simply return a Boolean from my $callback, since the callback becomes unnecessarily complex otherwise (imo). Another thing: You are relying on the callback to return the correct item here - is that intended? What if the callback returns something which is not even in the array - it would break the function's semantics.
      • 1
    • No matter what it returns, as long as it is not null it breaks the loop. You could use false as well if you need to, but I think of false as an actual value. An example use would be to return the name of the first employee with a salary over 50000. You fill the array with employee objects, check $i->salary in the callback and return $i->name. Of course, if you don't want this, you can return true just as well. It all depends on you exact needs.
      • 2
    • And an advantage of this one over wrapping array_filter, is that this loop is actually terminated as soon as an item is found, which can speed things up for larger arrays.
      • 1
    • I actually think that wrapping array_filter will be faster. Why? array_filter is a built-in function, probably implemented directly in the interpreter and highly optimized. Writing your own loop will most certainly be slower (especially since it's interpreted). And the point about terminating early is only applicable if all your arrays actually have a match which is located somewhere close to the beginning.
      • 1
    • It's not interpreted on each iteration. The PHP file is interpreted once and compiled into memory. The compiled version can even be cached if you have APC (which you probably have in a production environment). That aside, my function only loops through the array, while array_filter is actually generating a new array and returns that array. So even though it is internal, it does have to do extra work, (creating object, allocating memory, copying items) and it also does that work using your (just as interpreted) callback function for each item.

You can write your own function ;)

function callback_search ($array, $callback) { // name may vary
    return array_filter($array, $callback);
}

This maybe seems useless, but it increases semantics and can increase readability

  • -58
Reply Report
      • 2
    • Good point about readability/semantics, and I like the simplicity of this. I'm usually not a fan of keeping my own "standard library enhancements" around, but if I keep stumbling upon this, I might give it a go.
    • callback_search is exactly the same as array_filter in this case. Any call to callback_search can be safely replaced with array_filter; they are equal.
    • array_filter is formally documented, though! Wrapping existing functions in new names for the sake of readability is just asking for unreadable, unmaintainable code, which comes first.

Warm tip !!!

This article is reproduced from Stack Exchange / Stack Overflow, please click

Trending Tags

Related Questions