Jey
Jey

Reputation: 1511

Group array rows by one column and only create a subarray from another column if more than one value

How can I group row data from a two-dimensional array and only create a subarray of another column if the respective group contains more than one value?

In other words, I need to group rows by id and conditionally restructure an array of arrays to have a variable depth of either 2 or 3 levels.

Input:

[
    ['id' => 567, 'value' => 780],
    ['id' => 676, 'value' => 743],
    ['id' => 676, 'value' => 721],
    ['id' => 234, 'value' => 766],
    ['id' => 234, 'value' => 680]
]

Desired output:

[
    ['id' => 567, 'value' => 780],
    ['id' => 676, 'value' => [743, 721]],
    ['id' => 234, 'value' => [766, 680]]
]

Upvotes: 0

Views: 185

Answers (5)

mickmackusa
mickmackusa

Reputation: 48000

This task can certainly be done concisely with one loop.

Use id column values as temporary first level keys. This makes identifying duplicates most efficient and easy.

While iterating:

  1. if a row's id is new to the result array, then push the whole row (in its original, flat structure) into the result array; or

  2. if a row's id was encountered before, then cast the stored row's value element as an array before pushing the current row's value into that subarray.

If you do not want your result to have id values as the first level keys, then call array_values() to re-index the result.

The special action done implemented below is that when casting a scalar or null data type to an array, the value becomes the lone element of the newly formed array. If an array is explicitly cast as an array, then there is no effect on the data structure at all. This is why (array) can be unconditionally applied in the else branch.

Code: (Demo)

foreach ($array as $row) {
    if (!isset($result[$row['id']])) {
        $result[$row['id']] = $row;
    } else {
        $result[$row['id']]['value'] = array_merge(
            (array) $result[$row['id']]['value'],
            [$row['value']]
        );
    }
}
var_export(array_values($result));

An alternative without array_merge(): (Demo)

foreach ($array as $row) {
    if (!isset($result[$row['id']])) {
        $result[$row['id']] = $row;
    } else {
        $result[$row['id']]['value'] = (array) $result[$row['id']]['value'];
        $result[$row['id']]['value'][] = $row['value'];
    }
}
var_export(array_values($result));

An alternative with array_reduce(): (Demo)

var_export(
    array_values(
        array_reduce(
            $array,
            function($result, $row) {
                if (!isset($result[$row['id']])) {
                    $result[$row['id']] = $row;
                } else {
                    $result[$row['id']]['value'] = (array) $result[$row['id']]['value'];
                    $result[$row['id']]['value'][] = $row['value'];
                }
                return $result;
            }
        )
    )
);

Upvotes: 0

goosman.lei
goosman.lei

Reputation: 456

<?php
$arr['key1']    = array(
    array(
        'id'    => 567, 
        'value' => 780, 
    ), 
    array(
        'id'    => 676, 
        'value' => 743, 
    ), 
    array(
        'id'    => 676, 
        'value' => 721, 
    ), 
    array(
        'id'    => 234, 
        'value' => 766, 
    ), 
    array(
        'id'    => 234, 
        'value' => 780, 
    ), 
);

/* handle element merge */
function array_internal_merge_func($a, $b) {
    if ( is_array($a['value']) )
        $a['value'][]   = $b['value'];
    else 
        $a['value']     = array($a['value'], $b['value']);
    return $a;
}
/* handle array merge */
function array_internal_merge($array, $key, $merge_func) {
    $hashed = array();
    $result = array();
    foreach ( $array as $idx => $ele ) 
        $hashed[$ele[$key]][]   = $idx;
    foreach ( $hashed as $key => $idxies ) {
        reset($idxies);
        $idx0           = current($idxies);
        $result[$idx0]  = $array[$idx0];
        while ( $idx = next($idxies) ) 
            $result[$idx0]  = $merge_func($result[$idx0], $array[$idx]);
    }
    return $result;
}

print_r(array_internal_merge($arr['key1'], 'id', 'array_internal_merge_func'));

Upvotes: 0

Hugo Delsing
Hugo Delsing

Reputation: 14173

There, all the work done for you. How easy is that!

//create the array as you have now
$array[0] = ['id'=>567, 'value'=>780];
$array[1] = ['id'=>676, 'value'=>743];
$array[2] = ['id'=>676, 'value'=>721];
$array[3] = ['id'=>234, 'value'=>766];
$array[4] = ['id'=>234, 'value'=>780];

print_r($array);
print chr(10).chr(10);

//create a new array with the values combined on key
$concat = array();
foreach($array as $val) {
  $i = $val['id'];
  $v = $val['value'];

  if (!is_array($concat[$i]))
    $concat[$i] = array();

  $concat[$i][] = $v;
}

print_r($concat);
print chr(10).chr(10);

//create a new array to show the data as you want.
$newarray = array();
foreach($concat as $key=>$val) {
  $t = array();
  $t['id'] = $key;

  if (count($val)==1)
    $t['value'] = $val[0];
  else {
    $t['value'] = array();
    foreach($val as $v)
      $t['value'][] = $v;
  } 

  $newarray[] = $t;
}

print_r($newarray);
print chr(10).chr(10);

Result:

Array
(
    [0] => Array
        (
            [id] => 567
            [value] => 780
        )

    [1] => Array
        (
            [id] => 676
            [value] => 743
        )

    [2] => Array
        (
            [id] => 676
            [value] => 721
        )

    [3] => Array
        (
            [id] => 234
            [value] => 766
        )

    [4] => Array
        (
            [id] => 234
            [value] => 780
        )

)


Array
(
    [567] => Array
        (
            [0] => 780
        )

    [676] => Array
        (
            [0] => 743
            [1] => 721
        )

    [234] => Array
        (
            [0] => 766
            [1] => 780
        )

)


Array
(
    [0] => Array
        (
            [id] => 567
            [value] => 780
        )

    [1] => Array
        (
            [id] => 676
            [value] => Array
                (
                    [0] => 743
                    [1] => 721
                )

        )

    [2] => Array
        (
            [id] => 234
            [value] => Array
                (
                    [0] => 766
                    [1] => 780
                )

        )

)

Upvotes: 0

Antony
Antony

Reputation: 15106

Are you sure you want to have the value as an integer when there is one value and an array when there are more?

<?php
$array = array(
    array('id' => 567, 'value' => 780),
    array('id' => 676, 'value' => 743),
    array('id' => 676, 'value' => 721),
    array('id' => 234, 'value' => 766),
    array('id' => 234, 'value' => 680)
);

foreach ($array as $item) {
    $result[$item['id']][] = $item['value'];
}

foreach ($result as $id => $value) {
    if (count($value) > 1) {
        $output[] = array(
            'id' => $id,
            'value' => $value
        );
    } else {
        $output[] = array(
            'id' => $id,
            'value' => $value[0]
        );
    }
}

echo '<pre>';
print_r($output);
echo '</pre>';
?>

If not

<?php
$array = array(
    array('id' => 567, 'value' => 780),
    array('id' => 676, 'value' => 743),
    array('id' => 676, 'value' => 721),
    array('id' => 234, 'value' => 766),
    array('id' => 234, 'value' => 680)
);

foreach ($array as $item) {
    $result[$item['id']][] = $item['value'];
}

foreach ($result as $id => $value) {
    $output[] = array(
        'id' => $id,
        'value' => $value
    );
}

echo '<pre>';
print_r($output);
echo '</pre>';
?>

Upvotes: 2

shukshin.ivan
shukshin.ivan

Reputation: 11340

try this one

$array['key1'] = array(
    0=>array('id'=>567, 'value'=>780),
    1=>array('id'=>676, 'value'=>743),
    2=>array('id'=>676, 'value'=>721),
    3=>array('id'=>234, 'value'=>766),
    4=>array('id'=>234, 'value'=>780)
    );

foreach($array['key1'] as $subarray){
    $group_id = $subarray['id'];

    if(!isset($return[$group_id]))
        $return[$group_id] = $subarray;
    else{
        if(is_array($return[$group_id]['value']))
            array_push($return[$group_id]['value'], $subarray['value']);
        else 
            $return[$group_id]['value'] = array($subarray['value'], $return[$group_id]['value']);
    }
}

// reset grouping keys into 0,1...
$return = array_values($return);
print_r($return);

Upvotes: 0

Related Questions