2

I wonder if there is a faster way to sum i.e. the weight of each item by qty.

$items = [
    [
        'qty'    => 1,
        'weight' => 1,
    ],
    [
        'qty'    => 2,
        'weight' => 1,
    ],
    [
        'qty'    => 3,
        'weight' => 1,
    ],
];

$totalWeight = 0.0;
foreach ($items as $item) {
    $totalWeight += $item['weight'] * $item['qty'];
}
echo $totalWeight . PHP_EOL;

If i would not need the qty offset, i just could use

array_sum(array_column($items, 'weight'))

But this wont work ofc in this example.

Anybody has an idea if and how this could be done faster?

Thanks /cottton

EDIT

Test script:

$items = [];
for ($i = 0; $i < 1000; $i++) {
    $items[] = [
        'foo'    => 1,
        'qty'    => $i,
        'bar'    => 2,
        'weight' => $i,
        'baz'    => 3,
    ];
}
$totalWeight = 0.0;


$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
    $totalWeight = 0.0;
    foreach ($items as $item) {
        $totalWeight += $item['weight'] * $item['qty'];
    }
}
$elapsed = sprintf('%f', microtime(true) - $start);
echo "Elapsed: {$elapsed}\r\n";
echo "Total weight: {$totalWeight}\r\n";
// Elapsed: 0.744311
// Total weight: 332833500
cottton
  • 1,522
  • 14
  • 29

3 Answers3

2

Use https://www.w3schools.com/php/func_array_reduce.asp

    <?php
    $items = [
        [
            'qty'    => 1,
            'weight' => 1,
        ],
        [
            'qty'    => 2,
            'weight' => 1,
        ],
        [
            'qty'    => 3,
            'weight' => 1,
        ],
    ];


    $totalWeight = array_reduce($items, 
        function($acc, $e) { return $acc + ($e['weight'] * $e['qty']); });
    echo $totalWeight . PHP_EOL;

?>
Kalecser
  • 1,143
  • 12
  • 15
  • Thanks but unfortunately this is a bit slower than the foreach (see edit ^) - foreach 0.744311, your method 1.174606 :( – cottton May 03 '18 at 19:15
2

You can map array_product over the inner arrays and array_sum the resulting array of products.

$totalWeight = array_sum(array_map('array_product', $items));

(This may not work if the example you showed was simplified for the the question here and the inner arrays include other fields that aren't shown.)

Don't Panic
  • 41,125
  • 10
  • 61
  • 80
0

Here is a method that loops the unique weights and finds the sum of qty of each weights and multiply it with the weight.

In your case it makes one loop.

$weights = array_column($items, 'weight');
$result =0;
Foreach(array_unique($weights) as $weight){
    // Finds the items with the weight 1 for example
    $weightitems = preg_grep("/". $weight . "/", $weights);
    // Sum quantity of items with the weight of 1 and multiply with weight (1).
    $result += array_sum(array_column(array_intersect_key($items, $weightitems), "qty")) * $weight;
}
Echo $result;

Notice I added a "5" just to see that it worked correct in my example code.

https://3v4l.org/V293P


A slightly more optimized code, but still not faster than a simple loop as far as I can see.
I can make it 4-ish times slower depending on the input but I can't get it closer than that.

$weights =array_unique(array_column($items, 'weight'));
$result =[];
Foreach($weights as $weight){
    $weightitems = array_filter($items, function ($var) use ($weight) {
        return ($var['weight'] == $weight);
    });
    $result[] = array_sum(array_column($weightitems, "qty")) * $weight;
}
Echo array_sum($result) ."\n";

https://3v4l.org/aB7d9


How accurate does it have to be?

This is faster than looping but is off by 1 on 3000 items and a total of 17992.
Is that within error margin, then this is faster.

I calculate the average of the sumproduct of the two columns using array_sum, array_column and count.

$count = count($items);
$weights = array_sum(array_column($items, 'weight'));
$qty = array_sum(array_column($items, 'qty'));

Echo ($qty * $weights)/$count ."\n";
// 17991 (should be 17992)

https://3v4l.org/c3CIg

I think I have tried all possible solutions now.

Andreas
  • 23,610
  • 6
  • 30
  • 62
  • tried that and this is way to slow. It is that slow that 1 run take the time of 10000 runs of the foreach loop. – cottton May 03 '18 at 19:08
  • What array do you input? Is it the example in question? This code is probably more suitable on a large array with many different weights and quantities – Andreas May 03 '18 at 19:10
  • @cottton that code creates an array of 1000 unique items, obviously this code is not suitable for all unique values. This code is supposed to group "similar" items and count them as a group. If that is a true representative of your input then you can discard this as an answer. If it's not a representative of your input then we can test it on something else more correct. – Andreas May 03 '18 at 19:29
  • Well, it should get the actual sum. An average is not usable - at least not for me. – cottton May 04 '18 at 16:17
  • @cottton please read again. It takes the sum of quantity and the sum of weights. Multiply the two. Then takes the product and divides it with the count of items. The number you get now is kind of an average. Not per see an average. As I wrote above it is 1 off from the actual number. If we calculate how far off from the actual number it is, it's `1/17992` that what I meant, how accurate does it have to be? – Andreas May 04 '18 at 17:19
  • 1
    Sorry, i dont know what you expect as an answer. "how accurate does it have to be" - It has to be accurate. Thats it. – cottton May 06 '18 at 18:53