Search code examples
phparraysmultidimensional-arraygrouping

Group rows of data by subarray in column and create subsets with variable depth


I have an array with two items, which are also arrays themselves: product and countries.

There are cases in which the countries array is the same for more than one product, like basic and pro in the example below.


Given this array:

$array = [

    [
        'product'   => [
            'value' => 'basic',
            'label' => 'Basic'
        ],
        'countries'  => [
            'Japan', // these
            'Korea'  // two...
        ],
    ],

    [
        'product'   => [
            'value' => 'pro',
            'label' => 'Pro'
        ],
        'countries'  => [
            'Japan', // ...and these two
            'Korea'  // are identical...
        ],
    ],

    [
        'product'   => [
            'value' => 'expert',
            'label' => 'Expert'
        ],
        'countries'  => [
            'Japan',
            'France'
        ],
    ]

];

I would like to create new arrays grouped by countries, more precisely,

this is the result I'm after:

$array = [

    [
        'product'   => [
            [
                'value' => 'basic',
                'label' => 'Basic'
            ],
            [
                'value' => 'pro',
                'label' => 'Pro'
            ]
        ],
        'countries'  => [
            'Japan', // ...so they are now one single array
            'Korea'  // as the two products 'basic' and 'pro' have been grouped
        ],
    ],

    [
        'product'   => [
            'value' => 'expert',
            'label' => 'Expert'
        ],
        'countries'  => [
            'Japan',
            'France'
        ],
    ]

];

As you can see in the second snippet, what I'm trying to do is to group basic and pro together in the same array, since they both share the exact same countries (Korea and Japan).

I've been trying for days to play around with this code, but it only seems to work if product and countries are strings rather than arrays:

$grouped = array();
foreach ($array as $element) {
    $grouped[$element['countries']][] = $element;
}
var_dump($grouped);

Solution

  • This might be what you want

    $productsByCountrySet = [];
    foreach ($array as $product) {
        $countries = $product['countries'];
        sort($countries);
        $countrySet = implode('/', $countries);
        if (isset($productsByCountrySet[$countrySet])) {
            $productsByCountrySet[$countrySet]['product'][] = $product['product'];
        } else {
            $productsByCountrySet[$countrySet] = [
                'product' => [$product['product']],
                'countries' => $countries,
            ];
        }
    }
    $products = [];
    foreach ($productsByCountrySet as $p) {
        if (count($p['product']) == 1) {
            $p['product'] = $p['product'][0];
        }
        $products[] = $p;
    }
    print_r($products);
    

    It produces the output you're aiming for. It assumes that the order of countries is not significant (ie ['Japan', 'Korea'] is the same as ['Korea', 'Japan'])

    It works by turning your countries array into a string (['Japan', 'Korea'] becomes 'Japan/Korea'), then uses that as a unique key for the entries. It builds up the desired output array by first assembling the unique key (I called it 'country set') and then checking if it has already been seen. If it has, the product is appended, if not, a new item is added to the output array.

    The final section handles the case where there is only one product for a country set. We loop and catch this state, modifying the output accordingly.