Asked  7 Months ago    Answers:  5   Viewed   35 times

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?

 Answers

32

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
Wednesday, March 31, 2021
 
Wookai
answered 7 Months ago
51

Well, if you're keys are unique relatively to all levels, you could do:

array_walk_recursive($options, function($value, $key){
  if($value && in_array($key, array('menu_slug', 'page_title', 'option_group', 'core_template')))
    print $value;  
});

:)

But I assume that your real purpose is to wrap this text in HTML strings, so you should use switch instead of my if statement and print whatever you want. If you need to print the stuff in a certain order, use a temporary array to store the output you generate, and print it after you're done.

Saturday, May 29, 2021
 
JakeGR
answered 5 Months ago
64

To check multi-deminsions try something like this:

$pageWithNoChildren = array_map('unserialize',
    array_diff(array_map('serialize', $pageids), array_map('serialize', $parentpage)));
  • array_map() runs each sub-array of the main arrays through serialize() which converts each sub-array into a string representation of that sub-array
    • the main arrays now have values that are not arrays but string representations of the sub-arrays
  • array_diff() now has a one-dimensional array for each of the arrays to compare
  • after the difference is returned array_map() runs the array result (differences) through unserialize() to turn the string representations back into sub-arrays

Q.E.D.

Monday, June 14, 2021
 
radmen
answered 5 Months ago
63

use array_column function:

$array = array(
    array(
        'id' => 123456
    ),
    array(
        'id' => 52458
    ),
);

$id_array = array_column($array, 'id');

print_r($id_array);

output:

Array
(
    [0] => 123456
    [1] => 52458
)

for PHP version < 5.5 you can use:

$data = array_map(function($element) {
    return $element['id'];
}, $array);

print_r($data);
Friday, July 30, 2021
 
freeMagee
answered 3 Months ago
93

As a general point: it is easy to turn strings into WORD!s (e.g. to-word "foo"). However, it can be tough to magically make that WORD! reference be bound to "the variable you meant". The wily reasons for this have to do with the fact that there is no scope. See:

Is there a overall explanation about definitional scoping in Rebol and Red

So what you are trying to do is going to be a little dodgy regardless. There are better ways. But to try to avoid un-asking the question, I'll explain what's happening here and how to fix it in the style you were attempting.

corrected version is for instructional purposes only. please do this another way.

compose rejoin [namelist/:i "f/text"]

REJOIN is applied to blocks, and merges the contents, with a result type loosely based on the first element. (It's a questionable operation, but historically popular in Rebol code.)

Since namelist/:i is a string, your REJOIN will produce a string...and this string will wind up being passed to COMPOSE. But COMPOSE is meant to be applied to BLOCK!s...and searches for parenthesized groups inside of it, evaluating them while leaving the rest of the code alone. It's a kind of templating system for blocks, with no effect on other kinds of input...so you'll get the same string out.

TO-SET-PATH is thus being fed a STRING! (e.g. "var1f/text"). I didn't even know that path conversion accepted strings. I find the behavior of this operation to be puzzling, because it apparently LOADs the string and then makes it the singular element of a length 1 SET-PATH!.

>> p: to-set-path "foo/bar"
== foo/bar: ;-- huh? really, did that work?

>> type? p
== set-path! ;-- ok, good, I guess.

>> length? p
== 1 ;-- wait, what?

>> type? first p
== path! ;-- a PATH! inside a SET-PATH!...?

>> length? first p
== 2

>> type? first first p
== word!

>> foo: 10
>> get first first p
== 10 ;-- well, at least it's bound

That's not making the kind of SET-PATH! you want; you want a SET-PATH! with 2 WORD! elements. Converting a BLOCK! to a SET-PATH! would be a way of doing this.

to-set-path compose [(load rejoin [namelist/:i "f"]) text]

Now we see COMPOSE being used correctly, where it will run the evaluation inside the parentheses and leave the text word alone. This produces a block with 2 elements in it, which is easily converted to a SET-PATH!. I'm using LOAD instead of TO-WORD to take care of some of the "magic" of connecting to an actual variable that plain word conversion would not do. But it's just a workaround--not a sure thing, and won't always be the answer to the problem.

But producing a SET-PATH! doesn't mean it runs. If I say:

s: to-set-word "x"
probe type? s

No SET-WORD! is executed, it's merely generated. And in this case, stored in the variable s. But if I hadn't stored it in a variable, the evaluation product would have just been thrown out...the way 2 is simply thrown out if I write 1 + 1 print "hi". To execute the SET-PATH!, you need to put it in a context where it will be composed into source and evaluated.

(Note: Ren-C has a primitive called EVAL which can do this on the fly, e.g. eval (quote x:) 10 will assign 10 to x.)

But in Red you'll need to do something like this:

namelist: ["var1" "var2"]
var1: 5
var2: 10

process: [
    repeat i length? namelist [
        do probe compose [
            (to-set-path compose [(load rejoin [namelist/:i "f"]) text])
            to-string
            (load namelist/:i)
        ]
    ]
]

lay: layout [ 
    text "Values to appear here: "
    var1f: field "a"
    var2f: field "b"

    button "Click" [do process]
]

view lay

Now your outer COMPOSE is building an 3-element block, where the first element will be a SET-PATH!, the second a WORD! that was literally left alone to convert your integer to a string, and the third a WORD! that will be evaluated to the relevant integer. The DO of that block will have the assignment effect.

I changed your to-word namelist/:i to load namelist/:i. Again, for the reason I mentioned...TO-WORD alone doesn't put on a "binding".

I left a PROBE in there so you could see what is built and executed:

[var1f/text: to-string var1]
[var2f/text: to-string var2]

PROBE is a very helpful tool, which outputs its argument but also passes it through. You can insert it at various points in your code to get a better understanding of what's going on.

(Note: If you're wondering why I don't suggest writing a narrow EVAL-2 helper operation that only works for SET-PATH!, it's because such a thing exists with a better name. It's called SET. Try set (quote x:) 10 then print x. In fact, variants of this is how you'd actually want to do things... obj: make object! [a: 10] then set (in obj 'a) 20 then print obj/a. As I said, there's a lot better ways to go about what you're doing, but I tried to stay focused on doing it the-way-you-were-trying.)

Saturday, August 28, 2021
 
aphoria
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :