Related
I have an array that looks like this.
$array = [
0 => 'abc',
1 => [
0 => 'def',
1 => 'ghi'
],
'assoc1' => [
'nassoc' => 'jkl',
'nassoc2' => 'mno',
'nassoc3' => '',
'nassoc4' => false
]
];
The $array can have numeric keys or be an assoc array or a mixed one. The level of nesting is not known. Also the values of the array can also be bool or null or an empty string ''
I need to able to convert this into a scalar array with key value pairs. And then later reconvert it back to the exact same array.
So the scalar array could look like
$arrayScalar = [
'0' => 'abc',
'1[0]' => 'def',
'1[1]' => 'ghi',
'assoc1[nassoc]' => 'jkl',
'assoc1[nassoc2]' => 'mno',
'assoc1[nassoc3]' => '',
'assoc1[nassoc4]' => false
];
And then later be able to get back to the initial $array.
I wrote a parser and it does not currently handle bool values correctly.
I have a feeling this is at best a super hacky method to do what I am after. Also I have been able to test it only so much.
function flattenNestedArraysRecursively($nestedArray, $parent = '', &$flattened = [])
{
$keys = array_keys($nestedArray);
if (empty($keys)) {
$flattened[$parent] = 'emptyarray';
} else {
foreach ($keys as $value) {
if (is_array($nestedArray[$value])) {
$reqParent = (!empty($parent)) ? $parent . '|!|' . $value : $value;
$this->flattenNestedArraysRecursively($nestedArray[$value], $reqParent, $flattened);
} else {
$reqKey = (!empty($parent)) ? $parent . '|!|' . $value : $value;
$flattened[$reqKey] = $nestedArray[$value];
}
}
}
return $flattened;
}
function reCreateFlattenedArray($flatArray): array
{
$arr = [];
foreach ($flatArray as $key => $value) {
$keys = explode('|!|', $key);
$arr = $this->reCreateArrayRecursiveWorker($keys, $value, $arr);
}
return $arr;
}
function reCreateArrayRecursiveWorker($keys, $value, $existingArr)
{
//Outside to Inside
$keyCur = array_shift($keys);
//Check if keyCur Exists in the existingArray
if (key_exists($keyCur, $existingArr)) {
// Check if we have reached the deepest level
if (empty($keys)) {
//Return the Key => value mapping
$existingArr[$keyCur] = $value;
return $existingArr;
} else {
// If not then continue to go deeper while appending deeper levels values to current key
$existingArr[$keyCur] = $this->reCreateArrayRecursiveWorker($keys, $value, $existingArr[$keyCur]);
return $existingArr;
}
} else {
// If Key does not exists in current Array
// Check deepest
if (empty($keys)) {
//Return the Key => value mapping
$existingArr[$keyCur] = $value;
return $existingArr;
} else {
// Add the key
$existingArr[$keyCur] = $this->reCreateArrayRecursiveWorker($keys, $value, []);
return $existingArr;
}
}
}
Is there a better more elegant way of doing this, maybe http_build_query or something else I am not aware of.
Sandbox link -> http://sandbox.onlinephpfunctions.com/code/50b3890e5bdc515bc145eda0a1b34c29eefadcca
Flattening:
Your approach towards recursion is correct. I think we can make it more simpler.
We loop over the array. if the value is an array in itself, we recursively make a call to this new child subarray.
This way, we visit each key and each value. Now, we are only left to manage the keys to assign them when adding to our final resultant array, say $arrayScalar.
For this, we make a new function parameter which takes the parent key into account when assigning. That's it.
Snippet:
$arrayScalar = [];
function flatten($array,&$arrayScalar,$parent_key){
foreach($array as $key => $value){
$curr_key = empty($parent_key) ? $key : $parent_key . '[' . $key . ']';
if(is_array($value)){
flatten($value,$arrayScalar,$curr_key);
}else{
$arrayScalar[$curr_key] = $value;
}
}
}
flatten($array,$arrayScalar,'');
var_export($arrayScalar);
Demo: http://sandbox.onlinephpfunctions.com/code/1e3092e9e163330f43d495cc9d4acb672289a987
Unflattening:
This one is a little tricky.
You might have already noticed that the keys in the flattened array are of the form key1[key2][key3][key4] etc.
So, we collect all these individually in a new array, say $split_key. It might look like this.
array (
'key1',
'key2',
'key3',
'key4',
)
To achieve the above, we do a basic string parsing and added in-between keys to the array whenever we reach the end of the key string or [ or ].
Next, to add them to our final resultant array, we loop over the collected keys and check if they are set in our final array. If not so, set them. We now pass child array reference to our temporary variable $temp. This is to edit the same copy of the array. In the end, we return the result.
Snippet:
<?php
function unflatten($arrayScalar){
$result = [];
foreach($arrayScalar as $key => $value){
if(is_int($key)) $key = strval($key);
$split_key = [];
$key_len = strlen($key);
$curr = '';
// collect them as individual keys
for($i = 0; $i < $key_len; ++$i){
if($key[ $i ] == '[' || $key[ $i ] == ']'){
if(strlen($curr) === 0) continue;
$split_key[] = $curr;
$curr = '';
}else{
$curr .= $key[ $i ];
}
if($i === $key_len - 1 && strlen($curr) > 0){
$split_key[] = $curr;
}
}
// collecting them ends
//add them to our resultant array.
$temp = &$result;
foreach($split_key as $sk){
if(!isset($temp[ $sk ])){
$temp[ $sk ] = [];
}
$temp = &$temp[$sk];
}
$temp = $value;
}
return $result;
}
var_export(unflatten($arrayScalar));
Demo: http://sandbox.onlinephpfunctions.com/code/66136a699c3c5285eed3d3350ed4faa5bbce4b76
How do i convert multidimensional array to file path.
I have this array :
$data = [
"users" => [
"joe" => [
"photos" => ["a.jpg","b.jpg"],
"files" => ["a.doc","b.doc"]
],
"annie" => [
"photos" => ["a.jpg","b.jpg"],
"files" => ["a.doc","b.doc"]
],
]
];
that i must convert to path example :
"users/joe/photos/a.jpg";
"users/joe/photos/b.jpg";
"users/joe/files/a.doc";
"users/joe/files/b.doc";
"users/annie/photos/a.jpg";
"users/annie/photos/b.jpg";
"users/annie/files/a.doc";
"users/annie/files/b.doc";
But i can't have the best result with this functions :
$path = "";
function iterate($data, $path)
{
echo "<br>";
foreach ($data as $key => $item){
if (is_array($item)){
$path .= $key.DIRECTORY_SEPARATOR;
iterate($item, $path);
}else{
echo $path.$item."<br>";
}
}
}
output :
users/joe/photos/a.jpg
users/joe/photos/b.jpg
users/joe/photos/files/a.doc
users/joe/photos/files/b.doc
users/joe/annie/photos/a.jpg
users/joe/annie/photos/b.jpg
users/joe/annie/photos/files/a.doc
users/joe/annie/photos/files/b.doc
Please help.
Thanks
You could make use of RecursiveIteratorIterator + RecursiveArrayIterator:
function computeFilePaths(array $fileTree): array
{
$filePaths = [];
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($fileTree));
foreach ($iterator as $fileName) {
for ($folders = [], $pos = 0, $depth = $iterator->getDepth(); $pos < $depth; $pos++) {
$folders[] = $iterator->getSubIterator($pos)->key();
}
$filePaths[] = implode('/', $folders) . '/' . $fileName;
}
return $filePaths;
}
print_r(computeFilePaths($yourArrayGoesHere));
I highly suggest this question's selected answer to understand how these iterators work.
I have this general data structure:
$levels = array('country', 'state', 'city', 'location');
I have data that looks like this:
$locations = array(
1 => array('country'=>'USA', 'state'=>'New York', 'city'=>'NYC', 'location'=>'Central Park', 'count'=>123),
2 => array('country'=>'Germany', ... )
);
I want to create hierarchical arrays such as
$hierarchy = array(
'USA' => array(
'New York' => array(
'NYC' => array(
'Central Park' => 123,
),
),
),
'Germany' => array(...),
);
Generally I would just create it like this:
$final = array();
foreach ($locations as $L) {
$final[$L['country']][$L['state']][$L['city']][$L['location']] = $L['count'];
}
However, it turns out that the initial array $levels is dynamic and can change in values and length So I cannot hard-code the levels into that last line, and I do not know how many elements there are. So the $levels array might look like this:
$levels = array('country', 'state');
Or
$levels = array('country', 'state', 'location');
The values will always exist in the data to be processed, but there might be more elements in the processed data than in the levels array. I want the final array to only contain the values that are in the $levels array, no matter what additional values are in the original data.
How can I use the array $levels as a guidance to dynamically create the $final array?
I thought I could just build the string $final[$L['country']][$L['state']][$L['city']][$L['location']] with implode() and then run eval() on it, but is there are a better way?
Here's my implementation. You can try it out here:
$locations = array(
1 => array('country'=>'USA', 'state'=>'New York', 'city'=>'NYC', 'location'=>'Central Park', 'count'=>123),
2 => array('country'=>'Germany', 'state'=>'Blah', 'city'=>'NY', 'location'=>'Testing', 'count'=>54),
);
$hierarchy = array();
$levels = array_reverse(
array('country', 'state', 'city', 'location')
);
$lastLevel = 'count';
foreach ( $locations as $L )
{
$array = $L[$lastLevel];
foreach ( $levels as $level )
{
$array = array($L[$level] => $array);
}
$hierarchy = array_merge_recursive($hierarchy, $array);
}
print_r($hierarchy);
Cool question. A simple approach:
$output = []; //will hold what you want
foreach($locations as $loc){
$str_to_eval='$output';
for($i=0;$i<count($levels);$i++) $str_to_eval .= "[\$loc[\$levels[$i]]]";
$str_to_eval .= "=\$loc['count'];";
eval($str_to_eval); //will build the array for this location
}
Live demo
If your dataset always in fixed structure, you might just loop it
$data[] = [country=>usa, state=>ny, city=>...]
to
foreach ($data as $row) {
$result[][$row[country]][$row[state]][$row[city]] = ...
}
In case your data is dynamic and the levels of nested array is also dynamic, then the following is an idea:
/* convert from [a, b, c, d, ...] to [a][b][...] = ... */
function nested_array($rows, $level = 1) {
$data = array();
$keys = array_slice(array_keys($rows[0]), 0, $level);
foreach ($rows as $r) {
$ref = &$data[$r[$keys[0]]];
foreach ($keys as $j => $k) {
if ($j) {
$ref = &$ref[$r[$k]];
}
unset($r[$k]);
}
$ref = count($r) > 1 ? $r : reset($r);
}
return $data;
}
try this:
<?php
$locations = [
['country'=>'USA', 'state'=>'New York', 'city'=>'NYC', 'location'=>'Central Park', 'street'=>'7th Ave', 'count'=>123],
['country'=>'USA', 'state'=>'Maryland', 'city'=>'Baltimore', 'location'=>'Harbor', 'count'=>24],
['country'=>'USA', 'state'=>'Michigan', 'city'=>'Lansing', 'location'=>'Midtown', 'building'=>'H2B', 'count'=>7],
['country'=>'France', 'state'=>'Sud', 'city'=>'Marseille', 'location'=>'Centre Ville', 'count'=>12],
];
$nk = array();
foreach($locations as $l) {
$jsonstr = json_encode($l);
preg_match_all('/"[a-z]+?":/',$jsonstr,$e);
$narr = array();
foreach($e[0] as $k => $v) {
if($k == 0 ) {
$narr[] = '';
} else {
$narr[] = ":{";
}
}
$narr[count($e[0]) -1] = ":" ;
$narr[] = "";
$e[0][] = ",";
$jsonstr = str_replace($e[0],$narr,$jsonstr).str_repeat("}",count($narr)-3);
$nk [] = $ko =json_decode($jsonstr,TRUE);
}
print_r($nk);
Database have three field:
here Name conatin contry state and city name
id,name,parentid
Pass the contry result to array to below function:
$data['contry']=$this->db->get('contry')->result_array();
$return['result']=$this->ordered_menu( $data['contry'],0);
echo "<pre>";
print_r ($return['result']);
echo "</pre>";
Create Function as below:
function ordered_menu($array,$parent_id = 0)
{
$temp_array = array();
foreach($array as $element)
{
if($element['parent_id']==$parent_id)
{
$element['subs'] = $this->ordered_menu($array,$element['id']);
$temp_array[] = $element;
}
}
return $temp_array;
}
I am having a string below
$string = ot>4>om>6>we>34>ff>45
I would like the JSON output be like
[{"name":"website","data":["ot","om","we","ff"]}]
and
[{"name":"websitedata","data":["4","6","34","45"]}]
what I've tried
$query = mysql_query("SELECT month, wordpress, codeigniter, highcharts FROM project_requests");
$category = array();
$category['name'] = 'website';
$series1 = array();
$series1['name'] = 'websitedata';
while($r = mysql_fetch_array($query)) {
$category['data'][] = $r['month'];
}
$result = array();
array_push($result,$category);
array_push($result,$series1);
print json_encode($result, JSON_NUMERIC_CHECK);
but the above code is applicable only if the data are present in rows from a mysql table, what i want is achieve the same result with the data from the above string. that is
$string = ot>4>om>6>we>34>ff>45
NEW UPDATE:
I would like to modify the same string
$string = ot>4>om>6>we>34>ff>45
into
json output:
[
{
"type" : "pie",
"name" : "website",
"data" : [
[
"ot",
4
],
[
"om",
6
],
[
"we",
34
]
]
}
]
I have updated the answer please check the json part, I would like the php code.
regards
You can explode() on the >s, and then loop through the elements:
$string = "ot>4>om>6>we>34>ff>45";
$array1 = [
'name'=>'website',
'data'=>[]
]
$array2 = [
'name'=>'websitedata',
'data'=>[]
]
foreach(explode('>', $string) as $index => $value){
if($index & 1) //index is odd
$array2['data'][] = $value;
else //index is even
$array1['data'][] = $value;
}
echo json_encode($array1); //prints {"name":"website","data":["ot","om","we","ff"]}
echo json_encode($array2); //prints {"name":"websitedata","data":["4","6","34","45"]}
A solution using preg_match_all():
$string = "ot>4>om>6>we>34>ff>45";
preg_match_all('/(\w+)>(\d+)/', $string, $matches);
$array1 = [
'name'=>'website',
'data'=> $matches[1]
];
$array2 = [
'name'=>'websitedata',
'data'=> $matches[2]
];
echo json_encode($array1); //prints {"name":"website","data":["ot","om","we","ff"]}
echo json_encode($array2); //prints {"name":"websitedata","data":["4","6","34","45"]}
Update:
To get the second type of array you wanted, use this:
//since json_encode() wraps property names in double quotes (which prevents the chart script from working), you'll have to build the json object manually
$string = "ot>4>om>6>we>34>ff>45";
preg_match_all('/(\w+)>(\d+)/', $string, $matches);
$data = [];
foreach($matches[1] as $index => $value){
if(isset($matches[2][$index]))
$data[] = '["' . $value . '",' . $matches[2][$index] . ']';
}
$type = 'pie';
$name = 'website';
echo $jsonString = '[{type:"' . $type . '",name:"' . $name . '",data:[' . implode(',', $data) . ']}]'; // prints [{type:"pie",name:"website",data:[["ot",4],["om",6],["we",34],["ff",45]]}]
Update #2:
This code uses explode(), and although it's probably not the most efficient way of doing it, it works.
//since json_encode() wraps property names in double quotes (which prevents the chart script from working), you'll have to build the json object manually
$string = "ot>4>om>6>we>34>ff>45";
$keys = [];
$values = [];
foreach(explode('>', $string) as $key => $value){
if(!($key & 1)) //returns true if the key is even, false if odd
$keys[] = $value;
else
$values[] = $value;
}
$data = [];
foreach($keys as $index => $value){
if(isset($values[$index]))
$data[] = '["' . $value . '",' . $values[$index] . ']';
}
$type = 'pie';
$name = 'website';
echo $jsonString = '[{type:"' . $type . '",name:"' . $name . '",data:[' . implode(',', $data) . ']}]'; // prints [{type:"pie",name:"website",data:[["ot",4],["om",6],["we",34],["ff",45]]}]
This should work, though there are probably better ways to do it.
$string = "ot>4>om>6>we>34>ff>45";
$website = ["name" => "website", "data" => []];
$websiteData = ["name" => "websitedata", "data" => []];
foreach(explode(">", $string) as $i => $s) {
if($i % 2 === 0) {
$website["data"][] = $s;
} else {
$websiteData["data"][] = $s;
}
}
echo json_encode($website);
echo json_encode($websiteData);
A regex alternative:
preg_match_all("/([a-z]+)>(\d+)/", $string, $matches);
$website = ["name" => "website", "data" => $matches[1]];
$websiteData = ["name" => "websitedata", "data" => $matches[2]];
Try this code:
$string = 'ot>4>om>6>we>34>ff>45';
$string_split = explode('>', $string);
$data = array("name" => "website");
$data2 = $data;
foreach ($string_split as $key => $value)
{
if (((int)$key % 2) === 0)
{
$data["data"][] = $value;
}
else
{
$data2["data"][] = $value;
}
}
$json1 = json_encode($data, JSON_NUMERIC_CHECK);
$json2 = json_encode($data2, JSON_NUMERIC_CHECK);
echo $json1;
echo $json2;
Output:
{"name":"website","data":["ot","om","we","ff"]}
{"name":"website","data":[4,6,34,45]}
Regards.
Try this snippet:
$strings = explode('>', 'ot>4>om>6>we>34>ff>45');
// print_r($string);
$x = 0;
$string['name'] = 'website';
$numbers['name'] = 'websitedata';
foreach ($strings as $s)
{
if ($x == 0) {
$string['data'][] = $s;
$x = 1;
} else {
$numbers['data'][] = $s;
$x = 0;
}
}
print_r(json_encode($string));
echo "<br/>";
print_r(json_encode($numbers));
As usual - it is long winded but shows all the steps to get to the required output.
<?php //https://stackoverflow.com/questions/27822896/need-help-php-to-json-array
// only concerned about ease of understnding not code size or efficiency.
// will speed it up later...
$inString = "ot>4>om>6>we>34>ff>45";
$outLitRequired = '[{"name":"website","data":["ot","om","we","ff"]}]';
$outValueRequired = '[{"name":"websitedata","data":["4","6","34","45"]}]';
// first: get a key / value array...
$itemList = explode('>', $inString);
/* debug */ var_dump(__FILE__.__LINE__, $itemList);
// outputs ------------------------------------
$outLit = array();
$outValue = array();
// ok we need to process them in pairs - i like iterators...
reset($itemList); // redundant but is explicit
// build both output 'data' lists
while (current($itemList)) {
$outLit[] = current($itemList);
next($itemList); // advance the iterator.
$outValue[] = current($itemList);
next($itemList);
}
/* debug */ var_dump(__FILE__.__LINE__, $itemList, $outLit, $outValue);
// make the arrays look like the output we want...
// we need to enclose them as arrays to get the exact formatting required
// i do that in the 'json_encode' statements.
$outLit = array('name' => 'website', 'data' => $outLit);
$outValue = array('name' => 'websitedata', 'data' => $outValue);
// convert to JSON.
$outLitJson = json_encode(array($outLit));
$outValueJson = json_encode(array($outValue));
// show required and calculated values...
/* debug */ var_dump(__FILE__.__LINE__, 'OutLit', $outLitRequired, $outLitJson);
/* debug */ var_dump(__FILE__.__LINE__, 'OutValue', $outValueRequired, $outValueJson);
'I have this flat array:
$folders = [
'test/something.txt',
'test/hello.txt',
'test/another-folder/myfile.txt',
'test/another-folder/kamil.txt',
'test/another-folder/john/hi.txt'
]
And I need it in the following format:
$folders = [
'test' => [
'something.txt',
'hello.txt',
'another-folder' => [
'myfile.txt',
'kamil.txt',
'john' => [
'hi.txt'
]
]
]
];
How do I do this? Thanks.
Recursion is your friend :-)
function createArray($folders, $output){
if(count($folders) > 2){
$key = array_shift($folders);
$output[$key] = createArray(
$folders, isset($output[$key]) ? $output[$key] : []
);
}
else{
if(!isset($output[$folders[0]])){
$output[$folders[0]] = [];
}
$output[$folders[0]][] = $folders[1];
}
return $output;
}
Keep drilling down until you get to the file name, then add them all together in an array.
You need to call this function for each element in your array, like this:
$newFolders = [];
foreach($folders as $folder){
$newFolders = createArray(explode('/', $folder), $newFolders);
}
DEMO: https://eval.in/139240
<?php
$folders = [
'test/something.txt',
'test/hello.txt',
'test/another-folder/myfile.txt',
'test/another-folder/kamil.txt',
'test/another-folder/john/hi.txt'
];
$new_folders = array();
foreach ($folders as $folder) {
$reference =& $new_folders;
$parts = explode('/', $folder);
$file = array_pop($parts);
foreach ($parts as $part) {
if(!isset($reference[$part])) {
$reference[$part] = [];
}
$reference =& $reference[$part];
}
$reference[] = $file;
}
var_dump($new_folders);