0

Let's pretend I have this:

$str = "/a/b/c/d/";
$arr = array_filter( explode("/", $str );

At this point $arr contains 4 elements. Is there a way I could create a path in an array with those 4 elements, such as:

$result = [
    "a" => [
        "b" => [
            "c" => [
                "d" => [

                ]
            ]
        ]
    ]
]

...without iterating over $arr?

I know that

$x["a"]["b"]["c"]["d"] = 1;

is perfectly valid and it will create a 4 levels array even if $x wasn't declared as an array, so what I'm asking should be possible.

alexandernst
  • 14,352
  • 22
  • 97
  • 197
  • For what reason don't you want to iterate over the array in the first place ? – Virus721 May 19 '15 at 14:55
  • 2
    _"...without iterating over $arr?"_ NO... – AbraCadaver May 19 '15 at 14:55
  • @Virus721 I'll be processing quite a huge amount of data, I was hoping I could skip some iterations. – alexandernst May 19 '15 at 14:57
  • Depends on what you mean by iteration. I'm not sure how PHP works internally, but it certainly creates the index used in `$x["a"]["b"]["c"]["d"]` one by one when evaluating the left member of the assignment. No magic can create all of them at the same time. `$x["a"]["b"]["c"]["d"]` probably results in `(((($x)["a"])["b"])["c"])["d"])` which is recursive, like most expressions parsing. – Virus721 May 19 '15 at 14:59
  • @Virus721 By "iteration" I mean `for($arr as $key)` and then create each node in the array. – alexandernst May 19 '15 at 15:03
  • This looks so specific that it's not surprising there aren't any function that does what you want. All you can do is make it by yourself in a loop... – Random May 19 '15 at 15:10
  • Your "perfectly valid" code produces a warning if `$x` already exists and is a string. – axiac May 19 '15 at 15:15
  • @axiac A corner case that I won't do, for sure. – alexandernst May 19 '15 at 15:20

3 Answers3

1

I DO NOT recommend this as there are security implications when using eval(). However, because I stated in the comments that it couldn't be done without iteration, I felt compelled to post this as an answer (yes, I know implode() iterates internally).

$str = "/a/b/c/d/";
$arr = array_filter( explode("/", $str ));

$keys = '["'.implode('"]["', $arr).'"]';
eval('$x'.$keys.' = 1;');

print_r($x);

For a more practical way see How to write getter/setter to access multi-leveled array by dot separated key names?

Community
  • 1
  • 1
AbraCadaver
  • 78,200
  • 7
  • 66
  • 87
1

I wrote a function once, that had this behaviour as a side effect. It doesn't iterate, but uses recursion.

See: https://github.com/feeela/php-utils/blob/master/function.getArrayValueReference.php

You may call like that:

<?php

$newArray = array();
$keys = 'a/b/c/d';

$referenceToD =& getArrayValueReference( $newArray, explode( '/', $keys ), true );

$referenceToD[0] = 'foo';
$referenceToD[1] = 'bar';

print_r( $newArray );

This modifies the array $newArray and creates all the levels. The functions return value is a reference to the last key ('d' in that example).

…which results in:

Array (
    [a] => Array (
        [b] => Array (
            [c] => Array (
                [d] => Array (
                    [0] => foo
                    [1] => bar
                )
            )
        )
    )
)
feeela
  • 29,399
  • 7
  • 59
  • 71
0

There is no way to use all the values of $arr without iterating over it.

I guess you don't want to write a foreach loop but use some PHP function that does the iteration for you.

A simple solution that iterates two times over the array (behind the scene)

This is a possible solution:

$x = array_reduce(
    array_reverse($arr),
    function ($carry, $item) {
        return [$item => $carry];
    },
    1
);

It generates the same result as:

$x = [];
$x['a']['b']['c']['d'] = 1;

Unfortunately it iterates over $arr two times (array_reverse() and array_reduce()).

Another solution that generates a hierarchy of objects

Another approach that generates the required embedding using objects (stdClass) instead of arrays:

$out = new stdClass;
array_reduce(
    $arr,
    function ($carry, $item) {
        $v = new stdClass;
        $carry->{$item} = $v;
        return $v;
    },
    $out
);

It works using a single iteration over $arr but it relies on the way the objects are handled in PHP to work (and this doesn't work with arrays).

PHP handles the objects in a way that makes them look like they are passed by reference. It's a common misconception that the objects are "passed by reference" in PHP but this is not true. A double indirection is responsible for this behaviour.

A recursive solution

function makeArray(array $arr, $initial)
{
    if (! count($arr)) {
        return $initial;
    } else {
        $key = array_shift($arr);
        return [ $key => makeArray($arr, $initial) ];
    }
}

$out = makeArray($arr, 1);

This solution iterates only once over the array and generates a hierarchy of arrays but recursivity is disastrous for large input arrays because it uses a lot of memory.

Community
  • 1
  • 1
axiac
  • 68,258
  • 9
  • 99
  • 134