I have an array of this sort:
$array = [
"Darren" => [
"age" => "18",
"work" => [
"occupation" => "developer",
"company" => "ABC Ltd"
]
],
"John" => [
"age" => "24",
"work" => [
"occupation" => "developer",
"company" => "ABC Ltd",
"url" => "www.example.com"
],
]
]
And would like to merge the keys with a dot in between, depending on the array's hierachy:
"Darren.age"
"Darren.work.occupation"
"Darren.work.company"
...
The function that I made so far is
public function buildExpressionKey($array, $parentKey = null){
$expression = [];
foreach($array as $key=>$value){
if(is_array($value)){
array_push($expression, $parentKey. implode(".",
$this->buildExpressionKey($value, $key)));
}else{
array_push($expression, $key);
}
}
return $expression;
}
it is returning this value at the moment:
[
[0] => "age.Darrenoccupation.company"
[1] => "age.Johnoccupation.company.url"
]
Was wondering if it is possible to make a function which automatically does merges the keys like that, thanks in advance :)
What you are currently asking for:
<?php
$people =
[
'John' =>
[
'Occupation' => 'Developer',
'Age' => 18
],
'Darren' =>
[
'Occupation' => 'Manager',
'Age' => 40
]
];
foreach($people as $name => $value)
foreach($value as $k => $v)
$strings[] = $name . '.' . $k;
var_export($strings);
Output:
array (
0 => 'John.Occupation',
1 => 'John.Age',
2 => 'Darren.Occupation',
3 => 'Darren.Age',
)
Managed to resolve this issue :)
/**
* #param $array
* #return array
*/
public function buildExpressionKey($array){
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array));
$keys = array();
foreach ($iterator as $key => $value) {
// Build long key name based on parent keys
for ($i = $iterator->getDepth() - 1; $i >= 0; $i--) {
$key = $iterator->getSubIterator($i)->key() . '.' . $key;
}
$keys[] = $key;
}
return $keys;
}
Found something similar on here: Get array's key recursively and create underscore separated string
Related
I have a dynamic multidimensional array as below:
$cityList = [
'AUS' => [
'VIC' => [
'population' => [ 'total' => '5M']
'Richmond' => [
'population' => [ 'total' => '0.15M']
]
],
'NSW' => [
'Carlton' => [
'population' => [ 'total' => '8M']
]
]
]
];
Here, the column population may or may not be present on all dimension. However, if it's present then it'll always have total as sub array as above.
Now, I need to traverse through the array and generate all path to population if exist.
I've written code as below:
public function fetchAllPopulation(array $cityList, $path = '', &$cityWithPopulation)
{
foreach ($cityList as $key => $city) {
if (is_array($city) && $key != 'population') {
$path .= $path == '' ? $key: "##$key";
$this->fetchAllPopulation($city, $path, $cityWithPopulation);
} else {
$population = $city['total'];
$cityWithPopulation[$path] = $population;
}
}
return $assetWithPathsAndIds;
}
Expected output:
[
'AUS##VIC' => '5M',
'AUS##VIC##Richmond' => '0.15M',
'AUS##NSW##Carlton' => '8M'
]
Actual output:
[
'AUS##VIC' => '5M',
'AUS##VIC##Richmond' => '0.15M',
'AUS##VIC##NSW##Carlton' => '8M' // this is incorrect
]
The problem is if any column has more than 2 dimensions, then the previous key will be appended on the next one as above.
Any feedback or correction to my code will be appreciated. Thanks!
This way:
public function fetchAllPopulation(array $cityList, $path, &$cityWithPopulation)
{
foreach ($cityList as $key => $city) {
if (is_array($city) && $key != 'population') {
$subPath = $path . ($path == '' ? $key: "##$key");
$this->fetchAllPopulation($city, $subPath, $cityWithPopulation);
} else {
$population = $city['total'];
$cityWithPopulation[$path] = $population;
}
}
return $assetWithPathsAndIds;
}
So, basically what i'm trying to do is getting combinations of the array children, for example i got the array:
[
"name" => "Item1",
"children" =>
[
"name" => "Item2",
"children" => [
["name" => "Item3"],
["name" => "Item4"]
]
],
["name" => "Item5"]
];
I tried to work with some functions i got on the stackoverflow, but i only got it to work with all of them at once, i was getting just
[
"Item4" => "Item1/Item2/Item4",
"Item5" => "Item1/Item5"
];
The output should be
[
"Item1" => "Item1",
"Item2" => "Item1/Item2",
"Item3" => "Item1/Item2/Item3"
"Item4" => "Item1/Item2/Item4"
"Item5" => "Item1/Item5"
];
As asked, the function i was working with before:
function flatten($arr) {
$lst = [];
/* Iterate over each item at the current level */
foreach ($arr as $key => $item) {
/* Get the "prefix" of the URL */
$prefix = $item['slug'];
/* Check if it has children */
if (array_key_exists('children', $item) and sizeof($item['children'])) {
/* Get the suffixes recursively */
$suffixes = flatten($item['children']);
/* Add it to the current prefix */
foreach($suffixes as $suffix) {
$url = $prefix . '/' . $suffix;
$lst[$item['id']] = $url;
}
} else {
/* If there are no children, just add the
* current prefix to the list */
$lst[$item['id']] = $prefix;
}
}
return $lst;
}
I've had to fix the data as the levels of data don't match up. The rest of the code is new as I found so many errors from your existing code.
Comments in code...
$data = [
"name" => "Item1",
"children" =>
[[
"name" => "Item2",
"children" =>[
["name" => "Item3"],
["name" => "Item4"]]
],
["name" => "Item5"]]
];
print_r(flatten($data));
function flatten($arr, $pathSoFar = '') {
$lst = [];
$path = $pathSoFar."/";
foreach ( $arr as $key => $value ) {
if ( $key === 'name' ) {
// Add name of current level onto path
$path .= $value;
$lst[$value] = $path;
}
else if ( $key === 'children' ) {
//Process child elements recursively and add into current array
$lst = array_merge($lst, flatten($value, $path));
}
else {
// This is for sub-elements which probably are (for example) 0, 1
// (removing trailing / to stop multiples)
$lst = array_merge($lst, flatten($value, rtrim($path,"/")));
}
}
return $lst;
}
I have 3 array like
$arr = [
"color" => [["name"=>"red"]],
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]]
]
$combo = array();
foreach ($arr['size'] as $size) {
foreach($arr['color'] as $color){
foreach ($arr['type'] as $type) {
$variant = json_encode(['size' => $size->name, 'color' =>
$color->name, 'type' => $type->name]);
array_push($combo,$variant);
}
}
}
echo $combo;
// result
0 => "{"size":"15 inch","color":"yellow","type":"metal"}"
1 => "{"size":"18 inch","color":"yellow","type":"plastic"}"
It works properly but but there is can be less or more variants. How can I handle this.
For example
$arr = [
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]]
]
Or
$arr = [
"color" => [["name"=>"red"]],
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]],
"brand" => [['name' => 'something']],
]
For what i understand, you have to combine the arrays of properties into one array of
object.
I have to leave now, but if you need a explanation leave a comment and i updated the answers
$arr = [
"color" => [["name"=>"red"],['name'=>'yellow']],
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]],
"brand" => [['name' => 'something']],
];
function runFor($arr ,&$array, $keys,$index,&$positions){
foreach ($arr[$keys[$index]] as $key => $espec){
$positions[$keys[$index]] = $key;
if($index + 1 < count($keys)){
runFor($arr,$array,$keys, $index+1,$positions);
}else{
$item = (object)[];
foreach ($keys as $key){
$item->$key = $arr[$key][$positions[$key]]['name'];
}
array_push($array,$item);
}
unset($positions[$keys[$index]]);
}
}
$array = array();
$keys = array_keys($arr);
$positions = [];
runFor($arr,$array,$keys,0,$positions);
$combo = array();
foreach ($array as $item){
array_push($combo,json_encode($item));
}
var_dump($combo);
I have this array:
0 => array:3 [
"product_id" => "1138"
"product_image" => "/resources/medias/shop/products/shop-6500720--1.png"
"product_sku" => "6500722"
]
1 => array:3 [
"product_id" => "1144"
"product_image" => "/resources/medias/shop/products/shop-6501041--1.png"
"product_sku" => "6501046"
]
2 => array:3 [
"product_id" => "113"
"product_image" => "/resources/medias/shop/products/shop-6294909--1.png"
"product_sku" => "6294915"
]
What I am looking for is a way to get a multiple array with only required columns (array_column is not a option, since it's give me only 1 column).
What I have done
function colsFromArray($array, $keys)
{
return array_map(function ($el) use ($keys) {
return array_map(function ($c) use ($el) {
return $el[$c];
}, $keys);
}, $array);
}
$array = array(
[
"product_id" => "1138",
"product_image" => "/resources/medias/shop/products/shop-6500720--1.png",
"product_sku" => "6500722"
],
[
"product_id" => "1144",
"product_image" => "/resources/medias/shop/products/shop-6501041--1.png",
"product_sku" => "6501046"
],
[
"product_id" => "113",
"product_image" => "/resources/medias/shop/products/shop-6294909--1.png",
"product_sku" => "6294915"
]
);
colsFromArray($array, array("product_id", "product_sku"));
//0 => array:3 [
// "product_id" => "1138"
// "product_sku" => "6500722"
// ]
//1 => array:3 [
// "product_id" => "1144"
// "product_sku" => "6501046"
// ]
//2 => array:3 [
// "product_id" => "113"
// "product_sku" => "6294915"
//]
The problem is that it seems too laggy, since it iterates twice over this.
Is there any way to get multiple columns without this workaround?
I'm using PHP5.6
If you need two columns from an array where one is SKU (which generally is unique) then you can use array_column with the third parameter.
$new = array_column($arr, "product_id", "product_sku");
This will return a flat array with the SKU as the key and ID as value making the array easy to work with also.
Output:
array(3) {
[6500722]=>
string(4) "1138"
[6501046]=>
string(4) "1144"
[6294915]=>
string(3) "113"
}
https://3v4l.org/UDGiO
I think the bigger issue is you lose the keys
Original Code
array (
0 =>
array (
0 => '1138',
1 => '6500722',
),
1 =>
array (
0 => '1144',
1 => '6501046',
),
2 =>
array (
0 => '113',
1 => '6294915',
);
You can use a simple foreach instead of the second array_map:
function colsFromArray(array $array, $keys)
{
if (!is_array($keys)) $keys = [$keys];
return array_map(function ($el) use ($keys) {
$o = [];
foreach($keys as $key){
// if(isset($el[$key]))$o[$key] = $el[$key]; //you can do it this way if you don't want to set a default for missing keys.
$o[$key] = isset($el[$key])?$el[$key]:false;
}
return $o;
}, $array);
}
Output
array (
0 =>
array (
'product_id' => '1138',
'product_sku' => '6500722',
),
1 =>
array (
'product_id' => '1144',
'product_sku' => '6501046',
),
2 =>
array (
'product_id' => '113',
'product_sku' => '6294915',
),
)
Sandbox
the problem is that it seems too laggy, since it iterates twice over this.
There is no real way to not iterate over it 2 times, but you probably don't want to throw away the keys either.
That said you can recursively unset the items you don't want.
function colsFromArray(array &$array, $keys)
{
if (!is_array($keys)) $keys = [$keys];
foreach ($array as $key => &$value) {
if (is_array($value)) {
colsFromArray($value, $keys); //recursive
}else if(!in_array($key, $keys)){
unset($array[$key]);
}
}
}
colsFromArray($array, array("product_id", "product_sku"));
var_export($array);
Same output as before
This is easier to do by reference. Rather or not that is faster you'll have to test the 2 and see.
Sandbox
As a final note you shouldn't assume the key will exist or that keys will be an array unless you type cast it as an array.
You could also do it with array filter
function colsFromArray(array $array, $keys)
{
if (!is_array($keys)) $keys = [$keys];
$filter = function($k) use ($keys){
return in_array($k,$keys);
};
return array_map(function ($el) use ($keys,$filter) {
return array_filter($el, $filter, ARRAY_FILTER_USE_KEY );
}, $array);
}
There is some small performance benefit to declaring the function for filtering outside of the loop (array_map).
Sandbox
If you do not want to change your original array and want your desired output
Use array_insersect_key function to get your desired output as following
$array = array(
[
"product_id" => "1138",
"product_image" => "/resources/medias/shop/products/shop-6500720--1.png",
"product_sku" => "6500722"
],
[
"product_id" => "1144",
"product_image" => "/resources/medias/shop/products/shop-6501041--1.png",
"product_sku" => "6501046"
],
[
"product_id" => "113",
"product_image" => "/resources/medias/shop/products/shop-6294909--1.png",
"product_sku" => "6294915"
]
);
$keys = array("product_id"=>1, "product_sku"=>2);
$filteredArray = array_map(function($a) use($keys){
return array_intersect_key($a,$keys);
}, $array);
print_r($filteredArray);
I refactored the elegant approach from #Chayan into a function so it can be used like array_column(). Keys to be filtered can now be presented as a simple array.
Btw this is most likely also the fastest approach, since it uses build-in functions for most of the heavy lifting.
function array_columns(array $arr, array $keysSelect)
{
$keys = array_flip($keysSelect);
return array_map(
function($a) use($keys) {
return array_intersect_key($a,$keys);
},
$arr
);
}
$arr = [
[
"product_id" => "1138",
"product_image" => "/resources/medias/shop/products/shop-6500720--1.png",
"product_sku" => "6500722"
],
[
"product_id" => "1144",
"product_image" => "/resources/medias/shop/products/shop-6501041--1.png",
"product_sku" => "6501046"
],
[
"product_id" => "113",
"product_image" => "/resources/medias/shop/products/shop-6294909--1.png",
"product_sku" => "6294915"
]
];
$keysSelect = ["product_id" , "product_sku"];
$filteredArray = array_columns($arr, $keysSelect);
var_dump($filteredArray);
If I understand your question correctly, you could try a traditional foreach - it might be a little faster.
function colsFromArray($array, $filterKeys) {
$newArr = [];
foreach($array as $val) {
$element = [];
foreach($filterKeys as $filterKey) {
$element[$filterKey] = $val[$filterKey];
}
$newArr[] = $element;
}
}
(Not tested)
The problem is that it seems too laggy, since it iterates twice over this
Your original code isn't iterating twice over the same array. You won't be able to get around iterating over the main array and then the filterKeys array if you want to have an array where each element is another array of elements with keys from the filterKeys array.
This is a refactored function based on Chayan's with added renaming of selected columns:
/** Function - array_columns Selects columns from multidimantional array and renames columns as required
*
* #param array $arr, array $selectColRenameKeys
* example: (NewName1->colNameneeded1,NewName2->colNameneeded2,ect...)
* #return array
* #access public
*
*/
private function array_columns( $arr,$selectColRenameKeys) {
$keys = array_flip($selectColRenameKeys);
$filteredArray = array_map(function($a) use($keys){
$data = array_intersect_key($a,$keys);
$rename_arr= array();
foreach ($data as $colname => $value){
$r_arr[$keys[$colname]]= $value ;
}
return $r_arr;
}, $arr);
return $filteredArray;
}
An added feature to the array_columns function that eventually traces back to Chayan's answer, this time extended from Joseph Mangion's function.
I occasionally have long lists of the selected columns where I want to preserve the keys and don't necessarily want to follow the cumbersome ['orignal_field_name'] => ['original_field_name'] format for a great number of fields.
This version preserves the original key for each field by default unless a new key is specified.
// See answer from Joseph Mangion: https://stackoverflow.com/questions/52706383/php-get-multiple-columns-from-array
/** Function - array_columns Selects columns from multidimensional array and renames columns as required
*
* #param array $in_array, array $select_columns_rename_keys
* example of $select_columns_rename_keys:
* ['new_column_name1' => 'original_column_name1', 'original_column_name2', 'original_column_name3', 'new_column_name4' => 'original_column_name4', ...]
* This will use the original keys for columns 2 and 3 and rename columns 1 and 4
* #return array
* #access public
*
*/
public function array_columns($in_array, $select_columns_rename_keys) {
foreach ($select_columns_rename_keys as $k => $v)
if (is_int($k)) {
$select_columns_rename_keys[$v] = $v;
unset($select_columns_rename_keys[$k]);
}
$keys = array_flip($select_columns_rename_keys);
$filtered_array =
array_map(function($a) use($keys) {
$data = array_intersect_key($a, $keys);
$return_array = [];
foreach ($data as $column_name => $value) $return_array[$keys[$column_name]] = $value;
return $return_array;
}, $in_array);
return $filtered_array;
}
The question is simple, I want to create the array below dynamically, but the code I got now only outputs the last row. Is there anybody who knows what is wrong with my dynamically array creation?
$workingArray = [];
$workingArray =
[
0 =>
[
'id' => 1,
'name' => 'Name1',
],
1 =>
[
'id' => 2,
'name' => 'Name2',
]
];
echo json_encode($workingArray);
/* My not working array */
$i = 0;
$code = $_POST['code'];
$dynamicArray = [];
foreach ($Optionsclass->get_options() as $key => $value)
{
if ($value['id'] == $code)
{
$dynamicArray =
[
$i =>
[
'id' => $key,
'name' => $value['options']
]
];
$i++;
}
}
echo json_encode($dynamicArray);
You dont need to have the $i stuff that is adding another level to your array that you dont want.
$code = $_POST['code'];
$dynamicArray = [];
foreach ($Optionsclass->get_options() as $key => $value)
{
if ($value['id'] == $code)
{
$dynamicArray[] = ['id' => $key, 'name' => $value['options'];
}
}
echo json_encode($dynamicArray);
You are creating a new dynamic array at each iteration:
$dynamicArray =
[
$i =>
[
'id' => $key,
'name' => $value['options']
]
];
Instead, declare $dynamicArray = []; above the foreach, and then use:
array_push($dynamicArray, [ 'id' => $key, 'name' => $value['options']);
inside the array.