両方のイテレータが常にソートされている場合は、両方をキャッシュし、反復ごとにどちらが先に来るかを比較して(等しくない場合)、そのイテレータを処理できます。等しい場合は、両方を等しく処理します。
等しくない:
$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つのイテレータを持つ配列があるとしましょう。これで、それを並べ替えることができます。最初の要素を次の要素と比較して、それらが等しいかどうかを判断することもできます。
これで、複数のイテレータで構成されるイテレータを作成できるようになりました。各反復で、アタッチされたイテレータがソートされ、最初のイテレータ(およびそれに等しいイテレータ)のみが現在のイテレータとして返され、転送されます。このフローのようなもの:
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);
}
}
これがお役に立てば幸いです。これは、「内部」イテレータ内の事前にソートされたデータでのみ機能します。そうでない場合、現在の要素の比較によるマージ/追加戦略は意味がありません。