hondnl's avatar

Multidimensional Array , group by keys and unique values

We got this array : Let's assume we don't know the multi keys in advance.

  $input =[
            ['name' =>'John McClane','age'=>'40' ],
            ['name' =>'John McClane','age'=>'50' ],
            ['name' =>'Hans Gruber','age'=>'40' ],
            ['name' =>'Hans Gruber','age'=>'50' ],
            ['name' =>'Holly McClane','age'=>'20' ],
            ['name' =>'Holly McClane','age'=>'49' ],
        ];

We want the output to be :

$output =[
            'name' => [
                'John McClane',
                'Hans Gruber',
                'Holly McClane'
            ],
            'age' =>[
                '40',
                '50',
                '20',
                '49'
            ]
        ];

We can do this in plain PHP with :

 foreach($input as $arr){
            $keys = array_keys($arr);
            foreach ($keys as $key) {
                $output[$key] =  array_unique(array_column($input, $key));
            }
        }

How would one do this with laravel collections?

0 likes
7 replies
lostdreamer_nl's avatar

This should do:


        $input = collect([
            ['name' =>'John McClane','age'=>'40' ],
            ['name' =>'John McClane','age'=>'50' ],
            ['name' =>'Hans Gruber','age'=>'40' ],
            ['name' =>'Hans Gruber','age'=>'50' ],
            ['name' =>'Holly McClane','age'=>'20' ],
            ['name' =>'Holly McClane','age'=>'49' ],
        ]);
        
        $data = [];
        foreach($input as $row) {
            foreach($row as $key => $val) {
                $data[$key] = $input->pluck($key)->toArray();
            }
        }
        dd($data);
hondnl's avatar

@LOSTDREAMER_NL - Not really satisfying , is it ? It does not make this code any more efficient. Is there really not way to use groupBy , to achieve what I want ? I cannot imagine that I am the only one wanting this with my collections.

lostdreamer_nl's avatar

not unless you also know the keys.

If you do not, you're going to have to loop over them dont you?

hondnl's avatar

@LOSTDREAMER_NL - Just as an interesting testcase , could we do this, just with laravel collections?

The whole point of collections , is not using our happy for each. Getting this input in a single line collection to the desired output. I am sure It can be done.

We might need an extra macro into our collection. But I am sure it can be done.

lostdreamer_nl's avatar

At most, you can remove 1 foreach loop by getting all keys at once.....

But even when putting macros on the collection, or creating a custom collection class, you're going to have to loop somewhere as you have 2 unknowns.....

$input = collect([
    ['name' =>'John McClane','age'=>'40' ],
    ['name' =>'John McClane','age'=>'50' ],
    ['name' =>'Hans Gruber','age'=>'40' ],
    ['name' =>'Hans Gruber','age'=>'50' ],
    ['name' =>'Holly McClane','age'=>'20' ],
    ['name' =>'Holly McClane','age'=>'49' ],
]);
$data = [];
$keys = array_keys($input->first());
foreach($keys as $key) {
    $data[$key] = $input->pluck($key)->toArray();
}
dd($data);
hondnl's avatar

@LOSTDREAMER_NL - Yes , off course you are completely right, it is just strange there is not a macro for this.

->pluckByKeys ($level=1){

// loop level. $keys= array_keys($item);

// got keys ['name,age] pluck them

// create new collection.

})-;

Well untested , but will create a macro for it. There must be a reason why it is not in the base collection. Will find out soon enough ;)

lostdreamer_nl's avatar

After some playing around, this is the most 'laravel way' I could think of :

    $input =[
        ['name' =>'John McClane','age'=>'40' ],
        ['name' =>'John McClane','age'=>'50' ],
        ['name' =>'Hans Gruber','age'=>'40' ],
        ['name' =>'Hans Gruber','age'=>'50' ],
        ['name' =>'Holly McClane','age'=>'20' ],
        ['name' =>'Holly McClane','age'=>'49' ],
    ];
    $input = collect($input);


    $data = $input->groupBy(function($row) {
        return array_keys($row);
    })->map(function($row, $key) {
        return array_values(array_unique(array_pluck($row, $key)));
    });


    dd($data->toArray());
/**
 *
 * Outputs:
 * array:2 [▼
 *   "name" => array:3 [▼
 *     0 => "John McClane"
 *     1 => "Hans Gruber"
 *     2 => "Holly McClane"
 *   ]
 *   "age" => array:4 [▼
 *     0 => "40"
 *     1 => "50"
 *     2 => "20"
 *     3 => "49"
 *   ]
 * ]
*/

(I added an array_values in there so you wont accidentally get an object instead of array when converting to json)

But to be clear, about the "... make this code any more efficient." part :

When we compare the actual code the version in your example is actually more efficient. instead of 2 nested foreach loops and only assigning variables you want, we have now 'hidden' that second loop within array_pluck, which actually maps to Arr::pluck :


    public static function pluck($array, $value, $key = null)
    {
        $results = [];

        [$value, $key] = static::explodePluckParameters($value, $key);

        foreach ($array as $item) {
            $itemValue = data_get($item, $value);

            if (is_null($key)) {
                $results[] = $itemValue;
            } else {
                $itemKey = data_get($item, $key);

                if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
                    $itemKey = (string) $itemKey;
                }

                $results[$itemKey] = $itemValue;
            }
        }

        return $results;
    }

So, yeah... it looks nicer, but you're actually running more code / variables and checks now.

Please or to participate in this conversation.