I am attempting to convert a string into a multi-dimensional array with keys that are only allowed.
Given the string:
"type:blue, type:red, shift:second, shift:first, category:circle, system:unknown"
With the only possible keys:
$possibleKeys = [ "category", "shift", "type" ];
Create the array:
$searchArray = array( "type" => [ "blue", "red" ],
"shift" => [ "second", "first" ],
"category" => [ "circle" ]
);
So system:unknown is not added to the $searchArray as it is not in the $possibleKeys array.
Here is my code:
$myString = "type:blue, type:red, shift:second, shift:first, category:circle, system:unknown";
$params = explode( ",", $myString );
$possibleKeys = [ "category", "shift", "type", "chances" ];
$nodes = [];
foreach ($params as $param) {
$node = explode( ":", $param );
if (array_key_exists( $node[ 0 ], $nodes ) && in_array( $node[ 0 ], $possibleKeys )) {
array_push( $nodes[ $node[ 0 ] ], $node[ 1 ] );
} else {
$nodes[ $node[ 0 ] ] = $node[ 1 ];
}
}
But when I var_dump the array is garbage and some keys have extra spaces.
array(4) {
["type"]=>
string(9) "blue"
[" type"]=>
string(8) "red"
[" shift"]=>
string(5) "first"
[" category"]=>
string(3) "circle"
}
What am I doing wrong?
First build your associative array with your allowed keys.
Break your string up - first explode into an array by comma delimiter to get your pairs, then trim and explode each pair. If the left side is an allowed key, add the right side to the associative array element. As usual more than one way to do it, but this is what my current caffeine levels call for...
<?php
$dataString="type:blue, type:red, shift:second, shift:first, category:circle, system:unknown";
$desiredResult=array();
foreach(array("category", "shift", "type") as $desiredKey){
$desiredResult[$desiredKey]=array();
}
$tmpArr=explode(",",$dataString);
foreach($tmpArr as $pair){
$pair=trim($pair);
$pairArr=explode(":",$pair);
if(isset($desiredResult[$pairArr[0]])){
$desiredResult[$pairArr[0]][]=$pairArr[1];
}
}
print_r($desiredResult);
?>
Gives
Array
(
[category] => Array
(
[0] => circle
)
[shift] => Array
(
[0] => second
[1] => first
)
[type] => Array
(
[0] => blue
[1] => red
)
)
You can use regex to find the values and loop only the possible keys.
This means it only does three iterations to find all values and put them in the array.
The preg_match will find all words connected to the key in the full string.
$myString = "type:blue, type:red, shift:second, shift:first, category:circle, system:unknown";
$possibleKeys = [ "category", "shift", "type" ];
foreach($possibleKeys as $key){
preg_match_all("/" . $key . ":(.*?),/", $myString, $m);
$new[$key] = $m[1]; // $m[1] will hold all the matching values to the key searched for
}
var_dump($new);
The output is:
array(3) {
["category"]=>
array(1) {
[0]=>
string(6) "circle"
}
["shift"]=>
array(2) {
[0]=>
string(6) "second"
[1]=>
string(5) "first"
}
["type"]=>
array(2) {
[0]=>
string(4) "blue"
[1]=>
string(3) "red"
}
}
https://3v4l.org/XAeRC
Because you explode with "," try to explode with ", " (comma space) instead.
You can do small tweaks with your code and avoid some extra method using like trim() or array_key_exits()
<?php
$myString = "type:blue, type:red, shift:second, shift:first, category:circle, system:unknown";
$params = explode( ", ", $myString ); // explode by comma & space with
$possibleKeys = [ "category", "shift", "type", "chances" ];
$ignore = ['system'];
$nodes = [];
foreach ($params as $param) {
$node = explode( ":", $param );
if (in_array( $node[0], $possibleKeys )) {
$nodes[$node[0]][] = $node[1];
} else {
$nodes[$node[0]] = $node[1];
}
}
foreach($ignore as $key) {
unset($nodes[$key]);
}
print '<pre>';
print_r($nodes);
print '</pre>';
?>
DEMO: https://3v4l.org/Udav9
Related
I need to group values in my flat array so that each non-integer value starts a new group/row in my result array and every integer value encountered until the next occurring non-integer should be pushed into a subarray in the respective group/row.
Input:
$unitsList = [
"Aulas Gratuitas",
"149",
"151",
"153",
"Módulo 0",
"964",
"989",
"Módulo 1",
"985",
"1079",
"1001",
"1003"
"Módulo 2",
"1009"
];
Current code:
$newArr = array();
foreach( $unitsList as $item ) {
if( !is_numeric($item) ) {
$title = array( "title" => $item, "units" => "" );
array_push( $newArr, $title);
} elseif ( is_numeric($item) ) {
$number = $item;
array_push( $newArr, $number);
}
}
Current result:
[
[
"title" => "Aulas Gratuitas",
"units" => ""
]
"149",
"151",
"153",
[
"title" => "Módulo 0",
"units" => ""
],
"964",
"989",
[
"title" => 'Módulo 1',
"units" => ""
],
"985",
"1079",
"1001",
"1003"
[
"title" => 'Módulo 2',
"units" => ''
],
"1009"
]
As you can see, I am having a hard time adding the numbers into the "units" key.
Desired result:
[
[
"title" => "Aulas Gratuitas",
"units" => ["149", "151", "153"]
],
[
"title" => 'Módulo 0',
"units" => ["964", "989"]
],
[
"title" => 'Módulo 1',
"units" => ["985", "1079", "1001", "1003"]
],
[
"title" => "Módulo 2",
"units" => ["1009"]
]
]
So that you don't need to keep track of first level indexes, use a reference variable and push related data into that AFTER pushing the new row into the result array.
Code: (Demo)
$result = [];
foreach ($array as $value) {
if (!ctype_digit($value)) {
unset($units);
$units = [];
$result[] = ['title' => $value, 'units' => &$units];
} else {
$units[] = $value;
}
}
var_export($result);
unset() is used to destroy the reference to the previous group, otherwise newly pushed data would be sent to two (or more) places in the result array.
Reference variables have a default value of null. If your title values are guaranteed to be followed by an integer/unit value, then $units = []; can be removed. If you have a title and then another title there will be no data for the former title group. With the explicit array declaration, the group will have an empty units array instead of null.
Related questions answered with the same general technique:
Group array of objects into deeper parent-child structure
Split flat array into grouped subarrays containing values from consecutive key in the input array
How to split a string by repeated characters in PHP?
If you are running PHP7.3 or higher (and by this point you definitely should be) AND you are intimidated/mystified by using a reference variable, then you can leverage array_key_last() to locate the latest created array row.
Code: (Demo)
$result = [];
foreach ($array as $value) {
if (!ctype_digit($value)) {
$result[] = ['title' => $value, 'units' => []];
} else {
$result[array_key_last($result)]['units'][] = $value;
}
}
var_export($result);
Items in the given list aren't regular. The first item has three units, and the second has two units. We cannot convert them into the expected structure without controlling the type of each item. My solution is below. I added explanations as a comment line.
$values = array(
"string",
11111,
22222,
33333,
"string_2",
44444,
55555
);
$formattedArray = [];
$index = -1;
foreach ($values as $value) {
// If the value is a string, create the new array structure item and assign the title
if (is_string($value)) {
$index++;
$formattedArray[$index]['title'] = $value;
$formattedArray[$index]['units'] = [];
// The rest of the code in "foreach scope" is for integer values, so skip the remaining part
continue;
}
// If the following line is executing, the value is an integer
// Push the value to the current item's units
$formattedArray[$index]['units'][] = $value;
}
var_dump($formattedArray);
$originalArray = ['a', 1, 2, 3, 'b', 4, 5, 6];
function formatArray($input) {
$output = [];
foreach($input as $inputRow) {
if (is_string($inputRow)) {
$output[] = ['title' => $inputRow, 'units' => []];
} elseif (count($output)) {
$output[count($output)-1]['units'][] = $inputRow;
}
}
return $output;
}
var_dump(formatArray($originalArray));
Note that your numbers after the title CANNOT be strings, otherwise this function will recognize it as new titles.
This code will output:
array(2) {
[0]=>
array(2) {
["title"]=>
string(1) "a"
["units"]=>
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
}
[1]=>
array(2) {
["title"]=>
string(1) "b"
["units"]=>
array(3) {
[0]=>
int(4)
[1]=>
int(5)
[2]=>
int(6)
}
}
}
Approach: loop over values in array, check for value if its converted to int is equal to 0, So group by that key, otherwise accumelate they next elements.
<?php
$unitsList = [ "Aulas Gratuitas", "149", "151", "153", "Módulo 0", "964", "989", "Módulo 1", "985", "1079", "1001", "1003", "Módulo 2", "1009" ];
$result = [];
foreach ($unitsList as $key => $value) {
if(intval($value)==0){
$result[] = ['title' => $value, 'units' => []];
}else{
$result[count($result)-1]['units'][] = $value;
}
}
print_r(array_values($result));
?>
I have an array that looks something like this:
array:2 [
"test1_test2_test3" => "result"
"category_slug" => "the_slug"
]
What I need to do is convert it to a multidimensional array that would look something like this:
array:2 [
"test1" => [
"test2" => [
"test3" => "result"
]
]
"category" => [
"slug" => "the_slug"
]
]
I know that I can explode on the key to get an array but am unsure of how to then go from this to end up with the final result.
EDIT The array looks like that initially because it's being pulled from request parameters: http://url.com?test1.test2.test3=result&category.slug=the_slug and Laravel auto converts it to an array.
A simple solution:
$result = [];
foreach ($array as $key => $value) {
foreach (array_reverse(explode('_', $key)) as $key_part) {
$value = [$key_part => $value];
}
$result += $value;
}
If you need to handle several keys with the same parts (such as test1_test2_test3 and test1_test2_test4), replace the last line with:
$result = array_merge_recursive($result, $value);
My approach would be to reverse the array, then loop through the keys and nest them.
The code below should do the trick.
$array = [
"test1_test2_test3" => "result",
"category_slug" => "the_slug"
];
$array = array_map(function ($key, $value) {
$keys = array_reverse(explode('_', $key));
while($key = array_shift($keys)) {
$value = [$key => $value];
}
return $value;
}, array_keys($array), $array);
$array = call_user_func_array('array_merge', $array);
var_dump($array);
/**
array(2) {
["test1"]=>
array(1) {
["test2"]=>
array(1) {
["test3"]=>
string(6) "result"
}
}
["category"]=>
array(1) {
["slug"]=>
string(8) "the_slug"
}
}
*/
One way to go:
$arr = array("test1_test2_test3" => "result", "category_slug" => "the_slug");
$res = array();
foreach($arr as $k=>$v) {
$t = explode("_", $k);
$new_arr = array();
$tmp = $v;
for($i=count($t)-1; $i > 0; $i--) {
$new_arr[$t[$i]] = $tmp;
$tmp = $new_arr;
$new_arr = array();
}
$res[$t[0]] = $tmp;
}
print_r($res);
Result:
Array
(
[test1] => Array
(
[test2] => Array
(
[test3] => result
)
)
[category] => Array
(
[slug] => the_slug
)
)
Looking through the Laravel documentation I found a helper array_set, which means in order to change the key to a multidimensional array I can change the key to use dot notation with str_replace and then use the helper to convert it over:
$array = [
"test1_test2_test3" => "result"
"category_slug" => "the_slug"
]
$new_array = []
foreach($array as $key => $value)
{
$key = str_replace('_', '.', $key);
array_set($new_array, $key, $value);
}
Result:
array:2 [
"test1" => [
"test2" => [
"test3" => "result"
]
]
"category" => [
"slug" => "the_slug"
]
]
$variable = '
persons.0.name = "peter"
persons.0.lastname = "griffin"
persons.1.name = "homer"
persons.1.lastname = "simpsons"';
I want to generate from that $variable an array that looks like this
array(2) {
[0]=>
array(2) {
["name"]=>
string(5) "peter"
["lastname"]=>
string(7) "griffin"
}
[1]=>
array(2) {
["name"]=>
string(5) "homer"
["lastname"]=>
string(7) "simpson"
}
}
so far this is what I have so far.
$temp = explode('\r\n', $persons);
$sets = [];
foreach ($temp as $value)
{
$array = explode('=', $value);
if ($array[0] != '')
{
$array[1] = trim($array[1], '"');
$sets[$array[0]] = $array[1];
$output = $sets;
}
}
that generates "persons.1.name" as a key and "peter" as a value
I´m not sure how to generate arrays based on "." thank you.
I tried with parse_ini_string() but basically is doing the same thing.
You can use array_reduce and explode
$variable = '
persons.0.name = "peter"
persons.0.lastname = "griffin"
persons.1.name = "homer"
persons.1.lastname = "simpsons"';
$temp = explode(PHP_EOL, $variable);
$result = array_reduce($temp, function($c, $v){
$v = explode( "=", $v );
if ( trim( $v[0] ) !== "" ) {
$k = explode( ".", $v[0] );
$c[ $k[ 1 ] ][ $k[ 2 ] ] = $v[1];
}
return $c;
}, array());
echo "<pre>";
print_r( $result );
echo "</pre>";
This will result to:
Array
(
[0] => Array
(
[name ] => "peter"
[lastname ] => "griffin"
)
[1] => Array
(
[name ] => "homer"
[lastname ] => "simpsons"
)
)
Doc: http://php.net/manual/en/function.array-reduce.php
UPDATE: If you want to set depth, you can
$variable = '
persons.0.name = "peter"
persons.0.lastname = "griffin"
persons.1.name = "homer"
persons.1.lastname = "simpsons"
data = "foo"
url = so.com?var=true
';
$temp = explode(PHP_EOL, $variable);
$result = array_reduce($temp, function($c, $v){
$v = explode( "=", $v, 2 );
if ( trim( $v[0] ) !== "" ) {
$k = explode( ".", $v[0] );
$data = $v[1];
foreach (array_reverse($k) as $key) {
$data = array( trim( $key ) => $data);
}
$c = array_replace_recursive( $c, $data );
}
return $c;
}, array());
echo "<pre>";
print_r( $result );
echo "</pre>";
This will result to:
Array
(
[persons] => Array
(
[0] => Array
(
[name] => "peter"
[lastname] => "griffin"
)
[1] => Array
(
[name] => "homer"
[lastname] => "simpsons"
)
)
[data] => "foo"
[url] => so.com?var=true
)
PHP's ini parsing is limited and wouldn't parse that even if it was persons[0][name] = "peter".
Taken from my answer here How to write getter/setter to access multi-level array by key names?, first just explode on = to get the key path and value, and then explode on . for the keys and build the array:
$lines = explode("\n", $variable); //get lines
list($path, $value) = explode('=', $lines); //get . delimited path to build keys and value
$path = explode('.', $path); //explode path to get individual key names
$array = array();
$temp = &$array;
foreach($path as $key) {
$temp =& $temp[trim($key)];
}
$temp = trim($value, '"');
Also trims spaces and ".
Because each line contains the full address to and data for the array, we can create everything with a loop instead of recursion.
// Create variable for final result
$output=[];
// Loop over input lines, and let PHP figure out the key/val
foreach (parse_ini_string($variable) AS $key=>$val) {
$stack = explode('.', $key);
$pos = &$output;
// Loop through elements of key, create when necessary
foreach ($stack AS $elem) {
if (!isset($pos[$elem]))
$pos[$elem] = [];
$pos = &$pos[$elem];
}
// Whole key stack created, drop in value
$pos = $val;
}
// The final output
var_dump($output);
Output:
array(1) {
["persons"]=>
array(2) {
[0]=>
array(2) {
["name"]=>
string(5) "peter"
["lastname"]=>
string(7) "griffin"
}
[1]=>
array(2) {
["name"]=>
string(5) "homer"
["lastname"]=>
&string(8) "simpsons"
}
}
}
I have an array like(result of json_decode):
array(2) {
[0]=>
object(stdClass)#1 (3) {
["key"]=>
string(6) "sample"
["startYear"]=>
string(4) "2000"
["endYear"]=>
string(4) "2015"
}
[1]=>
object(stdClass)#2 (3) {
["key"]=>
string(13) "second_sample"
["startYear"]=>
string(4) "1986"
["endYear"]=>
string(4) "1991"
}
}
I want to convert it to array like:
array(2) {
["sample"]=>
array(2) {
["startYear"]=>
string(4) "2000"
["endYear"]=>
string(4) "2015"
}
["second_sample"]=>
array(2) {
["startYear"]=>
string(4) "1986"
["endYear"]=>
string(4) "1991"
}
}
Is there beauty way to do this (cureently I'm using foreach, but I'm not sure it is a best solution).
Added a code example:
<?php
$str='[{"key":"sample","startYear":"2000","endYear":"2015"},{"key":"second_sample","startYear":"1986","endYear":"1991"}]';
$arr=json_decode($str);
var_dump($arr);
$newArr=array();
foreach ($arr as $value){
$value=(array)$value;
$newArr[array_shift($value)]=$value;
}
var_dump($newArr);
You can use array_reduce
$myArray = array_reduce($initialArray, function ($result, $item) {
$item = (array) $item;
$key = $item['key'];
unset($item['key']);
$result[$key] = $item;
return $result;
}, array());
You can create the desired output without making any iterated function calls by using a technique called "array destructuring" (which is a functionless version of list()). Demo
Language Construct Style:
$result = [];
foreach ($array as $object) {
[
'key' => $key,
'startYear' => $result[$key]['startYear'],
'endYear' => $result[$key]['endYear']
] = (array)$object;
}
var_export($result);
Functional Style:
var_export(
array_reduce(
$array,
function($result, $object) {
[
'key' => $key,
'startYear' => $result[$key]['startYear'],
'endYear' => $result[$key]['endYear']
] = (array)$object;
return $result;
},
[]
)
);
Both will output:
array (
'sample' =>
array (
'startYear' => '2000',
'endYear' => '2015',
),
'second_sample' =>
array (
'startYear' => '1985',
'endYear' => '1991',
),
)
Simply you can use array_map like as
$result = array_map('get_object_vars',$your_array);
Edited:
As after checking your code that you've added an example over here there's no need to use an extra functions or loop to convert your array of objects into associative array instead you simply need to pass second parameter true within json_decode function like as
$arr = json_decode($json,true);
Demo
An alternative to array_reduce and other provided solutions could be:
$list = array_combine(
array_column($list, 'key'),
array_map(fn ($item) => (array) $item, array_values($list))
);
Or:
$list = array_combine(
array_column($list, 'key'),
array_map('get_object_vars', $list)
);
in PHP, is it possible to cut the |xyz part away from the key names?
The array looks like this:
array(30) {
["1970-01-01|802"]=>
array(4) {
["id"]=>
string(3) "176"
["datum"]=>
string(10) "1970-01-01"
["title"]=>
string(8) "Vorschau"
["alias"]=>
string(16) "vorschau-v15-176"
}
["1970-01-01|842"]=>
array(4) {
["id"]=>
string(3) "176"
["datum"]=>
string(10) "1970-01-01"
["title"]=>
string(8) "Vorschau"
["alias"]=>
string(16) "vorschau-v15-176"
} ...
Thank you for your help,
toni
For example, you might use this:
$newArray = array();
foreach( $oldArray as $key => $value ) {
$newArray[ substr( $key, 0, 10 ) ] = $value;
}
Or modify the array in-place:
foreach( $someArray as $key => $value ) {
unset( $someArray[ $key ] );
$someArray[ substr( $key, 0, 10 ) ] = $value;
}
Both solutions will loose value
Since the keys in your source array are
1970-01-01|802
1970-01-01|842
the output array will loose some array values: Both keys get mapped to a single destination key:
1970-01-01
Keeping all values
If you don't want to loose values, try this:
$newArray = array();
foreach( $someArray as $key => $value ) {
$newKey = substr( $key, 0, 10 );
if ( ! isset( $newArray[ $newKey ] )) {
$newArray[ $newKey ] = array();
}
$newArray[ $newKey ][] = $value;
}
Result array structure of this solution:
array(
'1970-01-01' =>
array(
0 => ...,
1 => ...
),
'1970-01-02' =>
array(
0 => ...,
1 => ...,
2 => ...
),
...
);
Kind of.. just create a new array with the trimmed key, then set the old aray equal to the new one.
$newArray = array();
foreach ($arrayList as $key => $data) {
$keyParts = explode("|", $key);
$newArray[$keyParts[0]] = $data;
}
$arrayList = $newArray;
It could be possible but in this case you would end up with 2 of the same array keys.
["1970-01-01"] and ["1970-01-01"]
The xyz behind it is required in this case.
You can do it with preg_replace:
$keys = preg_replace('/(.+)\|\d+/', '$1', array_keys($arr));
$arr = array_combine($keys, $arr);