reducing an array into another array

Viewed 814

Inside a laravel blade template, I am trying to reduce an array like this:

$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];

into something like this: $reducedList = [['count' => 2, 'kg' => 2], ['count' => 1, 'kg' => 3]];

This is what I have so far:

@php
          $variableWeights = isset($sale->variable_weight_json) ? collect(json_decode($sale->variable_weight_json, true)) : null;
          $groups = array();

          if (isset($variableWeights)) {
            $groups = $variableWeights->reduce(function($carry, $item) {
              $index = array_search($item['kg'], array_column($carry, 'weight'));
              if (isset($index)) {
                $existing = $carry[$index];
                array_splice($carry, $index, 1, [
                  'count' => $existing['count'] + 1,
                  'weight' => $item['kg']
                ]);
              } else {
                array_push($carry, [
                  'count' => 1,
                  'weight' => $item['kg'],
                ]);
              }
              return $carry;
            }, array());
          }
        @endphp

But it is giving me the error Undefined offset: 0

I am new to php. How should the code be corrected or is there a better approach to achieve the desired result?

5 Answers

Why dont you reduce it to something simpler like this

$reducedList = [2 => 2, 3 => 1]

where the weight is the index and the value is the count.

$reducedList = [];
foreach ($longList as $box) {
    if (isset($reducedList[$box['kg']]) {
        $reducedList[$box['kg']]++;
    } else {
        $reducedList[$box['kg']] = 1;
    }
}

This way you avoid complexity but you still get the same amount of information.

don't use isset() function. it checks only variable existing. use empty() or other condition, it will check variable existing and value. try this.

@php
          $variableWeights = isset($sale->variable_weight_json) ? collect(json_decode($sale->variable_weight_json, true)) : null;
          $groups = array();

          if ($variableWeights->isNotEmpty()) {
            $groups = $variableWeights->reduce(function($carry, $item) {
              $index = array_search($item['kg'], array_column($carry, 'weight'));
              if ($index != false ) {
                $existing = $carry[$index]?: false;
                if ($existing) {
                   array_splice($carry, $index, 1, [
                     'count' => $existing['count'] + 1,
                     'weight' => $item['kg']
                   ]);
                }
              } else {
                array_push($carry, [
                  'count' => 1,
                  'weight' => $item['kg'],
                ]);
              }
              return $carry;
            }, array());
          }
        @endphp

I guess you can achieve it with code like:

$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];

$reducedList = array_values(array_reduce(
    $longList,
    function($carry, $item) {
        if (isset($carry[$item['kg']])) {
            ++$carry[$item['kg']]['count'];
        } else {
            $carry[$item['kg']] = ['count' => 1, 'kg' => $item['kg']];
        }

        return $carry;
    },
    []
));

print_r($reducedList);

Here is a working example.

You can use the sexy countBy() method (combined with the famous map() one) of the Collection class.

Try this:

$longList = [['box' => 1, 'kg' => 2], ['box' => 2, 'kg' => 2], ['box' => 3, 'kg' => 3]];

$shortList = collect($longList)
    ->countBy('kg')
    ->map(function ($count, $kg) {
        return [
            'kg' => $kg, 
            'count' => $count,
        ];
    });

With that, you'll get this:

dd($shortList);
=> Illuminate\Support\Collection {#3380
     all: [
       2 => [
         "kg" => 2,
         "count" => 2,
       ],
       3 => [
         "kg" => 3,
         "count" => 1,
       ],
     ],
   }

Here you have a working demo.

isset checks if variable is set (i.e it exists and not NULL). As I (and core developers) can hardly imagine in which cases array_search can return NULL, there's a manual which says:

Returns the key for needle if it is found in the array, FALSE otherwise.

So, you need to check if $index is not false:

$index = array_search($item['kg'], array_column($carry, 'weight'));
// Note that I use `!==` because using `!=` will convert 
// `0` index to false, which is not correct in your case
if (false !== $index) {
Related