To pull the first one from the array, or return false:
current(array_filter($myArray, function($element) { ... }))
More info on current() here.
Answer from Izkata on Stack Overflowarray_filter gets you all items that match a given condition, but what I want is to get the first such item
Let's say I want to display a link to a country's capital, given an ID number and a query of the country's cities.
I could do something like
echo $country["Capital"] ?
cityToLink(
array_filter($cities,
function($city){
global $country;
return $city["ID"] == $country["Capital"];
}
)[0]):
"N/A"but that falls apart if the index of the needed city isn't 0, since it seems that array_filter keeps the original indexes intact
Why don't you make your cities array indexed by id?
Looks like you got a solution, but for the record, there is no equivalent function, your solution using array filter is the closest one possible but it's also kind of "clever" and that's considered bad practice because it makes it harder for others to read.
Luckily it's very easy to implement...
function array_find(callable $callback, array $array) {
foreach ($array as $key => $value) {
if ($callback($value, $key, $array)) {
return $value;
}
}
}
Writing that once is cleaner looking and takes about the same amount of lines as doing it in-place like so:
$found = null;
foreach ($array as $key => $value) {
if (/* condition is met */) {
$found = $value;
break;
}
}
I work on many codebases where I see that form of the same concept repeated hundreds of times because nobody wanted or thought to write a helper function or use a library that provided one.
Speaking of which, there are many collection classes that provide wrappers around arrays and make them easier to deal with, I've used and like Knapsack.
To pull the first one from the array, or return false:
current(array_filter($myArray, function($element) { ... }))
More info on current() here.
Here's a basic solution
function array_find(
f) {
foreach (
x) {
if (call_user_func(
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 returns x)
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( instead of just calling x)
. This is appropriate here because it allows you to use any compatible callablex)
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(
f) {
foreach (
x) {
if (call_user_func(
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; }); // []
Videos
If it's a large array and in a loop, neither is "best". Instead use array_flip() on your array, so urls become keys. And use isset() to check for the presence.
There's no real answer here. So I tried it, myself.
$haystack = array
(
'apple',
'banana',
'cherry',
'lemon',
'lime',
'orange',
'potato',
'rutabaga'
);
$haySize = count($haystack);
$loops = isset( $_SERVER['argv'][1] ) ? $_SERVER['argv'][1] : 10000;
// echo 'Loops: ' . $loops . "\n";
$start = microtime(true);
for (
i < $loops; $i++)
{
$needle = $haystack[
haySize ];
}
$zeroTime = microtime(true) - $start;
// echo sprintf('%0.3f', $zeroTime * 1000) . ' ms : zero time' . "\n";
$start = microtime(true);
for (
i < $loops; $i++)
{
$needle = $haystack[
haySize ];
$dummy = array_search($needle, $haystack);
}
echo sprintf('%0.3f', (microtime(true) - $start - $zeroTime) * 1000) . ' ms : array_search' . "\n";
$start = microtime(true);
for (
i < $loops; $i++)
{
$needle = $haystack[
haySize ];
$dummy = in_array($needle, $haystack);
}
echo sprintf('%0.3f', (microtime(true) - $start - $zeroTime) * 1000) . ' ms : in_array' . "\n";
echo sprintf('%0.3f', (microtime(true) - $start) * 1000).' ms : in_array'."\n";
For a typical use case, in_array wins, but the difference is negligible:
22.662 ms : array_search
22.104 ms : in_array
Updated 2014-01-02: added noop loop to "zero the scale". Running PHP 5.4.17 on a new MacBook pro, this is a typical result:
24.462 ms : array_search
24.984 ms : in_array
For the canonical reference:
$obj = array_column($array, null, 'id')['one'] ?? false;
The false is per the question's requirement to return false. It represents the nonmatching value, e.g., you can make it null for example as an alternative suggestion.
This works transparently since PHP 7.0. In case you (still) have an older version, there are user-space implementations of it that can be used as a drop-in replacement.
However array_column also means to copy a whole array. This might not be wanted.
Instead it could be used to index the array and then map over with array_flip:
$index = array_column($array, 'id');
$map = array_flip($index);
$obj = $array[$map['one'] ?? null] ?? false;
On the index, the search problem might still be the same. The map just offers the index in the original array, so there is a reference system.
Keep in mind though that this might not be necessary as PHP has copy-on-write. So there might be less duplication as intentionally thought. So this is to show some options.
Another option is to go through the whole array and unless the object is already found, check for a match. One way to do this is with array_reduce:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry === false && $item->id === 'one' ? $item : $carry;
}, false);
This variant again is with the returning false requirement for no-match.
It is a bit more straight forward with null:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry ?? ($item->id === 'one' ? $item : $carry);
}, null);
And a different no-match requirement can then be added with $obj = ...) ?? false; for example.
Fully exposing to foreach within a function of its own even has the benefit to directly exit on match:
$result = null;
foreach ($array as $object) {
if ($object->id === 'one') {
$result = $object;
break;
}
}
unset($object);
$obj = $result ?? false;
This is effectively the original answer by hsz, which shows how universally it can be applied.
You can iterate that objects:
function findObjectById($id){
$array = array( /* your array of objects */ );
foreach ( $array as $element ) {
if ( $id == $element->id ) {
return $element;
}
}
return false;
}
Faster way is to have an array with keys equals to objects' ids (if unique);
Then you can build your function as follow:
function findObjectById($id){
$array = array( /* your array of objects with ids as keys */ );
if ( isset( $array[$id] ) ) {
return $array[$id];
}
return false;
}