How to recursively create a multidimensional array? - php

I am trying to create a multi-dimensional array whose parts are determined by a string. I'm using . as the delimiter, and each part (except for the last) should be an array
ex:
config.debug.router.strictMode = true
I want the same results as if I were to type:
$arr = array('config' => array('debug' => array('router' => array('strictMode' => true))));
This problem's really got me going in circles, any help is appreciated. Thanks!

Let’s assume we already have the key and value in $key and $val, then you could do this:
$key = 'config.debug.router.strictMode';
$val = true;
$path = explode('.', $key);
Builing the array from left to right:
$arr = array();
$tmp = &$arr;
foreach ($path as $segment) {
$tmp[$segment] = array();
$tmp = &$tmp[$segment];
}
$tmp = $val;
And from right to left:
$arr = array();
$tmp = $val;
while ($segment = array_pop($path)) {
$tmp = array($segment => $tmp);
}
$arr = $tmp;

I say split everything up, start with the value, and work backwards from there, each time through, wrapping what you have inside another array. Like so:
$s = 'config.debug.router.strictMode = true';
list($parts, $value) = explode(' = ', $s);
$parts = explode('.', $parts);
while($parts) {
$value = array(array_pop($parts) => $value);
}
print_r($parts);
Definitely rewrite it so it has error checking.

Gumbo's answer looks good.
However, it looks like you want to parse a typical .ini file.
Consider using library code instead of rolling your own.
For instance, Zend_Config handles this kind of thing nicely.

I really like JasonWolf answer to this.
As to the possible errors: yes, but he supplied a great idea, now it is up to the reader to make it bullet proof.
My need was a bit more basic: from a delimited list, create a MD array. I slightly modified his code to give me just that. This version will give you an array with or without a define string or even a string without the delimiter.
I hope someone can make this even better.
$parts = "config.debug.router.strictMode";
$parts = explode(".", $parts);
$value = null;
while($parts) {
$value = array(array_pop($parts) => $value);
}
print_r($value);

// The attribute to the right of the equals sign
$rightOfEquals = true;
$leftOfEquals = "config.debug.router.strictMode";
// Array of identifiers
$identifiers = explode(".", $leftOfEquals);
// How many 'identifiers' we have
$numIdentifiers = count($identifiers);
// Iterate through each identifier backwards
// We do this backwards because we want the "innermost" array element
// to be defined first.
for ($i = ($numIdentifiers - 1); $i >=0; $i--)
{
// If we are looking at the "last" identifier, then we know what its
// value is. It is the thing directly to the right of the equals sign.
if ($i == ($numIdentifiers - 1))
{
$a = array($identifiers[$i] => $rightOfEquals);
}
// Otherwise, we recursively append our new attribute to the beginning of the array.
else
{
$a = array($identifiers[$i] => $a);
}
}
print_r($a);

Related

How to break string by separator into multidimensional array in PHP

I have a string 'value1/value2'. The required output is $_SESSION['value1']['value2']. i tried using explode and then array_reduce over explode values but with no success.
My code looks like
function set($key, $value){
/* code */
}
set('key1/key2', 'some_text');
required output like $_SESSION['key1']['key2'] = 'some_text';
key1/key2 is not fixed it may be 'key1' or 'key1/key2/key3' and so on.
Anyone be make fiddle of it is Highly appreciable.
Thanks
Accessing a value via a key-path string, as in your original question, using your original idea and let array_reduce do the work looks like:
$session = ['value1' => [ 'value2' => [ 'value3' => 'there you are!' ]]];
$path = explode('/', 'value1/value2/value3');
$val = array_reduce($path,
function(&$carry, $key) { return $carry[$key];},
$session);
echo $val
--> "there you are!"
Setting a value can be done e.g. like this, following the path by reference, creating arrays as needed:
function set($path, $value) {
$path = explode('/', $path);
$key = array_pop($path);
$arr = &$_SESSION;
foreach($path as $part) {
// carefull, this might lose values to accommodate
// the structure wanted with $path
(isset($arr[$part]) && is_array($arr[$part])) || ($arr[$part] = []);
$arr =& $arr[$part];
}
$arr[$key] = $value;
};
Try this
<?php
session_start();
$string = 'value1/value2';
$array = explode("/",$string);
$_SESSION[$array[0]][$array[1]] = "ccccccc";//$_SESSION['value1']['value2']
For a general case (i.e. for more than two pieces), you'll need to iterate over the segments, and incrementally index further into your target array:
<?php
$string = 'value1/value2/value3';
$_SESSION = ['value1' => ['value2' => ['value3' => 'My String']]];
$target = $_SESSION;
foreach (explode('/', $string) as $piece) {
$target =& $target[$piece];
}
echo $target; // My String

remove the extension from the key

How to remove the extension from the array key, ie .md.
It should look like this key: [about] => pages/about.md
Array:
Array:
(
[_desktop.md] => pages/_desktop.md
[about.md] => pages/about.md
[contact.md] => pages/contact.md
[errorpages] => Array
(
[403.md] => pages/errorpages/403.md
[404.md] => pages/errorpages/404.md
[500.md] => pages/errorpages/500.md
[503.md] => pages/errorpages/503.md
)
[home.md] => pages/home.md
[indexpage.md] => pages/indexpage.md
)
Code:
function generatePathTree($dir) {
$pathstack = array($dir);
$contentsroot = array();
$contents = &$contentsroot;
while ($path = array_pop($pathstack)) {
$contents[basename($path)] = array();
$contents = &$contents[basename($path)];
foreach (scandir($path) as $filename) {
if ('.' != substr($filename, 0, 1)) {
$newPath = $path.'/'.$filename;
if (is_dir($newPath)) {
array_push($pathstack, $newPath);
$contents[basename($newPath)] = array();
} else {
$contents[basename($filename)] = $newPath;
}
}
$contentsroot = preg_replace("/\\.[^.]*$/", "", basename($filename));
}
}
return $contentsroot[basename($dir)];
}
I tried so:
$contentsroot = preg_replace("/\\.[^.]*$/", "", basename($filename));
But alas.
How to do?
In this code block (This is where filename gets assigned instead of dir):
else {
$fileExtRemoved = preg_replace("/[\.](.*)/", "", $filename);
$contents[basename($fileExtRemoved)] = $newPath;
}
Your regexp is a little bit off, this is the correct one:
https://3v4l.org/mMPrt
You can assigne the old value to new key and then unset the old key
$arr['about'] = $arr['about.md'];
unset($arr['about.md']);
It's three characters on all of them?
So why not just use sub_str($key , 0, -3);?
If it's always three chars it should work, and as I see it, it seems to be three.
So maybe:
$contents[basename(substr($filename,0,-3))];
Requirement: remove any part of the array key string after the first . if it exists in the string.
Solution:
list($key,) = explode('.', $old_key);
$arr[$key] = $arr[$old_key];
unset($arr[$old_key]);
A side note: there must be a better way to do the recursive loop. But a quick look suggests that array_walk_recursive only operates on the leaf nodes; array_map and array_reduce don't have recursive variants and they only take values anyway. Since you didn't explicitly ask for this, I'll stop there.

Creating a function that takes arguments and passes variables

I am creating a script that will locate a field in a text file and get the value that I need.
First used the file() function to load my txt into an array by line.
Then I use explode() to create an array for the strings on a selected line.
I assign labels to the array's to describe a $Key and a $Value.
$line = file($myFile);
$arg = 3
$c = explode(" ", $line[$arg]);
$key = strtolower($c[0]);
if (strpos($c[2], '~') !== false) {
$val = str_replace('~', '.', $c[2]);
}else{
$val = $c[2];
}
This works fine but that is a lot of code to have to do over and over again for everything I want to get out of the txt file. So I wanted to create a function that I could call with an argument that would return the value of $key and $val. And this is where I am failing:
<?php
/**
* #author Jason Moore
* #copyright 2014
*/
global $line;
$key = '';
$val = '';
$myFile = "player.txt";
$line = file($myFile); //file in to an array
$arg = 3;
$Character_Name = 3
function get_plr_data2($arg){
global $key;
global $val;
$c = explode(" ", $line[$arg]);
$key = strtolower($c[0]);
if (strpos($c[2], '~') !== false) {
$val = str_replace('~', '.', $c[2]);
}else{
$val = $c[2];
}
return;
}
get_plr_data2($Character_Name);
echo "This character's ",$key,' is ',$val;
?>
I thought that I covered the scope with setting the values in the main and then setting them a global within the function. I feel like I am close but I am just missing something.
I feel like there should be something like return $key,$val; but that doesn't work. I could return an Array but then I would end up typing just as much code to the the info out of the array.
I am missing something with the function and the function argument to. I would like to pass and argument example : get_plr_data2($Character_Name); the argument identifies the line that we are getting the data from.
Any help with this would be more than appreciated.
::Updated::
Thanks to the answers I got past passing the Array.
But my problem is depending on the arguments I put in get_plr_data2($arg) the number of values differ.
I figured that I could just set the Max of num values I could get but this doesn't work at all of course because I end up with undefined offsets instead.
$a = $cdata[0];$b = $cdata[1];$c = $cdata[2];
$d = $cdata[3];$e = $cdata[4];$f = $cdata[5];
$g = $cdata[6];$h = $cdata[7];$i = $cdata[8];
$j = $cdata[9];$k = $cdata[10];$l = $cdata[11];
return array($a,$b,$c,$d,$e,$f,$g,$h,$i,$j,$k,$l);
Now I am thinking that I can use the count function myCount = count($c); to either amend or add more values creating the offsets I need. Or a better option is if there was a way I could generate the return array(), so that it would could the number of values given for array and return all the values needed. I think that maybe I am just making this a whole lot more difficult than it is.
Thanks again for all the help and suggestions
function get_plr_data2($arg){
$myFile = "player.txt";
$line = file($myFile); //file in to an array
$c = explode(" ", $line[$arg]);
$key = strtolower($c[0]);
if (strpos($c[2], '~') !== false) {
$val = str_replace('~', '.', $c[2]);
}else{
$val = $c[2];
}
return array($key,$val);
}
Using:
list($key,$val) = get_plr_data2(SOME_ARG);
you can do this in 2 way
you can return both values in an array
function get_plr_data2($arg){
/* do what you have to do */
$output=array();
$output['key'] =$key;
$output['value']= $value;
return $output;
}
and use the array in your main function
you can use reference so that you can return multiple values
function get_plr_data2($arg,&$key,&$val){
/* do job */
}
//use the function as
$key='';
$val='';
get_plr_data2($arg,$key,$val);
what ever you do to $key in function it will affect the main functions $key
I was over thinking it. Thanks for all they help guys. this is what I finally came up with thanks to your guidance:
<?php
$ch_file = "Thor";
$ch_name = 3;
$ch_lvl = 4;
$ch_clss = 15;
list($a,$b)= get_char($ch_file,$ch_name);//
Echo $a,': ',$b; // Out Puts values from the $cdata array.
function get_char($file,$data){
$myFile = $file.".txt";
$line = file($myFile);
$cdata = preg_split('/\s+/', trim($line[$data]));
return $cdata;
}
Brand new to this community, thanks for all the patience.

How to use Explode in my case?

I have a variable that contains text with values according to an example below:
$data = "5:7|4:1|504:2|1:3|"
And I would like to achieve results like this:
$data[5] = 7;
$data[4] = 1;
$data[504] = 2;
$data[1] = 3;
I tried with explode:
$data = explode("|", $data);
//but it makes $data[0]="5:7"; $data[1]="4:1"; and so on.
Should I use explode again? Is it has any sense, or is there another way? I would like to ask for a hint or help.
There may be a more clever way, but I'd do it like this:
$data = array();
foreach (explode("|", $your_data) as $part)
{
$pieces = explode(':', $part);
// Assumes we have 2 pieces, might want to make sure here...
$data[$pieces[0]] = $pieces[1];
}
Also, I'm not sure what this data represents but keep in mind that array keys will overwrite each other, so 1:1|1:2 will result in an array with only one item (the last piece). There may be good reason to take another approach.
Sure, explode again:
$data = "5:7|4:1|504:2|1:3";
$array = array();
foreach (explode('|', $data) as $pair) {
list($id, $val) = explode(':', $pair);
$array[$id] = $val;
}
Yes you should use explode twice, like this
$newData = array();
$pairs = explode('|',$data);
foreach($pairs as $pair){
$tmp = explode(':',$pair);
$newData[$tmp[0]] = $tmp[1];
}
Try using a regexp:
$data = preg_split ("\||:", $data);
One line version:
$data = array_map(function($d) { return (int)explode(":", $d)[1]; }, explode("|", $data));

PHP Better way to Replace/Match $1

I am trying to replace $1, $2, $3 variables in a URL with another URL.
You can copy paste my example below and see my solution.
But I feel like there is a more elegant way with an array mapping type function or a better preg_replace type of thing. I just need a kick in the right direction, can you help?
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
/**
* Turn these URI's into arrays
*/
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
/**
* Store the array position of the match
*/
$positions = array();
foreach($desiredString as $key => $value) {
/**
* Look for $1, $2, $3, etc..
*/
if (preg_match('#(\$\d)#', $value)) {
$positions[$key] = $value;
}
}
/**
* Loop through the positions
*/
foreach($positions as $key => $value){
$desiredString[$key] = $findMe[$key];
}
/**
* The final result
*/
echo implode('/', $desiredString);
}
Sometimes you are out of luck and the functions you need to solve a problem directly just aren't there. This happens with every language regardless of how many libraries and builtins it has.
We're going to have to write some code. We also need to solve a particular problem. Ultimately, we want our solution to the problem to be just as clean as if we had the ideal functions given to us in the first place. Therefore, whatever code we write, we want most of it to be out of the way, which probably means we want most of the code in a separate function or class. But we don't just want to just throw around arbitrary code because all of our functions and classes should be reusable.
My approach then is to extract a useful general pattern out of the solution, write that as a function, and then rewrite the original solution using that function (which will simplify it). To find that general pattern I made the problem bigger so it might be applicable to more situations.
I ended up making the function array array_multi_walk(callback $callback [, array $array1 [, array $array2 ... ]]). This function walks over each array simultaneously and uses $callback to select which element to keep.
This is what the solution looks like using this function.
$chooser = function($a, $b) {
return strlen($a) >= 2 && $a[0] == '$' && ctype_digit($a[1])
? $b : $a;
};
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER'
);
$explodeSlashes = function($a) { return explode('/', $a); };
$find = array_map($explodeSlashes, array_keys($data));
$replace = array_map($explodeSlashes, array_values($data));
$solution = array_multi_walk(
function($f, $r) use ($chooser) {
return array_multi_walk($chooser, $f, $r);
},
$find, $replace);
And, as desired, array_multi_walk can be used for other problems. For example, this sums all elements.
$sum = function() {
return array_sum(func_get_args());
};
var_dump(array_multi_walk($sum, array(1,2,3), array(1,2,3), array(10)));
// prints the array (12, 4, 6)
You might want to make some tweaks to array_multi_walk. For example, it might be better if the callback takes the elements by array, rather than separate arguments. Maybe there should be option flags to stop when any array runs out of elements, instead of filling nulls.
Here is the implementation of array_multi_walk that I came up with.
function array_multi_walk($callback)
{
$arrays = array_slice(func_get_args(), 1);
$numArrays = count($arrays);
if (count($arrays) == 0) return array();
$result = array();
for ($i = 0; ; ++$i) {
$elementsAti = array();
$allNull = true;
for ($j = 0; $j < $numArrays; ++$j) {
$element = array_key_exists($i, $arrays[$j]) ? $arrays[$j][$i] : null;
$elementsAti[] = $element;
$allNull = $allNull && $element === null;
}
if ($allNull) break;
$result[] = call_user_func_array($callback, $elementsAti);
}
return $result;
}
So at the end of the day, we had to write some code, but not only is the solution to the original problem slick, we also gained a generic, reusable piece of code to help us out later.
Why there should not be $2,$4 but $1,$2 ?if you can change your array then it can be solved in 3 or 4 lines codes.
$data = array(
'project/$2/details/$4' => 'newby/EXAMPLE/something/OTHER'
);
foreach($data as $desiredString => $findMe)
{
$regexp = "#(".implode(')/(',explode('/',$findMe)).")#i";
echo preg_replace($regexp,$desiredString,$findMe);
}
I've shortened your code by removing comments for better readability. I'm using array_map and the mapping function decides what value to return:
<?php
function replaceDollarSigns($desired, $replace)
{
return preg_match('#(\$\d)#', $desired) ? $replace : $desired;
}
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
var_dump(implode('/', array_map('replaceDollarSigns', $desiredString, $findMe)));
}
?>
Working example: http://ideone.com/qVLmn
You can also omit the function by using create_function:
<?php
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/something/OTHER',
);
foreach($data as $desiredString => $findMe)
{
$desiredString = explode('/', $desiredString);
$findMe = explode('/', $findMe);
$result = array_map(
create_function(
'$desired, $replace',
'return preg_match(\'#(\$\d)#\', $desired) ? $replace : $desired;'
),
$desiredString,
$findMe);
var_dump(implode('/', $result));
}
?>
Working example: http://ideone.com/OC0Ak
Just saying, why don't use an array pattern/replacement in preg_replace? Something like this:
<?php
/**
* Key = The DESIRED string
* Value = The ORIGINAL value
*
* Desired Result: project/EXAMPLE/something/OTHER
*/
$data = array(
'project/$1/details/$2' => 'newby/EXAMPLE/details/OTHER'
);
$string = 'project/$1/details/$2';
$pattern[0] = '/\$1/';
$pattern[1] = '/\$2/';
$replacement[0] = 'EXAMPLE';
$replacement[1] = 'OTHER';
$result = preg_replace($pattern, $replacement, $string);
echo $result;
I think that it's much easier than what you're looking for. You can see that it works here: http://codepad.org/rCslRmgs
Perhaps there's some reason to keep the array key => value to accomplish the replace?

Categories