【问题标题】:Elegant way to search an PHP array using a user-defined function使用用户定义函数搜索 PHP 数组的优雅方式
【发布时间】:2012-12-22 21:15:49
【问题描述】:

基本上,我希望能够获得 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) 
    //...

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

【问题讨论】:

  • 在没有找到的情况下,您是否不必在 C++/Smalltalk/etc 中做同样的事情?还是您假设总会有至少一个结果?
  • 看起来您正在寻找 JS(准确地说是 ES5)的 PHP 等效项 Array.some 方法 - 但此方法返回布尔值,而不是对象。
  • 布尔返回也可以。 @Izkata:是的,你当然是对的,只有在有结果的情况下才能停止迭代——所以无论如何这都是一种特殊情况。我的错。
  • 有时我会尝试使用这样的函数,但位置日志中的标准 foreach 会产生更简洁、更易读的代码。

标签: php collections functional-programming


【解决方案1】:

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

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

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

    return false;
}

【讨论】:

  • 我喜欢这个。 FWIW,我将我的版本命名为 first_key,以提醒自己它返回的是 key 而不是 value
【解决方案2】:

这是一个基本的解决方案

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

【讨论】:

  • 关于你的最后一段代码,arraycallable 类型提示分别从5.1.0 and 5.4.0 开始出现在 PHP 中。
  • 你不需要 PHP 7 来添加这些类型提示。
  • 您只能在较旧的 PHP 中提示选定类型。我不建议编写不一致的代码。
  • 我知道这是一个小问题,大多数人不会在意,但我可能会称这些参数为 $array$comparator,这样你就可以从签名中知道它们是什么.不过,绝对是最直接的解决方案,也是我倾向于使用的解决方案。
【解决方案3】:

使用来自nikic 的iter library 的原始迭代函数中的\iter\search()。它还有一个额外的好处,它可以同时在数组 Traversable 集合上运行。

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

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

【讨论】:

  • 一个涉及到场外资源的好答案参考了以下内容:你在说什么?我在哪里安装它?我该如何安装它?我如何使用这个东西来解决我的问题中的确切问题?你是否以任何方式、形状或形式与这件事有关联?见:How can I link to an external resource in a community-friendly way?
【解决方案4】:

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

例如,这个函数允许你传递一个回调函数。回调可以返回 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); });

【讨论】:

  • 我更愿意以一种我可以简单地从我的 $callback 返回布尔值的方式来执行此操作,因为否则回调会变得不必要地复杂 (imo)。另一件事:您依靠回调在此处返回正确的项目-这是有意的吗?如果回调返回的东西甚至不在数组中 - 它会破坏函数的语义。
  • 不管它返回什么,只要它不为空,它就会中断循环。如果需要,您也可以使用 false,但我认为 false 是一个实际值。一个示例用法是返回第一个工资超过 50000 的员工的姓名。您用员工对象填充数组,在回调中检查 $i->salary 并返回 $i->name。当然,如果你不想要这个,你也可以返回true。这完全取决于您的确切需求。
  • 与包装 array_filter 相比,这个循环的一个优点是,一旦找到一个项目,这个循环实际上就会终止,这可以加快更大数组的速度。
  • 我实际上认为包装 array_filter 会更快。为什么? array_filter 是一个内置函数,可能直接在解释器中实现并高度优化。编写自己的循环肯定会更慢(特别是因为它被解释了)。而关于提前终止的观点仅适用于您的所有数组实际上都有一个匹配且位于靠近开头的地方。
  • 每次迭代都不会解释它。 PHP 文件被解释一次并编译到内存中。如果您有 APC(您可能在生产环境中拥有),甚至可以缓存已编译的版本。除此之外,我的函数只循环遍历数组,而 array_filter 实际上正在生成一个新数组并返回该数组。因此,即使它是内部的,它也必须做额外的工作,(创建对象、分配内存、复制项目)并且它还使用你的(就像解释的)每个项目的回调函数来完成这些工作。
【解决方案5】:

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

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

More info on current() here.

【讨论】:

  • 这具有低效的 O(n) 时间复杂度。
  • @QuolonelQuestions 所有答案都是O(n),除非有一些预处理(排序、散列等)
  • @Izkata 这显然是错误的。一旦找到元素,搜索应该停止。无论何时找到匹配项,您的粗略代码都会继续处理每个元素。
  • @QuolonelQuestions 还是O(n),线性时间复杂度
  • 使用 foreach 的简单实现将有 O(1) 最好的情况,O(n) 最坏的情况。在这两种情况下都是O(n)。此外,O(n) 空间复杂度而不是 O(1)(即整个数组可能会在内存中重复)。
【解决方案6】:

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

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

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

【讨论】:

  • callback_search 在这种情况下与array_filter完全相同。任何对callback_search 的调用都可以安全地替换为array_filter;他们是平等的。
  • array_filter 已正式记录在案!为了可读性而用新名称包装现有函数只是要求不可读、不可维护的代码,这是第一位的。
  • @KingCrunch startsWith 是一个不同的函数。
  • 这仍然不是一个好的解决方案,因为它一旦找到匹配项就不会停止搜索。
  • 没有人提到这将如何破坏某些文本编辑器和 IDE 中的自动完成/建议/类型提示功能
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 2012-06-25
  • 1970-01-01
  • 2011-10-23
  • 2011-06-05
  • 2013-01-28
相关资源
最近更新 更多