【问题标题】:array_map on collection with array interfaces?带有数组接口的集合上的array_map?
【发布时间】:2013-04-22 19:11:36
【问题描述】:

我有一个名为Collection 的类,它存储相同类型的对象。 Collection 实现数组接口:IteratorArrayAccessSeekableIteratorCountable

我想将Collection 对象作为数组参数传递给array_map 函数。但这失败并出现错误

PHP 警告:array_map():参数 #2 应该是一个数组

我可以通过实现其他/更多接口来实现这一点,以便将Collection 对象视为数组吗?

【问题讨论】:

  • 滚动你自己的collection_map函数?
  • @Adder Course 我可以,但现在我正在寻找解决方案,如果我可以将我的 Collection 与 buildin php funcs 一起使用:)

标签: php arrays iterator array-map


【解决方案1】:

array_map 顾名思义,想要数组。毕竟它不叫iterator_map。 ;)

除了iterator_to_array(),它会产生一个可能很大的临时数组,没有任何技巧可以让可迭代对象与array_map一起工作。

Functional PHP 库有一个 map 实现,适用于任何可迭代集合。

【讨论】:

  • Functional PHP map 实现不是内存效率的:结果存储在数组中。我找到了一个更好的库:github.com/SuRaMoN/itertools 以及一篇解释如何自己构建它的博文:a-basketful-of-papayas.net/2012/07/…
  • Aad,一般来说,map 函数的结果是一个 new 数组——内存开销是该方法固有的,在绝大多数用例中可以忽略不计。
  • “让可迭代对象与array_map一起工作没有技巧。”这个技巧是iterator_to_array()
  • @MarkFox 如果迭代器产生大量大型数组或对象的大型集合,并且可调用对象旨在将它们中的每一个汇总为更小的数组、对象或基元,则第一次调用 @MarkFox 的内存开销987654331@ 可以很重要。
【解决方案2】:

array_map() 函数不支持 Traversable 作为其数组参数,因此您必须执行转换步骤:

array_map($fn, iterator_to_array($myCollection));

除了对集合进行两次迭代之外,它还会产生一个以后不会使用的数组。

另一种方法是编写自己的地图函数:

function map(callable $fn)
{
    $result = array();

    foreach ($this as $item) {
        $result[] = $fn($item);
    }

    return $result;
}

更新

从您的用例来看,您似乎甚至对地图操作的结果都不感兴趣;因此使用iterator_apply() 更有意义。

iterator_apply($myCollection, function($obj) {
    $obj->method1();
    $obj->method2();

    return true;
});

【讨论】:

  • 这确实有效,但会降低性能,因为它会在 iterator_to_array 步骤中进行迭代,并且会在 array_map 步骤中再次迭代。
  • @EelkevandenBos 我在回答中给出了两个解决方案,后者没有表现出这种“性能损失”;除此之外,在这两种情况下,运行时间都是 O(n)。
  • 我认为对 iterator_apply 的回调没有接收当前实例作为参数。来自文档:“此函数仅接收给定的参数,因此默认情况下它是无效的。”文档中的示例通过将迭代器本身作为参数传递并使用 $iterator->current() 解决了这个问题。文档:php.net/iterator_apply
  • @Ole 谢谢你,这可能是我编写更新时的疏忽
【解决方案3】:

我想出了以下解决方案:

//lets say you have this iterator
$iterator = new ArrayIterator(array(1, 2, 3));

//and want to append the callback output to the following variable
$out = [];

//use iterator to apply the callback to every element of the iterator
iterator_apply(
    $iterator,
    function($iterator, &$out) {
        $current = $iterator->current();
        $out[] = $current*2;
        return true;
    },
    array($iterator, &$out) //arguments for the callback
);

print_r($out);

这样,您可以生成一个数组,而无需像使用以下方法那样迭代两次:

$iterator = new ArrayIterator(array(1,2,3));
$array = iterator_to_array($iterator); //first iteration
$output = array_map(function() {}, $array); //second iteration

祝你好运!

【讨论】:

    【解决方案4】:

    如果您对创建一个映射到原始数组的函数的新数组感兴趣,则可以只使用 foreach 循环(因为您实现了Iterator)。

    foreach($item in $myCollection) {
        $item->method1();
        $item->method2();
    }
    

    如果您真的想使用地图,那么我认为您必须自己实现。我建议将其作为 Collection 上的方法,例如:

    $mutatedCollection = $myCollection->map(function($item) { 
        /* do some stuff to $item */
        return $item;
    });
    

    我会问自己,你是真的想使用map,还是真的只是想foreach

    【讨论】:

      【解决方案5】:

      我刚刚偶然发现了这个问题,我设法将集合转换为一个数组以使其工作:

      array_map($cb, (array) $collection);
      

      免责声明对于最初的问题,这可能不是一个合适的选择,但我在寻找解决问题时发现了这个问题,我用这个解决方案解决了这个问题。我建议在可能/可行的情况下使用自定义迭代器映射。

      另一种选择是这样做:

      foreach($collection as &$item) {
          $item = $cb($item);
      }
      

      这将改变底层集合。

      编辑:

      有人指出,强制转换为数组可能会产生不必要的副作用。最好向您的集合添加一个方法以从迭代器返回数组并遍历它,或者添加一个接受回调并在底层迭代器上运行循环的map 方法。

      【讨论】:

      • 盲目转换为数组可能会产生令人讨厌的副作用,因为您最终可能会在迭代的过程中得到来自对象的其他数据,而不仅仅是获取迭代器对象正在包装的数据/导航。
      • 我同意这个原则,但是由于对象是一个集合,所以假设没有其他数据绑定到该对象。
      • 鉴于原始问题中实现的接口,可以安全地假设转换为数组将返回迭代器的数组表示,但是我已经编辑了我的答案以考虑人们试图遍历不实现 OP 中接口的类
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-28
      • 2014-10-17
      • 2011-05-04
      • 1970-01-01
      • 2013-03-03
      • 1970-01-01
      • 2018-03-05
      相关资源
      最近更新 更多