php通过其中一个值合并两个或多个ArrayIterators

kac*_*har 3 php arrays merge iterator

我有两个迭代器,我必须合并到一个结果.

以下是数据样本:

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
            )

    )

)
Run Code Online (Sandbox Code Playgroud)

我想通过关键'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
            )

    )

)
Run Code Online (Sandbox Code Playgroud)

如果我们不使用foreach,for,while或任何其他循环,那将是最好的.那是因为数据很大,我们不希望有内存问题.我正在尝试使用current()next()使用内部数组指针.

如果有人知道如何解决这个问题,请告知.

hak*_*kre 7

它总是对迭代器进行排序,你可以将它们都缓存,每次迭代比较哪一个(如果不相等)并处理那个迭代器.如果相等,则平等处理.

不相等:

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

注:我不知道怎么说不要在源数据(存在的元素$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();
Run Code Online (Sandbox Code Playgroud)

您可以将此处理包装成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');
Run Code Online (Sandbox Code Playgroud)

只有两个包含比较值的数组.这些变成了两个迭代器:

$it1 = new ArrayIterator($ar1);
$it2 = new ArrayIterator($ar2);
Run Code Online (Sandbox Code Playgroud)

写出的问题仅限于两个迭代器.为了更一般地解决问题,它应该与0或更多迭代器一起使用.所以会发生的是,每次迭代时,迭代器会根据它们的当前值进行相互比较.为此,使用比较功能.您可以将其与usortDocs的工作方式进行比较:函数比较A和B,并根据它返回一个整数值:

  • A <B:-1(A小于B,返回值小于零)
  • A = B:0(A等于B,返回值等于零)
  • 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;
};
Run Code Online (Sandbox Code Playgroud)

注意:我不知道你使用的是哪种日期格式,也许我和白天混合,只是交换变量,它几乎是自描述的.

所有这个函数都是为了获得一个可以从迭代器中轻松比较的值.这还没有进行上述比较,因此需要另一个函数来使用这个比较值函数作为依赖:

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

这就是比较函数,基于strcmp字符串比较函数并使用$compareValue函数获取字符串进行比较.

所以假设你有一个带有两个迭代器的数组,现在可以对它进行排序.也可以将第一个元素与下一个元素进行比较,以确定它们是否相等.

完成后,现在可以创建一个由多个迭代器组成的迭代器,并且在每次迭代时,附加的迭代器都会被排序,只有第一个迭代器(和那些等于它的迭代器)将作为当前迭代器返回并转发.像这样的流程:

DITAA图表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);
    }
}
Run Code Online (Sandbox Code Playgroud)

此用法示例将输出它所在的迭代以及与该迭代相关联的值.在这种情况下,只有作为示例数组的时间信息才由这些组成.它还放入方括号中的迭代器(第一个为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
Run Code Online (Sandbox Code Playgroud)

如您所见,对于那些(预先排序的)迭代器中相同的比较值,将作为一对返回.在您的情况下,您需要进一步处理这些值,例如在提供默认值时合并它们:

$defaults = array('bl_subs' => 0, ...);
foreach ($it as $values) {
    array_unshift($values, $default);
    $value = call_user_func_array('array_merge', $values);
}
Run Code Online (Sandbox Code Playgroud)

所以这实际上是使用它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);
    }
}
Run Code Online (Sandbox Code Playgroud)

希望这是有帮助的.它只适用于"内部"迭代器中的预分类数据,否则合并/追加策略与当前元素的比较没有意义.