1

I currently have coded a way to turn a multidimensional array to comma separated values (I'm using pipes instead of commas for ease of debugging). The problem is, I know that the code I use to do this is bloody awful. It works how I want it to, but it's not nice at all.

What I need

Currently the arr_to_csv() function works for five levels of nested data within the multidimensional array. I need a recursive function to perform the same for one or an unlimited number of nested arrays, or a good nudge in the right direction. Recursion is not my strong point at all, but I know it's the way forward.

Data input

A multi-dimensional array is passed to the function.

array
  'name' => 
      array
         'singular' => null
         'plural' => null
  'fields' => 
      array
         'price' => 
            array
               'label' => string 'Preis' (length=5)
               'company_id' => 
                  array
                     'label' => null
                     'placeholder' => null
                     //...the array could go on...

The function returns the following...

This is exactly what I want...

0 => string 'name||singular||null' (length=20)
1 => string 'name||plural||null' (length=18)
2 => string 'fields||price||label||Preis' (length=27)
3 => string 'fields||company_id||label||null' (length=31)
4 => string 'fields||company_id||placeholder||null' (length=37)
5 => string 'fields||name||label||null' (length=25)
6 => string 'fields||name||placeholder||null' (length=31)

My horrible constructed function

I'm no good with recursion, so here's my awful list of foreachs. As you can see from the below code, this is terrible (no need to read the whole thing, it just copies itself). Please help me sort out my horrible code!

function arr_to_csv($data,$csv = '||') {

$array = array();

/* Epic amount of for each's. This could be done with recursion */
foreach($data as $key => &$value) {
    if (!is_array($value)) {
        $array[] = $key . $csv .(is_null($value)?'null':$value);
    } else {
        foreach ($value as $k => &$v) {
            if (!is_array($v)) {
                $array[] = $key . $csv . $k . $csv . (is_null($v) ? 'null' : $v);
            } else {
                foreach ($v as $kk => &$vv) {
                    if (!is_array($vv)) {
                        $array[] = $key . $csv . $k . $csv . $kk . $csv . (is_null($vv) ? 'null' : $vv);
                    } else {
                        foreach ($vv as $x => &$y) {
                            if (!is_array($y)) {
                                $array[] = $key . $csv . $k . $csv . $kk . $csv. $x . $csv . (is_null($y) ? 'null' : $y);
                            } else {
                                foreach ($y as $too => $long) {
                                    if(!is_array($long)) {
                                        $array[] = $key . $csv . $k . $csv . $kk . $csv. $x . $csv . $too . $csv. (is_null($long)?'null':$long);
                                    } else {
                                        foreach ($long as $omg => $why) {
                                            if(!is_array($why)) {
                                                $array[] = $key . $csv . $k . $csv . $kk . $csv. $x . $csv . $too . $csv . $omg . $csv . (is_null($why) ? 'null' : $why);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    } 
}    
return $array;
}
Jeffrey
  • 1,239
  • 8
  • 15
Jimbo
  • 25,790
  • 15
  • 86
  • 131
  • XML would be good. But there are also structs. See this SO post to get an idea. http://stackoverflow.com/questions/254514/php-and-enums – Frank Tudor Jan 02 '13 at 17:05
  • 1
    XML is old, try JSON. Just do json_encode($array) and you're done :) – Jeffrey Jan 02 '13 at 17:09
  • Hey guys, I should've added this really: The data is being stored with parent / child values in a MySQL DB (yeah, a tree structure shoved in there) so the values can be individually retrieved, modified and re-saved. Therefore I just need my function to work recursively and with far fewer lines of code :) – Jimbo Jan 02 '13 at 17:14

3 Answers3

2

This is some pseudocode, but it is a start:

$strings = [];
$flattenArray = function($arr, $level) use (&$strings, &$flattenArray) {

    foreach($arr as $key=>$value){
        $s = &$strings[$level];
        if(!isset($s)) { $s = array(); }
        $s[] = $key;
        if(is_array($value)) {
           $flattenArray($value, $level);
        }
        else {
           $s[] = $value;
        }
        $level ++;
    }
};
$flattenArray($myArray, 0);
foreach($strings as &$arr) {
     $arr = implode("||", $arr);
}

Small demo with your array: http://codepad.viper-7.com/CR2SPY <-- It does not work fully, but it is a start


Update:

Here is a demo that I think works the way you want: http://codepad.viper-7.com/shN4pH

Code:

$strings = [];
$flattenArray = function($arr, $level, $k = null) use (&$strings, &$flattenArray) {

    foreach($arr as $key=>$value){
        if($k === null) {
            $s = &$strings[$key];
        }
        else {
            $s = &$strings[$k];
        }
        if(!isset($s)) { 
            $s = array();  
        }
        $str = &$s[$level];
        if(!isset($str)) { 
            $str = array(); 
            
            if($k !== null) { $str[] = $k; }
        }
        $str[] = $key;
        if(is_array($value)) {
           $flattenArray($value, $level, ($k === null) ? $key : $k);
        }
        else {
            $str[] = is_null($value) ? "null" : $value;
        }
        $level ++;
    }
};
$flattenArray($myArray, 0);
$all = [];
foreach($strings as $k => $arr){
    $new = array();
    foreach($arr as $ky => $ar) {
        $all[] = implode("||", $ar);
    }
}

print_r($all);
Community
  • 1
  • 1
Naftali
  • 144,921
  • 39
  • 244
  • 303
  • Thanks for your help so far Neal, I need to figure out the recursion in this and get it working properly - but it is a good start so thank you for the help! – Jimbo Jan 03 '13 at 15:52
  • @Jimbo no problem ^_^ happy to help! – Naftali Jan 03 '13 at 21:22
  • @ROYFinley haha thanks ^_^ idk if that last solution fully works, but it is pretty darn close :-) It just needs a lil more tweaking! – Naftali Jan 03 '13 at 22:11
  • It's SO DAMN CLOSE! But, for example there's `fields||white||null` instead of `fields||categories||colour||white||null`; so I'm not sure what's going on there. – Jimbo Jan 04 '13 at 09:44
  • @Jimbo I am sure you can figure it out :-) – Naftali Jan 04 '13 at 13:43
1

I didn't check it, so in case it doesn't work it should be corrected.

function readarray($from_array, $addr = array()) {
    global $output;
    foreach ($from_array as $key => $value) {
        if (is_Array($value) && count($value) > 0) {
            $addr[] = $key;
            readarray($value, $addr);
        } else {
            $output[] = implode('||', $addr) . $value;
        }
    }

}


$output = array();
foreach ($my_array as $key=>$value){
readarray($value); 
}

// improved to get separate arrays of the root of initial array

el Dude
  • 5,003
  • 5
  • 28
  • 40
  • @Artaex Media: ahah, my style =) – el Dude Jan 02 '13 at 17:18
  • @Artaex Media hey-hey-hey, You've teared away "global $my_array", that will not work without it – el Dude Jan 02 '13 at 17:20
  • @EL This doesn't work correctly because it returns `name||fields||price||Preis`. Name and fields are two separate arrays. It should return `fields||price||Preis`. It also places `name||` twice at the top for some reason. Might need a little help from to make it more reliable? – Jimbo Jan 02 '13 at 17:30
  • @EL I don't know who taught you PHP, but have you ever heard of 'return'? You should never use global variables when it can be done in a different way. – Jeffrey Jan 02 '13 at 17:39
  • @Artaex Media: there's no need in ''return'' in this case. If you want, alter code by adding third parameter for output data. – el Dude Jan 02 '13 at 17:44
1

Not sure if this will help you, but would it not be easier to flatten the array first and then format in in the way you want? To flatten the array try this:

$array = "YOUR ARRAY";

$FlatArray = array();

foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $k=>$v)
  {
    $FlatArray[$k] = $v;
  }

Been trying all morning to come up with a recursive function for this. This is as close as I got, Maybe you can improve upon this.

$data = array('name' =>array('singular' => NULL,'plural' => NULL,'fields' =>array('price' =>array('label' =>'Preis','company_id' =>array('label' => NULL,'placeholder' => NULL)))));


function arr_to_csv($data,$csv = '||')
{
$list = "";
foreach($data as $key => &$value)
        {
        $list .= $key . $csv .((!is_array($value))?(is_null($value)?'null':$value): arr_to_csv($value))."<br>";
        }
return $list;
}   



print_r(arr_to_csv($data));

Returns This:

name||singular||null

plural||null

fields||price||label||Preis

company_id||label||null

placeholder||null
ROY Finley
  • 1,406
  • 1
  • 9
  • 18
  • Flattening the array causes it to lose it's structure. Anything that happens to this array must be reversible so this won't work, unfortunately. – Jimbo Jan 03 '13 at 09:58
  • @Jimbo just edited my answer with an attempt at a recursive function. it is close, maybe we can combine with Neals answer... looking at that now. – ROY Finley Jan 03 '13 at 18:12
  • @ROYFinley your answer won't work with an array within an array withing an array winthin an array (etc...) – Naftali Jan 03 '13 at 21:21