Collection pipeline library for PHP
Knapsack is a collection pipeline library implementing most of the sequence operations proposed by Clojures sequences
The heart of Knapsack is its Collection class. It is an iterator implementor that accepts Traversable object or array as constructor argument. It provides most of Clojures sequence function plus some extra ones. 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.
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.
Check out the documentation (which is prettified version of this readme) at http://dusankasan.github.io/Knapsack
$collection1 = new Collection([1,2,3]);
$collection2 = new Collection(new ArrayIterator([1,2,3]);
$collection = new Collection([1,2]);
$result = $collection
->map(function($v) {return $v*2;})
->reduce(0, function($tmp, $v) {return $tmp+$v;});
echo $result; //6
$collection = new Collection([[1,1]]);
$result = $collection
->iterate(function($v) {return [$v[1], $v[0]+$v[1]];})
->map(function($v) {return $v[0];})
->take(5);
foreach ($result as $item) {
echo $item . PHP_EOL;
}
//1
//1
//2
//3
//5
Prettified basic map reduce from before.
function multiplyBy2($v)
{
return $v*2;
}
function add($a, $b)
{
return $a + $b;
}
$collection = new Collection([1,2]);
$result = $collection
->map('multiplyBy2')
->reduce(0, 'add');
echo $result; //6
function multiplyBy2($v)
{
return $v*2;
}
function multiplyBy3($v)
{
return $v*3;
}
function add($a, $b)
{
return $a + $b;
}
$collection = new Collection([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
It would harm performance. This is only a problem if you need to call toArray(), then you should call resetKeys() before.
$collection = new Collection([1,2]);
$result = $collection->concat([3,4]);
//arrays have unique keys
$result->toArray(); //[3,4]
$result->resetKeys()->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
These are the operations (methods) provided by Collection class.
It implements http://php.net/manual/en/class.iterator.php
Returns a lazy collection of items of this collection with $item added as last element. Its key will be 0.
$collection = new Collection([1, 3, 3, 2]);
$collection
->append(1)
->resetKeys() //both 1 have 0 key
->toArray(); //[1, 3, 3, 2, 1]
Returns a lazy collection of items of this collection with $item added as last element. Its key will be $key.
$collection = new Collection([1, 3, 3, 2]);
$collection
->appendWithKey('a', 1)
->toArray(); //[1, 3, 3, 2, 'a' => 1]
Returns a lazy collection with items from this collection followed by items from $collection.
$collection = new Collection([1,3,3,2]);
$collection
->concat([4,5])
->resetKeys() //If we would convert to array here, we would loose 2 items because of same keys
->toArray() //[1, 3, 3 => 2] - each item has key of the first occurrence
Returns true if $needle is present in the collection.
$collection = new Collection([1, 3, 3, 2]);
$collection->contains(2); //true
Returns a collection of items whose keys are the return values of $differentiator and values are the number of items in this collection for which the $differentiator returned this value. $differentiator could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->countBy(function ($i) {
return $v % 2 == 0 ? 'even' : 'odd';
})
->toArray(); //['odd' => [1, 3, 5], 'even' => [2, 4]]
Returns an infinite lazy collection of items in this collection repeated infinitely.
$collection = new Collection([1, 3, 3, 2]);
$collection
->cycle()
->take(8) //we take just 8 items, since this collection is infinite
->resetKeys()
->toArray(); //[1, 3, 3, 2, 1, 3, 3, 2]
Returns a lazy collection of distinct items. The comparison whether the item is in the collection or not is the same as in in_array.
$collection = new Collection([1,3,3,2]);
$collection
->distinct()
->toArray() //[1, 3, 3 => 2] - each item has key of the first occurrence
A form of slice that returns all but first $numberOfItems items.
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->drop(4)
->toArray(); //[4 => 5]
Returns a lazy collection with last $numberOfItems items skipped. These are still realized, just skipped.
$collection = new Collection([1, 2, 3]);
$collection
->dropLast()
->toArray(); //[1, 2]
$collection = new Collection([1, 2, 3]);
$collection
->dropLast(2)
->toArray(); //[1]
Returns a lazy collection by removing items from this collection until first item for which $predicament returns false. $predicament could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection
->dropWhile(function ($v) {
return $v < 3;
})
->toArray(); //[1 => 3, 2 => 3, 3 => 2])
$collection = new Collection([1, 3, 3, 2]);
$collection
->dropWhile(function ($k, $v) {
return $k < 2 && $v < 3;
})
->toArray(); //[1 => 3, 2 => 3, 3 => 2])
Returns a lazy collection in which $callback is executed for each item. $callback could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->each(function ($i) {
echo $i . PHP_EOL;
})
->toArray(); //[1, 2, 3, 4, 5]
//1
//2
//3
//4
//5
Returns true if $predicament returns true for every item in this collection, false otherwise. $predicament could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection->every(function ($v) {
return $v < 3;
}); //false
$collection = new Collection([1, 3, 3, 2]);
$collection->find(function ($k, $v) {
return $v < 4 && $k < 2;
}, 10); //false
Returns a lazy collection of items for which $filter returned true. $filter could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1,3,3,2]);
$collection->filter(function ($item) {
return $item > 2;
})
->toArray() //[1 => 3, 2 => 3]
$collection = new Collection([1,3,3,2]);
$collection->filter(function ($key, $item) {
return $item > 2 && $key > 1;
})
->toArray() //[2 => 3]
Returns first value matched by callable. If no value matches, return $ifNotFound. $filter could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection->find(function ($v) {
return $v < 3;
}); //1
$collection = new Collection([1, 3, 3, 2]);
$collection->find(function ($v) {
return $v > 3;
}, 10); //10
$collection = new Collection([1, 3, 3, 2]);
$collection->find(function ($k, $v) {
return $v < 3 && $k > 1;
}); //2
Like find, but converts the return value to Collection if possible (i.e. if it's an array). $filter could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([[1, 2], [2, 3]]);
$collection
->findCollection(function ($v) {
return $v[0] + $v[1] > 4;
})
->toArray(); //[2, 3]
$collection = new Collection([[1, 2], [2, 3]]);
$collection
->findCollection(function ($k, $v) {
return $k > 0;
})
->toArray(); //[2, 3]
Returns a lazy collection with one or multiple levels of nesting flattened. Removes all nesting when no $depth value is passed.
$collection = new Collection([1,[2, [3]]]);
$collection
->flatten()
->resetKeys() //1, 2 and 3 have all key 0
->toArray() //[1, 2, 3]
$collection = new Collection([1,[2, [3]]]);
$collection
->flatten(1)
->resetKeys() //1, 2 and 3 have all key 0
->toArray() //[1, 2, [3]]
Returns a collection where keys are distinct items from this collection and their values are number of occurrences of each value.
$collection = new Collection([1, 3, 3, 2]);
$collection
->frequencies()
->toArray(); //[1 => 1, 3 => 2, 2 => 1]
Returns value at the key $key. If multiple values have this key, return first. If no value has this key, return $ifNotFound.
$collection = new Collection([1, 3, 3, 2]);
$collection->get(2); //3
Like get, but converts the return value to Collection if possible (i.e. if it's an array).
$collection = new Collection(['a' => [1, 2], 'b' => [2, 3]]);
$collection
->getCollection('a')
->toArray(); //[1, 2]
Returns collection which items are separated into groups indexed by the return value of $differentiator. $differentiator could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->groupBy(function ($i) {
return $i % 2;
})
->toArray(); //[1 => [1, 3, 5], 0 => [2, 4]]
Returns a lazy collection by changing keys of this collection for each item to the result of $indexer for that key/value. $indexer could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection
->indexBy(function ($v) {
return $v;
})
->toArray(); //[1 => 1, 3 => 3, 2 => 2]
Returns a lazy collection of first item from first collection, first item from second, second from first and so on.
$collection = new Collection([1, 3, 3, 2]);
$collection
->interleave(['a', 'b', 'c', 'd', 'e'])
->resetKeys()
->toArray(); //[1, 'a', 3, 'b', 3, 'c', 2, 'd', 'e']
Returns a lazy collection of items of this collection separated by $separator item.
$collection = new Collection([1, 2, 3]);
$collection
->interpose('a')
->resetKeys() // we must reset the keys, because each 'a' has undecided key
->toArray(); //[1, 'a', 2, 'a', 3]
Returns true if is collection is empty. False otherwise.
$collection = new Collection([1, 3, 3, 2]);
$collection->isEmpty(); //false
Opposite of isEmpty
$collection = new Collection([1, 3, 3, 2]);
$collection->isNotEmpty(); //true
Returns lazy collection which is infinite passing last item of this collection to the $iterator and using its return value as next item (and key). If you wish to pass the key, you must yield 2 values from $iterator, first is key, second is item. $iterator could take 1 argument (the item) or 2 arguments (key, item). If you throw a NoMoreItems exception, you will mark the end of the collection.
$collection = new Collection([1]);
$collection
->iterate(function ($v) {
return $v++;
});
$it->rewind();
$it->valid() == true; //always true, we iterate to infinity
$it->key();// == ?; Keys are undecided
$it->current() == 1;
$it->next();
$it->valid() == true;
$it->current() == 2;
$it->next();
$it->valid() == true;
$it->current() == 3;
$collection = new Collection([1]);
$collection
->iterate(function ($v) {
yield $v--; //key
yield $v++; //value
});
$it->rewind();
$it->valid() == true; //always true, we iterate to infinity
$it->key() == 0;
$it->current() == 1;
$it->next();
$it->valid() == true;
$it->key() == 1;
$it->current() == 2;
$it->next();
$it->valid() == true;
$it->key() == 2;
$it->current() == 3;
Returns a lazy collection of the keys of this collection.
$collection = new Collection(['a' => [1, 2], 'b' => [2, 3]]);
$collection
->keys()
->toArray(); //['a', 'b']
Returns collection where each key/item is changed to the output of executing $mapper on each key/item. If you wish to modify keys, yield 2 values in the callable. First is key, second is item. $mapper could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1,3,3,2]);
$collection
->map(function ($item) {
return $item + 1;
})
->toArray() //[2, 4, 4, 3]
$collection = new Collection([1,3,3,2]);
$collection
->map(function ($key, $item) {
yield $key + 1;
yield $item;
})
->toArray() //[1 => 1, 2 => 3, 3 => 3, 4 => 2]
$collection = new Collection([1,3,3,2]);
$collection
->map(function ($key, $item) {
yield $item + 1;
})
->toArray() //[2, 4, 4, 3]
Returns a lazy collection which is a result of calling map($mapper) and then flatten(1). $mapper could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection
->mapcat(function ($v) {
return [[$v]];
})
->toArray(); //[[1], [3], [3], [2]]
$collection = new Collection([1, 3, 3, 2]);
$collection
->mapcat(function ($k, $v) {
return [[$k]];
})
->toArray(); //[[0], [1], [2], [3]]
Returns a lazy collection of collections of $numberOfItems items each, at $step step apart. If $step is not supplied, defaults to $numberOfItems, i.e. the partitionsdo not overlap. If a $padding collection is supplied, use its elements asnecessary to complete last partition up to $numberOfItems items. In case there are not enough padding elements, return a partition with less than $numberOfItems items.
$collection = new Collection([1, 3, 3, 2]);
$collection
->partition(3, 2, [0, 1])
->toArray(); //[[1, 3, 3], [2 => 3, 3 => 2, 0 => 0]]
$collection = new Collection([1, 3, 3, 2]);
$collection
->partition(3, 2)
->toArray(); //[[1, 3, 3], [2 => 3, 3 => 2]]
$collection = new Collection([1, 3, 3, 2]);
$collection
->partition(3)
->toArray(); //[[1, 3, 3], [3 => 2]]
Creates a lazy collection of collections created by partitioning this collection every time $partitioning will return different result.
$collection = new Collection([1, 3, 3, 2]);
$collection
->partitionBy(function ($v) {
return $v % 3 == 0;
})
->toArray(); //[[1], [1 => 3, 2 => 3], [3 => 2]]
Returns a lazy collection of items of this collection with $item added as first element. Its key will be 0.
$collection = new Collection([1, 3, 3, 2]);
$collection
->prepend(1)
->resetKeys() //both 1 have 0 key
->toArray(); //[1, 1, 3, 3, 2]
Returns a lazy collection of items of this collection with $item added as first element. Its key will be $key.
$collection = new Collection([1, 3, 3, 2]);
$collection
->prependWithKey('a', 1)
->toArray(); //['a' => 1, 0 => 1, 1 => 3, 2 => 3, 3 => 2]
Reduces the collection to single value by iterating over the collection and calling callable while passing $start and current key/item as parameters. The output of callable is used as $start in next iteration. The output of callable on last element is the return value of this function.
$collection = new Collection([1, 3, 3, 2]);
$collection->reduce(0, function ($tmp, $i) {
return $tmp + $i;
}); //9
Like reduce, but walks from last item to the first one.
$collection = new Collection([1, 3, 3, 2]);
$collection->reduceRight(0, function ($tmp, $i) {
return $tmp + $i;
}); //9
Returns a lazy collection of reduction steps.
$collection = new Collection([1, 3, 3, 2]);
$collection
->reductions(0, function ($tmp, $i) {
return $tmp + $i;
})
->toArray(); //[1, 4, 7, 9]
Returns a lazy collection of items for which $filter returned false. $filter could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1,3,3,2]);
$collection->reject(function ($item) {
return $item > 2;
})
->toArray() //[1, 3 => 2]
$collection = new Collection([1,3,3,2]);
$collection->reject(function ($key, $item) {
return $item > 2 && $key > 1;
})
->toArray() ////[1, 1 => 3, 3 => 2]
Returns a lazy collection with items from this collection equal to any key in $replacementMap replaced for their value.
$collection = new Collection([1, 3, 3, 2]);
$collection
->replace([3 => 'a'])
->toArray(); //[1, 'a', 'a', 2]
Returns collection of items from this collection but with keys being numerical from 0 upwards.
$collection = new Collection(['asd' => 1]);
$collection
->resetKeys()
->toArray(); //[1]
Returns collection of items in this collection in reverse order.
$collection = new Collection([1, 2, 3]);
$collection
->reverse()
->toArray(); //[2 => 3, 1 => 2, 0 => 1]
Returns a collection of shuffled items from this collection
$collection = new Collection([1, 3, 3, 2]);
$collection
->shuffle()
->toArray(); //something like [2 => 3, 0 => 1, 3 => 2, 1 => 3]
Returns the number of items in this collection.
$collection = new Collection([1, 3, 3, 2]);
$collection->size(); //4
Returns a lazy collection of items which are part of the original collection from item number $from to item number $to inclusive. The items before $from are also realized, just not returned.
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->slice(2, 4)
->toArray(); //[1 => 2, 2 => 3, 3 => 4]
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->slice(4)
->toArray(); //[3 => 4, 4 => 5]
Returns true if $predicament returns true for at least one item in this collection, false otherwise. $predicament could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection->every(function ($v) {
return $v < 3;
}); //true
$collection = new Collection([1, 3, 3, 2]);
$collection->find(function ($k, $v) {
return $v < 4 && $k < 2;
}, 10); //true
Returns collection sorted using $sort($item1, $item2). $sort should return true if first item is larger than the second and false otherwise.
$collection = new Collection([3, 1, 2]);
$collection
->sort(function ($a, $b) {
return $a > $b;
})
->toArray(); //[1 => 1, 2 => 2, 0 => 3]
$collection = new Collection([3, 1, 2]);
$collection
->sort(function ($k1, $v1, $k2, $v2) {
return $v1 < $v2;
})
->toArray(); //[2 => 2, 1 => 1, 0 => 3]
Returns a collection of lazy collections: [take($position), drop($position)].
$collection = new Collection([1, 3, 3, 2]);
$collection
->splitAt(2)
->toArray(); //[[1, 3], [2 => 3, 3 => 2]]
Returns a collection of lazy collections: [takeWhile($predicament), dropWhile($predicament)]. $predicament could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection
->splitWith(function ($v) {
return $v < 3;
})
->toArray(); //[[1], [1 => 3, 2 => 3, 3 => 2]]
$collection = new Collection([1, 3, 3, 2]);
$collection
->splitWith(function ($k, $v) {
return $k < 2 && $v < 3;
})
->toArray(); //[[1], [1 => 3, 2 => 3, 3 => 2]]
A form of slice that returns first $numberOfItems items.
$collection = new Collection([1, 2, 3, 4, 5]);
$collection
->take(2)
->toArray(); //[1, 2]
Returns a lazy collection of every nth item in this collection
$collection = new Collection([1, 3, 3, 2]);
$collection
->takeNth(2)
->toArray(); //[1, 2 => 3]
Returns a lazy collection of items from the start of the collection until the first item for which $predicament returns false. $predicament could take 1 argument (the item) or 2 arguments (key, item).
$collection = new Collection([1, 3, 3, 2]);
$collection
->takeWhile(function ($v) {
return $v < 3;
})
->toArray(); //[1]
$collection = new Collection([1, 3, 3, 2]);
$collection
->takeWhile(function ($k, $v) {
return $k < 2 && $v < 3;
})
->toArray(); //[1]
Converts the collection to array recursively. Obviously this is not lazy since all the items must be realized. Calls iterator_to_array internaly.
$collection = new Collection([1, 3, 3, 2]);
$collection->toArray(); //[1, 3, 3, 2]
- any callback passed with an argument with Collection typehint, should have that argument converted from array|Traversable to Collection if possible before passing to that function
- multiple collections can be passed to lets say concat
- implement somemthing like this (in a function executor object?):
$a = new Collection(['as as', 'as ds']); $a->mapcat('explode', [' ', Arg::value()]) $a->toArray() == ['as','as','as','ds'];
- write some scenario tests (multiple chained methods) and move examples into these tests