76

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)

4
  • 1
    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? Commented Jan 8, 2013 at 22:08
  • It looks like you're looking for PHP equivalent of JS (ES5, to be precise) Array.some method - but this method returns boolean, not an object. Commented Jan 8, 2013 at 22:12
  • 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. Commented Jan 8, 2013 at 22:21
  • Sometimes I temped to used such a function, but a standard foreach in a log of locations yield more concise an more readable code. Commented Sep 6, 2020 at 4:51

7 Answers 7

78

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

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

More info on current() here.

Sign up to request clarification or add additional context in comments.

11 Comments

This has inefficient O(n) time complexity.
@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.
@QuolonelQuestions That's still O(n), linear time complexity
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).
|
69

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) { ...

If your array may contain null elements, array_find cannot return null to signal no element was not found. As @dossy suggests, you could use an array result containing either one or zero elements -

function array_find($xs, $f) {
  foreach ($xs as $x) {
    if (call_user_func($f, $x) === true)
      return [$x]; // result
  }
  return []; // not found
}

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; }); // []

10 Comments

Regarding your last piece of code, array and callable type hinting have been in PHP since 5.1.0 and 5.4.0 respectively.
You don't need PHP 7 to add those type hints.
You can only hint selected types in older PHP. Writing inconsistent code isn't a recommendation I would make.
I know this is a minor nitpick and most people won't care, but I'd probably have called the arguments $array and $comparator so you can actually tell what they are from the signature. Definitely the most straight forward solution, and the one I tend to go with, though.
It would be better for array_find() to return an array, either empty if no element evaluates to true, or an array with only one value, the first value where the callable evaluated to true. Returning null means you cannot distinguish between no matches found vs. a match whose value is null.
|
9

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

1 Comment

I like this. FWIW, I named my version of this first_key, to remind myself that it returns the key not the value.
8

PHP 8.4+ includes array_find() which will do exactly what you want. If you cannot install PHP 8.4+, you can use the Symfony polyfill of 8.4 which includes array_find().

Comments

6

Pulled from Laravel's Illuminate\Collections\Arr::first method:

if (!function_exists('array_first')) {
    /**
     * Return the first element in an array passing a given truth test.
     *
     * @param  iterable  $array
     * @param  callable|null  $callback
     * @param  mixed  $default
     * @return mixed
     */
    function array_first($array, callable $callback = null, $default = null)
    {
        if (is_null($callback)) {
            if (empty($array)) {
                return $default;
            }

            foreach ($array as $item) {
                return $item;
            }
        }

        foreach ($array as $key => $value) {
            if ($callback($value, $key)) {
                return $value;
            }
        }

        return $default;
    }
}

I think it's pretty good. There is also the Illuminate\Collections\Arr::last method, but it's probably not as optimized since it reverses the array and just calls the first method. It does get the job done, though.

if (!function_exists('array_last')) {
    /**
     * Return the last element in an array passing a given truth test.
     *
     * @param  array  $array
     * @param  callable|null  $callback
     * @param  mixed  $default
     * @return mixed
     */
    function array_last($array, callable $callback = null, $default = null)
    {
        if (is_null($callback)) {
            return empty($array) ? $default : end($array);
        }

        return array_first(array_reverse($array, true), $callback, $default);
    }
}

Pro-tip: If you have an array of objects, then you can specify the type of the callback's argument for that sweet IDE auto-completion.

$john = array_first($users, function(User $user) {
    return $user->name === 'John';
});

// Works with pretty much anything.

$activeUsers = array_filter($users, function(User $user) {
    return $user->isActive;
});

// Class example:
class User {
    public string $name;
    public bool $isActive;
    //...
}

If you want to use some variable from the outer scope, you can use the use(&$var) syntax like this

foreach($values as $key => $value) {
  array_find($conditionsRecords, function($row) use(&$key) {
    $keyToFind = $key;
    return $keyToFind;
  })
}

1 Comment

array_find() was created as a native PHP function in PHP8.4 (circa April 2024). array_first() was added to PHP8.5 (circa March 2025). php.watch/versions/8.5/array_first-array_last
1

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 Comment

A good answer that involves an off-site resource references the following: What is this thing you're talking about? Where do I install it? How do I install it? How do I use this thing to solve the exact problem I have in my question? Are you affiliated with this thing in any way, shape, or form? See: How can I link to an external resource in a community-friendly way?
0

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

6 Comments

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.