0

I have a list of budget categories, where subcategories are separated by colons, like this:

<?php
$categories = [
    'Budget:Necessities' => 0,
    'Budget:Necessities:Phone' => 200.00,
    'Budget:Necessities:Home' => 0,
    'Budget:Necessities:Home:Rent' => 450.00,
    'Budget:Savings' => 500,
    'Budget:Savings:Vacation' => 300,
];

I'd like it to turn into something like this:

<?php
$categories = [
    'Necessities' => [
        'balance' => 0,
        'Phone' => [
            'balance' => 200.00,
        ],
        'Home' => [
            'balance' => 0,
            'Rent' => [
                'balance' => 450,
            ],
        ],
    ],
    'Savings' => [
        'balance' => 500.00,
        'Vacation' => [
            'balance' => 300.00,
        ],
    ],
];

I'm having trouble figuring out how to turn one into the other.

I figure a recursive function would be best but it's eluding me.

devbanana
  • 466
  • 4
  • 14
  • This should be helpful: https://stackoverflow.com/questions/27929875/how-to-access-and-manipulate-multi-dimensional-array-by-key-names-path – Don't Panic Aug 08 '18 at 22:30
  • As should this: https://github.com/illuminate/support/blob/master/Arr.php#L514 – fubar Aug 08 '18 at 22:33

1 Answers1

1

Credit to the link posted by fubar, which https://github.com/illuminate/support/blob/master/Arr.php#L514 contains the a "set" function, which will set array elements dynamically at different depths, based on a string input.

e.g. if you have an empty array called $foo and you call set($foo, "level1.level2", "someValue");, then $foo will subsequently look this this (from a call to print_r()):

Array
(
    [level1] => Array
        (
            [level2] => someValue
        )

)

Having shamelessly incorporated that incredibly useful function, the solution is then really quite simple:

<?php
$categories = [
    'Budget:Necessities' => 0,
    'Budget:Necessities:Phone' => 200.00,
    'Budget:Necessities:Home' => 0,
    'Budget:Necessities:Home:Rent' => 450.00,
    'Budget:Savings' => 500,
    'Budget:Savings:Vacation' => 300,
];

$output = array();
//loop each array entry. $key will be the string, $val will be the balance
foreach ($categories as $key => $val)
{
  $levels = str_replace(":", ".", $key); //replace : with . for compatibility with the set() function
  $levels = substr(strstr($levels, '.'), 1); //remove the "Budget" entry
  set($output, "$levels.balance", $val); //set a "balance" entry for each item
}

print_r($output); //output using print_r for demonstration, but you can output some other way, as per your needs.

function set(&$array, $key, $value)
    {
        if (is_null($key)) {
            return $array = $value;
        }
        $keys = explode('.', $key);
        while (count($keys) > 1) {
            $key = array_shift($keys);
            // If the key doesn't exist at this depth, we will just create an empty array
            // to hold the next value, allowing us to create the arrays to hold final
            // values at the correct depth. Then we'll keep digging into the array.
            if (! isset($array[$key]) || ! is_array($array[$key])) {
                $array[$key] = [];
            }
            $array = &$array[$key];
        }
        $array[array_shift($keys)] = $value;
        return $array;
    }
?>

Which will get you output as follows:

Array
(
    [Necessities] => Array
        (
            [balance] => 0
            [Phone] => Array
                (
                    [balance] => 200
                )
            [Home] => Array
                (
                    [balance] => 0
                    [Rent] => Array
                        (
                            [balance] => 450
                        )
                )
        )
    [Savings] => Array
        (
            [balance] => 500
            [Vacation] => Array
                (
                    [balance] => 300
                )
        )
)

Working demo here: https://eval.in/1045835

ADyson
  • 57,178
  • 14
  • 51
  • 63
  • 1
    Thanks, that did the trick! Quite useful. I don't use assign by reference enough, never even thought of it. – devbanana Aug 09 '18 at 01:04