Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<?php
#
# Input multiple SELECT statements from different SQL queries
# get back a single stream of rows with records merged if
# they have the same key
#
function genOneTwoThree() {
foreach ([1=>'one',2=>'two',3=>'three'] as $key => $value) {
# echo "...ready to yield: $key => $number\n";
yield $key => $value;
}
}
function genTwoThreeFour() {
foreach ([1=>'uno',2=>'does',4=>'quatro'] as $key => $value) {
# echo "...ready to yield: $key => $number\n";
yield $key => $value;
}
}
$emptyIterator = new EmptyIterator();
/**
* mergeGenerators Combines multiple generators and has unique keys
* I use it to merge sort queries from multiple databases and
* combine records that have duplicate keys across them
*
* @access public
* @param array $generators Yields key => some object and keys sorted
* @param callable $merger Takes arroy of 2 or more objects and returns one
* @return void
*/
function mergeGenerators(array $generators, callable $merger) {
while (count($generators) > 0) {
// Find minimum input key
$minimumKey = null;
foreach ($generators as $i => $generator) {
if (!$generator->valid()) {
unset($generators[$i]);
continue;
}
if (is_null($minimumKey) || $generator->key() < $minimumKey) {
$minimumKey = $generator->key();
}
}
if (count($generators) == 0) {
return;
}
// Merge all minimum values
$outputItems = array();
foreach ($generators as $generator) {
while ($generator->key() == $minimumKey) {
$outputItems[] = $generator->current();
$generator->next();
}
}
yield $minimumKey => count($outputItems) > 1 ? $merger($outputItems) : $outputItems[0];
}
}
function combiner($items) {
return max($items);
}
$a = mergeGenerators([genOneTwoThree(), $emptyIterator, genTwoThreeFour()], 'combiner');
echo "\n\nYIELDED:\n";
foreach ($a as $key => $value) {
var_dump($key, $value);
}
@fulldecent

This comment has been minimized.

Copy link
Owner Author

@fulldecent fulldecent commented Dec 16, 2016

Something like this also works

    // Merge all minimum values
    $outputItems = array();
    foreach ($generators as $generator) {
      while ($generator->key() == $minimumKey) {
        $outputItems[] = $generator->current();
      }
    }
    yield $minimumKey => count($outputItems) > 1 ? $merger($outputItems) : $outputItems[0];
@fulldecent

This comment has been minimized.

Copy link
Owner Author

@fulldecent fulldecent commented Dec 8, 2017

Regarding ^^ https://gist.github.com/fulldecent/a4cac03087cadec4c275a54317a6f623#gistcomment-1949315, the latest version now includes this approach

@fulldecent

This comment has been minimized.

Copy link
Owner Author

@fulldecent fulldecent commented Sep 6, 2019

Cleaner function

  /**
   * Combines multiple generators and has unique keys
   * I use it to merge sort queries from multiple databases and
   * combine records that have duplicate keys across them
   *
   * http://stackoverflow.com/q/37973732/300224
   *
   * @access public
   * @param array $generators Yields key => some object and keys sorted by keyComparitor
   * @param callable $keyComparitor Puts keys in order, returns <0, 0, >0
   * @param callable $merger Takes arroy of 2 or more objects and returns one
   * @return void
   */
  static function mergeGenerators(array $generators, callable $keyComparitor, callable $merger)
  {
    while (count($generators) > 0) {
      $generators = array_filter($generators, function(Iterator $g){return $g->valid();});
      if (empty($generators)) {
        return;
      }
      usort($generators, function(Iterator $a, Iterator $b) use ($keyComparitor) {
        return $keyComparitor($a->key(), $b->key());
      });
      $minimumKey = $generators[0]->key();
      $outputItems = [];
      foreach ($generators as $generator) {
        if ($keyComparitor($generator->key(), $minimumKey) !== 0) break; // Sorted, later ones must also not be same
        while ($keyComparitor($generator->key(), $minimumKey) === 0) {
          $outputItems[] = $generator->current();
          $generator->next();
        }
      }
      yield $minimumKey => count($outputItems) > 1 ? $merger($outputItems) : $outputItems[0];
    }
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.