Ryan Fisch
Ryan Fisch

Reputation: 2654

PHP Flatten Array with multiple leaf nodes

What is the best way to flatten an array with multiple leaf nodes so that each full path to leaf is a distinct return?

array("Object"=>array("Properties"=>array(1, 2)));

to yield

  1. Object.Properties.1
  2. Object.Properties.2

I'm able to flatten to Object.Properties.1 but 2 does not get processed with recursive function:

function flattenArray($prefix, $array)
{
    $result = array();
    foreach ($array as $key => $value)
    {
        if (is_array($value))
            $result = array_merge($result, flattenArray($prefix . $key . '.', $value));
        else
            $result[$prefix . $key] = $value;
    }   
    return $result;
}

I presume top down will not work when anticipating multiple leaf nodes, so either need some type of bottom up processing or a way to copy array for each leaf and process (althought that seems completely inefficient)

Upvotes: 1

Views: 808

Answers (3)

L. Sanna
L. Sanna

Reputation: 6552

function flatten(array $data, $separator = '.') {
  $result = array();
  $stack = array();
  $path = null;

  reset($data);
  while (!empty($data)) {
    $key = key($data);
    $element = $data[$key];
    unset($data[$key]);  
    if (is_array($element)) {
      if (!empty($data)) {
        $stack[] = array($data, $path);
      }
      $data = $element;
      $path .= $key . $separator;
    } else {
      $result[$path . $key] = $element;
    }

    if (empty($data) && !empty($stack)) {
      list($data, $path) = array_pop($stack);
    }
  }
  return $result;
}

var_dump(flatten(array("Object"=>array("Properties"=>array(1, 2)))));

Output:

array(2) {
  ["Object.Properties.0"]=>
  int(1)
  ["Object.Properties.1"]=>
  int(2)
}

Upvotes: 2

Eineki
Eineki

Reputation: 14909

I would use a wrapper function to hide implementation details (the prefix parameter) and added an if branch to test for empty arrays. At last, in case of simple leaf you should use the $value variable and not the $key one.

$x = array("Object"=>array("Properties"=>array(1, 2), "test"=>array(), "post"));

function flatten ($array) {
    return flattenArray('',$array);
}

function flattenArray($prefix, $array) {
    $result = array();
    foreach ($array as $key => $value) {
        if (is_array($value)) {
            if(count($value)) {
                $result = array_merge($result, flattenArray($prefix."$key.", $value));
            } else {
                $result[] = "$prefix$key";
            }
        } else {
            $result[] = "$prefix$value";
        }
    }   
    return $result;
}

echo join("\n", flatten($x));

If you want to mimic a tree structure, maybe you can use a different array structure. Something like this:

$y = array ("Object", 
            array("Properties", 1, 2),
            "test",
            "post"
);

and flattenArray becomes:

function flattenArray($prefix, $array) {
    $result = array();
    $prefix .=array_shift($array).'.';
    foreach ($array as $value) {
        if (is_array($value)) {
            $result = array_merge($result, flattenArray($prefix, $value));
        } else {
            $result[] = "$prefix$value";
        }
    }   
    return $result;
}

Upvotes: 1

jasir
jasir

Reputation: 1471

Use function flatMapAssoc() from Kdyby Framework:

$flattened= array();
flatMapAssoc($array, function ($value, $keys) use (&$flattened) {
        $flattened[implode('.', $keys)] = $value;
});



/**
 * @param array|\Traversable $array
 * @param callable $callback
 * @return array
 */
    function flatMapAssoc($array, $callback)
{
    $callback = callback($callback);
    $result = array();
    $walker = function ($array, $keys = array()) use (&$walker, &$result, $callback) {
        foreach ($array as $key => $value) {
            $currentKeys = $keys + array(count($keys) => $key);
            if (is_array($value)) {
                $walker($value, $currentKeys);
                continue;
            }
            $result[] = $callback($value, $currentKeys);
        }

        return $result;
    };

    return $walker($array);
}

Upvotes: 1

Related Questions