0

I've been digging around and trying to solve this issue the cleanest way possible, but haven't quite found the right approach.

I have an array of objects like so:

$myArray = [
    {field: "Diameter", measurement: 15, count: 4},
    {field: "Diameter", measurement: 16, count: 1},
    {field: "Diameter", measurement: 17, count: 15},
    {field: "Width", measurement: 7, count: 12},
    {field: "Width", measurement: 8, count: 8},
    {field: "Brands", measurement: "blah", count: 1},
    {field: "Brands", measurement: "doubleBlah", count: 3},
    {field: "Brands", measurement: "blah", count: 1},
    {field: "Brands", measurement: "doubleBlah", count: 3},
    {field: "Brands", measurement: "blah", count: 12}
    ]

and I need to combine the objects that have duplicate measurement fields and combine the counts like this:

$myBetterArray = [
    {field: "Diameter", measurement: 15, count: 4},
    {field: "Diameter", measurement: 16, count: 1},
    {field: "Diameter", measurement: 17, count: 15},
    {field: "Width", measurement: 7, count: 12},
    {field: "Width", measurement: 8, count: 8},
    {field: "Brands", measurement: "blah", count: 14},
    {field: "Brands", measurement: "doubleBlah", count: 6},
    ]

can it be done with something like array_map or something equally clean and not too verbose? Any help would be much appreciated.

2 Answers 2

1

Based on the keys being "hidden" like that, the data format known, and the way the question is written('the cleanest way possible', 'not too verbose') I'd simply go for foreach():

$myArray = '[
{"field": "Diameter", "measurement": 15, "count": 4},
{"field": "Diameter", "measurement": 16, "count": 1},
{"field": "Diameter", "measurement": 17, "count": 15},
{"field": "Width", "measurement": 7, "count": 12},
{"field": "Width", "measurement": 8, "count": 8},
{"field": "Brands", "measurement": "blah", "count": 1},
{"field": "Brands", "measurement": "doubleBlah", "count": 3},
{"field": "Brands", "measurement": "blah", "count": 1},
{"field": "Brands", "measurement": "doubleBlah", "count": 3},
{"field": "Brands", "measurement": "blah", "count": 12}
]';
$myArray = json_decode($myArray);

// Sort the data
$_im = [];
foreach($myArray as $item) {
    $_im[$item->field][$item->measurement] = $item->count + ($_im[$item->field][$item->measurement] ?? 0);
}

// Output it in the desired format
$res = [];
foreach($_im as $field => $fdata) {
    foreach($fdata as $measurement => $count) {
        $res []= (object) compact('field', 'measurement', 'count');
    }
}

print_r($res);

Process the data in an intermediary array where you can use the keys(if you don't do it that way searching for a given pair of keys later would be more complex), and then another foreach to put the result together in the desired format.

This should be enough for 2 levels. Also, it might be nice to put the code in a function to keep $_im from polluting the namespace.

(object) [] is a simple way to cast an array into an object(stdClass), since that seems to be the format of the data in the question.

?? 0 creates a start value for the case when the current $_im[$item->field][$item->measurement] isn't there yet (i.e. each time there's a new field,measurement pair). Shorter to write than using isset().

Sign up to request clarification or add additional context in comments.

4 Comments

I can't seem to run this code on PHP 7.4.3. It works if I replace the compact() with $res[]= [ 'field' => $field, 'measurement' => $measurement, 'count' => $count ]; (I still get several E_NOTICE messages)
@LSerni My bad, compact() wants the variable names, not the variables themselves.
I had to change this line to this: $_im[$item['field']][$item['measurement']] = $item['count'] + ($_im[$item['field']][$item['measurement']] ?? 0); but that did the trick and it wasn't too verbose, thank you!
@BRose Ok, so $item is an array, not an object? Sorry, I misinterpreted the data then. Glad it worked!
0

First sort the array so that all measurements to be possibly connected are contiguous; this will allow you to only check contiguous elements.

This approach makes it easier to change the field specification (also called signature or syndrome) of each record, uses less memory and is resistant to the "Schlemiel the Painter" problem in case of very long arrays.

$keys = [ 'field', 'measurement' ];

uasort($array, function($item1, $item2) use ($keys) {
    foreach ($keys as $key) {
        if ($item1[$key] === $item2[$key]) {
            continue;
        }
        if ($item1[$key] < $item2[$key]) {
            return -1;
        }
        return 1;
    }
    return 0;
});
// This is to be sure that the array will reencode as a JSON array
$array = array_values($array);

Now your array should look like this:

 [
    {"field":"Brands","measurement":"blah","count":1},
    {"field":"Brands","measurement":"blah","count":1},
    {"field":"Brands","measurement":"blah","count":12},
    {"field":"Brands","measurement":"doubleBlah","count":3},
    {"field":"Brands","measurement":"doubleBlah","count":3},
    {"field":"Diameter","measurement":15,"count":4},
    {"field":"Diameter","measurement":16,"count":1},
    {"field":"Diameter","measurement":17,"count":15},
    {"field":"Width","measurement":7,"count":12},
    {"field":"Width","measurement":8,"count":8}
 ]

Now, scan the array coalescing the counts with the same key-syndrome.

$j = -1;
$advance = true;
foreach ($array as $i => $row) {
    if ($j >= 0) {
        $advance = false;
        foreach ($keys as $key) {
            if ($array[$i][$key] !== $array[$j][$key]) {
                $advance = true;
                break;
            }
        }
        if (!$advance) {
            // Row $i is a duplicate of row $j
            $array[$j]['count'] += $array[$i]['count'];
        }
    }
    if ($advance) {
        // Row $i is moved in row $j
        $j++;
        if ($j !== $i) {
            $array[$j] = $array[$i];
        }
    }
}
// All rows past j must be deleted
$array = array_slice($array, 0, $j+1);

Array is now:

[
    {"field":"Brands","measurement":"blah","count":14},
    {"field":"Brands","measurement":"doubleBlah","count":6},
    {"field":"Diameter","measurement":15,"count":4},
    {"field":"Diameter","measurement":16,"count":1},
    {"field":"Diameter","measurement":17,"count":15},
    {"field":"Width","measurement":7,"count":12},
    {"field":"Width","measurement":8,"count":8}
]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.