10

I need to convert a flat array where the array keys indicate the structure into a nested array where the parent element becomes element zero, i.e. in the example:

$education['x[1]'] = 'Georgia Tech';

It needs to be converted to:

$education[1][0] = 'Georgia Tech';

Here is an example input array:

$education = array(
  'x[1]'     => 'Georgia Tech',
  'x[1][1]'  => 'Mechanical Engineering',
  'x[1][2]'  => 'Computer Science',
  'x[2]'     => 'Agnes Scott',
  'x[2][1]'  => 'Religious History',
  'x[2][2]'  => 'Women\'s Studies',
  'x[3]'     => 'Georgia State',
  'x[3][1]'  => 'Business Administration',
);

And here is what the output should be:

$education => array(
  1 => array(
    0 => 'Georgia Tech',
    1 => array( 0 => 'Mechanical Engineering' ),
    2 => array( 0 => 'Computer Science' ),
  ),
  2 => array(
    0 => 'Agnes Scott',
    1 => array( 0 => 'Religious History' ),
    2 => array( 0 => 'Women\'s Studies' ),
  ),
  3 => array(
    0 => 'Georgia State',
    1 => array( 0 => 'Business Administration' ),
  ),
);

I've banged my head against the wall for hours and still can't get it working. I think I've been looking at it too long. Thanks in advance.

P.S. It should be fully nestable, i.e. it should be able to convert a key that looks like this:

x[1][2][3][4][5][6] 

P.P.S. @Joseph Silber had a clever solution but unfortunately using eval() is not an option for this as it's a WordPress plugin and the WordPress community is trying to stamp out the use of eval().

MikeSchinkel
  • 4,947
  • 4
  • 38
  • 46
  • Quite the pickle... mind if I ask how the input ends up like that...? – PlagueEditor Aug 19 '11 at 04:13
  • @PlaqueEditor That is how it is stored as meta_keys within wp_postmeta using a custom WordPress plugin. – MikeSchinkel Aug 19 '11 at 04:18
  • When faced with a key such as `x[2][2]`, how would you know whether it's supposed to be `x[2][2] = 'whatever'` or `x[2][2][0] = 'whatever'`? – Joseph Silber Aug 19 '11 at 04:19
  • @Joseph Silber - Good question. Maybe it should always be `x[2][2][0].` Let me think about it for a bit. – MikeSchinkel Aug 19 '11 at 04:20
  • @Joseph Silber - Good catch. I think my output example was faulty. I've updated it, does my update make more sense? *(note that this is causing my head to spin so I could still have it 'wrong'.)* And thanks for the help. – MikeSchinkel Aug 19 '11 at 04:26
  • Just wanted to comment in general, the answers on the question are a perfect example of how and why StackOverflow can be so awesome. Thanks all! – MikeSchinkel Aug 19 '11 at 05:15

6 Answers6

5

Here is some code to handle what you had originally proposed as output.

/**
 * Give it and array, and an array of parents, it will decent into the
 * nested arrays and set the value.
 */
function set_nested_value(array &$arr, array $ancestors, $value) {
  $current = &$arr;
  foreach ($ancestors as $key) {

    // To handle the original input, if an item is not an array, 
    // replace it with an array with the value as the first item.
    if (!is_array($current)) {
      $current = array( $current);
    }

    if (!array_key_exists($key, $current)) {
      $current[$key] = array();
    }
    $current = &$current[$key];
  }

  $current = $value;
}


$education = array(
  'x[1]'     => 'Georgia Tech',
  'x[1][1]'  => 'Mechanical Engineering',
  'x[1][2]'  => 'Computer Science',
  'x[2]'     => 'Agnes Scott',
  'x[2][1]'  => 'Religious History',
  'x[2][2]'  => 'Women\'s Studies',
  'x[3]'     => 'Georgia State',
  'x[3][1]'  => 'Business Administration',
);

$neweducation = array();

foreach ($education as $path => $value) {
  $ancestors = explode('][', substr($path, 2, -1));
  set_nested_value($neweducation, $ancestors, $value);
}

Basically, split your array keys into a nice array of ancestor keys, then use a nice function to decent into the $neweducation array using those parents, and set the value.

If you want the output that you have updated your post to have, add this in the foreach loop after the line with 'explode'.

$ancestors[] = 0;
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
3
$result = array();

foreach( $education as $path => $value ) {

    $parts = explode('][', trim( $path, 'x[]' ) );
    $target =& $result;

    foreach( $parts as $part )
        $target =& $target[$part];

    $target = array($value);
}

var_dump($result);
Rarst
  • 2,335
  • 16
  • 26
2
<?php
$education = array(
  'x[1]'     => 'Georgia Tech',
  'x[1][1]'  => 'Mechanical Engineering',
  'x[1][2]'  => 'Computer Science',
  'x[2]'     => 'Agnes Scott',
  'x[2][1]'  => 'Religious History',
  'x[2][2]'  => 'Women\'s Studies',
  'x[3]'     => 'Georgia State',
  'x[3][1]'  => 'Business Administration',
);
$x = array();
foreach ($education as $key => $value) {
        parse_str($key . '[0]=' . urlencode($value));
}
var_dump($x);
realmfoo
  • 433
  • 3
  • 11
  • Nice answer, thanks, but same comments as to @Joseph Silber, `eval()` isn't an option to use for this use-case. – MikeSchinkel Aug 19 '11 at 04:37
  • I know, and I know that to store such structure isn't good too. I'll try to figure out fast but safe solution, if it's possible. – realmfoo Aug 19 '11 at 04:40
  • Magic quotes are deprecated since php 5.3 and removed since php 5.4, so if you still is using it --- drop it – realmfoo Aug 19 '11 at 05:05
  • @stereofrog - Can you please give me an example of magic quotes issue? Thanks in advance. – MikeSchinkel Aug 19 '11 at 05:08
  • realmfoo - In an ideal world magic quotes would be dropped, but not in the legacy world of WordPress where the core team has made a decision not to drop it *(see: http://bit.ly/wordpress-magic-quotes)* any WordPress plugin must consider it active. – MikeSchinkel Aug 19 '11 at 05:12
  • @MikeSchinkel: if the values contain quotes (like in your example) and magic_quotes are on in php.ini, `parse_str` will insert slashes before quotes, "Women's studios" becomes "Women\'s studios". – user187291 Aug 19 '11 at 05:23
  • So if the call of get_magic_quotes_gpc() returns true, you should use stripslashes_deep (wordpress function, http://codex.wordpress.org/Function_Reference/stripslashes_deep) after foreach loop. – realmfoo Aug 19 '11 at 06:28
1

Based on the first suggestion above I found a solution that worked for my .ini file by modifying the $ancestors variable.

EDIT: Here is a complete version of my working code: https://stackoverflow.com/a/38480646/1215633

//$ancestors = explode('][', substr($path, 2, -1));
$ancestors = explode('.', $path);

I had this setup in my array, based on an .ini file:

[resources.db.adapter] => PDO_MYSQL
[resources.db.params.host] => localhost
[resources.db.params.dbname] => qwer
[resources.db.params.username] => asdf
[resources.db.params.password] => zxcv
[resources.db.params.charset] => utf8
[externaldb.adapter] => PDO_MYSQL
[externaldb.params.host] => localhost
[externaldb.params.dbname] => tyui
[externaldb.params.username] => ghjk
[externaldb.params.password] => vbnm
[externaldb.params.charset] => latin1

The outcome became as desired:

Array
(
[resources] => Array
    (
        [db] => Array
            (
                [adapter] => PDO_MYSQL
                [params] => Array
                    (
                        [host] => localhost
                        [dbname] => qwer
                        [username] => asdf
                        [password] => zxcv
                        [charset] => utf8
                    )

            )

    )

[externaldb] => Array
    (
        [adapter] => PDO_MYSQL
        [params] => Array
            (
                [host] => localhost
                [dbname] => tyui
                [username] => ghjk
                [password] => vbnm
                [charset] => latin1
            )

    )

)
orjtor
  • 55
  • 6
1
$education = array(
  'x[1]'     => 'Georgia Tech',
  'x[1][1]'  => 'Mechanical Engineering',
  'x[1][2]'  => 'Computer Science',
  'x[2]'     => 'Agnes Scott',
  'x[2][1]'  => 'Religious History',
  'x[2][2]'  => 'Women\'s Studies',
  'x[3]'     => 'Georgia State',
  'x[3][1]'  => 'Business Administration',
  // Uncomment to test deep nesting.
  // 'x[1][2][3][4][5][6] ' => 'Underwater Basket Weaving',
);

$newarray = array();
foreach ($education as $key => $value) {

  // Parse out the parts of the key and convert them to integers.
  $parts = explode('[', $key);
  for($i = 1; $i < count($parts); $i += 1) {
    $parts[$i] = intval(substr($parts[$i], 0, 1));
  }

  // Walk the parts, creating subarrays as we go.
  $node = &$new_array;
  for($i = 1; $i < count($parts); $i += 1) {
    // Create subarray if it doesn't exist.
    if (!isset($node[$parts[$i]])) {
      $node[$parts[$i]] = array();
    }
    // Step down to the next dimension.
    $node = &$node[$parts[$i]];
  }
  // Insert value.
  $node[0] = $value;
}
$education = $new_array;

var_dump($education);

UPDATE: Modified solution to handle the new requirements. UPDATE: Cleaned up variable names and added comments. (Last edit, I promise :))

Stoney
  • 698
  • 5
  • 11
0

If you'll always also store the first element in the array with [0], then you can use this:

$education = array(
    'x[1][0]' => 'Georgia Tech',
    'x[1][1]' => 'Mechanical Engineering',
    'x[1][2]' => 'Computer Science',
    'x[2][0]' => 'Agnes Scott',
    'x[2][1]' => 'Religious History',
    'x[2][2]' => 'Women\'s Studies',
    'x[3][0]' => 'Georgia State',
    'x[3][1]' => 'Business Administration'
);

$x = array();

foreach ($education as $key => $val)
{
    eval('$'.$key.'=$val;');
}

print_r($x);
Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • Clever, thanks, but I should have mentioned that using `eval()` is not an option. – MikeSchinkel Aug 19 '11 at 04:32
  • if $val contains any double quotes (") your code will result in parse error. You should use var_export to avoid this. – realmfoo Aug 19 '11 at 05:00
  • oh, and another point --- you are using eval, so instead of var_export or simple imploding of the value use the variable: eval('$'.$key.'=$val;'); :) – realmfoo Aug 19 '11 at 05:07