我有两个迭代器,必须合并成一个结果。
以下是数据示例:
ArrayIterator Object
(
[storage:ArrayIterator:private] => Array
(
[0] => Array
(
[period] => 04/04/2012 16:00:00
[bl_subs] => 1
[bl_unsubs] => 1
[bl_block_total] => 1
)
[1] => Array
(
[period] => 04/04/2012 17:00:00
[bl_subs] => 1
[bl_unsubs] => 2
[bl_block_total] => 0
)
[2] => Array
(
[period] => 04/04/2012 18:00:00
[bl_subs] => 0
[bl_unsubs] => 0
[bl_block_total] => -1
)
[3] => Array
(
[period] => 04/04/2012 19:00:00
[bl_subs] => 2
[bl_unsubs] => 0
[bl_block_total] => -2
)
[4] => Array
(
[period] => 04/04/2012 20:00:00
[bl_subs] => 2
[bl_unsubs] => 0
[bl_block_total] => 1
)
)
)
ArrayIterator Object
(
[storage:ArrayIterator:private] => Array
(
[0] => Array
(
[period] => 04/04/2012 15:00:00
[bl_avg] => 5
[bl_full] => 0
)
[1] => Array
(
[period] => 04/04/2012 17:00:00
[bl_avg] => 0
[bl_full] => 7
)
[2] => Array
(
[period] => 04/04/2012 18:00:00
[bl_avg] => 1
[bl_full] => 0
)
)
)
我想按关键字“period”将它们合并到一个摘要迭代器中。
最终结果应该是:
ArrayIterator Object
(
[storage:ArrayIterator:private] => Array
(
[0] => Array
(
[period] => 04/04/2012 15:00:00
[bl_subs] => 0
[bl_unsubs] => 0
[bl_avg] => 5
[bl_full] => 0
[bl_block_total] => 0
)
[1] => Array
(
[period] => 04/04/2012 16:00:00
[bl_subs] => 1
[bl_unsubs] => 1
[bl_avg] => 0
[bl_full] => 0
[bl_block_total] => 1
)
[2] => Array
(
[period] => 04/04/2012 17:00:00
[bl_subs] => 1
[bl_unsubs] => 2
[bl_avg] => 0
[bl_full] => 7
[bl_block_total] => 0
)
[3] => Array
(
[period] => 04/04/2012 18:00:00
[bl_subs] => 0
[bl_unsubs] => 0
[bl_avg] => 1
[bl_full] => 0
[bl_block_total] => -1
)
[4] => Array
(
[period] => 04/04/2012 19:00:00
[bl_subs] => 2
[bl_unsubs] => 0
[bl_avg] => 0
[bl_full] => 0
[bl_block_total] => -2
)
[5] => Array
(
[period] => 04/04/2012 20:00:00
[bl_subs] => 2
[bl_unsubs] => 0
[bl_avg] => 0
[bl_full] => 0
[bl_block_total] => 1
)
)
)
最好不要使用foreach、for、while或任何其他循环。那是因为数据会很大,我们不想有内存问题。我尝试使用
current()
和next()
使用内部数组指针。如果有人知道解决办法,请通知我。
最佳答案
如果两个迭代器都是排序的,您可以缓存它们,比较每个迭代中哪个优先(如果不相等),并处理那个迭代。如果相等,则对两者进行同等处理。
不平等:
$it1[[period] => 04/04/2012 16:00:00] > $it2[[period] => 04/04/2012 15:00:00]
=> process $it2 data:
[period] => 04/04/2012 15:00:00
[bl_avg] => 5
[bl_full] => 0
as current():
[period] => 04/04/2012 15:00:00
[bl_subs] => 1
[bl_unsubs] => 1
[bl_avg] => 5
[bl_full] => 0
[bl_block_total] => 1
+ $it2->next();
注意:我不知道源数据(
$it2[0] (15:00)
)[bl_subs => 1]
、[bl_unsubs] => 1
和[bl_block_total] => 1
中不存在的元素是如何产生的。这是默认值吗?相等:(跳过一次迭代)
$it1[[period] => 04/04/2012 17:00:00] == $it2[[period] => 04/04/2012 17:00:00]
=> process $it1 and $it2 data:
$it1:
[period] => 04/04/2012 17:00:00
[bl_subs] => 1
[bl_unsubs] => 2
[bl_block_total] => 0
$it2:
[period] => 04/04/2012 17:00:00
[bl_avg] => 0
[bl_full] => 7
as current():
[period] => 04/04/2012 17:00:00
[bl_subs] => 1
[bl_unsubs] => 2
[bl_avg] => 0
[bl_full] => 7
[bl_block_total] => 0
+ $it1->next(); $it2->next();
你可以把这个过程封装成它自己的
Iterator
,所以它被很好的封装了。因为给定的信息是有限的,所以我创建了一个简化的例子,将日期减少到问题域:一次迭代两个迭代器。如果两个迭代器相等,则返回两者。如果不相等,则返回比较两者时的第一个。使用的简化数据:
$ar1 = array('04/04/2012 16:00:00', '04/04/2012 17:00:00', '04/04/2012 18:00:00', '04/04/2012 19:00:00', '04/04/2012 20:00:00');
$ar2 = array('04/04/2012 15:00:00', '04/04/2012 17:00:00', '04/04/2012 18:00:00');
只有两个数组包含比较值。它们被转换为两个迭代器:
$it1 = new ArrayIterator($ar1);
$it2 = new ArrayIterator($ar2);
写出的问题仅限于两个迭代器。为了更一般地解决这个问题,它应该使用0个或更多的迭代器。因此,每次迭代都会根据当前值对迭代器进行比较。为此,使用比较函数。您可以将其与
usort
Docs的工作方式进行比较:一个函数比较a和b,并基于两者返回一个整数值:A
A>B:1(A大于B,返回值大于零)
这允许相互比较无限数量的对。它只需要两个函数:一个从我们使用的迭代器获得当前值,另一个函数在A和B之间进行实际比较(实际上,您可以将两个函数合并为一个函数,但是这是示例性的,并且您的数组/迭代器有一点不同,我认为分离是值得的,这样你以后就可以更容易地修改它了。首先,从迭代器中获取值的函数,我与ISO日期时间值进行比较,因为我可以用一个简单的
strcmp
/**
* Get Comparison-Value of an Iterator
*
* @param Iterator $iterator
* @return string
*/
$compareValue = function(Iterator $iterator) {
$value = $iterator->current();
sscanf($value, '%d/%d/%d %s', $month, $day, $year, $timeISO);
$dateISO = sprintf('%04d-%02d-%02d %s', $year, $month, $day, $timeISO);
return $dateISO;
};
注:我不知道你用的是哪种日期格式,也许我把月和日混合在一起,只需要交换变量,这基本上是自描述性的。
所有这些函数都是为了获得一个与迭代器很容易比较的值。此操作尚未执行上述比较,因此需要另一个函数,该函数将使用此比较值函数作为依赖项:
/**
* Compare two Iterators by it's value
*
* @param Iterator $a
* @param Iterator $b
* @return int comparison result (as of strcmp())
*/
$compareFunction = function(Iterator $a, Iterator $b) use ($compareValue) {
return strcmp($compareValue($a), $compareValue($b));
};
这就是compare函数,它基于
strcmp
字符串比较函数,并使用$compareValue
函数获取用于比较的字符串。假设你有一个有两个迭代器的数组,现在可以对它进行排序了。还可以将第一个元素与下一个元素进行比较,以确定它们是否相等。
完成后,现在可以创建一个由多个迭代器组成的迭代器,在每次迭代中,附加的迭代器都会被排序,只有第一个迭代器(以及与之相等的迭代器)将作为当前迭代器返回并转发。类似这样的流程:
Src
由于排序已经用比较函数完成,所以只需要封装这个迭代逻辑。对于任何大小的数组(0个或更多元素),排序都是通用的。用法示例:
/**
* Usage
*/
$it = new MergeCompareIterator($compareFunction, array($it1, $it2));
foreach ($it as $index => $values) {
printf("Iteration #%d:\n", $index);
foreach ($values as $iteratorIndex => $value) {
printf(" * [%d] => %s\n", $iteratorIndex, $value);
}
}
这个使用示例将输出它所在的迭代以及该迭代的关联值。在这种情况下,作为示例数组的时间信息仅由这些组成。它还将其放入迭代器所在的方括号中(0代表第一个,1代表第二个)。这将生成以下输出:
Iteration #0:
* [1] => 04/04/2012 15:00:00
Iteration #1:
* [0] => 04/04/2012 16:00:00
Iteration #2:
* [0] => 04/04/2012 17:00:00
* [1] => 04/04/2012 17:00:00
Iteration #3:
* [0] => 04/04/2012 18:00:00
* [1] => 04/04/2012 18:00:00
Iteration #4:
* [0] => 04/04/2012 19:00:00
Iteration #5:
* [0] => 04/04/2012 20:00:00
如您所见,对于两个(预排序的)迭代器中相等的比较值,作为一对返回。在您的情况下,您需要进一步处理这些值,例如在提供默认值的同时合并它们:
$defaults = array('bl_subs' => 0, ...);
foreach ($it as $values) {
array_unshift($values, $default);
$value = call_user_func_array('array_merge', $values);
}
这就是
MergeCompareIterator
的用法。实现是相当直接的,到目前为止这一个还没有缓存排序/当前迭代器,如果您想改进它,我将此作为一个练习。完整代码:
<?php
/**
* @link http://stackoverflow.com/q/10024953/367456
* @author hakre <http://hakre.wordpress.com/>
*/
$ar1 = array('04/04/2012 16:00:00', '04/04/2012 17:00:00', '04/04/2012 18:00:00', '04/04/2012 19:00:00', '04/04/2012 20:00:00');
$ar2 = array('04/04/2012 15:00:00', '04/04/2012 17:00:00', '04/04/2012 18:00:00');
$it1 = new ArrayIterator($ar1);
$it2 = new ArrayIterator($ar2);
/**
* Get Comparison-Value of an Iterator
*
* @param Iterator $iterator
* @return string
*/
$compareValue = function(Iterator $iterator)
{
$value = $iterator->current();
sscanf($value, '%d/%d/%d %s', $month, $day, $year, $timeISO);
$dateISO = sprintf('%04d-%02d-%02d %s', $year, $month, $day, $timeISO);
return $dateISO;
};
/**
* Compare two Iterators by it's value
*
* @param Iterator $a
* @param Iterator $b
* @return int comparison result (as of strcmp())
*/
$compareFunction = function(Iterator $a, Iterator $b) use ($compareValue)
{
return strcmp($compareValue($a), $compareValue($b));
};
/**
* Iterator with a comparison based merge-append strategy over 0 or more iterators.
*
* Compares 0 or more iterators with each other. Returns the one that comes first
* and any additional one that is equal to the first as an array of their current()
* values in this current().
* next() forwards all iterators that are part of current().
*/
class MergeCompareIterator implements Iterator
{
/**
* @var Iterator[]
*/
private $iterators;
/**
* @var callback
*/
private $compareFunction;
/**
* @var int
*/
private $index;
/**
* @param callback $compareFunction (same sort of usort()/uasort() callback)
* @param Iterator[] $iterators
*/
public function __construct($compareFunction, array $iterators = array())
{
$this->setCompareFunction($compareFunction);
foreach ($iterators as $iterator) {
$this->appendIterator($iterator);
}
}
/**
* @param callback $compareFunction
*/
public function setCompareFunction($compareFunction)
{
if (!is_callable($compareFunction)) {
throw new InvalidArgumentException('Compare function is not callable.');
}
$this->compareFunction = $compareFunction;
}
public function appendIterator(Iterator $it)
{
$this->iterators[] = $it;
}
public function rewind()
{
foreach ($this->iterators as $it) {
$it->rewind();
}
$this->index = 0;
}
/**
* @return Array one or more current values
* @throws RuntimeException
*/
public function current()
{
$current = array();
foreach ($this->getCurrentIterators() as $key => $value) {
$current[$key] = $value->current();
}
return $current;
}
/**
* @return Iterator[]
*/
private function getCurrentIterators()
{
/* @var $compareFunction Callable */
$compareFunction = $this->compareFunction;
$iterators = $this->getValidIterators();
$r = uasort($iterators, $compareFunction);
if (FALSE === $r) {
throw new RuntimeException('Sorting failed.');
}
$compareAgainst = reset($iterators);
$sameIterators = array();
foreach ($iterators as $key => $iterator) {
$comparison = $compareFunction($iterator, $compareAgainst);
if (0 !== $comparison) {
break;
}
$sameIterators[$key] = $iterator;
}
ksort($sameIterators);
return $sameIterators;
}
/**
* @return Iterator[]
*/
private function getValidIterators()
{
$validIterators = array();
foreach ($this->iterators as $key => $iterator) {
$iterator->valid() && $validIterators[$key] = $iterator;
}
return $validIterators;
}
/**
* @return int zero based iteration count
*/
public function key()
{
return $this->index;
}
public function next()
{
foreach ($this->getCurrentIterators() as $iterator) {
$iterator->next();
}
$this->index++;
}
public function valid()
{
return (bool)count($this->getValidIterators());
}
}
/**
* Usage
*/
$it = new MergeCompareIterator($compareFunction, array($it1, $it2));
foreach ($it as $index => $values) {
printf("Iteration #%d:\n", $index);
foreach ($values as $iteratorIndex => $value) {
printf(" * [%d] => %s\n", $iteratorIndex, $value);
}
}
希望这对你有帮助。它只适用于“内部”迭代器中预先排序的数据,否则比较当前元素的merge/append策略没有意义。
关于php - php通过其值之一合并两个或多个ArrayIterators,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10024953/