2

I am looking to group an array into subarrays based on its keys.

Sample Array

Array
(
    [0] => Array
        (
            [a_id] => 1
            [a_name] => A1
            [b_id] => 1
            [b_name] => B1
            [c_id] => 1
            [c_name] => C1
        )

    [1] => Array
        (
            [a_id] => 1
            [a_name] => A1
            [b_id] => 1
            [b_name] => B1
            [c_id] => 2
            [c_name] => C2
        )

    [2] => Array
        (
            [a_id] => 1
            [a_name] => A1
            [b_id] => 2
            [b_name] => B2
            [c_id] => 3
            [c_name] => C3
        )

    [3] => Array
        (
            [a_id] => 2
            [a_name] => A2
            [b_id] => 3
            [b_name] => B3
            [c_id] => 4
            [c_name] => C4
        )

)

I need this sample array to be converted into a JSON array of the following format:

Expected Output

[{
    "a_id": 1,
    "a_name": "A1",
    "b_list": [{
        "b_id": 1,
        "b_name": "B1",
        "c_list": [{
            "c_id": 1,
            "c_name": "C1"
        }, {
            "c_id": 2,
            "c_name": "C2"
        }]
    }, {
        "b_id": 2,
        "b_name": "B2",
        "c_list": [{
            "c_id": 3,
            "c_name": "C3"
        }]
    }]
}, {
    "a_id": 2,
    "a_name": "A2",
    "b_list": [{
        "b_id": 3,
        "b_name": "B3",
        "c_list": [{
            "c_id": 4,
            "c_name": "C4"
        }]
    }]
}]

I was able to group by a key using the code below.

$array = array(
array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "1","c_name" => "C1"),
array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "2","c_name" => "C2"),
array("a_id" => "1","a_name" => "A1","b_id" => "2","b_name" => "B2","c_id" => "3","c_name" => "C3"),
array("a_id" => "2","a_name" => "A2","b_id" => "3","b_name" => "B3","c_id" => "4","c_name" => "C4")
);
$return = array();
foreach($array as $val) {
    $return[$val["a_id"]][] = $val;
}
print_r($return);

But my actual scenario involves grouping into sub arrays didn't worked.

Looking forward to see if there is an optimized way or useful function to get into my expected JSON response.

Note: I am looking into a generalized use case here . For example : a_list as countries,b_list as states and c_list as cities.

LF00
  • 27,015
  • 29
  • 156
  • 295
Surabhil Sergy
  • 1,946
  • 1
  • 23
  • 40

6 Answers6

2

Man that is very specific use case for arrays. Well here is your solution.

$array = <YOUR SAMPLE ARRAY>
$output = [];
/*
 * Nesting array based on a_id, b_id
 */
foreach ($array as $item) {
    $aid = $item['a_id'];
    $bid = $item['b_id'];
    $cid = $item['c_id'];
    if(!isset($output[$aid])){
        $output[$aid] = [
            'a_id' => $item['a_id'],
            'a_name' => $item['a_name'],
            'b_list' => [
                $bid => [
                    'b_id' => $item['b_id'],
                    'b_name' => $item['b_name'],
                    'c_list' => [
                        $cid = [
                            'c_id' => $item['c_id'],
                            'c_name' => $item['c_name']
                        ]
                    ]
                ]
            ]
        ];
    } else if (!isset($output[$aid]['b_list'][$bid])){
        $output[$aid]['b_list'][$bid] =  [
            'b_id' => $item['b_id'],
            'b_name' => $item['b_name'],
            'c_list' => [
                $cid => [
                    'c_id' => $item['c_id'],
                    'c_name' => $item['c_name']
                ]
            ]
        ];
    } else if(!isset($output[$aid]['b_list'][$bid]['c_list'][$cid])) {
        $output[$aid]['b_list'][$bid]['c_list'][$cid] = [
            'c_id' => $item['c_id'],
            'c_name' => $item['c_name']
        ];
    } else {
        // Do/Dont overrider
    }
}
/*
 * Removing the associativity from the b_list and c_list
 */
function indexed($input){

    $output = [];
    foreach ($input as $key => $item) {
        if(is_array($item)){
            if($key == 'b_list' || $key == 'c_list'){
                $output[$key] = indexed($item);
            } else {
                $output[] = indexed($item);
            }
        } else {
            $output[$key] = $item;
        }
    }
    return $output;
}
$indexed = indexed($output);
print_r(json_encode($indexed, 128));
anwerj
  • 2,386
  • 2
  • 17
  • 36
2

Interesting requirement there. Here is my generalized solution that is also extendable.

function transform($array, $group=[
    ['a_id','a_name','b_list'],
    ['b_id','b_name','c_list'],
    ['c_id','c_name'],
]){
    foreach($array as $a){
        $r = &$result;
        foreach($group as $g){
            $x = &$r[$a[$g[0]]];
            $x[$g[0]] = $a[$g[0]];
            $x[$g[1]] = $a[$g[1]];
            if(isset($g[2])) $r = &$x[$g[2]]; else break;
        }
    }
    return transformResult($result);
}

function transformResult($result){
    foreach($result as &$a)
        foreach($a as &$b)
            if(is_array($b)) $b = transformResult($b);
    return array_values($result);
}

To extend this solution, all you have to do is modify the $group parameter, either directly in the function declaration or by passing an appropriate value as the 2nd parameter.

Usage example:

echo json_encode(transform($array), JSON_PRETTY_PRINT);

This will return the same output assuming the same $array input in your example.

Rei
  • 6,263
  • 14
  • 28
2

Now here is the code that works best in the given situation. I have created a similar situation and then explained the solution in detail.

Situation
The Order Form is multipage depending on the number of days served based on the package selected. Details of each package are stored in the database with the following fields:

  1. package_id (Unique Field)
  2. package_name (Name of the Package, e.g. Package A)
  3. servings_count (Total Servings in a Day)
  4. days_served (Number of Days Served in a Month)

In order to carry forward the selection of meals for each day and serving of that day to store as an Order in the database, I required a Multidimensional Array of PHP that can be defined/populated dynamically.

Expected output is something like:

Array
(
    [Day 1] => Array
        (
            [meal_id_1] => Unique ID //to be replaced with user selection
            [meal_code_1] => Meal Name //to be replaced with user selection
            [meal_type_1] => Meal //prefilled based on the selected package
            [meal_id_2] => Not Available //to be replaced with user selection
            [meal_code_2] => 2 //to be replaced with user selection
            [meal_type_2] => Meal //prefilled based on the selected package
        )

    [Day 2] => Array
        (
            [meal_id_1] => Unique ID //to be replaced with user selection
            [meal_code_1] => Meal Name //to be replaced with user selection
            [meal_type_1] => Meal //prefilled based on the selected package
            [meal_id_2] => Not Available //to be replaced with user selection
            [meal_code_2] => 2 //to be replaced with user selection
            [meal_type_2] => Meal //prefilled based on the selected package
        )

This above array has been created 100% dynamically based on the explained structure and number of servings and days. Below is the code with some explanation.

First, we have to declare two PHP Arrays.

$total_meals_array = []; //Primary, Multidimension Array
$meals_selected_array = []; //Meals Details Array to be used as primary array's key value.

After doing this, run MySQL query to read packages from the database. Now based on the result, do the following:

$total_meals_array = []; //Primary, Multidimension Array
$meals_selected_array = []; //Meals Details Array to be used as primary array's key value.

if( $num_row_packages >= 1 ) {
    while($row_packages = mysqli_fetch_array ($result_packages)) {
        $package_id = $row_packages['package_id'];
        $package_name = $row_packages['package_name'];
        $servings_count = $row_packages['servings_count'];
        $days_served = $row_packages['days_served'];

        //this for loop is to repeat the code inside `$days_served` number of times. This will be defining our primary and main Multidimensional Array `$total_meals_array`.
        for ($y = 1; $y <= $days_served; $y++) {
            //once inside the code, now is the time to define/populate our secondary array that will be used as primary array's key value. `$i`, which is the meal count of each day, will be added to the key name to make it easier to read it later. This will be repeated `$meals_count` times.

            for ($i = 1; $i <= $meals_count; $i++) {
                $meals_selected_array["meal_id_" . $i] = "Unique ID";
                $meals_selected_array["meal_code_" . $i] = "Meal Name";
                $meals_selected_array["meal_type_" . $i] = "Meal";
            }

            //once our secondary array, which will be used as the primary array's key value, is ready, we will start defining/populating our Primary Multidimensional Array with Keys Named based on `$days_served`.
            $total_meals_array["Day " . $y] = $meals_selected_array;
        }
    }
}

That's it! Our dynamic Multidimensional Array is ready and can be viewed by simply the below code:

print "<pre>";
print_r($total_meals_array);
print "</pre>";

Thank you everyone, specially @yarwest for being kind enough to answer my question.

1

Here is the code, you can use it for index from a_ to y_ deep. The innerest element is null, if you don't want it. Terminate the for loop before last element, then process last element seperately. You also can do some improvement on this code. Hope this helps.

 <?php
    $array = array(
    array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "1","c_name" => "C1"),
    array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "2","c_name" => "C2"),
    array("a_id" => "1","a_name" => "A1","b_id" => "2","b_name" => "B2","c_id" => "3","c_name" => "C3"),
    array("a_id" => "2","a_name" => "A2","b_id" => "3","b_name" => "B3","c_id" => "4","c_name" => "C4")
    );
    $arrays = array_map(function($v){return array_chunk($v, 2, true);}, $array);
    $result = [];
    foreach($arrays as $value)
    {
        $ref = &$result;
        $len = count($value);
        $index = 0;
        for(; $index < $len; $index++)
        {
            $arr = $value[$index];
            $char = key($arr)[0];
            $charAdd = chr(ord($char)+1);
            $key = $arr[$char.'_id'].$arr[$char.'_name'];
            $listKey = $charAdd.'_list';
            foreach($arr as $k => $v)
            {
                $ref[$key][$k] = $v;
            }
            $ref = &$ref[$key][$listKey];
        }
    }
    var_dump($result);

Output: the online live demo

ei@localhost:~$ php test.php
array(2) {
  ["1A1"]=>
  array(3) {
    ["a_id"]=>
    string(1) "1"
    ["a_name"]=>
    string(2) "A1"
    ["b_list"]=>
    array(2) {
      ["1B1"]=>
      array(3) {
        ["b_id"]=>
        string(1) "1"
        ["b_name"]=>
        string(2) "B1"
        ["c_list"]=>
        array(2) {
          ["1C1"]=>
          array(3) {
            ["c_id"]=>
            string(1) "1"
            ["c_name"]=>
            string(2) "C1"
            ["d_list"]=>
            NULL
          }
          ["2C2"]=>
          array(3) {
            ["c_id"]=>
            string(1) "2"
            ["c_name"]=>
            string(2) "C2"
            ["d_list"]=>
            NULL
          }
        }
      }
      ["2B2"]=>
      array(3) {
        ["b_id"]=>
        string(1) "2"
        ["b_name"]=>
        string(2) "B2"
        ["c_list"]=>
        array(1) {
          ["3C3"]=>
          array(3) {
            ["c_id"]=>
            string(1) "3"
            ["c_name"]=>
            string(2) "C3"
            ["d_list"]=>
            NULL
          }
        }
      }
    }
  }
  ["2A2"]=>
  array(3) {
    ["a_id"]=>
    string(1) "2"
    ["a_name"]=>
    string(2) "A2"
    ["b_list"]=>
    array(1) {
      ["3B3"]=>
      array(3) {
        ["b_id"]=>
        string(1) "3"
        ["b_name"]=>
        string(2) "B3"
        ["c_list"]=>
        array(1) {
          ["4C4"]=>
          array(3) {
            ["c_id"]=>
            string(1) "4"
            ["c_name"]=>
            string(2) "C4"
            ["d_list"]=>
            &NULL
          }
        }
      }
    }
  }
}
LF00
  • 27,015
  • 29
  • 156
  • 295
  • I find your solution is a bit clumsy, having to process the last entry separately. It has a (admitedly high) limit on how many levels are attainable. As far as I can tell, mine is the only solution that actually returns exactly the sample proposed by op, without any limitation on depth, both on number of levels and key value pairs to be kept on each level, and allows actual parametrization of these keys. Every other has hardcoded keys and non recursive behavior. – Félix Adriyel Gagnon-Grenier Jan 19 '17 at 05:22
  • @FélixGagnon-Grenier I don't you think you answer is good is the reason for downvoting others. – LF00 Jan 19 '17 at 05:57
  • no, it's because yours is bad: it's not recursive, it's not parameterizable, it can't go deeply. have you actually read my comment? ;) and as such, do you really think your *revenge* downvoting is better? care to comment what is actually bad about mine? – Félix Adriyel Gagnon-Grenier Jan 19 '17 at 05:59
  • noop, have you see my answer, for now it at least go 25levels from a to y. – LF00 Jan 19 '17 at 06:18
  • yes, 25 is a limit. not *unlimited*. I have read it very carefully. also, what if a_ was just an example, and can dynamically change? OP would have to rewrite your function for each different use case? what if there are suddenly more values, OP would have to rewrite it again to incorporate more values? I am not sure, but I start to think we have a language barrier here. Does *unlimited* means the same thing to you? because 25 and unlimited is not the same. Do you understand the difference between OP's sample and your result? Your result is not json. As such, you are not answering the question. – Félix Adriyel Gagnon-Grenier Jan 19 '17 at 06:19
  • I don't think SO is for coding for product, it's a question and answer site. – LF00 Jan 19 '17 at 06:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/133518/discussion-between-felix-gagnon-grenier-and-kris-roofe). – Félix Adriyel Gagnon-Grenier Jan 19 '17 at 06:57
0

This is rather interesting. As far as I can tell, you are trying to transform a flat array into a multidimensional array, as well as transforming the keys into a multidimensional representation.

The top level difference seems to reside in the part before the underscore of the a_* keys.

Then, for each of these keys, every other *_ letters should induce it's own list.

This recursive function does the trick without hardcoding, will work with whatever number of levels, letters (or whatever else) and right identifiers.

It seems to return exactly the json you show in the sample ($array being the array as defined in your question)

$multidimension = multidimensionalify($array, ['a', 'b', 'c'], ['name']);
var_dump(json_encode($multidimension, JSON_PRETTY_PRINT));

function multidimensionalify(
    array $input,
    array $topLevelLetters,
    array $rightHandIdentifiers,
    $level = 0,
    $parentId = null,
    $uniqueString = 'id'
)
{
    $thisDimension = [];
    $thisLetter = $topLevelLetters[$level];
    foreach ($input as $entry)
    {
        $thisId = $entry["{$thisLetter}_{$uniqueString}"];
        $condition = true;
        if ($parentId !== null)
        {
            $parentLetter = $topLevelLetters[$level - 1];
            $condition = $entry["{$parentLetter}_{$uniqueString}"] === $parentId;
        }
        if (!isset($thisDimension[$thisId]) && $condition)
        {
            $thisObject = new stdClass;
            $thisObject->{"{$thisLetter}_{$uniqueString}"} = $thisId;
            foreach ($rightHandIdentifiers as $identifier)
            {
                $thisObject->{"{$thisLetter}_{$identifier}"} = $entry["{$thisLetter}_{$identifier}"];
            }
            if (isset($topLevelLetters[$level + 1])) {
                $nextLetter = $topLevelLetters[$level + 1];
                $thisObject->{"{$nextLetter}_list"} = multidimensionalify($input, $topLevelLetters, $rightHandIdentifiers, $level + 1, $thisId, $uniqueString);
            }
            $thisDimension[$thisId] = $thisObject;
        }
    }
    return array_values($thisDimension);
}
-1

Try this function just pass your array and key name for grouping and then convert to json.

public function _group_by($array, $key) {
    $return = array();
    foreach ($array as $val) {
        $return[$val[$key]][] = $val;
    }
    return $return;
}
Ankit Gupta
  • 182
  • 9
  • If this was the answer (and I am pretty sure that it isn't), then you should have voted to close this question as a duplicate of https://stackoverflow.com/q/14113256/2943403 instead of answering. – mickmackusa Dec 05 '20 at 15:08