2012-04-05 28 views
3

私は1つの結果にマージする必要がある2つのイテレータを持っています。ここでphp 2つ以上のArrayIteratorsをその値の1つでマージします

は、データのサンプルです:

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 
      ) 

    ) 

) 

私は要約イテレータへのキー「期間」でそれらを統合したいと思います。

最終的な結果は次のようになります。私たちは、しばらくまたはその他のループのために、foreachのを使用していない場合、それは最高になります

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 
      ) 

    ) 

) 

。データが大きくなり、メモリの問題が発生したくないからです。私はcurrent()next()を使用して内部配列ポインタを使用しようとしていました。

誰かがこれを回避する方法を知っている場合は、アドバイスをお願いします。

+0

これらは常にソートされていますか? – hakre

+1

あなたはPHPであなたの正確な配列を追加することができます..入力を開始したくない.. – Baba

答えて

7

この両方のイテレータは常にソートされている、あなたは1つが等しくない場合は(最初に来る各反復ごとに比較し、それらの両方をキャッシュすることができ)、それを処理します。等しい場合、両方を等しく処理します。

等しくない:

$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にこの処理をラップすることができます(一回の反復は省略しました)。与えられた情報が限られていたため、問題のドメインの日付を減らす簡単な例を作成しました。一度に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以上のイテレータで動作する必要があります。つまり、各反復ごとに、イテレータは現在の値に基づいて相互に比較されます。そのために比較関数が使用されます。関数はAとBを比較し、2に基づいて、それは整数値を返します:あなたはどのようにusortDocs作品であること比較することができ

  • < B:-1(AがBよりも小さい、戻り値は以下でありますゼロ)
  • A = B 0(AはBに等しく、戻り値がゼロに等しい)
  • A> B:1(AがBより大きい場合、戻り値がゼロよりも大きい)

これは可能無限の数の対を互いに比較することができる。 2つの関数しか必要ありません:使用するイテレータから現在の値を取得する関数と、AとBの実際の比較を行う関数です(実際には両方を1つの関数にマージできますが、イテレーターは少し違うので、後でもっと簡単に変更できるように、分ける価値があると思いました)。だから、最初のイテレータのうちの値を取得する機能、私はシンプルなstrcmpでこれを行うことができますので、私はISO日付時刻値との比較:

/** 
* 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; 
}; 

注:私は知りませんあなたが使用している日付形式、おそらく私は月ごとに1日を混ぜて、変数を交換するだけで、かなり自己記述的です。

このすべての機能は、イテレータと簡単に比較できる値を取得することです。これはまだ、上記の比較を行いませんので、別の関数が依存性として、この比較値関数を使用しますが必要とされる:

/** 
* 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 chartSrc

ソートがすでに比較機能で行われているとして、これだけ反復ロジックをカプセル化する必要があります。任意のサイズ(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); 
    } 
} 

希望はこれが便利です。それはあなたの "内側の"イテレータ内のプリコーディングされたデータでのみ機能します。そうしないと、現在の要素の比較によるマージ/アペンド戦略が意味をなさない。

+0

私は04/04/2012 15:00:00レコード。それは手の間違いだった。デフォルト値は0です。 – kachar

+0

イテレータをキャッシュするとどういう意味ですか?それはどのようにPHPで見えるだろうか? – kachar

+0

@ilko:実際にはキャッシュが必要だと思っていましたが、そうはしません。私は例を集め、私の答えにいくつかの説明とともにそれを加えました。 – hakre

0

AppendIteratorは、詳細についてはhttp://php.net/manual/en/class.appenditerator.phpを参照してください。

例: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)); 

おかげ

:)

+0

いいえ、同じ "キー"値( 'period')を持つエントリは、マージする必要があります - 追加されません。さらにいくつかの並べ替えが適用されるかもしれません。だから、 'AppendIterator'は答えではありません。 – hakre

+0

これは便利ですが、結果をマージしていないので、関数はarray + arrayのように追加します。それを正しくマージするには、最終的な結果をループする必要があります。 – kachar

+0

私は今あなたが欲しいものを理解しています....私はすぐに私の答えを更新します – Baba

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つのイテレータをマージするのに十分な汎用性があります。

+0

これは本当に迅速かつ簡単な解決策です。それは魅力のように働いた! – kachar

関連する問題