Knapsack
Collection pipeline library for PHP
Install / Use
/learn @DusanKasan/KnapsackREADME
Knapsack
Collection pipeline library for PHP
Knapsack is a collection library for PHP >= 5.6 that implements most of the sequence operations proposed by Clojures sequences plus some additional ones. All its features are available as functions (for functional programming) and as a collection pipeline object methods.
The heart of Knapsack is its Collection class. However its every method calls a simple function with the same name that does the actual heavy lifting. These are located in DusanKasan\Knapsack namespace and you can find them here. Collection is a Traversable implementor (via IteratorAggregate) that accepts Traversable object, array or even a callable that produces a Traversable object or array as constructor argument. It provides most of Clojures sequence functionality plus some extra features. It is also immutable - operations preformed on the collection will return new collection (or value) instead of modifying the original collection.
Most of the methods of Collection return lazy collections (such as filter/map/etc.). However, some return non-lazy collections (reverse) or simple values (count). For these operations all of the items in the collection must be iterated over (and realized). There are also operations (drop) that iterate over some items of the collection but do not affect/return them in the result. This behaviour as well as laziness is noted for each of the operations.
If you want more example usage beyond what is provided here, check the specs and/or scenarios. There are also performance tests you can run on your machine and see the computation time impact of this library (the output of these is included below).
Feel free to report any issues you find. I will do my best to fix them as soon as possible, but community pull requests to fix them are more than welcome.
Documentation
Check out the documentation (which is prettified version of this readme) at http://dusankasan.github.io/Knapsack
Installation
Require this package using Composer.
composer require dusank/knapsack
Usage
Instantiate via static or dynamic constructor
use DusanKasan\Knapsack\Collection;
$collection1 = new Collection([1, 2, 3]);
$collection2 = Collection::from([1, 2, 3]); //preferred since you can call methods on its result directly.
Work with arrays, Traversable objects or callables that produce Traversables
$collection1 = Collection::from([1, 2, 3]);
$collection2 = Collection::from(new ArrayIterator([1, 2, 3]);
//Used because Generator can not be rewound
$collection2 = Collection::from(function() { //must have 0 arguments
foreach ([1, 2, 3] as $value) {
yield $value;
}
});
Basic map/reduce
$result = Collection::from([1, 2])
->map(function($v) {return $v*2;})
->reduce(function($tmp, $v) {return $tmp+$v;}, 0);
echo $result; //6
The same map/reduce using Knapsack's collection functions
$result = reduce(
map(
[1, 2],
function($v) {return $v*2;}
),
function($tmp, $v) {return $tmp+$v;},
0
);
echo $result; //6
Get first 5 items of Fibonacci's sequence
$result = Collection::iterate([1,1], function($v) {
return [$v[1], $v[0] + $v[1]]; //[1, 2], [2, 3] ...
})
->map('\DusanKasan\Knapsack\first') //one of the collection functions
->take(5);
foreach ($result as $item) {
echo $item . PHP_EOL;
}
//1
//1
//2
//3
//5
If array or Traversable would be returned from functions that return an item from the collection, it can be converted to Collection using the optional flag. By default it returns the item as is.
$result = Collection::from([[[1]]])
->first(true)
->first();
var_dump($result); //[1]
Collections are immutable
function multiplyBy2($v)
{
return $v * 2;
}
function multiplyBy3($v)
{
return $v * 3;
}
function add($a, $b)
{
return $a + $b;
}
$collection = Collection::from([1, 2]);
$result = $collection
->map('multiplyBy2')
->reduce(0, 'add');
echo $result; //6
//On the same collection
$differentResult = $collection
->map('multiplyBy3')
->reduce(0, 'add');
echo $differentResult; //9
Keys are not unique by design
It would harm performance. This is only a problem if you need to call toArray(), then you should call values() before.
$result = Collection::from([1, 2])->concat([3,4]);
//arrays have unique keys
$result->toArray(); //[3,4]
$result->values()->toArray(); //[1, 2, 3, 4]
//When iterating, you can have multiple keys.
foreach ($result as $key => $item) {
echo $key . ':' . $item . PHP_EOL;
}
//0:1
//1:2
//0:3
//1:4
Collection trait is provided
If you wish to use all the Collection methods in your existing classes directly, no need to proxy their calls, you can just use the provided CollectionTrait. This will work on any Traversable by default. In any other class you will have to override the getItems() method provided by the trait. Keep in mind that after calling filter or any other method that returns collection, the returned type will be actually Collection, not the original Traversable.
class AwesomeIterator extends ArrayIterator {
use CollectionTrait;
}
$iterator = new AwesomeIterator([1, 2, 3]);
$iterator->size(); //3
Performance tests
PHP 5.6
+------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
| operation details | native execution time | collection execution time | difference (percent) |
+------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
| array_map vs Collection::map on 10000 integers (addition) | 0.0034945011138916s | 0.0034625053405762s | 99% |
| array_map vs Collection::map on 10000 strings (concatenation) | 0.004361891746521s | 0.0049739360809326s | 114% |
| array_map vs Collection::map on 10000 objects (object to field value) | 0.02332329750061s | 0.027161455154419s | 116% |
| array_map vs Collection::map on 10000 md5 invocations | 0.0086771726608276s | 0.0080755949020386s | 93% |
| array_map vs Collection::map on 10000 integers n, counting sum(0, n) the naive way | 1.5985415458679s | 1.580038356781s | 98% |
+------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
PHP 7.1.1
+------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
| operation details | native execution time | collection execution time | difference (percent) |
+------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
| array_map vs Collection::map on 10000 integers (addition) | 0.00082111358642578s | 0.001681661605835s | 204% |
| array_map vs Collection::map on 10000 strings (concatenation) | 0.00081214904785156s | 0.0015116214752197s | 186% |
| array_map vs Collection::map on 10000 objects (object to field value) | 0.0015491008758545s | 0.0036969423294067s | 238% |
| array_map vs Collection::map on 10000 md5 invocations | 0.0032038688659668s | 0.0039427280426025s | 123% |
| array_map vs Collection::map on 10000 integers n, counting sum(0, n) the naive way | 0.93844709396362s | 0.93354930877686s | 99% |
+------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
Constructors
These are ways how to create the Collection class. There is one default constructor and few named (static) ones.
new(iterable|callable $input)
The default constructor accepts array, Traversable o




