3

1 つの結果にマージする必要がある 2 つの反復子があります。

データ サンプルは次のとおりです。

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
            )

    )

)

キー「期間」でそれらを要約イテレータにマージしたいと思います。

最終結果は次のようになります。

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()

抜け道を知っている方がいましたらアドバイスお願いします。

4

3 に答える 3

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();

注:$it2[0] (15:00)ソースデータ( ) [bl_subs => 1]に存在[bl_unsubs] => 1しない要素がどのように発生するのかわかりません[bl_block_total] => 1。それはデフォルト値ですか?

等しい: (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独自のものにラップして、適切にカプセル化することができます。提供される情報が限られていたため、問題のドメインに日付を減らす単純化された例を作成しました。一度に2つのイテレーターを繰り返します。両方のイテレータが等しい場合は、両方を返します。等しくない場合は、両方を比較したときに最初になるものを返します。

使用される簡略化されたデータ:

$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');

比較値を含む2つの配列のみ。それらは2つのイテレータに変換されます。

$it1 = new ArrayIterator($ar1);
$it2 = new ArrayIterator($ar2);

書き出される問題は、2つのイテレータに限定されています。問題をより一般的に解決するには、0個以上のイテレータで機能する必要があります。つまり、反復ごとに、イテレータが現在の値に基づいて相互に比較されます。そのために、比較関数が使用されます。usortこれをドキュメントの動作と比較できます。関数はAとBを比較し、2つに基づいて整数値を返します。

  • A <B:-1(AはB未満、戻り値はゼロ未満)
  • A = B:0(AはBに等しく、戻り値はゼロに等しい)
  • A> B:1(AはBより大きく、戻り値はゼロより大きい)

これにより、無制限の数のペアを相互に比較できます。必要な関数は2つだけです。1つは使用するイテレータから現在の値を取得し、もう1つはAとBを実際に比較します(実際には両方を1つの関数にマージできますが、これは模範的であり、配列/イテレータは少し異なります。後で簡単に変更できるように、分離する価値があると思いました)。したがって、最初にイテレータから値を取得する関数を使用します。これは簡単な方法で実行できるため、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));
};

strcmpこれが、文字列比較関数に基づいた比較関数であり、この$compareValue関数を使用して比較用の文字列を取得します。

つまり、2つのイテレータを持つ配列があるとしましょう。これで、それを並べ替えることができます。最初の要素を次の要素と比較して、それらが等しいかどうかを判断することもできます。

これで、複数のイテレータで構成されるイテレータを作成できるようになりました。各反復で、アタッチされたイテレータがソートされ、最初のイテレータ(およびそれに等しいイテレータ)のみが現在のイテレータとして返され、転送されます。このフローのようなもの:

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);
    }
}

この使用例は、その反復とそれに関連する値をその反復に出力します。この場合、配列例としての時間情報のみがこれらのみで構成されています。また、イテレータの元の角かっこに入れます(最初の場合は0、2番目の場合は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);
    }
}

これがお役に立てば幸いです。これは、「内部」イテレータ内の事前にソートされたデータでのみ機能します。そうでない場合、現在の要素の比較によるマージ/追加戦略は意味がありません。

于 2012-04-05T08:36:55.047 に答える
1

わかりました、これはかなり簡単です。両方のイテレータがソートされていると仮定すると、(基本的に) マージソートを実行するだけで済みます。

function mergeIterators(Iterator $it1, Iterator $it2, $compare, $merge) {
    $result = array();
    //rewind both itertators
    $it1->rewind();
    $it2->rewind();
    while ($it1->valid() || $it2->valid()) {
        if (!$it1->valid()) {
            $cmp = 1;
        } elseif (!$it2->valid()) {
            $cmp = -1;
        } else {
            $cmp = $compare($it1->current(), $it2->current());
        }

        if ($cmp === 0) {
            // equal, merge together
            $result[] = $merge($it1->current(), $it2->current());
            $it1->next();
            $it2->next();
        } elseif ($cmp < 0) {
            //first is less than second
            $result[] = $it1->current();
            $it1->next();
        } else {
            $result[] = $it2->current();
            $it2->next();
        }
    }
    return $result;
}

ここでの唯一のトリックは、正しい$compare関数と$merge関数を渡すことです...

簡単なサンプルを次に示します。

$compare = function(array $val1, array $val2) {
    return strtotime($val1['period']) - strtotime($val2['period']);
};

$merge = function(array $val1, array $val2) {
    return array_merge($val1, $val2);
};

マージ機能は、ブルート マージのみを行っているため、より複雑なこと (キーを一緒に追加するなど) を行う必要がある場合に変更する必要がある場合があります...

しかし、この関数は、ユースケースだけでなく、任意の 2 つのイテレータをマージするために使用できるほど汎用的です...

于 2012-04-05T12:02:41.787 に答える
0

使用できるものは、 http://php.net/manual/en/class.appenditerator.phpで詳細なドキュメントAppendIteratorを参照してください。

例: http://php.net/manual/en/function.iterator-to-array.php

$first = new ArrayIterator ( array (
        'k1' => 'a',
        'k2' => 'b',
        'k3' => 'c',
        'k4' => 'd' 
) );
$second = new ArrayIterator ( array (
        'k1' => 'X',
        'k2' => 'Y',
        'Z' 
) );

$combinedIterator = new AppendIterator ();
$combinedIterator->append ( $first );
$combinedIterator->append ( $second );

var_dump ( iterator_to_array ( $combinedIterator, false ) );

ありがとう

:)

于 2012-04-05T08:20:03.830 に答える