2

I am having trouble figuring out a way to simply parse a string input and find the correct location within a multidimensional array.

I am hoping for one or two lines to do this, as the solutions I have seen rely on long (10-20 line) loops.

Given the following code (note that the nesting could, in theory, be of any arbitrary depth):

function get($string)
{
    $vars = array(
        'one' => array(
            'one-one' => "hello",
            'one-two' => "goodbye"
        ),
        'two' => array(
            'two-one' => "foo",
            'two-two' => "bar"
        )
    );

    return $vars[$string]; //this syntax isn't required, just here to give an idea
}

get("two['two-two']");  //desired output: "bar".  Actual output: null

Is there a simple use of built-in functions or something else easy that would recreate my desired output?

rockerest
  • 10,412
  • 3
  • 37
  • 67
  • 1
    Don't confuse "infinite" with "arbitrarily large". There is no infinite natural number, but there are arbitrarily large natural numbers. – Kerrek SB Aug 09 '11 at 22:04
  • 2
    How is `get("two['two-two']")` better than `$vars['two']['two-two']` ? – Mārtiņš Briedis Aug 09 '11 at 22:08
  • Is the proposed syntax a requirement? how about `get(array('two', 'two-two'));`? – Karoly Horvath Aug 09 '11 at 22:09
  • Can you add an example, how `$string` does look like when you want to access sub-elements? // Your question has reminded me a bit to this one: http://stackoverflow.com/questions/6981188/php-call-array-from-string/6981260 and this one: http://stackoverflow.com/questions/6965868/dynamic-token-parsing/6965893 – hakre Aug 09 '11 at 22:13
  • @yi_H the proposed `get()` syntax isn't a requirement, but it would be _really_ nice to maintain the "familiar" array retrieval syntax. – rockerest Aug 09 '11 at 22:14
  • @hakre, I'm not sure what you mean by `sub-elements`? Do you mean deeper than `two['two-two']`? – rockerest Aug 09 '11 at 22:18
  • 2
    imho this question indicates you have a serious design problem. – David Chan Aug 09 '11 at 22:18
  • 3
    @David Chan, thanks for that opinion. Could you elaborate a little? – rockerest Aug 09 '11 at 22:20
  • @rockerest: Yes, deeper than `two['two-two']`. – hakre Aug 09 '11 at 22:28
  • I think it may be too simplified to allow us to help. The problem doesn't really make any sense. – vascowhite Aug 09 '11 at 22:30
  • @hakre the syntax would be something like `two['two-two']['more']`. This is an extremely fringe case that I doubt would ever happen, but I was looking to make this as solid as possible. In other words, sub-elements will always be accessed just like a normal array, but in 99.9% of cases, it will only be two dimensional (`two` or `one` followed by any number of keys) – rockerest Aug 09 '11 at 22:31
  • @rockerest: I updated [my answer](http://stackoverflow.com/questions/7003559/use-strings-to-access-potentially-large-multidimensional-arrays/7003761#7003761) with some example code that corresponds to the syntax of the string you're looking for. – hakre Aug 09 '11 at 22:56

4 Answers4

5

Considering $vars being your variables you would like to get one['one-one'] or two['two-two']['more'] from (Demo):

$vars = function($str) use ($vars)
{
    $c = function($v, $w) {return $w ? $v[$w] : $v;};
    return array_reduce(preg_split('~\[\'|\'\]~', $str), $c, $vars);
};
echo $vars("one['one-one']"); # hello
echo $vars("two['two-two']['more']"); # tea-time!

This is lexing the string into key tokens and then traverse the $vars array on the keyed values while the $vars array has been turned into a function.


Older Stuff:

Overload the array with a function that just eval's:

$vars = array(
    'one' => array(
        'one-one' => "hello",
        'one-two' => "goodbye"
    ),
    'two' => array(
        'two-one' => "foo",
        'two-two' => "bar"
    )
);

$vars = function($str) use ($vars)
{
    return eval('return $vars'.$str.';');
};

echo $vars("['one']['one-two']"); # goodbye

If you're not a fan of eval, change the implementation:

$vars = function($str) use ($vars)
{
    $r = preg_match_all('~\[\'([a-z-]+)\']~', $str, $keys);
    $var = $vars;
    foreach($keys[1] as $key)
        $var = $var[$key];
    return $var;
};
echo $vars("['one']['one-two']"); # goodbye
hakre
  • 193,403
  • 52
  • 435
  • 836
2

How about

$vars = array(
    'one' => array(
        'one-one' => "hello",
        'one-two' => "goodbye"
    ),
    'two' => array(
        'two-one' => "foo",
        'two-two' => "bar"
    )
);

function get( $string, $vars )
{
    $keys = explode( '][', substr( $string, 1, -1 ) );
    foreach( $keys as $key ) {
        $vars = $vars[$key];
    }
    return $vars;
}

echo get( '[two][two-one]', $vars );
hashchange
  • 7,029
  • 1
  • 45
  • 41
0

For one, you've not got a $var in your get() function. $var was defined outside the function, and PHP scoping rules do not make "higher" vars visible in lower scopes unless explictly made global in the lower scope:

function get($string) {
   global $vars;
   eval('$x = $vars' . $string);
   return $x;
}

get("['two']['two-two']");

might work, but this isn't tested, and using eval is almost always a very bad idea.

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • I've seen the `eval()` answer in other places, which reduces the answer to my question to, indeed, a single line. However...yeah, I was a quite wary of `eval()`. Fixing the scoping issue... – rockerest Aug 09 '11 at 22:09
  • replace ' with " and you'll get a lot of problems – PauAI Mar 21 '17 at 02:28
0

Kohana has a nice Config class which alows something like this:

echo Config::get("two.two-two");

You can check it out here: http://kohanaframework.org/3.1/guide/api/Config

Mārtiņš Briedis
  • 17,396
  • 5
  • 54
  • 76