0

I've a list of associative arrays as below:

[
"country" => "AU",
"state" => "VIC",
"suburb" => "Carlton",
"precedence" => ["country", "state", "suburb"]
]

And I want a new multidimensional array like below where the elements are nested based on the order defined by precedence key on first array:

[
 "country" => [
  "AU" => [
    "state" => [
      "VIC" => [
        "suburb" => "Carlton
      ]
     ]
    ]
   ]
]

The above is just an example and I want a generic solution that will work for any kinds of array. Only 1 condition that'll be satisfied by all input arrays is that they'll have a precedence element denoting the order in which the output array needs to be generated.

I've tried some recursive solution but it's not working as expected and I've got PHP Fatal error: Allowed memory size of 1073741824 bytes exhausted (looks like it's running infinitely):

function generateArray(&$array)
    {
        foreach ($array['precedence'] as $key => $property) {
            if ($key == sizeof($array['precedence']) - 1) {
                return [$property => $array[$property]];
            } else {
                return generateAssetConfig($array);
            }
        }
    }
1
  • why do you need to make this structure? what if you need state as-is, if its this structure then you won't know if its a key or the final value after all the nesting. Commented Dec 14, 2021 at 3:29

4 Answers 4

2

You could loop the reversed items of the precedence part.

If there are no items in the result array yet, add the first key => value pair.

Else wrap the current result in a multidimensional array, setting the current value if the iteration as the outer key, and wrap the value (for that key in the source array) together with the current result in a second array.

$source = [
    "country" => "AU",
    "state" => "VIC",
    "suburb" => "Carlton",
    "precedence" => ["country", "state", "suburb"]
];

function generateArray($array)
{
    $result = [];
    foreach(array_reverse($array["precedence"]) as $v) {
        $result =! $result ? [$v => $array[$v]] : [$v => [$array[$v] => $result]];
    }
    return $result;
}

var_export(generateArray($source));

Output

array (
  'country' => 
  array (
    'AU' => 
    array (
      'state' => 
      array (
        'VIC' => 
        array (
          'suburb' => 'Carlton',
        ),
      ),
    ),
  ),
)
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you everyone! All of the below answers are correct as well but I found this one to be simplest so I've upvoted it.
Wow, great solution! I couldn't even think about this. Well done!
1

Try this:

function generateNestedArray($arr) {
  $precedence = $arr['precedence'];
  $nestedArray = [];
  for ($i = count($precedence)-1; $i >= 0; $i--) {
    $key = $precedence[$i];
    if (!$nestedArray) {
      $nestedArray[$key] = $arr[$key];
    } else {
      $nestedArray = [$key => [ $arr[$key]=> $nestedArray]];
    }
  } 
  
  return $nestedArray;
}

Comments

1

Here's a recursive algorithm to do this:

<?php

$raw = [
    [
        "country" => "AU",
        "state" => "VIC",
        "suburb" => "Carlton",
        "precedence" => ["country", "state", "suburb"]
    ],
    [
        "country" => "AU",
        "state" => "NSW",
        "suburb" => "Sydney",
        "precedence" => ["country", "state", "suburb"]
    ]
];

function generateFromPrecedence($array)
{
    if (!isset($array['precedence']))
        throw new Exception('Precedence array does not exist');

    if (!empty(array_diff($array['precedence'], array_diff(array_keys($array), ['precedence']))))
        throw new Exception('Keys and precendence keys different');

    return generateStructure($array);
}

function generateStructure($array, $precedence = 0)
{
    if ($precedence == count($array['precedence'])-1)
        return [$array['precedence'][$precedence] => $array[$array['precedence'][$precedence]]];

    return [$array['precedence'][$precedence] => [$array[$array['precedence'][$precedence]] => generateStructure($array, ++$precedence)]];
}

$output = generateFromPrecedence($raw[0]);

var_dump($output);

Outputs:

array(1) {
["country"]=>
array(1) {
    ["AU"]=>
    array(1) {
    ["state"]=>
    array(1) {
        ["NSW"]=>
        array(1) {
        ["suburb"]=>
        string(6) "Sydney"
        }
    }
    }
}
}

Comments

1

Simplest solution (recursive function):

function generateArrayRecursion($array, $precedenceIndex = 0) {
    $precedence = $array['precedence'];
    
    return [
        $precedence[$precedenceIndex] => $precedenceIndex === \count($precedence) - 1
            ? $array[$precedence[$precedenceIndex]]
            : [$array[$precedence[$precedenceIndex]] => generateArrayRecursion($array, $precedenceIndex + 1)]
    ];
}

Alternative solution (loop and array references):

function generateArray($array) {
    $precedence = $array['precedence'];
    $result = [];
    $lastKey = $precedence[count($precedence) - 1];

    $currentElement = &$result;
    foreach ($precedence as $key) {
        if ($key === $lastKey) {
            $currentElement[$key] = $array[$key];
        } else {
            $currentElement[$key] = [$array[$key] => []];
            $currentElement = &$currentElement[$key][$array[$key]];
        }
    }
    
    return $result;
}

Usage example:

$array = [
    "country" => "AU",
    "state" => "VIC",
    "suburb" => "Carlton",
    "precedence" => ["country", "state", "suburb"]
];

var_dump(generateArrayRecursion($array));
var_dump(generateArray($array));

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.