使用用户定义函数搜索 PHP 数组的优雅方法

问题描述 投票:0回答:7

基本上,我希望能够获得C++的

find_if()
、Smalltalk的
detect:
等的功能:

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

但我不知道有任何 PHP 函数可以做到这一点。我想出的一个“近似值”:

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

这样做的缺点是代码的目的并不能立即明确。此外,即使找到元素,它也不会停止对数组进行迭代,尽管这更像是一个挑剔(如果数据集大到足以引起问题,线性搜索无论如何都不会是答案)

php collections functional-programming
7个回答
80
投票

从数组中取出第一个,或返回

false
:

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

有关 current() 的更多信息请参见此处


64
投票

这是一个基本的解决方案

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

如果

$f($x)
返回
true
,则回路短路并且
$x
立即返回。与
array_filter
相比,这更适合我们的用例,因为
array_find
在找到第一个正匹配后不必继续迭代。

如果回调从未返回 true,则返回值

null


注意,我使用了

call_user_func($f, $x)
而不是仅仅调用
$f($x)
。这在这里是合适的,因为它允许您使用任何兼容的 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'

当然它也适用于更复杂的数据结构

$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
// )

如果您使用 PHP 7,请添加一些类型提示

function array_find(array $xs, callable $f) { ...

如果您的数组可能包含

null
元素,则
array_find
无法返回
null
来表示未找到匹配项。正如 @dossy 所建议的,您可以使用包含 onezero 元素的数组结果 -

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

8
投票

原始

array_search
返回匹配值的键,而不是值本身(如果您稍后要更改原始数组,这可能很有用)。

尝试这个函数(它也适用于关联数组)

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

    return false;
}

5
投票

从 Laravel 的

Illuminate\Collections\Arr::first
方法中提取:

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

我觉得还不错。还有

Illuminate\Collections\Arr::last
方法,但它可能没有那么优化,因为它反转数组并只调用
first
方法。不过,它确实完成了工作。

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

专业提示:如果您有一个对象数组,那么您可以为该可爱的 IDE 自动完成指定回调参数的类型。

$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;
    //...
}

如果你想使用外部作用域中的某个变量,你可以使用像这样的

use(&$var)
语法

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

1
投票

使用 nikic 的原始迭代函数的

iter 库
中的 \iter\search()。它还有一个额外的好处,即它可以在数组
Traversable
集合上运行。

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

if ($foundItem !== null) {
    echo $foundItem; // 11
}

-1
投票

您可以自己编写这样的函数,尽管它只不过是一个循环。

例如,此函数允许您传递回调函数。回调可以返回 0 或一个值。我指定的回调返回大于 10 的整数。当回调返回非空值时,函数停止。

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

-91
投票

您可以编写自己的函数;)

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

这可能看起来没用,但它增加了语义并可以提高可读性

© www.soinside.com 2019 - 2024. All rights reserved.