diff --git a/README.md b/README.md index 1c1b325..83296da 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ $isOdd = function($num) { }; // Create our stream. -$stream = new phpstreams\Stream(fibonacci()); +$stream = new phpstreams\Stream(fibonacci()); // or phpstreams\Stream::of(fibonacci()); // Finally, use these to create our result. $oddFibo = $stream->filter($isOdd) // Keep only the odd numbers diff --git a/src/Collectors.php b/src/Collectors.php index 69af266..64dd488 100644 --- a/src/Collectors.php +++ b/src/Collectors.php @@ -4,6 +4,7 @@ use phpstreams\collectors\AveragingCollector; use phpstreams\collectors\ReducingCollector; +use phpstreams\collectors\MappingCollector; /** * Utility class containing various collectors. @@ -70,4 +71,16 @@ function ($current, $element) use (&$first, $delimiter) { return $current . $element; }); } + + /** + * Get a collector that maps keys and values. + * + * @param callable $keyMapper + * @param callable $valueMapper [optional] A value mapper. Defaults to pass-through mapper. + * @return Collector + */ + public static function mapping(callable $keyMapper, callable $valueMapper = null) + { + return new MappingCollector($keyMapper, $valueMapper); + } } diff --git a/src/Stream.php b/src/Stream.php index ebcca58..7a84954 100644 --- a/src/Stream.php +++ b/src/Stream.php @@ -41,6 +41,17 @@ public function __construct($source) $this->source = $source; } + /** + * Create a new stream from a traversable source. + * + * @param Traversable|array $source The source to create the stream from. + * @throws InvalidStreamException if the given source is not usable as a stream. + */ + public static function of($source) + { + return new static($source); + } + /** * Check whether a source is valid. * @@ -265,6 +276,21 @@ public function collect(Collector $collector) return $collector->get(); } + /** + * Get first element from stream + * + * @param $defaultValue [optional] Default value to return if stream is empty + * @return first element if available, default value otherwise + */ + public function first($defaultValue = null) + { + foreach ($this as $value) { + return $value; + } + + return $defaultValue; + } + /** * Flatten the underlying stream. * diff --git a/src/collectors/MappingCollector.php b/src/collectors/MappingCollector.php new file mode 100644 index 0000000..c000fd4 --- /dev/null +++ b/src/collectors/MappingCollector.php @@ -0,0 +1,61 @@ + + */ +class MappingCollector implements Collector +{ + /** + * Mapped array + * + * @var array + */ + private $map = []; + + /** + * Callable for mapping key + * + * @var callable + */ + private $keyMapper; + + /** + * Callable for mapping value + * + * @var callable + */ + private $valueMapper; + + public function __construct(callable $keyMapper, callable $valueMapper = null) + { + $this->keyMapper = $keyMapper; + + if (!is_callable($valueMapper)) + { + $valueMapper = function ($k, $v) { + return $v; + }; + } + + $this->valueMapper = $valueMapper; + } + + public function add($key, $value) + { + $keyMapper = $this->keyMapper; + $valueMapper = $this->valueMapper; + + $this->map[$keyMapper($key, $value)] = $valueMapper($key, $value); + } + + public function get() + { + return $this->map; + } +} diff --git a/tests/unit/CollectorsTest.php b/tests/unit/CollectorsTest.php index 83e341d..f627d45 100644 --- a/tests/unit/CollectorsTest.php +++ b/tests/unit/CollectorsTest.php @@ -59,4 +59,38 @@ public function testReducing() $this->assertEquals(24, $instance->get()); } + + public function testMapping() + { + $instance = Collectors::mapping(function ($k, $v) { + return $k . "a"; + }, function($k, $v) { + return $v + 10; + }); + + $instance->add("a", 2); + $instance->add("b", 3); + $instance->add("c", 4); + + $this->assertEquals([ + "aa" => 12, + "ba" => 13, + "ca" => 14 + ], $instance->get()); + + + $instance = Collectors::mapping(function ($k, $v) { + return $k . "a"; + }); + + $instance->add("a", 2); + $instance->add("b", 3); + $instance->add("c", 4); + + $this->assertEquals([ + "aa" => 2, + "ba" => 3, + "ca" => 4 + ], $instance->get()); + } } diff --git a/tests/unit/StreamTest.php b/tests/unit/StreamTest.php index a3195bb..01e40ac 100644 --- a/tests/unit/StreamTest.php +++ b/tests/unit/StreamTest.php @@ -36,6 +36,13 @@ public function testConstructor() $this->assertInstanceOf("Traversable", $value); } + public function testStaticCreation() + { + $value = Stream::of([]); + + $this->assertInstanceOf("Traversable", $value); + } + /** * @test * @expectedException phpstreams\exception\InvalidStreamException @@ -137,6 +144,30 @@ public function testCollect() $this->assertEquals(42, $instance->collect($collector)); } + public function testFirst() + { + $stream = new Stream([4, 5, 6, 7]); + $emptyStream = new Stream([]); + + $this->assertEquals(4, $stream->first()); + + $this->assertEquals(null, $emptyStream->first()); + + $this->assertEquals('empty', $emptyStream->first('empty')); + + $result = $stream->filter(function ($a) { + return $a > 10; + })->first(); + + $this->assertEquals(null, $result); + + $result = $stream->filter(function ($a) { + return $a > 5; + })->first(); + + $this->assertEquals(6, $result); + } + public function testIsSortedWithSortedSource() { $sortedSource = $this->getMockBuilder('phpstreams\Stream')