PHP Array function to compare and merge values - php

Appreciate your time!
After reviewing several 'Compare and Merge' threads, finally, I am going to request someone to help with this very specific scenario.
$input = array(
[ 2616 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
[ 987987986 ] => 987987986,
),
),
[ 2618 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
),
),
[ 'tmp-9878767654' ] => array(
[ 9878767654 ] => array(
[ 987987985 ] => 987987985,
[ 987987987 ] => 987987987,
),
),
[ 'tmp-9878767655' ] => array(
[ 9878767655 ] => array(
[ 987987975 ] => 987987975,
),
),
);
$desired_output = array(
[ 2616 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
[ 987987986 ] => 987987986,
[ 987987985 ] => 987987985,
),
),
[ 2618 ] => array(
[ 9878767654 ] => array(
[ 987987987 ] => 987987987,
[ 987987986 ] => 987987986,
[ 987987985 ] => 987987985,
),
),
[ 'tmp-9878767655' ] => array(
[ 9878767655 ] => array(
[ 987987975 ] => 987987975,
),
),
);
This is the inventory of products (listed by Product ID and Model ID) by Store ID. I want to merge the Model ID values WHERE the product id is the same FROM the array with store-ID starting with 'tmp-'. If product ID is not matched then I want that array to stay as it is. I hope I am making some sense.
Please help.

Here is a snippet to solve the specific problem posed by your example:
$temporaryStores = [];
$prefix = 'tmp-';
$prefixLength = strlen($prefix);
// extract the temporary store structures
foreach ($input as $storeId => $store) {
if (is_string($storeId) && strpos($storeId, $prefix) === 0) {
$productId = (int) substr($storeId, $prefixLength);
$temporaryStores[$productId] = $store;
unset($input[$storeId]);
}
}
// merge matching temporary store structures into the actual ones
$mergedProductIds = [];
foreach ($temporaryStores as $temporaryProductId => $temporaryModels) {
$temporaryModels = reset($temporaryModels); // Incompatible array structure
foreach ($input as $storeId => $store) {
foreach ($store as $productId => $models) {
if ($productId === $temporaryProductId) {
$modelsIds = array_merge($temporaryModels, $models);
$modelsIds = array_unique($modelsIds);
$input[$storeId][$productId] = $modelsIds;
$mergedProductIds[] = $temporaryProductId;
unset($temporaryStores[$temporaryProductId]);
}
}
}
}
// append leftover temporary store structures to the result
foreach ($temporaryStores as $temporaryProductId => $temporaryModels) {
if (!in_array($temporaryProductId, $mergedProductIds, true)) {
$input[$prefix . $temporaryProductId] = $temporaryModels;
}
}
var_dump($input);
This snippet might work for you or not. Either way, I strongly suggest you refactor this code into using a more object oriented design. Where it is made obvious what each value/structure represents, and validation can occur in isolation.
Now you are left having to deal with incompatible array structures that visually look like an incomprehensible mess.

Related

How to build nested array from list of arrays with common values? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last month.
Improve this question
Given a known hierarchy structure, for example:
group
|__unit
|__department
|__team
Can I create a nested array that matches the hierarchy structure given set of separated arrays?
For example, the following input:
[
["group" => "group2"],
["group" => "group2", "unit" => "unit11", "department" => "department50", "team" => "team10"],
["group" => "group2", "unit" => "unit11", "department" => "department50", "team" => "team58"],
["group" => "group2", "unit" => "unit10"],
["group" => "group5", "unit" => "unit23"],
["group" => "group5", "unit" => "unit23", "department" => "department101"]
]
Can I iterate the items and create a nested array with a structure of the hierarchy, like that:
[
"group2" => [
"unit11" => [
"department50" => [
["team10"],
["team58"]
],
],
"unit10" => [],
],
"group5" => [
"unit23" => [
"department101" => []
],
],
]
Or similar?
Walk through your data and create corresponding entities. Store references to these entities in a helper plain list to access them by name.
<?php
$data = [
["group" => "group2"],
["group" => "group2", "unit" => "unit11", "department" => "department50", "team" => "team10"],
["group" => "group2", "unit" => "unit11", "department" => "department50", "team" => "team58"],
["group" => "group2", "unit" => "unit10"],
["group" => "group5", "unit" => "unit23"],
["group" => "group5", "unit" => "unit23", "department" => "department101"]
];
// Just to know how entities are to be nested
$entities = [ 'group', 'unit', 'department', 'team' ];
// Plain entities lists for convinient access
$refs = ['group' => [], 'unit' => [], 'department' => [], 'team' => []];
$root = [];
foreach( $data as $row ){
foreach( $entities as $i => $entity ){
if( !empty( $row[$entity] ) ){
// If does not exist already
if( empty( $refs[ $entity ][ $row[$entity] ] ) ){
// Leaf entry
if( $i == count( $entities ) - 1 ){
$refs[ $entities[ $i - 1 ] ][ $row[ $entities[ $i - 1 ] ] ][] = $row[$entity];
} else{
unset($container);
$container = [];
// Saving ref for convinient access
$refs[ $entity ][ $row[$entity] ] = &$container;
// Intermediate Entry
if( $i ){
// Getting parent container
$refs[ $entities[ $i - 1 ] ][ $row[ $entities[ $i - 1 ] ] ][ $row[$entity] ] = &$container;
}
// Root entity
else{
$root[ $row[$entity] ] = &$container;
}
}
}
}
}
}
// Cleanup
unset($container);
// Result
print_r($root);

How to recursively sort associative array

I'd like to sort the following associative array:
$tree = [
"id" => 245974,
"children" => [
[
"id" => 111
],
[
"id" => 245982,
"children" => [
[
"id" => 246093,
"children" => [
[
"id" => 225892
],
[
"id" => 225893
],
[
"id" => 225902
]
]
]
]
]
]
];
Desired sort order after the "search value" of id => 225902:
[
"id" => 245974,
"children" => [
[
"id" => 245982, // <-- this is moved up
"children" => [
[
"id" => 246093,
"children" => [
[
"id" => 225902 // <-- this is moved up
],
[
"id" => 225892
],
[
"id" => 225893
]
]
]
]
],
[
"id" => 111
]
]
];
What I've tried:
<?php
$category_id = 225902;
function custom_sort(&$a, &$b) {
global $category_id;
if ($a['id'] === $category_id) {
return -1;
}
if ($b['id'] === $category_id) {
return 1;
}
if (array_key_exists('children', $a)) {
if (usort($a['children'], "custom_sort")) {
return -1;
}
}
if (array_key_exists('children', $b)) {
if (usort($b['children'], "custom_sort")) {
return 1;
}
}
return 0;
}
function reorder_tree($tree) {
usort($tree['children'], "custom_sort");
return $tree;
}
echo "<pre>";
var_dump(reorder_tree($tree));
echo "</pre>";
However, that returns:
[
"id" => 245974,
"children" => [
[
"id" => 245982, // <- this is moved up
"children" => [
[
"id" => 246093,
"children" => [
[
"id" => 225892
],
[
"id" => 225893
],
[
"id" => 225902 // <- this is *not* moved up
]
]
]
]
],
[
"id" => 111
],
]
];
How would I be able to also sort the children arrays?
Great attempt and very much on the right track. The problem with recursion in the comparator is that usort will not call the comparator function when the array length is 1, so whether or not you explore the whole tree is at the whim of usort. This will abandon id => 245982's branch of the tree.
The solution is to avoid recursing in the usort's comparator function directly. Rather, use a regular recursive function that calls usort as needed, namely, the current array or a child array contains the target id. I use a separate array to keep track of which elements should be moved forward, but you can break out of the loop and splice/unshift a single element to the front if you prefer.
We can also make $category_id a parameter to the function.
Here's one approach:
function reorder_tree_r(&$children, $target) {
$order = [];
$should_sort = false;
foreach ($children as $i => &$child) {
$order[$i] = false;
if (array_key_exists("children", $child) &&
reorder_tree_r($child["children"], $target) ||
$child["id"] === $target) {
$order[$i] = true;
$should_sort = true;
}
}
if ($should_sort) {
$priority = [];
$non_priority = [];
for ($i = 0; $i < count($children); $i++) {
if ($order[$i]) {
$priority[]= $children[$i];
}
else {
$non_priority[]= $children[$i];
}
}
$children = array_merge($priority, $non_priority);
}
return $should_sort;
}
function reorder_tree($tree, $target) {
if (!$tree || !array_key_exists("children", $tree)) {
return $tree;
}
reorder_tree_r($tree["children"], $target);
return $tree;
}
var_export(reorder_tree($tree, 225902));
Output:
array (
'id' => 245974,
'children' =>
array (
0 =>
array (
'id' => 245982,
'children' =>
array (
0 =>
array (
'id' => 246093,
'children' =>
array (
0 =>
array (
'id' => 225902,
),
1 =>
array (
'id' => 225892,
),
2 =>
array (
'id' => 225893,
),
),
),
),
),
1 =>
array (
'id' => 111,
),
),

How to sort array by subarray values with unknow index

I've got this PHP array:
<?php
$cities = [
'amsterdam' => $amsterdam,
'prague' => $prague,
'lisboa' => $lisboa
];
$amsterdam = [
65 => [
'table' => Object
'something' => false,
'data' => [
'foo' => 'boo',
'price' => 100
]
],
173 => [
'table' => Object
'something' => false,
'data' => [
'foo' => 'hoo',
'price' => 2500
]
],
...
];
$prague = [
132 => [
'table' => Object
'something' => false,
'data' => [
'foo' => 'boo',
'price' => 2100
]
],
956 => [
'table' => Object
'something' => false,
'data' => [
'foo' => 'hoo',
'price' => 2500
]
],
...
];
$lisboa = [
175 => [
'table' => Object
'something' => false,
'data' => [
'foo' => 'boo',
'price' => 6500
]
],
64 => [
'table' => Object
'something' => false,
'data' => [
'foo' => 'hoo',
'price' => 20
]
],
...
];
?>
and I need to sort it by the subarray value ['data']['price'] so the output is like this:
<?php
$cheapest_cities [
'lisboa' => $lisboa, // because 64->data->price is 20
'amsterdam' => $amsterdam, // beacuse 65->data->price is 100
'prague' => $prague // bacause 132->data->price is 2100
];
?>
I tried several usort combinations, but the problem is, that i never know what the subarray index will be (65, 173, 132, 956, 175, 64) in my example.
Do you have any idea how to sort it?
The data comes from database:
<?php
$amsterdam = $this->getTable()->where(['package_id' => [1,2,3]])->order('package_id')->fetchPairs('id');
$lisboa = $this->getTable()->where(['package_id' => [4,5]])->order('package_id')->fetchPairs('id');
$prague = $this->getTable()->where(['package_id' => [6]])->order('package_id')->fetchPairs('id');
return [
'amsterdam' => $amsterdam,
'lisboa' => $lisboa,
'prague' => $prague,
];
?>
Thank you
I would start by making a new array, which has the smallest price of every city as value
For this I use an array_map function which reduces the $items to the price with array_reduce
$map_prices = function($n) {
$reduce_smallest_price = function($carry, $item) {
return $item['data']['price'] < $carry
? $item['data']['price']
: $carry;
};
return array_reduce($n, $reduce_smallest_price, INF);
};
$cities_price = array_map($map_prices, $cities);
asort($cities_price);
I use this prices array to sort the original array with uksort
uksort($cities, function($a, $b) {
global $cities_price;
return strnatcmp($cities_price[$a], $cities_price[$b]);
});
Here is a live example on 3v4l: https://3v4l.org/8B9VN
Don't use usort as it will remove your keys. Use uasort.
Just a quick idea:
Inside the callback function of uasort you could search the minimum of your item e.g. via array_reduce.
array_reduce($city, function($carry, $item) {
return $carry === 0 ? $item['data']['price'] : min($carry, $item['data']['price']);
}, 0);
This snippet gets the minimum of a city array. Then it should be easy to compare the values.
Full example:
function getMinimumPrice($cityArray) {
return array_reduce($cityArray, function($carry, $item) {
return $carry === 0 ? $item['data']['price'] : min($carry, $item['data']['price']);
}, 0);
}
uasort($cities, function($city1, $city2) {
$priceCity1 = getMinimumPrice($city1);
$priceCity2 = getMinimumPrice($city2);
return $priceCity1 <=> $priceCity2;
});

Elasticsearch Sort based on value first

I want to sort results on the release date. I want 2015 movies first and the rest of the years sorted on the has_poster value. So I get results like:
2015, 2015, 1989, 2017, 2006
etc.
So far this is what I've got.
$params['index'] = 'movies';
$params['type'] = 'movie';
$params['body']['size'] = 50;
$params['body']['sort'] = array(
array('has_poster' => "desc"),
array('_score' => "desc"),
);
$params['body']['query']['filtered'] = array(
'query' => array(
'query_string' => array(
'query' => "survivor",
'fields' => array('name', 'orig_title'),
'default_operator' => "AND"
),
)
);
I need the equivalent of ... ORDER BY FIELD(releasedate, 2015), has_poster DESC ... in MySQL for Elasticsearch.
You need a scripted sorting:
{
"sort": [
{
"_script": {
"script": "if(doc['releasedate'].date.year==2015) return 1; else return 0;",
"type": "number",
"order": "desc"
}
},
{
"has_poster": "desc"
}
]
}
Im not work =>type name is string
$params['body']['sort']= array('name' => 'desc' );
or
$params=[
'index'=>'pets',
'type'=>'bird',
'body'=>[
'query'=>[
'bool'=>[
'must'=>[],
'filter'=>[
'range'=>[
'registered'=>[
'gte' => "2020-10-01",
'lte' => "2020-11-30"]
]
]
]
],
'sort'=>[
'name'=>[
'order'=>'asc'
]
]
]
];
but not work

PHP: How to rename all keys of a Nested List (Array) to 'items'

I need to "reformat" some data coming from an external API so it works with the nested list module of Sencha touch. I cannot change the data output of that external API. Here's an example of the data I get from the API:
$quest = array(
'gastronomy' => [
'restaurants' => [
'italians' => [
[
'title' => 'Al Castello',
'leaf' => true
],
[
'title' => 'Italia',
'leaf' => true
]
],
'asians' => [
[
'title' => 'Gautam',
'leaf' => true
],
[
'title' => 'Wok',
'leaf' => true
]
]
]
]
);
In order to make it work with sencha touch the data must look like this after "reformatting" it with a PHP Service:
$result = array(
'items' => [
[
'title' => 'gastronomy',
'items' => [
[
'title' => 'restaurants',
'items' => [
[
'title' => 'italians',
'items' => [
[
'title' => 'Al Castello',
'leaf' => true
],
[
'title' => 'Italia',
'leaf' => true
]
]
],
[
'title' => 'asians',
'items' => [
[
'title' => 'Gautam',
'leaf' => true
],
[
'title' => 'Wok',
'leaf' => true
]
]
]
]
]
]
]
]
);
I have tried every way I could think of but with no success. What really bugs me is that all keys must be renamed to items. (It's hard for me to access the deeper nested items because of that when I'm using a recursive function)
Haven't tested it, but it seems like a fairly simple recursive function should handle it.
For example:
function parseApi($arr) {
$result = array();
foreach ($arr as $key => $value) {
if (isset($value['leaf'])) {
$result[] = $value;
} else {
$result[] = array(
'title' => $key,
'items' => parseApi($value)
);
}
}
return $result;
}
$result = array( 'items' => $parseApi($quest);
You need a recursive function, and it needs to be able to tell the difference between associative and numerically-indexed arrays.
// from: http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
function isAssoc($arr) { return array_keys($arr) !== range(0, count($arr) - 1); }
function itemize($foo) {
$output = [];
if( ! isAssoc($foo) ) {
foreach( $foo as $value ) {
if( is_array($value) ) {
$output[] = itemize($value);
} else {
$output[] = $value;
}
}
} else {
foreach( $foo as $key => $value ) {
if( is_array($value) ) {
$output[] = [
'title' => $key,
'items' => itemize($value)
];
} else {
$output[$key] = $value;
}
}
}
return $output;
}
echo json_encode(itemize($quest), JSON_PRETTY_PRINT);
Output:
[
{
"title": "gastronomy",
"items": [
{
"title": "restaurants",
"items": [
{
"title": "italians",
"items": [
{
"title": "Al Castello",
"leaf": true
},
{
"title": "Italia",
"leaf": true
}
]
},
{
"title": "asians",
"items": [
{
"title": "Gautam",
"leaf": true
},
{
"title": "Wok",
"leaf": true
}
]
}
]
}
]
}
]

Categories