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

let*_*tar 28 php collections functional-programming

基本上,我希望能够获得C++ find_if(),Smalltalk detect:等的功能:

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

但我不知道有任何PHP函数可以做到这一点.我提出了一个"近似":

$check = array_filter($myArray, function($element) { ... });
if ($check) 
    //...
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是代码的目的不是立即清楚的.此外,即使找到该元素,它也不会停止迭代数组,尽管这更像是一个挑剔(如果数据集足够大而导致问题,线性搜索无论如何都不会是答案)

Izk*_*ata 45

从数组中拉出第一个,或返回false:

current(array_filter($myArray, function($element) { ... }))
Run Code Online (Sandbox Code Playgroud)

关于current()的更多信息在这里.

  • @QuononelQuestions这仍然是"O(n)",线性时间复杂度 (37认同)
  • @QuononelQuestions除非进行了一些预处理(排序,散列等),否则所有答案都是`O(n)` (14认同)
  • 使用foreach的直接实现将具有"O(1)"最佳情况,"O(n)"最坏情况.在这两种情况下都是"O(n)".另外,`O(n)`空间复杂度而不是'O(1)`(即整个数组可能在内存中重复). (11认同)
  • 这具有低效的O(n)时间复杂度. (10认同)
  • @Izkata这显然是假的.一旦找到元素,搜索应该停止.无论何时找到匹配项,您的原始代码都会继续处理每个元素. (9认同)
  • `current()` 是来自 PHP/FI 时代的不可理解、不可预测、有状态的黑暗自动魔法。它应该在一代人之前就从语言中删除,我指的是人类一代。 (4认同)
  • 您可以使用 SPL 迭代器(PHP 5.4+): `(new CallbackFilterIterator(new ArrayIterator($myArray), function($element) { return $element->foo() > 10; }))->current ()`虽然我认为这不符合“优雅”的合理定义。 (3认同)
  • 如果你想将你自己的参数传递给回调函数,你可以添加 "use" current(array_filter($myArray, function($element) use($my_param) { ... })) (2认同)
  • @Tgr 最好的情况将被描述为 Ω(1) (2认同)

Tha*_*you 29

这是一个基本的解决方案

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
Run Code Online (Sandbox Code Playgroud)

在事件$f($x)返回时true,循环短路并$x立即返回.相比之下array_filter,这对我们的用例更好,因为array_find在找到第一个正匹配后不必继续迭代.

如果回调永远不会返回true,null则返回值.


注意,我使用call_user_func($f, $x)而不是只是打电话$f($x).这在这里是合适的,因为它允许您使用任何兼容的可调用对象

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'
Run Code Online (Sandbox Code Playgroud)

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

$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
// )
Run Code Online (Sandbox Code Playgroud)

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

function array_find(array $xs, callable $f) { ...
Run Code Online (Sandbox Code Playgroud)

  • 关于你的最后一段代码,`array`和`因为〔5.1.0和5.4.0(http://php.net/manual/en/functions.arguments.php#functions callable`类型提示已经在PHP .arguments.type-declaration)分别. (6认同)
  • 我知道这是一个小问题,大多数人不会关心,但我可能会将参数称为“$array”和“$comparator”,这样您实际上可以从签名中看出它们是什么。绝对是最直接的解决方案,也是我倾向于采用的解决方案。 (2认同)

Roe*_*oey 8

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

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

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

    return false;
}
Run Code Online (Sandbox Code Playgroud)


mik*_*oid 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我觉得还不错。还有该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);
    }
}
Run Code Online (Sandbox Code Playgroud)

专业提示:如果您有一个对象数组,那么您可以为该可爱的 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;
    //...
}
Run Code Online (Sandbox Code Playgroud)

如果你想使用外部作用域中的某个变量,你可以使用use(&$var)这样的语法

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


Kin*_*nch -91

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

function callback_search ($array, $callback) { // name may vary
    return array_filter($array, $callback);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 在这种情况下,“callback_search”与“array_filter”*完全相同*。任何对“callback_search”的调用都可以安全地用“array_filter”替换;他们是平等的。 (47认同)
  • 不过,“array_filter”已被正式记录!为了可读性而用新名称包装现有函数只会带来不可读、不可维护的代码,而这是第一位的。 (33认同)
  • 这仍然不是一个好的解决方案,因为一旦找到匹配项它就不会停止搜索。 (19认同)
  • @KingCrunch `startsWith` 是一个不同的函数。 (3认同)
  • 没有人提到这将如何破坏某些文本编辑器和 IDE 中的自动完成/建议/打字提示功能 (2认同)