Mushfiq
Mushfiq

Reputation: 59

How to group an array of associative arrays and declare custom keys?

Can someone help me with converting a php array in a grouped format? I am trying to group them by id. I would like to have the following array converted:

$Arr1=Array
    (
        0 => Array
        (
            "id" => "4123",
            "test_number" => "1",
            "sat_total" => "1050"
        ),
        1 => Array
        (
            "id" => "4123",
            "test_number" => "2",
            "sat_total" => "1130"
        ),
        2 => Array
        (
            "id" => "4123",
            "test_number" => "3",
            "sat_total" => "1120"
        ),
        3 => Array
        (
            "id" => "5555",
            "test_number" => "1",
            "sat_total" => "1130"
        ),
        4 => Array
        (
            "id" => "5555",
            "test_number" => "2",
            "sat_total" => "1160"
        )
    );

into this:

$Arr2=Array
    (
        0 => Array
        (
            "id" => "4123",
            "Score1" => "1050",
            "Score2" => "1130",
            "Score3" => "1120"
        ),
        1 => Array
        (
            "id" => "5555",
            "Score1" => "1130",
            "Score2" => "1160"
        )
    );

I have tried a little bit, but can't seem to find how to make it work.

Upvotes: 0

Views: 337

Answers (4)

ggorlen
ggorlen

Reputation: 57185

I'm not sure this structure is ideal--it seems like your keys "Score1", "Score2" etc would be best as an array like scores => [1050, 1130, ...] and it feels like the ids should be keys in the result array. But in any case, this gives your requested output:

$res = [];

foreach ($arr as $e) {
    if (!array_key_exists($e['id'], $res)) {
        $res[$e['id']] = [];
    }

    $res[$e['id']]["Score".(count($res[$e['id']])+1)] = $e['sat_total'];
}

$count = 0;

foreach ($res as $k => $v) {
    $res[$k]['id'] = $k;
    $res[$count++] = $res[$k];
    unset($res[$k]);
}

print_r($res);

Output

Array
(
    [0] => Array
        (
            [Score1] => 1050
            [Score2] => 1130
            [Score3] => 1120
            [id] => 4123
        )

    [1] => Array
        (
            [Score1] => 1130
            [Score2] => 1160
            [id] => 5555
        )

)

Note that I did two passes which is a little verbose, but taking the time to key in the data ids into the array in the first pass should improve a linear search through the array for each element into O(1) hashing, so I think it's worth the extra loop block.

Upvotes: 0

mickmackusa
mickmackusa

Reputation: 48000

You only need to iterate your rows of data, determine if each row is the first occurring id value or not, then either declare the initial values, or add a variably keyed element to the group. When the loop finishes, call array_values() to reindex the array (remove the temporary keys).

Code: (Demo)

$Arr1=[
    ["id" => "4123", "test_number" => "1", "sat_total" => "1050"],
    ["id" => "4123", "test_number" => "2", "sat_total" => "1130"],
    ["id" => "4123", "test_number" => "3", "sat_total" => "1120"],
    ["id" => "5555", "test_number" => "1", "sat_total" => "1130"],
    ["id" => "5555", "test_number" => "2", "sat_total" => "1160"]
];

foreach ($Arr1 as $set) {
    if (!isset($result[$set['id']])) {
        $result[$set['id']] = ['id' => $set['id'], 'Score1' => $set['sat_total']];
    } else {
        $result[$set['id']]['Score' . sizeof($result[$set['id']])] = $set['sat_total'];
    }
}

var_export(array_values($result));

Output:

array (
  0 => 
  array (
    'id' => '4123',
    'Score1' => '1050',
    'Score2' => '1130',
    'Score3' => '1120',
  ),
  1 => 
  array (
    'id' => '5555',
    'Score1' => '1130',
    'Score2' => '1160',
  ),
)

Upvotes: 2

Jonathan
Jonathan

Reputation: 11494

$arr2 = [];
$i = 0;
$length = count($arr1);
do {
    $builder = $arr1[$i];
    // grab the first item from the original array

    $builder = [
        // set its initial properties
        'id' => $arr1[$i]['id'],
        'Score1' => $arr1[$i]['sat_total'],
    ];

    // initialise the subsequent score number
    $testNumber = 2;

    // prepare to look ahead in the original array for a matching id
    while (($i + 1) < $length) { // only look ahead if it is possible to
        if ($arr1[$i + 1]['id'] == $builder['id']) {
            // did you find a matching id? if so, let's set the subsequent score
            $builder["Score$testNumber"] = $arr1[$i + 1]['sat_total'];
            $testNumber++; // increase the next score number
            $i++; // increase the original array index
        } else {
            // no luck? let's go forwards and set the next record
            break;
        }
    }
    $arr2[] = $builder; // set the built record into the new array
    $i++; // move the pointer forwards
} while ($i < $length); // as long as there are items ahead

Not often you get to use a do-while. But it works :)

Feed it your original array $arr1 and $arr2 will be set.

It works by looking forward for matching ids. This solution assumes your original array is ordered by id! So unless you trust the input - don't use this solution!

Otherwise this is a simple, fast, and fairly readable solution to what looks to me like a school exercise?

If you want something that is safe, the other solutions here are suitable.

Upvotes: 0

Andreas
Andreas

Reputation: 23968

This method will find the scores matching the $id.
It uses three array_intersects to match all the values correct.
This method will only loop the number of unique IDs, in your case two times.
Plus the times to create the score keys.

I do agree with what ggorlen says about the keys. That will also create a more efficient code.

$ids = array_column($Arr1, "id");
$sat = array_column($Arr1, "sat_total");

foreach(array_unique($ids) as $id){
    $new[$id] = ["id" => $id];
    $tmp = array_values(array_intersect_key($sat,array_intersect_key($Arr1, array_intersect($ids, [$id]))));
    for($i=1;$i<=count($tmp);$i++) $new[$id]["Score" . $i] = $tmp[$i-1];
}
var_dump($new);

https://3v4l.org/ag3To

The output is an associative array with id as key.
You can use array_values if you want to make it indexed.


Just to show how much more efficient the code can be with one score array.
This is what it would look like:

$ids = array_column($Arr1, "id");
$sat = array_column($Arr1, "sat_total");

foreach(array_unique($ids) as $id){
    $new[] = ["id" => $id, "scores" => array_values(array_intersect_key($sat,array_intersect_key($Arr1, array_intersect($ids, [$id]))))];
}
var_dump($new);

https://3v4l.org/mdA0W

Upvotes: 0

Related Questions