Collection pipeline library for PHP

Overview

Knapsack

Collection pipeline library for PHP

SensioLabsInsight Build Status Code Coverage Scrutinizer Code Quality

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 or a callable that takes no arguments and produces Traversable or array. The use case for the callable argument is for example a Generator, which can not be rewound so the Collection must be able to reconstruct it when rewinding itself.

$collection = new Collection([1, 2, 3]);
$collection = new Collection(new ArrayIterator([1, 2, 3]));
$generatorFactory = function () {
    foreach ([1, 2] as $value) {
        yield $value;
    }
};

$collection = new Collection($generatorFactory);

from(iterable|callable $input)

Collection::from is a static alias of the default constructor. This is the preferred way to create a Collection.

$collection = Collection::from([1, 2, 3]);
$collection = Collection::from(new ArrayIterator([1, 2, 3]));
$generatorFactory = function () {
    foreach ([1, 2] as $value) {
        yield $value;
    }
};

$collection = Collection::from($generatorFactory);

iterate(mixed $input, callable $function)

Returns lazy collection of values, where first value is $input and all subsequent values are computed by applying $function to the last value in the collection. By default this produces an infinite collection. However you can end the collection by throwing a NoMoreItems exception.

$collection = Collection::iterate(1, function ($value) {return $value + 1;}); // 1, 2, 3, 4 ...

repeat(mixed $value, int $times = -1)

Returns a lazy collection of $value repeated $times times. If $times is not provided the collection is infinite.

Collection::repeat(1); //infinite collection of ones
Collection::repeat(1, 4)->toArray(); //[1, 1, 1, 1]

range(int $start = 0, int $end = null, int step = 1)

Returns a lazy collection of numbers starting at $start, incremented by $step until $end is reached.

Collection::range(0, 6, 2)->toArray(); //[0, 2, 4, 6]

Operations

These are the operations (methods) provided by Collection class. For each one, there is a function with the same name in Knapsack namespace. The function has the same footprint as the method, except it has one extra argument prepended - the collection (array or Traversable).

Standard Iterator methods

It implements http://php.net/manual/en/class.iterator.php

append(mixed $item, mixed $key = null) : Collection

Returns a lazy collection of items of this collection with $item added as last element. If $key is not provided, its key will be the next integer in the sequence.

Collection::from([1, 3, 3, 2])
    ->append(1)
    ->toArray(); //[1, 3, 3, 2, 1]
Collection::from([1, 3, 3, 2])
    ->append(1, 'key')
    ->toArray(); //[1, 3, 3, 2, 'key' => 1]
toArray(append([1, 3, 3, 2], 1, 'key')); //[1, 3, 3, 2, 'key' => 1]

average() : int|float

Returns average of values in this collection.

Collection::from([1, 2, 3, 2])->average(); //2
Collection::from([1, 2, 3, 2, 2])->average(); //2.2
Collection::from([])->average(); //0
average([1, 2, 3]); //2

combine(iterable $collection, bool $strict = false) : Collection

Combines the values of this collection as keys, with values of $collection as values. The resulting collection has length equal to the size of smaller collection. If $strict is true, the size of both collections must be equal, otherwise ItemNotFound is thrown. When strict, the collection is realized immediately.

Collection::from(['a', 'b'])
    ->combine([1, 2])
    ->toArray(); //['a' => 1, 'b' => 2]
toArray(combine(['a', 'b'], [1, 2])); //['a' => 1, 'b' => 2]

concat(iterable ...$collections) : Collection

Returns a lazy collection with items from this collection followed by items from the collection from first argument, then second and so on.

Collection::from([1, 3, 3, 2])
    ->concat([4, 5]) //If we would convert to array here, we would loose 2 items because of same keys [4, 5, 3, 2]
    ->values() 
    ->toArray(); //[1, 3, 3, 2, 4, 5]
toArray(values(concat([1, 3, 3, 2], [4, 5]))); //[1, 3, 3, 2, 4, 5]  

contains(mixed $needle) : bool

Returns true if $needle is present in the collection.

Collection::from([1, 3, 3, 2])->contains(2); //true
contains([1, 3, 3, 2], 2); //true

countBy(callable $function) : Collection

Returns a collection of items whose keys are the return values of $function(value, key) and values are the number of items in this collection for which the $function returned this value.

Collection::from([1, 2, 3, 4, 5])
    ->countBy(function ($value) {
        return $value % 2 == 0 ? 'even' : 'odd';
    })
    ->toArray(); //['odd' => 3, 'even' => 2]  
toArray(countBy([1, 2, 3, 4, 5], function ($value) {return $value % 2 == 0 ? 'even' : 'odd';}));      

cycle() : Collection

Returns an infinite lazy collection of items in this collection repeated infinitely.

Collection::from([1, 3, 3, 2])
    ->cycle()
    ->take(8) //we take just 8 items, since this collection is infinite
    ->values()
    ->toArray(); //[1, 3, 3, 2, 1, 3, 3, 2]
toArray(values(take(cycle([1, 3, 3, 2]), 8))); //[1, 3, 3, 2, 1, 3, 3, 2]

diff(iterable ...$collections) : Collection

Returns a lazy collection of items that are in $collection but are not in any of the other arguments, indexed by the keys from the first collection. Note that the ...$collections are iterated non-lazily.

Collection::from([1, 3, 3, 2])
    ->diff([1, 3])
    ->toArray(); //[3 => 2]
toArray(diff([1, 3, 3, 2], [1, 3])); //[3 => 2]

distinct() : Collection

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::from([1, 3, 3, 2])
    ->distinct()
    ->toArray(); //[1, 3, 3 => 2] - each item has key of the first occurrence
toArray(distinct([1, 3, 3, 2])); //[1, 3, 3 => 2] - each item has key of the first occurrence

drop(int $numberOfItems) : Collection

A form of slice that returns all but first $numberOfItems items.

Collection::from([1, 2, 3, 4, 5])
    ->drop(4)
    ->toArray(); //[4 => 5]
toArray(drop([1, 2, 3, 4, 5], 4)); //[4 => 5]    

dropLast($numberOfItems = 1) : Collection

Returns a lazy collection with last $numberOfItems items skipped. These are still realized, just skipped.

Collection::from([1, 2, 3])
    ->dropLast()
    ->toArray(); //[1, 2]
Collection::from([1, 2, 3])
$collection
    ->dropLast(2)
    ->toArray(); //[1]
toArray(dropLast([1, 2, 3], 2)); //[1]    

dropWhile(callable $function) : Collection

Returns a lazy collection by removing items from this collection until first item for which $function(value, key) returns false.

Collection::from([1, 3, 3, 2])
    ->dropWhile(function ($v) {
        return $v < 3;
    })
    ->toArray(); //[1 => 3, 2 => 3, 3 => 2])
Collection::from([1, 3, 3, 2])
    ->dropWhile(function ($v, $k) {
        return $k < 2 && $v < 3;
    })
    ->toArray(); //[1 => 3, 2 => 3, 3 => 2])
Collection::from([1, 3, 3, 2])
    ->dropWhile(function ($v, $k) {
        return $k < 2 && $v < 3;
    })
    ->toArray(); //[1 => 3, 2 => 3, 3 => 2])
toArray(values(dropWhile([1, 3, 3, 2], function ($v) {return $v < 3;}))); // [3, 3, 2]

dump(int $maxItemsPerCollection = null, $maxDepth = null) : array

Dumps this collection into array (recursively).

  • scalars are returned as they are,
  • array of class name => properties (name => value and only properties accessible for this class) is returned for objects,
  • arrays or Traversables are returned as arrays,
  • for anything else result of calling gettype($input) is returned

If specified, $maxItemsPerCollection will only leave specified number of items in collection, appending a new element at end '>>>' if original collection was longer.

If specified, $maxDepth will only leave specified n levels of nesting, replacing elements with '^^^' once the maximum nesting level was reached.

If a collection with duplicate keys is encountered, the duplicate keys (except the first one) will be change into a format originalKey//duplicateCounter where duplicateCounter starts from 1 at the first duplicate. So [0 => 1, 0 => 2] will become [0 => 1, '0//1' => 2]

Collection::from([1, 3, 3, 2])->dump(); //[1, 3, 3, 2]
$collection = Collection::from(
    [
        [
            [1, [2], 3],
            ['a' => 'b'],
            new ArrayIterator([1, 2, 3])
        ],
        [1, 2, 3],
        new ArrayIterator(['a', 'b', 'c']),
        true,
        new \DusanKasan\Knapsack\Tests\Helpers\Car('sedan', 5),
        \DusanKasan\Knapsack\concat([1], [1])
    ]
);

$collection->dump(2, 3);
//[
//    [
//        [1, '^^^', '>>>'],
//        ['a' => 'b'],
//        '>>>'
//    ],
//    [1, 2, '>>>'],
//    '>>>'
//]

$collection->dump();
//[
//    [
//        [1, [2], 3],
//        ['a' => 'b'],
//        [1, 2, 3]
//    ],
//    [1, 2, 3],
//    ['a', 'b', 'c'],
//    true,
//    [
//        'DusanKasan\Knapsack\Tests\Helpers\Car' => [
//            'numberOfSeats' => 5,
//        ],
//    ],
//    [1, '0//1' => 1]
//]
dump([1, 3, 3, 2], 2); // [1, 3, '>>>']

each(callable $function) : Collection

Returns a lazy collection in which $function(value, key) is executed for each item.

Collection::from([1, 2, 3, 4, 5])
    ->each(function ($i) {
        echo $i . PHP_EOL;
    })
    ->toArray(); //[1, 2, 3, 4, 5]

//1
//2
//3
//4
//5
each([1, 2, 3, 4, 5], function ($v) {echo $v . PHP_EOL;});

//1
//2
//3
//4
//5

every(callable $function) : bool

Returns true if $function(value, key) returns true for every item in this collection, false otherwise.

Collection::from([1, 3, 3, 2])
    ->every(function ($v) {
        return $v < 3;
    }); //false
Collection::from([1, 3, 3, 2])
    ->every(function ($v, $k) {
       return $v < 4 && $k < 2;
    }); //false
every([1, 3, 3, 2], function ($v) {return $v < 5;}); //true

except(iterable $keys) : Collection

Returns a lazy collection without the items associated to any of the keys from $keys.

Collection::from(['a' => 1, 'b' => 2])
    ->except(['a'])
    ->toArray(); //['b' => 2]
toArray(except(['a' => 1, 'b' => 2], ['a'])); //['b' => 2]

extract(mixed $keyPath) : Collection

Returns a lazy collection of data extracted from $collection items by dot separated key path. Supports the * wildcard. If a key contains \ or * it must be escaped using \ character.

$collection = Collection::from([['a' => ['b' => 1]], ['a' => ['b' => 2]], ['c' => ['b' => 3]]])
$collection->extract('a.b')->toArray(); //[1, 2]
$collection->extract('*.b')->toArray(); //[1, 2, 3]
toArray(extract([['a' => ['b' => 1]], ['a' => ['b' => 2]]], 'a.b')); //[1, 2]

filter(callable $function = null) : Collection

Returns a lazy collection of items for which $function(value, key) returned true.

Collection::from([1, 3, 3, 2])
    ->filter(function ($value) {
        return $value > 2;
    })
    ->values()
    ->toArray(); //[3, 3]
Collection::from([1, 3, 3, 2])
    ->filter(function ($value, $key) {
        return $value > 2 && $key > 1;
    })
    ->toArray(); //[2 => 3]
toArray(values(filter([1, 3, 3, 2], function ($value) {return $value > 2;}))); //[3, 3]

If $function is not provided, \DusanKasan\Knapsack\identity is used so every falsy value is removed.

Collection::from([0, 0.0, false, null, "", []])
    ->filter()
    ->isEmpty(); //true
isEmpty(values(filter([0, 0.0, false, null, "", []]))); //true

find(callable $function, mixed $ifNotFound = null, bool $convertToCollection = false) : mixed|Collection

Returns first value for which $function(value, key) returns true. If no item is matched, returns $ifNotFound. If $convertToCollection is true and the return value is an iterable an instance of Collection will be returned.

Collection::from([1, 3, 3, 2])
    ->find(function ($value) {
       return $value < 3;
    }); //1
Collection::from([1, 3, 3, 2])
    ->find(function ($value) {
       return $value > 3;
    }, 10); //10
Collection::from([1, 3, 3, 2])
    ->find(function ($value, $key) {
      return $value < 3 && $key > 1;
    }); //2
//if the output can be converted to Collection (it's array or Traversable), it will be.
Collection::from([1, [4, 5], 3, 2])
    ->find(function ($value) {
      return is_array($value);
    }, [], true)
    ->size(); //2 
find([1, 3, 3, 2], function ($value) {return $value > 2;}); //3

first(bool $convertToCollection = false) : mixed|Collection

Returns first value in the collection or throws ItemNotFound if the collection is empty. If $convertToCollection is true and the return value is an iterable an instance of Collection will be returned.

Collection::from([1, 2, 3])->first(); //1
Collection::from([[1], 2, 3])->first(); //[1]
Collection::from([])->first(); //throws ItemNotFound
first([1, 2, 3]); //1

flatten(int $depth = -1) : Collection

Returns a lazy collection with one or multiple levels of nesting flattened. Removes all nesting when no $depth value is passed.

Collection::from([1,[2, [3]]])
    ->flatten()
    ->values() //1, 2 and 3 have all key 0
    ->toArray(); //[1, 2, 3]
Collection::from([1,[2, [3]]])
    ->flatten(1)
    ->values() //1, 2 and 3 have all key 0
    ->toArray(); //[1, 2, [3]]
toArray(values(flatten([1, [2, [3]]]))); //[1, 2, 3]

flip() : Collection

Returns a lazy collection where keys and values are flipped.

Collection::from(['a' => 0, 'b' => 1])
    ->flip()
    ->toArray(); //['a', 'b']
toArray(flip(['a' => 0, 'b' => 1])); //['a', 'b']

frequencies() : Collection

Returns a collection where keys are distinct items from this collection and their values are number of occurrences of each value.

Collection::from([1, 3, 3, 2])
    ->frequencies()
    ->toArray(); //[1 => 1, 3 => 2, 2 => 1]
toArray(frequencies([1, 3, 3, 2])); //[1 => 1, 3 => 2, 2 => 1]

get(mixed $key, bool $convertToCollection = false) : mixed|Collection

Returns value at the key $key. If multiple values have this key, return first. If no value has this key, throw ItemNotFound. If $convertToCollection is true and the return value is an iterable an instance of Collection will be returned.

Collection::from([1, 3, 3, 2])->get(2); //3
Collection::from([1, [1, 2]])->get(1, true)->toArray(); //[1, 2]
Collection::from([1, 3, 3, 2])->get(5); //throws ItemNotFound
get([1, 3, 3, 2], 2); //3

getOrDefault(mixed $key, mixed $default = null, bool $convertToCollection = false) : mixed|Collection

Returns value at the key $key. If multiple values have this key, return first. If no value has this key, return $default. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned.

Collection::from([1, 3, 3, 2])->getOrDefault(2); //3
Collection::from([1, 3, 3, 2])->getOrDefault(5); //null
Collection::from([1, 3, 3, 2])->getOrDefault(5, 'asd'); //'asd'
Collection::from([1, 3, 3, 2])->getOrDefault(5, [1, 2], true)->toArray(); //[1, 2]
getOrDefault([1, 3, 3, 2], 5, 'asd'); //'asd'

groupBy(callable $function) : Collection

Returns collection which items are separated into groups indexed by the return value of $function(value, key).

Collection::from([1, 2, 3, 4, 5])
    ->groupBy(function ($value) {
        return $value % 2;
    })
    ->toArray(); //[1 => [1, 3, 5], 0 => [2, 4]]
toArray(groupBy([1, 2, 3, 4, 5], function ($value) {return $value % 2;})); //[1 => [1, 3, 5], 0 => [2, 4]]

groupByKey(mixed $key) : Collection

Returns collection where items are separated into groups indexed by the value at given key.

Collection::from([
        ['letter' => 'A', 'type' => 'caps'],
        ['letter' => 'a', 'type' => 'small'],
        ['letter' => 'B', 'type' => 'caps'],
    ])
    ->groupByKey('type')
    ->map('DusanKasan\Knapsack\toArray')
    ->toArray();
    // [ 'caps' => [['letter' => 'A', 'type' => 'caps'], ...], 'small' => [['letter' => 'a', 'type' => 'small']]]
$data = [
    ['letter' => 'A', 'type' => 'caps'],
    ['letter' => 'a', 'type' => 'small'],
    ['letter' => 'B', 'type' => 'caps'],
];
toArray(map(groupByKey($data, 'type'), 'toArray')); //[ 'caps' => [['letter' => 'A', 'type' => 'caps'], ...], 'small' => [['letter' => 'a', 'type' => 'small']]]

has(mixed $key) : bool

Checks for the existence of $key in this collection.

Collection::from(['a' => 1])->has('a'); //true
has(['a' => 1], 'a'); //true

indexBy(callable $function) : Collection

Returns a lazy collection by changing keys of this collection for each item to the result of $function(value, key) for that key/value.

Collection::from([1, 3, 3, 2])
    ->indexBy(function ($v) {
        return $v;
    })
    ->toArray(); //[1 => 1, 3 => 3, 2 => 2]
toArray(indexBy([1, 3, 3, 2], '\DusanKasan\Knapsack\identity')); //[1 => 1, 3 => 3, 2 => 2]

interleave(iterable ...$collections) : Collection

Returns a lazy collection of first item from first collection, first item from second, second from first and so on. Works with any number of arguments.

Collection::from([1, 3, 3, 2])
    ->interleave(['a', 'b', 'c', 'd', 'e'])
    ->values()
    ->toArray(); //[1, 'a', 3, 'b', 3, 'c', 2, 'd', 'e']
toArray(interleave([1, 3, 3, 2], ['a', 'b', 'c', 'd', 'e'])); //[1, 'a', 3, 'b', 3, 'c', 2, 'd', 'e']

interpose(mixed $separator) : Collection

Returns a lazy collection of items of this collection separated by $separator item.

Collection::from([1, 2, 3])
    ->interpose('a')
    ->values() // we must reset the keys, because each 'a' has undecided key
    ->toArray(); //[1, 'a', 2, 'a', 3]
toArray(interpose([1, 2, 3], 'a')); //[1, 'a', 2, 'a', 3] 

intersect(iterable ...$collections) : Collection

Returns a lazy collection of items that are in $collection and all the other arguments, indexed by the keys from the first collection. Note that the ...$collections are iterated non-lazily.

Collection::from([1, 2, 3])
    ->intersect([1, 3])
    ->toArray(); //[1, 2 => 3]
toArray(intersect([1, 2, 3],[1, 3])); //[1, 2 => 3] 

isEmpty() : bool

Returns true if is collection is empty. False otherwise.

Collection::from([1, 3, 3, 2])->isEmpty(); //false
isEmpty([1]); //false

isNotEmpty() : bool

Opposite of isEmpty

Collection::from([1, 3, 3, 2])->isNotEmpty(); //true
isNotEmpty([1]); //true

keys() : Collection

Returns a lazy collection of the keys of this collection.

Collection::from(['a' => [1, 2], 'b' => [2, 3]])
    ->keys()
    ->toArray(); //['a', 'b']
toArray(keys(['a' => [1, 2], 'b' => [2, 3]])); //['a', 'b']

last(bool $convertToCollection = false) : mixed|Collection

Returns last value in the collection or throws ItemNotFound if the collection is empty. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned.

Collection::from([1, 2, 3])->last(); //3
Collection::from([1, 2, [3]])->last(true)->toArray(); //[1]
Collection::from([])->last(); //throws ItemNotFound
last([1, 2, 3]); //3

map(callable $function) : Collection

Returns collection where each value is changed to the output of executing $function(value, key).

Collection::from([1, 3, 3, 2])
    ->map(function ($value) {
        return $value + 1;
    })
    ->toArray(); //[2, 4, 4, 3]
toArray(map([1, 3, 3, 2], '\DusanKasan\Knapsack\increment')); //[2, 4, 4, 3]

mapcat(callable $mapper) : Collection

Returns a lazy collection which is a result of calling map($mapper(value, key)) and then flatten(1).

Collection::from([1, 3, 3, 2])
    ->mapcat(function ($value) {
        return [[$value]];
    })
    ->values()
    ->toArray(); //[[1], [3], [3], [2]]
Collection::from([1, 3, 3, 2])
    ->mapcat(function ($key, $value) {
        return [[$key]];
    })
    ->values()
    ->toArray(); //[[0], [1], [2], [3]]
toArray(values(mapcat([1, 3, 3, 2], function ($value) {return [[$value]];}))); //[[1], [3], [3], [2]]

max() : mixed

Returns mthe maximal value in this collection.

Collection::from([1, 2, 3, 2])->max(); //3
Collection::from([])->max(); //null
max([1, 2, 3, 2]); //3

min() : mixed

Returns mthe minimal value in this collection.

Collection::from([1, 2, 3, 2])->min(); //1
Collection::from([])->min(); //null
min([1, 2, 3, 2]); //1

only(iterable $keys) : Collection

Returns a lazy collection of items associated to any of the keys from $keys.

Collection::from(['a' => 1, 'b' => 2])
    ->only(['b'])
    ->toArray(); //['b' => 2]
toArray(only(['a' => 1, 'b' => 2], ['b'])); //['b' => 2]

partition(int $numberOfItems, int $step = 0, iterable $padding = []) : Collection

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::from([1, 3, 3, 2])
    ->partition(3, 2, [0, 1])
    ->toArray(); //[[1, 3, 3], [2 => 3, 3 => 2, 0 => 0]]
Collection::from([1, 3, 3, 2])
    ->partition(3, 2)
    ->toArray(); //[[1, 3, 3], [2 => 3, 3 => 2]]
Collection::from([1, 3, 3, 2])
    ->partition(3)
    ->toArray(); //[[1, 3, 3], [3 => 2]]
toArray(partition([1, 3, 3, 2], 3)); //[[1, 3, 3], [3 => 2]]

partitionBy(callable $function) : Collection

Creates a lazy collection of collections created by partitioning this collection every time $function(value, key) will return different result.

Collection::from([1, 3, 3, 2])
    ->partitionBy(function ($v) {
        return $v % 3 == 0;
    })
    ->toArray(); //[[1], [1 => 3, 2 => 3], [3 => 2]]
toArray(partitionBy([1, 3, 3, 2], function ($value) {return $value % 3 == 0;})); //[[1], [1 => 3, 2 => 3], [3 => 2]]

prepend(mixed $item, mixed $key = null) : Collection

Returns a lazy collection of items of this collection with $item added as first element. Its key will be $key. If $key is not provided, its key will be the next numerical index.

Collection::from([1, 3, 3, 2])
    ->prepend(1)
    ->values() //both 1 have 0 key
    ->toArray(); //[1, 1, 3, 3, 2]
Collection::from([1, 3, 3, 2])
    ->prepend(1, 'a')
    ->toArray(); //['a' => 1, 0 => 1, 1 => 3, 2 => 3, 3 => 2]

printDump(int $maxItemsPerCollection = null, $maxDepth = null) : Collection

Calls dump on $input and then prints it using the var_export. Returns the collection. See dump function for arguments and output documentation.

Collection::from([1, 3, 3, 2])
    ->printDump()
    ->toArray(); //[1, 3, 3, 2]
toArray(printDump([1, 3, 3, 2])); //[1, 3, 3, 2]

realize() : Collection

Realizes collection - turns lazy collection into non-lazy one by iterating over it and storing the key/values.

$helper->setValue(1); 

$realizedCollection = Collection::from([1, 3, 3, 2])
    ->map(function ($item) use ($helper) {return $helper->getValue() + $item;})
    ->realize();
    
$helper->setValue(10);
$realizedCollection->toArray(); // [2, 4, 4, 3]
toArray(realize([1, 3, 3, 2])); //[1, 3, 3, 2]

reduce(callable $function, mixed $start, bool $convertToCollection = false) : mixed|Collection

Reduces the collection to single value by iterating over the collection and calling $function(tmp, value, key) 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. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned.

Collection::from([1, 3, 3, 2])
    ->reduce(
        function ($tmp, $i) {
            return $tmp + $i;
        }, 
        0
    ); //9
    
Collection::from([1, 3, 3, 2])
    ->reduce(
        function ($tmp, $i) {
            $tmp[] = $i + 1;
            return $tmp;
        }, 
        [],
        true
    )
    ->first(); //2
reduce([1, 3, 3, 2], function ($tmp, $value) {return $tmp + $value;}, 0); //9

reduceRight(callable $function, mixed $start, bool $convertToCollection = false) : mixed|Collection

Like reduce, but walks from last item to the first one. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned.

Collection::from([1, 3, 3, 2])
    ->reduceRight(
        function ($tmp, $i) {
            return $tmp + $i;
        }, 
        0
    ); //9
    
Collection::from([1, 3, 3, 2])
    ->reduce(
        function ($tmp, $i) {
            $tmp[] = $i + 1;
            return $tmp;
        }, 
        [],
        true
    )
    ->first(); //3
reduceRight([1, 3, 3, 2], function ($tmp, $value) {return $tmp + $value;}, 0); //9

reductions(callable $reduction, mixed $start) : Collection

Returns a lazy collection of reduction steps.

Collection::from([1, 3, 3, 2])
    ->reductions(function ($tmp, $i) {
        return $tmp + $i;
    }, 0)
    ->toArray(); //[1, 4, 7, 9]
toArray(reductions([1, 3, 3, 2], function ($tmp, $value) {return $tmp + $value;}, 0)); //[1, 4, 7, 9]

reject(callable $filter) : Collection

Returns a lazy collection of items for which $filter(value, key) returned false.

Collection::from([1, 3, 3, 2])
    ->reject(function ($value) {
        return $value > 2;
    })
    ->toArray(); //[1, 3 => 2]
Collection::from([1, 3, 3, 2])
    ->reject(function ($value, $key) {
        return $value > 2 && $key > 1;
    })
    ->toArray(); //[1, 1 => 3, 3 => 2]
toArray(reject([1, 3, 3, 2], function ($value) {return $value > 2;})); //[1, 1 => 3, 3 => 2]

replace(iterable $replacementMap) : Collection

Returns a lazy collection with items from this collection equal to any key in $replacementMap replaced for their value.

Collection::from([1, 3, 3, 2])
    ->replace([3 => 'a'])
    ->toArray(); //[1, 'a', 'a', 2]
toArray(replace([1, 3, 3, 2], [3 => 'a'])); //[1, 'a', 'a', 2]

replaceByKeys(iterable $replacementMap) : Collection

Returns a lazy collection with items from $collection, but items with keys that are found in keys of $replacementMap are replaced by their values.

Collection::from([1, 3, 3, 2])
    ->replace([3 => 'a'])
    ->toArray(); //[1, 3, 3, 'a']
toArray(replace([1, 3, 3, 2], [3 => 'a'])); //[1, 3, 3, 'a']

reverse() : Collection

Returns a non-lazy collection of items in this collection in reverse order.

Collection::from([1, 2, 3])
    ->reverse()
    ->toArray(); //[2 => 3, 1 => 2, 0 => 1]
toArray(reverse([1, 2, 3])); //[2 => 3, 1 => 2, 0 => 1]

second(bool $convertToCollection = false) : mixed|Collection

Returns the second item of $collection or throws ItemNotFound if $collection is empty or has 1 item. If $convertToCollection is true and the return value is an iterable it is converted to Collection.

Collection::from([1, 3, 3, 2])->second(); //3
second([1, 3]); //3

shuffle() : Collection

Returns a collection of shuffled items from this collection

Collection::from([1, 3, 3, 2])
    ->shuffle()
    ->toArray(); //something like [2 => 3, 0 => 1, 3 => 2, 1 => 3]
toArray(shuffle([1, 3, 3, 2])); //something like [2 => 3, 0 => 1, 3 => 2, 1 => 3]

size() : int

Returns the number of items in this collection.

Collection::from([1, 3, 3, 2])->size(); //4
size([1, 3, 3, 2]); //4

sizeIsBetween(int $fromSize, int $toSize) : bool

Checks whether this collection has between $fromSize to $toSize items. $toSize can be smaller than $fromSize.

Collection::from([1, 3, 3, 2])->sizeIsBetween(3, 5); //true
sizeIsBetween([1, 3, 3, 2], 3, 5); //true

sizeIs(int $size) : bool

Checks whether this collection has exactly $size items.

Collection::from([1, 3, 3, 2])->sizeIs(4); //true
sizeIs([1, 3, 3, 2], 4); //true

sizeIsGreaterThan(int $size) : bool

Checks whether this collection has more than $size items.

Collection::from([1, 3, 3, 2])->sizeIsGreaterThan(3); //true
sizeIsGreaterThan([1, 3, 3, 2], 3); //true

sizeIsLessThan(int $size) : bool

Checks whether this collection has less than $size items.

Collection::from([1, 3, 3, 2])->sizeIsLessThan(5); //true
sizeIsLessThan([1, 3, 3, 2], 5); //true

slice(int $from, int $to) : Collection

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::from([1, 2, 3, 4, 5])
    ->slice(2, 4)
    ->toArray(); //[2 => 3, 3 => 4]
Collection::from([1, 2, 3, 4, 5])
    ->slice(4)
    ->toArray(); //[4 => 5]
toArray(slice([1, 2, 3, 4, 5], 4)); //[4 => 5]

some(callable $function) : bool

Returns true if $function(value, key) returns true for at least one item in this collection, false otherwise.

Collection::from([1, 3, 3, 2])
    ->some(function ($value) {
       return $value < 3;
    }); //true
Collection::from([1, 3, 3, 2])
    ->some(function ($value, $key) {
       return $value < 4 && $key < 2;
    }); //true
some([1, 3, 3 ,2], function ($value) {return $value < 3;}); //true

sort(callable $function) : Collection

Returns collection sorted using $function(value1, value2, key1, key2). $function should return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.

Collection::from([3, 1, 2])
    ->sort(function ($a, $b) {
        return $a > $b;
    })
    ->toArray(); //[1 => 1, 2 => 2, 0 => 3]
Collection::from([3, 1, 2])
    ->sort(function ($v1, $v2, $k1, $k2) {
        return $v1 < $v2;
    })
    ->toArray(); //[2 => 2, 1 => 1, 0 => 3]
toArray(sort([3, 1, 2], function ($a, $b) {return $a > $b;})); //[1 => 1, 2 => 2, 0 => 3]

splitAt(int $position) : Collection

Returns a collection of lazy collections: [take($position), drop($position)].

Collection::from([1, 3, 3, 2])
    ->splitAt(2)
    ->toArray(); //[[1, 3], [2 => 3, 3 => 2]]
toArray(splitAt([1, 3, 3, 2], 2)); //[[1, 3], [2 => 3, 3 => 2]]

splitWith(callable $function) : Collection

Returns a collection of lazy collections: [takeWhile($function(value, key)), dropWhile($function(value, key))].

Collection::from([1, 3, 3, 2])
    ->splitWith(function ($value) {
        return $value < 3;
    })
    ->toArray(); //[[1], [1 => 3, 2 => 3, 3 => 2]]
Collection::from([1, 3, 3, 2])
    ->splitWith(function ($value, $key) {
        return $key < 2 && $value < 3;
    })
    ->toArray(); //[[1], [1 => 3, 2 => 3, 3 => 2]]
toArray(splitWith([1, 3, 3, 2], function ($value) {return $value < 3;})); //[[1], [1 => 3, 2 => 3, 3 => 2]]

sum() : int|float

Returns a sum of all values in this collection.

Collection::from([1, 2, 3])->sum(); //6
Collection::from([1, 2, 3, 1.5])->sum(); //7.5
Collection::from([])->sum(); //0
sum([1, 2, 3]); //6

take(int $numberOfItems) : Collection

A form of slice that returns first $numberOfItems items.

Collection::from([1, 2, 3, 4, 5])
    ->take(2)
    ->toArray(); //[1, 2]
toArray(take([1, 2, 3, 4, 5], 2)); //[1, 2]

takeNth(int $step) : Collection

Returns a lazy collection of every nth item in this collection

Collection::from([1, 3, 3, 2])
    ->takeNth(2)
    ->toArray(); //[1, 2 => 3]
toArray(takeNth([1, 3, 3, 2], 2)); //[1, 2 => 3]

takeWhile(callable $function) : Collection

Returns a lazy collection of items from the start of the collection until the first item for which $function(value, key) returns false.

Collection::from([1, 3, 3, 2])
    ->takeWhile(function ($value) {
        return $value < 3;
    })
    ->toArray(); //[1]
Collection::from([1, 3, 3, 2])
    ->takeWhile(function ($value, $key) {
        return $key < 2 && $value < 3;
    })
    ->toArray(); //[1]
toArray(takeWhile([1, 3, 3, 2], function ($value) {return $value < 3;})); //[1]

transform(callable $transformer) : Collection

Uses a $transformer callable on itself that takes a Collection and returns Collection. This allows for creating a separate and reusable algorithms.

$transformer = function (Collection $collection) {
    return $collection
        ->filter(function ($item) {
            return $item > 1;
        })
        ->map('\DusanKasan\Knapsack\increment');
};

Collection::from([1, 3, 3, 2])
    ->transform($transformer)
    ->toArray(); //[4, 4, 3]

toArray() : array

Converts the collection to array recursively. Obviously this is not lazy since all the items must be realized. Calls iterator_to_array internaly.

Collection::from([1, 3, 3, 2])->toArray(); //[1, 3, 3, 2]
toArray([1, 3, 3, 2]); //[1, 3, 3, 2]

toString() : string

Returns a string by concatenating this collection's values into a string.

Collection::from([1, 'a', 3, null])->toString(); //'1a3'
Collection::from([])->toString(); //''
toString([1, 'a', 3, null]); //'1a3'

transpose(iterable $collection) : string

Returns a non-lazy collection by interchanging each row and the corresponding column. The input must be a multi-dimensional collection or an InvalidArgumentException is thrown.

$arr = Collection::from([
    new Collection([1, 2, 3]),
    new Collection([4, 5, new Collection(['foo', 'bar'])]),
    new Collection([7, 8, 9]),
])->transpose()->toArray();

// $arr =
// [
//    new Collection([1, 4, 7]),
//    new Collection([2, 5, 8]),
//    new Collection([3, new Collection(['foo', 'bar']), 9]),
// ]

zip(iterable ...$collections) : Collection

Returns a lazy collection of non-lazy collections of items from nth position from this collection and each passed collection. Stops when any of the collections don't have an item at the nth position.

Collection::from([1, 2, 3])
    ->zip(['a' => 1, 'b' => 2, 'c' => 4])
    ->map('\DusanKasan\Knapsack\toArray')
    ->toArray(); //[[1, 'a' => 1], [1 => 2, 'b' => 2], [2 => 3, 'c' => 4]]

Collection::from([1, 2, 3])
    ->zip(['a' => 1, 'b' => 2])
    ->map('\DusanKasan\Knapsack\toArray')
    ->toArray(); //[[1, 'a' => 1], [1 => 2, 'b' => 2]]
toArray(map(zip([1, 2, 3], ['a' => 1, 'b' => 2, 'c' => 4]), '\DusanKasan\Knapsack\toArray')); //[[1, 'a' => 1], [1 => 2, 'b' => 2], [2 => 3, 'c' => 4]]

Utility functions

These are the functions bundled with Knapsack to make your life easier when transitioning into functional programming.

identity(mixed $value)

Returns $value

$value === identity($value); //true

compare(mixed $a, mixed $b)

Default comparator function. Returns a negative number, zero, or a positive number when $a is logically 'less than', 'equal to', or 'greater than' $b.

compare(1, 2); //-1

increment(int $value)

Returns value of $value incremented by one.

increment(0) === 1; //true

decrement(int $value)

Returns value of $value decremented by one.

decrement(2) === 1; //true
Comments
  • Update some vulnerable dependencies

    Update some vulnerable dependencies

    Some packages are claimed as vulnerable via the Scrutinizer task. So upgraded these packages. Btw I suppose it might be the time to drop PHP 5.6 support...

    opened by issei-m 12
  • Collection pipeline with more

    Collection pipeline with more "advanced" filtering

    Hi, first of all - what a library! I tip my hat.

    I'm looking for a lib where I could filter a collection (or create a "collection pipeline") based on a boolean expressions that are not only AND-ed.

    Example: I have a collection:

    $testCollection = [
        ['testField' => 'bar', 'otherValue' => 2],
        ['testField' => 'foo', 'otherValue' => 3],
        ['testField' => 'foo', 'otherValue' => 4],
    ];
    

    and would like to apply a logical filter that looks like A OR (B AND C) where: A - testField contains a letter o B - otherValue is even C - testField consists of 3 letters and otherValue is lower than 10

    The boolean statement and A, B, C are just random examples.

    I know it can be done by creating two collections: one for A, one for B AND C and then concatenating them. I'm looking for a more streamlined way though as the way to do it would differ with a way the statement look and would not scale well.

    Any ideas?

    opened by mirfilip 6
  • Add more collection methods?

    Add more collection methods?

    Currently implemented methods should be enough to express any collection operation. Nevertheless, review other popular collection libraries and their methods and consider porting them here.

    No methods that are used in single scope should be ported (think form values processing).

    enhancement question 
    opened by DusanKasan 6
  • Extract Interface for Collection Functions

    Extract Interface for Collection Functions

    While it is not possible to have true "named collections", because the CollectionTrait delegates to name-spaced functions that hard code Collection as return values. Changing this would require a significant re-write. We can achieve a "good enough" solution for when we want to pass objects that behave like Collection but are not instances of Collection themselves.

    This PR extracts an interface, CollectionInterface from CollectionTrait -- this is all the functionality we expect to see in a Collection instance, except for instantiation methods like range and from -- assuming that custom collections will want to determine how to instantiate themselves. Methods like range don't make sense when you are trying to represent a collection of users, for instance. We add this interface to Collection for completeness.

    One way a naive named collection could be made is like this:

    class UserCollection implements IteratorAggregate, CollectionInterface
    {
        use CollectionTrait;
    
        private $users;
    
        public function __construct(User ...$users)
        {
            $this->users = $users;
        }
    
        public function getIterator(): ArrayIterator
        {
            return new ArrayIterator($this->users);
        }
    }
    
    

    We can't do anything about the return values of the underlying methods like map, but this approach at least helps with type-safety when we want to be specific about the kind of collection we're dealing with.

    opened by andysnell 5
  • Does this library support diffing recursively?

    Does this library support diffing recursively?

    Are multi-dimension arrays supported? E.g., PHP's array_diff would return "Array to string conversion" error if any of the compared arrays contain a (sub-)array.

    opened by JanisE 5
  • Best way to do with return in foreach?

    Best way to do with return in foreach?

    Hi!

    I would like to know how can I use pipeline collection with this code:

    foreach ($myArrayOfAsciiCode as $ascii) {
        if (strpos($path, chr($ascii))) {
            return true;
        }
    }
    
    return false;
    

    I do this:

    return !Collection::from(self::INVALID_PATH_ASCII)
                      ->filter(
                          function (string $ascii) use ($path): bool {
                              return strpos($path, chr($ascii));
                          }
                      )
                      ->isEmpty();
    

    Is this the better way?

    Thanks

    opened by roukmoute 5
  • groupBy - Allow group by keys

    groupBy - Allow group by keys

    Hey,

    First of all thanks for the great library. I was just wondering if we can add an option to groupBy a key in the array. This is more useful when dealing with associated arrays. This feature is there in almost all collection libraries (Laravel, CakePHP, etc)

    An example usecase.

    $categories = [
       ['id' => 1, 'name' => 'Category 1', 'parent_id' => 0],
       ['id' => 2, 'name' => 'Category 2', 'parent_id' => 1],
       ['id' => 3, 'name' => 'Category 3', 'parent_id' => 1],
    ];
    
    $grouped = Collection::from($categories)->groupBy('parent_id');
    

    Cheers

    enhancement 
    opened by shameerc 5
  • New Feature: Allow $default to be a callback

    New Feature: Allow $default to be a callback

    Hello,

    It would be nice if the getOrDefault & find methods would execute and return the result of a closure if provided.

    return $default instanceof \Closure ? $default() : $default
    

    This would allow for code like this:

    Collection::from()->getOrDefault('foo', function() {
    	return new SomeExpensiveObject();
    });
    
    Collection::from()->getOrDefault('foo', function() {
    	throw new \Exception('foo is required');
    });
    

    I would be happy to build & submit a PR if this is a desired feature for this project.

    Thank You

    opened by josephlavin 4
  • Improve performance dramatically

    Improve performance dramatically

    Hi, thanks for releasing this great library! I prefer this rather than nikic/iter because this takes the OOP-like approach. However there's some points losing to iter in performance, so I want to improve that.

    Please see the following simple snippet comparing two libs performance:

    $filterFn = function () { return true; };
    $mapFn    = function ($v) { return $v; };
    
    $sampleCollection = array_fill(0, 1000000, 'x');
    
    $st = microtime(true);
    iterator_to_array(
        \iter\map(
            $mapFn,
            \iter\filter(
                $filterFn,
                $sampleCollection
            )
        )
    );
    echo sprintf('iter: %.6f sec', microtime(true) - $st), "\n";
    
    $st = microtime(true);
    iterator_to_array(
        \DusanKasan\Knapsack\Collection::from($sampleCollection)
            ->filter($filterFn)
            ->map($mapFn)
    );
    echo sprintf('Knapsack: %.6f sec', microtime(true) - $st), "\n";
    

    note: Both of them take the same approach, which is generating the lazy-collection that computes on operations of traversing.

    In my results:

    iter: 5.947058 sec
    Knapsack: 36.837850 sec
    

    As you can see, the difference is so huge, Knapsack is explicitly slow compared with iter.

    What's problem

    Knapsack\Collection implements Iterator but iter doesn't. So, it seems to be caused to lead bad performance impact, and my patch on this PR exactly focuses on that - Iterator is only used when the logic really needs (e.g. interleave).

    In fact, the result ends up like following after my patch is applied:

    iter: 5.480360 sec
    Knapsack: 5.591903 sec
    

    Besides, the performance check you provide achieves the good result than before:

    before:

    $ php tests/performance/map_vs_array_map.php
    +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | operation details                                                                  | native execution time | collection execution time | difference (percent) |
    +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | array_map vs Collection::map on 10000 integers (addition)                          | 0.014703154563904s    | 0.18897454738617s         | 1285%                |
    | array_map vs Collection::map on 10000 strings (concatenation)                      | 0.021801376342773s    | 0.25404767990112s         | 1165%                |
    | array_map vs Collection::map on 10000 objects (object to field value)              | 0.017388963699341s    | 0.20323369503021s         | 1168%                |
    | array_map vs Collection::map on 10000 md5 invocations                              | 0.029221034049988s    | 0.20992181301117s         | 718%                 |
    | array_map vs Collection::map on 10000 integers n, counting sum(0, n) the naive way | 9.6942321062088s      | 9.6334856987s             | 99%                  |
    +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    
    $ php tests/performance/reduce_vs_array_reduce.php
    +-------------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | operation details                                                                         | native execution time | collection execution time | difference (percent) |
    +-------------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | array_reduce vs Collection::reduce on 10000 integers (addition)                           | 0.025390815734863s    | 0.16423649787903s         | 646%                 |
    | array_reduce vs Collection::reduce on 10000 strings (concatenation)                       | 0.034023523330688s    | 0.13736398220062s         | 403%                 |
    | array_reduce vs Collection::reduce on 10000 object (object to field value)                | 0.018415665626526s    | 0.12140438556671s         | 659%                 |
    | array_reduce vs Collection::reduce on 10000 md5 invocations                               | 0.030937099456787s    | 0.12602620124817s         | 407%                 |
    | array_reduce vs Collection::reduce for 10000 integers n, counting sum(0, n) the naive way | 9.942040514946s       | 9.839556646347s           | 98%                  |
    +-------------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    

    after:

    $ php tests/performance/map_vs_array_map.php
    +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | operation details                                                                  | native execution time | collection execution time | difference (percent) |
    +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | array_map vs Collection::map on 10000 integers (addition)                          | 0.014729452133179s    | 0.027163410186768s        | 184%                 |
    | array_map vs Collection::map on 10000 strings (concatenation)                      | 0.015099954605103s    | 0.028594446182251s        | 189%                 |
    | array_map vs Collection::map on 10000 objects (object to field value)              | 0.017368984222412s    | 0.029714369773865s        | 171%                 |
    | array_map vs Collection::map on 10000 md5 invocations                              | 0.027954363822937s    | 0.040060210227966s        | 143%                 |
    | array_map vs Collection::map on 10000 integers n, counting sum(0, n) the naive way | 9.7906902074814s      | 10.129155540466s          | 103%                 |
    +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    
    $ php tests/performance/reduce_vs_array_reduce.php
    +-------------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | operation details                                                                         | native execution time | collection execution time | difference (percent) |
    +-------------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    | array_reduce vs Collection::reduce on 10000 integers (addition)                           | 0.014452695846558s    | 0.02052640914917s         | 142%                 |
    | array_reduce vs Collection::reduce on 10000 strings (concatenation)                       | 0.032758975028992s    | 0.039231991767883s        | 119%                 |
    | array_reduce vs Collection::reduce on 10000 object (object to field value)                | 0.016609573364258s    | 0.022549653053284s        | 135%                 |
    | array_reduce vs Collection::reduce on 10000 md5 invocations                               | 0.028601837158203s    | 0.034251570701599s        | 119%                 |
    | array_reduce vs Collection::reduce for 10000 integers n, counting sum(0, n) the naive way | 9.5106759309769s      | 9.3758726358414s          | 98%                  |
    +-------------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+
    

    BC Break

    This change slightly makes a BC break - Collection can't call methods defined at Iterator anymore, but it's a little thing. Not much impact I think.

    The replacing like following may help to work as in the previous:

    before:

    $collection = Collection::from([1, 2, 3]);
    $collection->rewind();
    $collection->key();
    $collection->current();
    // ...
    

    after:

    $it = new \IteratorIterator(Collecton::from([1, 2, 3]));
    $it->rewind();
    $it->key();
    $it->current();
    // ...
    

    Thanks!

    opened by issei-m 4
  • Add a transpose function

    Add a transpose function

    Hi, I would like to add a transpose function to a collection

    // input
    [
        [1,2,3],
        [4,5,6],
        [7,8,9]
    ]
    // output
    [
        [1,4,7],
        [2,5,8],
        [3,6,9]
    ]
    

    switching the row and column indexes. I have this PR, but I think the code could be improved and I need to update the README.md.

    Do you think this would be a useful function? Are there any suggestions you would have for testing edge cases?

    Thanks

    opened by petemcfarlane 4
  • Added groupByKey - method to group collection by key

    Added groupByKey - method to group collection by key

    This will enable us to group nested collection using a key. For example

    $categories = [
       ['id' => 1, 'name' => 'Category 1', 'parent_id' => 0],
       ['id' => 2, 'name' => 'Category 2', 'parent_id' => 1],
       ['id' => 3, 'name' => 'Category 3', 'parent_id' => 1],
    ];
    $grouped = Collection::from($categories)->groupByKey('parent_id');
    

    Ref #7

    opened by shameerc 4
  • Removed wrong @throws annotation

    Removed wrong @throws annotation

    The getOrDefault method cannot throw an ItemNotFound, as they are caught in the getOrDefault function (which doesn't have the annotation either). This requires me to annotate upstream methods, which annoys me :)

    opened by Radiergummi 0
  • slice function params

    slice function params

    The docs for the slice function says:

    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.

    But its $to param usage is exclusive in the implementation: if ($index >= $from && ($index < $to || $to == -1))

    Also, naming an index (starting at zero) as the item number may be a little confusing.

    Did I get something wrong, please? If this is an issue, is it a documentation or implementation one? Is it intended to be inclusive or exclusive?

    opened by jaroslavlibal 0
  • Find key?

    Find key?

    I hope I'm not missing something obvious..

    I want to use find() but have it return key rather than value so that I can use it with replace(). I could do this with reduce() instead I guess but was hoping I could make it more granular. Is there way to do this?

    opened by FoxxMD 0
  • first throws for an empty collection

    first throws for an empty collection

    In clojure (first nil) and (first ()) returns nil. Knapsack throws, which is quite disconcerting. It would be great if this behavior was consistent with how clojure does this.

    opened by akond 0
Releases(10.0.0)
  • 10.0.0(Apr 25, 2017)

  • 9.0.0(Apr 14, 2017)

  • 8.4.0(Apr 3, 2017)

  • 8.3.0(Jun 9, 2016)

  • 8.2.0(Jun 9, 2016)

  • 8.1.1(Jun 9, 2016)

    • Fixed bug: the only function always included the item with key equal to zero in the result. Caused by comparing string == 0. Also affected extract.
    Source code(tar.gz)
    Source code(zip)
  • 8.1.0(Jun 9, 2016)

  • 8.0.0(Jun 9, 2016)

    • Breaking change: sum function will return integer by default, float if there are float type elements
    • Breaking change: average function will not force return float and will return integer if the sum/count result is integer
    Source code(tar.gz)
    Source code(zip)
  • 7.0.0(Jun 9, 2016)

    • The functionality of sum, average, min, max and concatenate moved into collection.
    • Sum collection function added
    • Average collection function added
    • Min collection function added
    • Max collection function added
    • ToString collection function added
    • Breaking change: sum utility function removed
    • Breaking change: average utility function removed
    • Breaking change: min utility function removed
    • Breaking change: max utility function removed
    • Breaking change: concatenate utility function removed
    Source code(tar.gz)
    Source code(zip)
  • 6.2.0(May 27, 2016)

    • sizeIsExactly function added
    • sizeIsGreaterThan function added
    • sizeIsLessThan function added
    • sizeIsBetween function added

    Use these for size comparison as they don't need to traverse entire collection (if possible) to provide result.

    Source code(tar.gz)
    Source code(zip)
  • 6.1.0(May 20, 2016)

  • 6.0.0(May 4, 2016)

    • Intersect function added
    • Average utility function added
    • Concatenate utility function added
    • Reduce/reduceRight/second now have the returnAsCollection flag
    • Breaking change: getNth removed (to solve ambiguity with takeNth, use values()->get(n))
    • Breaking change: difference renamed to diff
    Source code(tar.gz)
    Source code(zip)
  • 5.0.0(May 2, 2016)

    • Zip function added // zips together items from multiple collections
    • Extract function added // extracts data from sub-collections using dot separated key path
    • Transform function added // transforms a collection using callable
    • Breaking change: combine now stops when it runs out of keys or values
    • Breaking change: pluck removed (replaced by extract)
    Source code(tar.gz)
    Source code(zip)
  • 3.1.0(Apr 27, 2016)

    There are some new functions, specifically:

    • second() // seems useless but really usefull :)
    • combine($values) // uses current collection as keys, $values as values
    • except($keys) // rejects every item with key found in $keys
    • only($keys) // filters only items with key found in $keys
    • difference(...$collections) // filters items that are in the original collection but not in any of $collections
    • flip() // flips keys with values
    • has($key) // checks for the existence of item with $key key

    CollectionTrait has been introduced and its usage documented in readme.

    A handful of bugfixes also:

    • Collection constructor might have experienced conflicts between callable (in array form) and array arguments
    • Pluck might have failed on heterogenous collections. Now ignores non-collection items.
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(Apr 21, 2016)

    Automatic conversion of return values to Collections is no longer happening if you do not explicitly require it. Details in documentation.

    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Apr 11, 2016)

    Project moved to new global namespace DusanKasan (whole namespace is DusanKasan\Knapsack) to avoid conflicts. Collection::realize was introduced to force materialization of the collection (turning lazy collection into non-lazy). Collection::concat and Collection::interleave are now variadic.

    Breaking change: toArray and Collection::toArray now behave more logicaly and do not convert items recursively.

    Documentation/Readme reflects all these changes.

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Apr 7, 2016)

  • 0.3.0(Nov 19, 2015)

    Collection class now uses Generator functions under the hood. The functions are also accessible allowing for functional programming. Performance improved 2x.

    Source code(tar.gz)
    Source code(zip)
Owner
Dušan Kasan
I'm a polyglot software developer based in Bratislava, Slovakia who specializes in scalable web-based solutions.
Dušan Kasan
Laravel Pipeline with DB transaction support, events and additional methods

Laravel Enhanced Pipeline Laravel Pipeline with DB transaction support, events and additional methods #StandWithUkraine Installation Install the packa

Michael Rubél 33 Dec 3, 2022
This library provides a collection of native enum utilities (traits) which you almost always need in every PHP project.

This library provides a collection of native enum utilities (traits) which you almost always need in every PHP project.

DIVE 20 Nov 11, 2022
🎓 Collection of useful PHP frequently asked questions, articles and best practices

PHP.earth documentation These files are available online at PHP.earth. Contributing and license We are always looking forward to see your contribution

PHP.earth 278 Dec 27, 2022
Collection of value objects that represent the types of the PHP type system

sebastian/type Collection of value objects that represent the types of the PHP type system. Installation You can add this library as a local, per-proj

Sebastian Bergmann 1.1k Dec 29, 2022
Collection of useful PHP functions, mini-classes, and snippets for every day.

JBZoo / Utils Collection of PHP functions, mini classes and snippets for everyday developer's routine life. Install composer require jbzoo/utils Usage

JBZoo Toolbox 786 Dec 30, 2022
A collection of samples that demonstrate how to call Google Cloud services from PHP.

PHP Docs Samples A collection of samples that demonstrate how to call Google Cloud services from PHP. See our other Google Cloud Platform github repos

Google Cloud Platform 875 Dec 29, 2022
A collection of samples that demonstrate how to call Google Cloud services from PHP.

PHP Docs Samples A collection of samples that demonstrate how to call Google Cloud services from PHP. See our other Google Cloud Platform github repos

Google Cloud Platform 796 Dec 22, 2021
A collection of command line scripts for Magento 2 code generation, and a PHP module system for organizing command line scripts.

What is Pestle? Pestle is A PHP Framework for creating and organizing command line programs An experiment in implementing python style module imports

Alan Storm 526 Dec 5, 2022
Collection of PHP functions, mini classes and snippets for everyday developer's routine life

JBZoo / Utils Collection of PHP functions, mini classes and snippets for everyday developer's routine life. Install composer require jbzoo/utils Usage

JBZoo Toolbox 776 Jun 2, 2022
A collection of standards as PHP Enums: ISO3166, ISO4217, ISO639...

Standards A collection of standards as PHP Enums Setup Make sure you are running PHP 8.1 or higher to use this package To start right away, run the fo

null 295 Dec 20, 2022
This is a collection of tutorials for learning how to use Docker with various tools. Contributions welcome.

Docker Tutorials and Labs At this time we are not actively adding labs to this repository. Our focus is on training.play-with-docker.com where new lab

Docker 11.1k Jan 2, 2023
SilverStripe Garbage Collection Module

SilverStripe Module for defining and processing Garbage Collection on SilverStripe Applications.

Brett Tasker 8 Aug 12, 2022
A collection of open source projects built using Laravel.

Open Laravel A repository of open source projects built using Laravel. Getting Started Clone the project repository by running the command below if yo

Chimezie Enyinnaya 111 Dec 12, 2022
A collection of type-safe functional data structures

lamphpda A collection of type-safe functional data structures Aim The aim of this library is to provide a collection of functional data structures in

Marco Perone 99 Nov 11, 2022
Mirror Laravel model inside Firestore collection.

Laravel Firestore Mirror This package can be used to store copy of Laravel model inside Firestore collection. Installation Install package: composer r

Firevel 5 Feb 27, 2022
A collection of functions to clean up WordPress

Clean WordPress Admin A collection of functions to clean up WordPress front and back-end to make it easier for editors to work and for you to look at

Vincent Orback 411 Dec 28, 2022
A collection of experimental block-based WordPress themes.

Frost An experimental block theme for designers, developers, and creators. About Frost is a Full Site Editing theme for WordPress that extends the inc

Fahim Murshed 0 Dec 25, 2021
WordPress plugin which contains a collection of modules to apply theme-agnostic front-end modifications

Soil A WordPress plugin which contains a collection of modules to apply theme-agnostic front-end modifications. Soil is a commercial plugin available

Roots 1k Dec 20, 2022
This component provides a collection of functions/classes using the symfony/intl package when the Intl extension is not installed.

Symfony Polyfill / Intl: ICU This package provides fallback implementations when the Intl extension is not installed. It is limited to the "en" locale

Symfony 2.4k Jan 6, 2023