Extract "path" from array - php

I'm working on documenting an API, so I am parsing example XML/JSOn output and need to find all the named keys to add definitions. So, I have an array like this:
$array = [
"user" => [
"name" => "John",
"email" => "email#email.com",
"products" => [
0 => "product A",
1 => "product B"
],
"files" => [
"logo" => "/path/logo.jpg",
"profile" => "/path/profile.jpg"
]
],
"offer" => [
0 => "My offer"
]
];
And I want to extract all the keys from the array, no matter its depth, and get an output akin to:
$keys = [
0 => ["user"],
1 => ["user", "name"],
2 => ["user", "email"],
3 => ["user", "products"],
4 => ["user", "files"],
5 => ["user", "files", "logo"],
6 => ["user", "files", "profile"],
7 => ["offer"]
];
Note that keys that are numeric are ignored, only named keys are included in the hierarchy. I have googled and tried to find something that does this, but I've come up blank. I have tried some function chaining but I just can't wrap my head around the loops and returns correctly. Any help is appreciated!

Ok, with help of #0stone0 I was directed to a Stackoverflow answer that led me right, this is the end function:
function definitionTree(array $array): array{
$tree = function($siblings, $path) use (&$tree) {
$result = [];
foreach ($siblings as $key => $val) {
$currentPath = is_numeric($key) ? $path : array_merge($path, [$key]);
if (is_array($val)) {
if (!is_numeric($key)) $result[] = join(' / ', $currentPath);
$result = array_merge($result, $tree($val, $currentPath));
} else {
$result[] = join(' / ', $currentPath);
}
}
return $result;
};
$paths = $tree($array, []);
return array_unique($paths);
}
Which returns the following:
Array
(
[0] => user
[1] => user / name
[2] => user / email
[3] => user / products
[6] => user / files
[7] => user / files / logo
[8] => user / files / profile
[9] => offer
)

Related

PHP - pick random value from array?

How is it possible to pick a random value with PHP from an Array?
Example:
$trees = [
"appletree" => [
"id" => "12378",
"age" => [15],
"height" => [6]
],
"bananatree" => [
"id" => "344343453",
"age" => [16],
"height" => [30]
],
"peachtree" => [
"id" => "34534543",
"age" => [35],
"height" => [4]
];
How would I access one of the id's randomly?
I tried using
$tree_id = array_rand($trees['id']);
echo $tree_id;
echo "\r\n";
but I'm slowly hitting a wall of understanding.
array_rand() returns a random array key. So give it the $trees array to get a tree name, then use that to index the array and access its id property.
$random_tree = array_rand($trees);
echo $trees[$random_tree]['id'];
maybe this function I created can help:
<?php
$trees = [
"appletree" => [
"id" => "123",
"age" => [15],
"height" => [6]
],
"bananatree" => [
"id" => "456",
"age" => [16],
"height" => [30]
],
"peachtree" => [
"id" => "789",
"age" => [35],
"height" => [4]
] // <- you were missing this bracket
];
function pickRand($array){
// Create Temp array
$temparray = [];
// Iterate through all ID's and put them into out temp array
foreach($array as $a) $temparray[] = $a['id'];
// Get a random number out of the number of ID's we have
$rand = rand(0, count($temparray) - 1);
// Return Result
return $temparray[$rand];
}
// Use Function
echo pickRand($trees);
Live Demo: http://sandbox.onlinephpfunctions.com/code/e71dc6b07c3ec93051c69adc66b28aafe555a104

PHP Nested Arrays: Imploding all the tree keys of each leaf results in a multidimensional array instead of a 1D associative one

From a nested array, I want to generate the 1D associative array which contains, for each leaf, its ascending keys concatenation.
Summary
Expected results example
1.1. Input
1.2. Output
Actual results example
1.1. Input
1.2. Output
Question
Minimal, Testable Executable Sources
4.1. Explanations
4.2. Sources & Execution
Expected results example
Input
The following nested array:
[
'key1' => 'foo',
'key2' => [
'key3' => [
0 => ['key4' => 'bar' ],
1 => ['key4' => 'azerty']
]
]
]
Output
The following 1D associative array (glue character for the concatenation of the keys: _):
[
'key1' => 'foo',
'key2_key3_0_key4' => 'bar',
'key2_key3_1_key4' => 'azerty'
]
Actual results example
Input
[
'etat' => 'bar',
'proposition_en_cours' => [
'fichiers' => [
0 => ['url_fichier' => 'foo' ],
1 => ['url_fichier' => 'bar']
]
]
]
Output
Array
(
[] => bar
[proposition_en_cours] => Array
(
[fichiers] => Array
(
[0] => Array
(
[url_fichier] => foo
)
[1] => Array
(
[url_fichier] => bar
)
)
)
[proposition_en_cours_fichiers] => Array
(
[0] => Array
(
[url_fichier] => foo
)
[1] => Array
(
[url_fichier] => bar
)
)
[proposition_en_cours_fichiers_0] => foo
[proposition_en_cours_fichiers_0_1] => bar
)
Question
As you can see, the array I get differs in all points from the expected one. I can't figure out why.
Minimal, Testable Executable Sources
Explanations
I initialize an array that must contain all the ascending keys for each leaf: $key_in_db_format = [];.
I iterate on the input array. For each element (leaf or subarray), I pop $key_in_db_format if, and only if, the current depth equals the last depth. If it's an array (i.e.: not a leaf): I add the key to $key_in_db_format. I set a value (the leaf) at the key that is the concatenation of the ascending keys.
Sources & Execution
First, define this array in an empty PHP script of your choice:
$values = [
'etat' => 'bar',
'proposition_en_cours' => [
'fichiers' => [
0 => [ 'url_fichier' => 'foo' ],
1 => [ 'url_fichier' => 'bar' ]
]
]
];
Then, copy/paste the following code and you will be able to execute it:
$values_to_insert_in_meta_table = [];
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($values), \RecursiveIteratorIterator::SELF_FIRST);
$last_depth = 0;
$key_in_db_format = [];
foreach ($iterator as $value_key_field => $value_value_field) {
if($iterator->getDepth() == $last_depth) {
array_pop($key_in_db_format);
}
if(is_array($value_value_field)) {
array_push($key_in_db_format, $value_key_field);
} else {
$values_to_insert_in_meta_table[implode('_', $key_in_db_format)] = $value_value_field;
}
$last_depth = $iterator->getDepth();
}
echo '<pre>';
print_r($values_to_insert_in_meta_table);
Maybe I missed something, but as far as I understand, I would do something like that:
<?php
function flatten(array $array, ?string $prefix = null): array {
$prefix = $prefix === null ? '' : "{$prefix}_";
$output = [];
foreach ($array as $key => $value) {
$key = $prefix . $key;
if (is_array($value)) {
$output = array_merge($output, flatten($value, $key));
} else {
$output[$key] = $value;
}
}
return $output;
}
var_export(flatten([
'key1' => 'foo',
'key2' => [
'key3' => [
0 => ['key4' => 'bar' ],
1 => ['key4' => 'azerty']
]
]
]));
Output:
array (
'key1' => 'foo',
'key2_key3_0_key4' => 'bar',
'key2_key3_1_key4' => 'azerty',
)
I think I've found a solution!!! :-)
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($values), \RecursiveIteratorIterator::SELF_FIRST);
$key_in_db_format = [];
$current_counter = 0;
foreach($iterator as $value_key_field => $value_value_field) {
if(is_array($value_value_field)) {
$current_counter = 0;
array_push($key_in_db_format, $value_key_field);
}
if(!is_array($value_value_field)) {
$key_to_insert_in_db = !empty($key_in_db_format) ? implode('_', $key_in_db_format) . '_' . $value_key_field : $value_key_field ;
$values_to_insert_in_meta_table[$key_to_insert_in_db] = $value_value_field;
if($current_counter == count($iterator->getSubIterator())) {
array_pop($key_in_db_format);
}
$current_counter++;
}
}
echo '<br /> <pre>';
print_r($values_to_insert_in_meta_table);
exit;
The idea is:
We add to the array of ascendent keys the key if, and only if, the current element is not a leaf.
If the current element is a leaf, then we define the key equalled to the imploded ascendent keys PLUS (concatenation) the current element's key. Moreover we pop the array of ascendent keys if there are not following siblings elements.

Regroup multidimensional array to reverse presentation of many-to-many relationship

I need to perform iterated explosions on values in one column of my two dimensional array, then re-group the data to flip the relational presentation from "tag name -> video id" to "video id -> tag name".
Here is my input array:
$allTags = [
[
"name" => "TAG-ONE",
"video" => "64070,64076,64110,64111",
],
[
"name" => "TAG-TWO",
"video" => "64070,64076,64110,64111",
],
[
"name" => "TAG-THREE",
"video" => "64111",
]
];
I want to isolate unique video ids and consolidate all tag names (as comma-separayed values) that relate to each video id.
Expected output:
$allTagsResult = [
[
"name" => "TAG-ONE,TAG-TWO",
"video" => "64070",
],
[
"name" => "TAG-ONE,TAG-TWO",
"video" => "64076",
],
[
"name" => "TAG-ONE,TAG-TWO",
"video" => "64110",
],
[
"name" => "TAG-ONE,TAG-TWO,TAG-THREE",
"video" => "64111",
],
];
Somehow I did it by checking the value using nested loops but I wish to know if you guys can suggest any shortest method to get the expected output.
If you want to completely remove foreach() loops, then using array_map(), array_walk_recursive(), array_fill_keys() etc. can do the job. Although I think that a more straightforward answer using foreach() would probably be faster, but anyway...
$out1 = array_map(function ($data) {
return array_fill_keys(explode(",", $data['video']), $data['name']); },
$allTags);
$out2 = [];
array_walk_recursive( $out1, function ( $data, $key ) use (&$out2) {
if ( isset($out2[$key])) {
$out2[$key]['name'] .= ",".$data;
}
else {
$out2[$key] = [ 'name' => $data, 'video' => $key ];
}
} );
print_r($out2);
will give...
Array
(
[64070] => Array
(
[name] => TAG-ONE,TAG-TWO
[video] => 64070
)
[64076] => Array
(
[name] => TAG-ONE,TAG-TWO
[video] => 64076
)
[64110] => Array
(
[name] => TAG-ONE,TAG-TWO
[video] => 64110
)
[64111] => Array
(
[name] => TAG-ONE,TAG-TWO,TAG-THREE
[video] => 64111
)
)
if you want to remove the keys, then
print_r(array_values($out2));
The code could be compressed by piling all of the code onto single lines, but readability is more useful sometimes.
Another method if you don't like looping:
$video_ids = array_flip(array_unique(explode(",",implode(",",array_column($allTags,'video')))));
$result = array_map(function($id){
return ['name' => '','video' => $id];
},array_flip($video_ids));
array_walk($allTags,function($tag_data) use (&$result,&$video_ids){
$ids = explode(",",$tag_data['video']);
foreach($ids as $id) $result[$video_ids[$id]]['name'] = empty($result[$video_ids[$id]]['name']) ? $tag_data['name'] : $result[$video_ids[$id]]['name'] . "," . $tag_data['name'];
});
Demo: https://3v4l.org/vlIks
Below is one way of doing it.
$allTags = [
'0' => [
"name" => "TAG-ONE",
"video" => "64070,64076,64110,64111",
],
'1' => [
"name" => "TAG-TWO",
"video" => "64070,64076,64110,64111",
],
'2' => [
"name" => "TAG-THREE",
"video" => "64111",
]
];
$allTagsResult = array();
$format = array();
foreach( $allTags as $a ) {
$name = $a['name'];
$videos = explode(',', $a['video']);
foreach( $videos as $v ) {
if( !isset( $format[$v]) ) {
$format[$v] = array();
}
$format[$v][] = $name;
}
}
foreach( $format as $video => $names) {
$allTagsResult[] = array('name' => implode(',', $names), 'video' => $video);
}
echo '<pre>';
print_r($allTagsResult);
die;
You can check Demo
I am typically in favor of functional style coding, but for this task I feel it only serves to make the script harder to read and maintain.
Use nested loops and explode the video strings, then group by those video ids and concatenate name strings within each group. When finished iterating, re-index the array.
Code: (Demo)
$result = [];
foreach ($allTags as $tags) {
foreach (explode(',', $tags['video']) as $id) {
if (!isset($result[$id])) {
$result[$id] = ['video' => $id, 'name' => $tags['name']];
} else {
$result[$id]['name'] .= ",{$tags['name']}";
}
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'video' => '64070',
'name' => 'TAG-ONE,TAG-TWO',
),
1 =>
array (
'video' => '64076',
'name' => 'TAG-ONE,TAG-TWO',
),
2 =>
array (
'video' => '64110',
'name' => 'TAG-ONE,TAG-TWO',
),
3 =>
array (
'video' => '64111',
'name' => 'TAG-ONE,TAG-TWO,TAG-THREE',
),
)

How to change the way php returns the results of a sum with very small values

you will see maybe this problem sounds silly but it has tended me a bit stuck.
You see, I have a function which receives an array, with this array a sum must be made, specifically sum one of its columns (I enter as the third parameter of the function the column I want to sum).This work good. The problem is the way it generates the result. I show you my code:
function sumArray($array, $index, $col) {
$returnArray = []; // temporary container
// sanity checks
if (!is_array($array)) {
return 'error not an array';
}
$firstRow = reset($array);
if (!array_key_exists($index, $firstRow) || !array_key_exists($col, $firstRow)) {
return 'error keys provided not found';
}
foreach ($array as $value) {
if (!isset($returnArray[$value[$index]])) { // initialize
$returnArray[$value[$index]] = [$index => $value[$index], $col => 0];
}
// add value
$returnArray[$value[$index]][$col] += $value[$col]; //here is the sum
}
return $returnArray;
}
$products = array ( //this is the array
array("Id" => "000001",
"Name" => "Cheese",
"Quantity" => "0.00000012",
"Price" => "10"),
array("Id" => "000001",
"Name" => "Cheese",
"Quantity" => "0.00000123",
"Price" => "20"),
array("Id" => "000001",
"Name" => "Cheese",
"Quantity" => "0.00000020",
"Price" => "30"),
array("Id" => "000002",
"Name" => "Ham",
"Quantity" => "0.00000346",
"Price" => "200"),
array("Id" => "000002",
"Name" => "Ham",
"Quantity" => "0.000000998",
"Price" => "100"),
array("Id" => "000003",
"Name" => "Baicon",
"Quantity" => "0.000000492",
"Price" => "900")
);
$summedArray = sumArray($products, 'Name', 'Quantity');
print_r($summedArray);
the result of my sum is a new array. But look at the Quantity column:
Array (
[Cheese] => Array ( [Name] => Cheese [Quantity] => 1.55E-6 )
[Ham] => Array ( [Name] => Ham [Quantity] => 4.458E-6 )
[Baicon] => Array ( [Name] => Baicon [Quantity] => 4.92E-7 )
)
This form: 4.458E-6 I don't like. I would like something like this: 0.000004458
Do you know what I could do to get something like that? A suggestion would be of great help.
It isn't really related to the fact that it's a sum. That's just the default string representation of a small float in PHP. Try this, for example:
$number = 0.000004458;
echo $number; // displays 4.458E-6
The only reason the appearance changes in your output is that those values are strings in your input array, but when you add them together they are converted to floats.
If you want those values to display differently, you'll need to use some kind of formatting function that returns a string.
Assuming you aren't going to be using print_r to display the value in your final product, you can use number_format or printf to display it with however many decimal points you'd like.
foreach ($summedArray as $product => $info) {
echo number_format($info['Quantity'], 9) . PHP_EOL;
// or printf('%.9f', $info['Quantity']) . PHP_EOL;
}

How to add array if key value is same

Hi i having difficulty to trace an multi dimensional array.
[
0 => array:7 [
"date" => "2016-01-19"
"placement_id" => 1
"requests" => 18
"revenue" => 1
],
1 => array:7 [
"date" => "2016-01-19"
"placement_id" => 1
"requests" => 2
"revenue" => 0.2
]
];
if placement_id are same i want resulted array:
1 => array:7 [
"date" => "2016-01-19"
"placement_id" => 1
"requests" => 20
"revenue" => 1.2
]
The requirement is to produce an output array that has:
Items with the same 'placement_id' having the 'requests' and revenue summed.
The key of the entry in the output array will be be the 'placement_id'.
That means that the output array will be smaller that the input array.
I decided to use the array_reduce function. There is no special reason, foreach loops work fine. It isn't any more efficient. It is just different.
The important point about array_reduce is that the $carry (accumulator) can be an array...
Working example at Eval.in
The code:
$outArray = array();
$outArray = array_reduce($src,
function($carry, $item) { // accumulate values if possible
$carryKey = $item['placement_id']; // array key
if (isset($carry[$carryKey])) { // accumulate values
$carry[$carryKey]['requests'] += $item['requests'];
$carry[$carryKey]['revenue'] += $item['revenue'];
} else { // is new - add to the output...
$carry[$carryKey] = $item;
}
return $carry;
},
array() /* accumulator ($carry) is an internal variable */);
Output Array:
array (2) [
'1' => array (4) [
'date' => string (10) "2016-01-19"
'placement_id' => integer 1
'requests' => integer 20
'revenue' => float 1.2
]
'666' => array (4) [
'date' => string (10) "2016-04-01"
'placement_id' => integer 666
'requests' => integer 266
'revenue' => float 666.20000000000005
]
]
Test Data:
$src = array(
0 => array(
"date" => "2016-01-19",
"placement_id" => 1,
"requests" => 18,
"revenue" => 1,
),
1 => array(
"date" => "2016-04-01",
"placement_id" => 666,
"requests" => 266,
"revenue" => 666.2,
),
2 => array(
"date" => "2016-01-19",
"placement_id" => 1,
"requests" => 2,
"revenue" => 0.2,
),
);
Taking that $arr parameter is the array that you show, we could create a function like this to look for duplicates ids and aggregate them. The $output array will return the results.
public function checkArray($arr) {
$output = array();
$deleted = array();
foreach($arr as $key => $value){
if (!in_array($key, $deleted)) {
$entry = array();
$entry['date'] = $value['date'];
$entry['placement_id'] = $value['placement_id'];
$entry['requests'] = $value['requests'];
$entry['revenue'] = $value['revenue'];
foreach($arr as $key2 => $value2){
if($key != $key2 && $value['placement_id'] == $value2['placement_id']){
$entry['requests'] += $value2['requests'];
$entry['revenue'] += $value2['revenue'];
$deleted[] = $key2;
}
}
$output[] = $entry;
}
}
return $output;
}

Categories