Athari / YaLinqo

Yet Another LINQ to Objects for PHP [Simplified BSD]
https://athari.github.io/YaLinqo
BSD 2-Clause "Simplified" License
439 stars 39 forks source link

how to 'sum my data' #40

Closed huangjuyuan closed 5 years ago

huangjuyuan commented 6 years ago

hello Alexander Prokhorov, I like YaLinqo and it make my work more efficient. But it's difficult when I 'sum my data'
The array is as follows, I want to group by catId and sum up quantity

$products = array(
    array('name' => 'Keyboard',    'catId' => 'hw', 'quantity' =>  10, 'id' => 1),
    array('name' => 'Mouse',       'catId' => 'hw', 'quantity' =>  20, 'id' => 2),
    array('name' => 'Monitor',     'catId' => 'hw', 'quantity' =>   0, 'id' => 3),
    array('name' => 'Joystick',    'catId' => 'hw', 'quantity' =>  15, 'id' => 4),
    array('name' => 'CPU',         'catId' => 'hw', 'quantity' =>  15, 'id' => 5),
    array('name' => 'Motherboard', 'catId' => 'hw', 'quantity' =>  11, 'id' => 6),
    array('name' => 'Windows',     'catId' => 'os', 'quantity' => 666, 'id' => 7),
    array('name' => 'Linux',       'catId' => 'os', 'quantity' => 666, 'id' => 8),
    array('name' => 'Mac',         'catId' => 'os', 'quantity' => 666, 'id' => 9),
);

new array like this

$products = array(
    'hw' => [
        ‘catId’ => ‘hw’,
        ‘quantity_sum’ => ‘xxx’,
    ], 
    'os'=>[
        ‘catId’ => ‘os’,
        ‘quantity_sum’ => ‘xxx’,
    ]
);

what should i do?

Athari commented 5 years ago

Sorry for not replying in time. I hope my answer is useful for somebody.

The method groupBy is just toLookup paired with select, so it has a lot of arguments and arguments of anonymous functions in its arguments are plentiful as well (and let's not forget separate handling of keys and values in PHP iterators), which can be confusing. For reference, here's simplified code of groupBy:

function groupBy ($keySelector, $valueSelector, $resultSelectorValue, $resultSelectorKey)
{
    return self::from($this->toLookup($keySelector, $valueSelector))
        ->select($resultSelectorValue, $resultSelectorKey);
}

The simplest solution with minimum number of calls is this:

$productGroups = Enumerable::from($products)
    ->groupBy(
        function($product) { return $product['catId']; },
        null,
        function($products, $catId) {
            return [
                'catId' => $catId,
                'totalQuantity' => Enumerable::from($products)->sum(
                    function($p) { return $p['quantity']; }
                )
            ];
        }
    );
var_dump($productGroups->toArray());

Here, we group products by category by specifying key, but skip transforming products until we get to the select stage of groupBy when the whole list of products is available. There we use sum on the array of products and return a category value.

The null passed to valueSelector means "return what was passed": function($product) { return $product; } (interpretation of default arguments is in the reference documentation).

If the groupBy call with 3 arguments is confusing, you can use something like this instead:

$productGroups = Enumerable::from($products)
    ->groupBy(
        function($product) { return $product['catId']; }
    )
    // here each element is `catId => productArray`
    ->select(
        function($products, $catId) {
            return [
                'catId' => $catId,
                'totalQuantity' => Enumerable::from($products)->sum(
                    function($p) { return $p['quantity']; }
                )
            ];
        }
    );
var_dump($productGroups->toArray());

This is essentially the same as the previous solution, though more functon calls are performed.