Aggregate arrays in multilevel array if their values match - php

I have an array in the following format:
array(5){
[0] =>
array(4){
['product-id'] => 8931
['product'] => 'Cake'
['description'] => 'Yellow cake'
['quantity'] => 1
}
[1] =>
array(4){
['product-id'] => 8921
['product'] => 'Cookies'
['description'] => 'Chocolate chip cookies'
['quantity'] => 2
}
[2] =>
array(4){
['product-id'] => 8931
['product'] => 'Cake'
['description'] => 'Yellow cake'
['quantity'] => 1
}
[3] =>
array(4){
['product-id'] => 8931
['product'] => 'Cake'
['description'] => 'Yellow cake'
['quantity'] => 4
}
[4] =>
array(4){
['product-id'] => 8933
['product'] => 'Cake'
['description'] => 'Chocolate cake'
['quantity'] => 1
}
}
How can I compare all the arrays to each other?
In my code I have sorted all the arrays by product ID, and written a for to compare two rows at a time, but now I see why that won't work.
function cmp($a, $b){
return strcmp($a[0], $b[0]);
}
function combineArrays($arrays){
usort($arrays, "cmp");
for($i = 1; $i < count($arrays); $i++){
$f_row = $arrays[$i];
$next = $i + 1;
$s_row = $arrays[$next];
if($f_row[0] == $s_row[0]){
if($f_row[1] == $s_row[1]){
if($f_row[2] == $s_row[2]){
$q1 = (int) $f_row[3];
$q2 = (int) $s_row[3];
$total = $q1 + $q2;
unset($f_row[3]);
$f_row[5] = $total;
unset($arrays[$next]);
}
}
}
}
return $arrays;
}
What is a better way to do this?
For example, to take the first array and compare it to the next, value for value. As soon as one of the first 3 values doesn't match, you go on to compare that row to the next one. If all of the first three values match, add up the quantity value of the two arrays, assign that to the first array's quantity, and get rid of the second array. There could be more matches, so continue comparing that array until you have gone through the whole list.

My favorite solution uses array_reduce():
$filtered = array_reduce(
// Reduce the original list
$arrays,
// The callback function adds $item to $carry (the partial result)
function (array $carry, array $item) {
// Generate a key that contains the first 3 properties
$key = $item['product-id'].'|'.$item['product'].'|'.$item['description'];
// Search into the partial list generated until now
if (array_key_exists($key, $carry)) {
// Update the existing item
$carry[$key]['quantity'] += $item['quantity'];
} else {
// Add the new item
$carry[$key] = $item;
}
// The array_reduce() callback must return the updated $carry
return $carry;
},
// Start with an empty list
array()
);

try this
$arr=array(
array(
'product-id' => 8931,
'product' => 'Cake',
'description' => 'Yellow cake',
'quantity' => 3,
),
array(
'product-id' => 8921,
'product' => 'Cookies',
'description' => 'Chocolate chip cookies',
'quantity' => 2,
),
array(
'product-id' => 8931,
'product' => 'Cake',
'description' => 'Yellow cake',
'quantity' => 1,
)
);
$id = array();
foreach ($arr as $key => $row)
{
$id[$key] = $row['product-id'];
}
array_multisort($id, SORT_DESC, $arr);
$result = array();
foreach ($arr as $key => $row)
{
$pid = $row['product-id'];
if(!isset($result[$pid]))
{
$result[$pid]=$row;
}else{
$result[$pid]['quantity']+=$row['quantity'];
}
}
print_r($result);

You are doing it the hard way, why not do it like this:
function combineProducts($products) {
$result = array();
foreach ($products as $product) {
//if you can have different product or descriptions
//per product id you can change this this to
//$productId = implode('|', array($product['product-id'], $product['product'], $product['description']);
$productId = $product['product-id'];
//check if we already have this product
if (isset($result[$productId])) {
//add to the quantity
$result[$productId]['quantity']+= $product['quantity'];
} else {
$result[$productId] = $product;
}
}
//sort the results (remove if not needed)
ksort($result);
//return values (change to return $result; if you want an assoc array)
return array_values($result);
}

Related

How can I get the lowest price include the others values from the array [duplicate]

This question already has an answer here:
get cheap price with min() from array
(1 answer)
Closed 7 months ago.
I have an array like this:
$flight = array (
array (
"home" => "AMS",
"away" => "LHR",
"price" => "270"
),
array (
"home" => "AMS",
"away" => "LGW",
"price" => "216"
),
array (
"home" => "EIN",
"away" => "LHR",
"price" => "427"
)
);
I want the values from the cheapest flight. The result should be: AMS LGW 216.
When I loop trough the array I can get the lowest price result but not the other values from the array home and away.
foreach ($flight as $value) {
echo $value["home"].' '.$value["away"].' '.$value["price"].'<br>';
$lowest = array_column($flight, "price");
$min = min($lowest);
}
echo $min;
The result now is only 216
But the result I want AMS LGW 216
How can I create this?
One option is to remember what the values were at the lowest price and just iterate over all of them:
$min = null;
foreach ($flight as $value) {
if ($min === null || $value['price'] < $min) {
$minValue = $value;
$min = $value['price'];
}
}
echo $minValue["home"].' '.$minValue["away"].' '.$minValue["price"];
Store the entire item not just the price.
$min = ["price" => 1e10];
foreach ($flight as $value) {
echo $value["home"] . ' ' . $value["away"] . ' ' . $value["price"] . '<br>';
if ($value['price'] < $min['price']) {
$min = $value;
}
}
print_r($min);
Sort the array by field (ASC order) and you'll find the lowest element in the head of your array:
usort($flights, fn($a, $b) => $a['price'] <=> $b['price'])
print_r($flights[0]);
You can use array_keys and with your code.
Here is the code:
$flight = array(
array(
"home" => "AMS",
"away" => "LHR",
"price" => "270"
),
array(
"home" => "AMS",
"away" => "LGW",
"price" => "216"
),
array(
"home" => "EIN",
"away" => "LHR",
"price" => "427"
)
);
$lowest = array_column($flight, "price");
$lowset_array = array_keys($lowest, min($lowest));
print_r($flight[reset($lowset_array)]);
//OR
//print_r($flight[$lowset_array[0]]);
And here is the output:
Array
(
[home] => AMS
[away] => LGW
[price] => 216
)
Before providing solutions: your implementation it's finding the lowest price several times (the number of flights). You can get the cheapest price with:
$lowest = array_column($flight, 'price');
echo min($lowest);
You may use two variables to save the lowest price and the associated flight:
function getCheapestFlight(array $flights): array
{
$cheapestFlight = null;
$lowest = PHP_INT_MAX;
foreach ($flights as $flight) {
$price = $flight['price'];
if ($price < $lowest) {
$lowest = $price;
$cheapestFlight = $flight;
}
}
return $cheapestFlight;
}
Or use only one variable for the cheapest flight:
function getCheapestFlight(array $flights): array
{
$cheapestFlight = reset($flights);
foreach ($flights as $flight) {
$price = $flight['price'];
if ($price < $cheapestFlight['price']) {
$cheapestFlight = $flight;
}
}
return $cheapestFlight;
}
If you prefer functional programming:
function getCheapestFlight(array $flights): array
{
return array_reduce(
$flights,
function ($cheapestFlight, $flight) {
return $flight['price'] < $cheapestFlight['price']
? $flight
: $cheapestFlight;
},
reset($flights)
);
}

MYSQL database to JSON format using PHP

I tried to convert get data from mysql in json format. For that I am using PHP.
My PHP code is
<?php
define('_HOST_NAME', 'localhost');
define('_DATABASE_USER_NAME', 'root');
define('_DATABASE_PASSWORD', 'admin321');
define('_DATABASE_NAME', 'tree');
$dbConnection = new mysqli(_HOST_NAME,
_DATABASE_USER_NAME, _DATABASE_PASSWORD, _DATABASE_NAME);
if ($dbConnection->connect_error) {
trigger_error('Connection
Failed: ' . $dbConnection->connect_error, E_USER_ERROR);
}
$_GLOBAL['dbConnection'] = $dbConnection;
$categoryList = categoryParentChildTree();
foreach($categoryList as $key => $value){
echo $value['name'].'<br>';
}
function categoryParentChildTree($parent = 0,
$spacing = '', $category_tree_array = '') {
global $dbConnection;
$parent = $dbConnection->real_escape_string($parent);
if (!is_array($category_tree_array))
$category_tree_array = array();
$sqlCategory = "SELECT id,name,parent FROM php WHERE parent = $parent ORDER BY id ASC";
$resCategory=$dbConnection->query($sqlCategory);
if ($resCategory->num_rows != null && $resCategory->num_rows>0) {
while($rowCategories = $resCategory->fetch_assoc()) {
$category_tree_array[] = array("id" => $rowCategories['id'], "name" => $spacing . $rowCategories['name']);
$category_tree_array = categoryParentChildTree(
$rowCategories['id'],
' '.$spacing . '- ',
$category_tree_array
);
}
}
return $category_tree_array;
}
?>
mysql table
ID PARENT NAME
1 0 category
2 1 fruit
3 2 apple
4 2 orange
5 1 animals
6 5 tiger
7 5 lion
8 1 car
My output is:
category
- fruit
- - apple
- - orange
- animal
- - tiger
- - lion
- cars
I want to get nested json output. Already asked here. No proper response.
I tried with json_encode, not getting nested json.
UPDATED PHP
<?php
$con=mysqli_connect("localhost","root","admin321","tree");
if (mysqli_connect_errno()) //con error
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$r = mysqli_query($con,"SELECT * FROM php ");
$data = array();
while($row = mysqli_fetch_assoc($r)) {
$data[] = $row;
}
function buildtree($src_arr, $parent_id = 0, $tree = array())
{
foreach($src_arr as $idx => $row)
{
if($row['parent'] == $parent_id)
{
foreach($row as $k => $v)
$tree[$row['id']][$k] = $v;
unset($src_arr[$idx]);
$tree[$row['id']]['children'] = buildtree($src_arr, $row['id']);
}
}
ksort($tree);
return $tree;
}
function insertIntoNestedArray(&$array, $searchItem){
// Insert root element
if($searchItem['parent'] == 0){
array_push($array, $searchItem);
return;
}
if(empty($array)){ return; }
array_walk($array, function(&$item, $key, $searchItem){
if($item['id'] == $searchItem['parent']){
array_push($item['children'], $searchItem);
return;
}
insertIntoNestedArray($item['children'], $searchItem);
}, $searchItem);
}
$nestedArray = array();
foreach($data as $itemData){
// First: Mount the nested array item
$nestedArrayItem['id'] = $itemData['id'];
$nestedArrayItem['name'] = $itemData['name'];
$nestedArrayItem['parent'] = $itemData['parent'];
$nestedArrayItem['children'] = array();
// Second: insert the item into the nested array
insertIntoNestedArray($nestedArray, $nestedArrayItem);
}
$json = json_encode($nestedArray);
echo $json;
?>
Simply this: json_encode($output , JSON_FORCE_OBJECT);
Your Nested Output is just an human-representation of the stored data you have in your database. Its a human thing. Machines can't understand that, that's why in mysql you need a column to tell the category parent.
So, your problem is that you're trying to convert to JSON your already manipulated data. You need to convert to JSON your raw data, and then manipulate it in the code that receives the JSON.
use json_encode to encode the raw data:
$raw_data = $resCategory->fetch_all();
return json_encode($raw_data);
Also, just a note: this $_GLOBAL variable you're using... you're not trying to refer to the internal php $GLOBALS superglobal, you are?
EDIT:
Ok, now that you explained that you need the nested json format, you will need to use some recursion to build an nested array of arrays and then use the json_encode on it.
First: Get the raw data:
$resCategory=$dbConnection->query($sqlCategory);
$raw_data = $resCategory->fetch_all();
Now, suppose this $raw_data variable returns an array like this:
array (
0 => array (
'ID' => 1,
'PARENT' => 0,
'NAME' => 'category',
),
1 => array (
'ID' => 2,
'PARENT' => 1,
'NAME' => 'fruit',
),
2 => array (
'ID' => 3,
'PARENT' => 2,
'NAME' => 'apple',
),
3 => array (
'ID' => 4,
'PARENT' => 2,
'NAME' => 'orange',
),
4 => array (
'ID' => 5,
'PARENT' => 1,
'NAME' => 'animals',
),
5 => array (
'ID' => 6,
'PARENT' => 5,
'NAME' => 'tiger',
),
6 => array (
'ID' => 7,
'PARENT' => 5,
'NAME' => 'lion',
),
7 => array (
'ID' => 8,
'PARENT' => 1,
'NAME' => 'car',
)
)
Second: Build up an recursive function to insert items of this array into another array, the $nestedArray (that we will create in the third step).
function insertIntoNestedArray(&$array, $searchItem){
// Insert root element
if($searchItem['parent'] == 0){
array_push($array, $searchItem);
return;
}
// Stop the recursion when the array to check is empty
if(empty($array)){ return; }
// Walk the array searching for the parent of the search item
array_walk($array, function(&$item, $key, $searchItem){
// If the parent is found, then append the search item to it
if($item['id'] == $searchItem['parent']){
array_push($item['children'], $searchItem);
return;
}
// If the parent wasn't found, walk thought the children of the array
insertIntoNestedArray($item['children'], $searchItem);
}, $searchItem);
}
Third: Create the $nestedArray and populate it by loop through the $raw_data array and calling the recursive function:
$nestedArray = array();
foreach($data as $itemData){
// First: Mount the nested array item
$nestedArrayItem['id'] = $itemData['ID'];
$nestedArrayItem['name'] = $itemData['NAME'];
$nestedArrayItem['parent'] = $itemData['PARENT'];
$nestedArrayItem['children'] = array();
// Second: insert the item into the nested array
insertIntoNestedArray($nestedArray, $nestedArrayItem);
}
Fourth: Now its just to json_encode the $nestedArray:
$json = json_encode($nestedArray);
You can do an echo $json and the result will be:
[{"id":1,"name":"category","parent":0,"children":[{"id":2,"name":"fruit","parent":1,"children":[{"id":3,"name":"apple","parent":2,"children":[]},{"id":4,"name":"orange","parent":2,"children":[]}]},{"id":5,"name":"animals","parent":1,"children":[{"id":6,"name":"tiger","parent":5,"children":[]},{"id":7,"name":"lion","parent":5,"children":[]}]},{"id":8,"name":"car","parent":1,"children":[]}]}]

Multisort a 2 deep array

Consider the following multisort method. In this case I have a array of items with a specific start date. Example array is shown:
0 -> array('title' => 'hello',
'attributes' => array('id' => 4, 'startdate' => '2013-06-11')),
1 -> array('title' => 'hello second entry',
'attributes' => array('id' => 6, 'startdate' => '2013-04-11'))
You can see that the 2nd entry should come before the first. Using my call currently will not work because It only checks to depth 1 of the array.
$albums = $this->multiSort($items, "SORT_ASC", 'startdate', true);
How would be the best way to modify this method to have a depth search on the items in the array. Even better would be to be able to specific the depth key. I would like to avoid having to add additional parameters to the method.
I could call the method like so and then write a for loop to get the key data, but having nested for loops is not something I want to do.
$albums = $this->multiSort($items, "SORT_ASC", array('attributes', 'startdate') , true);
What is the best way to optimize this method for my case?
public function multiSort($data, $sortDirection, $field, $isDate) {
if(empty($data) || !is_array($data) || count($data) < 2) {
return $data;
}
foreach ($data as $key => $row) {
$orderByDate[$key] = ($isDate ? strtotime($row[$field]) : $row[$field]);
}
if($sortDirection == "SORT_DESC") {
array_multisort($orderByDate, SORT_DESC, $data);
} else {
array_multisort($orderByDate, SORT_ASC, $data);
}
return $data;
}
UPDATED. This allows you to pass in a string for field that is delimited and is a path to your desired field.
$items = Array();
$items[0] = array('title' => 'hello',
'attributes' => array('id' => 4, 'startdate' => '2013-06-11'));
$items[1] = array('title' => 'hello second entry',
'attributes' => array('id' => 6, 'startdate' => '2013-04-11'));
function multiSort($data, $sortDirection, $field, $isDate) {
if(empty($data) || !is_array($data) || count($data) < 2) {
return $data;
}
// Parse our search field path
$parts = explode("/", $field);
foreach ($data as $key => $row) {
$temp = &$row;
foreach($parts as $key2) {
$temp = &$temp[$key2];
}
//$orderByDate[$key] = ($isDate ? strtotime($row['attributes'][$field]) : $row['attributes'][$field]);
$orderByDate[$key] = ($isDate ? strtotime($temp) : $temp);
}
unset($temp);
if($sortDirection == "SORT_DESC") {
array_multisort($orderByDate, SORT_DESC, $data);
} else {
array_multisort($orderByDate, SORT_ASC, $data);
}
return $data;
}
$albums = multiSort($items, "SORT_ASC", 'attributes/startdate', true);
print_r($albums);
Ouput:
Array
(
[0] => Array
(
[title] => hello second entry
[attributes] => Array
(
[id] => 6
[startdate] => 2013-04-11
)
)
[1] => Array
(
[title] => hello
[attributes] => Array
(
[id] => 4
[startdate] => 2013-06-11
)
)
)

Combine repeating elements as array in a multidimensional array

I was wondering when working with multimedional arrays, if a certain key is the same, is there a way to combine the contents of other keys into its own array if a certain key is the same?
Something like this:
// name is the same in both arrays
array(
array(
'name' => 'Pepsi',
'store' => 'Over here',
'number' => '1234567'
),
array(
'name' => 'Pepsi',
'store' => 'Over here',
'number' => '5556734'
)
)
into something like this
array(
array(
'name' => 'Pepsi',
'store' => array('Over here', 'Over here'),
'number' => array('1234567', '5556734')
)
)
The defining key is checking if the name element is the same for the other arrays.
You can try a function like this.
function mergeByKey($array,$key){
$tmp_array = array();
foreach ( $array as $k => $row ) {
$merged = false;
foreach ($tmp_array as $k2 => $tmp_row){
if ($row[$key] == $tmp_row[$key]){
foreach ( $row as $k3 => $value ) {
if ($k3 == $key) continue;
$tmp_array[$k2][$k3][] = $value;
$merged = true;
}
}
if ($merged) break;
}
if (!$merged) {
$new_row = array();
foreach ( $row as $k4 => $value ) {
if ($k4 == $key) $new_row[$k4] = $value;
else $new_row[$k4] = array($value);
}
$tmp_array[] = $new_row;
}
}
foreach ( $tmp_array as $t => $row ) {
foreach ( $row as $t2 => $value ) {
if ( count($value) == 1 && $t2 != $key ) $tmp_array[$t][$t2] = $value[0];
}
}
return $tmp_array;
}
passing the array as first parameter and the key as second one.
I'm referencing to your array structure
edited: missed a piece
edited2: if resultin array contains elements with one string, it returns a string and not a array with one element
demo
This function uses a given field name as the grouping identifier and turns all other fields into arrays.
Note that single occurrences of your field name will yield arrays with a single element for the other fields. I wasn't sure whether that's a desirable trait, but just making sure you know ;-)
$arr = array(
array(
'name' => 'Pepsi',
'store' => 'Over here',
'number' => '1234567'
),
array(
'name' => 'Pepsi',
'store' => 'Over here',
'number' => '5556734'
)
);
function mergeArray($array, $column)
{
$res = array();
foreach ($array as $item) {
foreach ($item as $key => $value) {
if ($key === $column) {
$res[$column][$key] = $value;
} else {
$res[$column][$key][] = $value;
}
}
}
return array_values($res);
}
print_r(mergeArray($arr, 'name'));
Demo
Thanks to Gianni Lovece for her answer but I was able to develop a much simpler solution based on this problem. Just plug in the $result_arr to browse through and the $key you want to use as basis and it immediately outputs a multidimensional array with non-repeating values for repeating elements (see example below).
function multiarray_merge($result_arr, $key){
foreach($result_arr as $val){
$item = $val[$key];
foreach($val as $k=>$v){
$arr[$item][$k][] = $v;
}
}
// Combine unique entries into a single array
// and non-unique entries into a single element
foreach($arr as $key=>$val){
foreach($val as $k=>$v){
$field = array_unique($v);
if(count($field) == 1){
$field = array_values($field);
$field = $field[0];
$arr[$key][$k] = $field;
} else {
$arr[$key][$k] = $field;
}
}
}
return $arr;
}
For example, in the sample array for this question, running multiarray_merge($mysample, 'name') returns
array(
'Pepsi' => array(
'name' => 'Pepsi',
'store' => 'Over here', // String: Not an array since values are not unique
'number' => array('1234567', '5556734') // Array: Saved as array since values are unique
)
);

php, long and deep matrix

I have a deep and long array (matrix). I only know the product ID.
How found way to product?
Sample an array of (but as I said, it can be very long and deep):
Array(
[apple] => Array(
[new] => Array(
[0] => Array([id] => 1)
[1] => Array([id] => 2))
[old] => Array(
[0] => Array([id] => 3)
[1] => Array([id] => 4))
)
)
I have id: 3, and i wish get this:
apple, old, 0
Thanks
You can use this baby:
function getById($id,$array,&$keys){
foreach($array as $key => $value){
if(is_array( $value )){
$result = getById($id,$value,$keys);
if($result == true){
$keys[] = $key;
return true;
}
}
else if($key == 'id' && $value == $id){
$keys[] = $key; // Optional, adds id to the result array
return true;
}
}
return false;
}
// USAGE:
$result_array = array();
getById( 3, $products, $result_array);
// RESULT (= $result_array)
Array
(
[0] => id
[1] => 0
[2] => old
[3] => apple
)
The function itself will return true on success and false on error, the data you want to have will be stored in the 3rd parameter.
You can use array_reverse(), link, to reverse the order and array_pop(), link, to remove the last item ('id')
Recursion is the answer for this type of problem. Though, if we can make certain assumptions about the structure of the array (i.e., 'id' always be a leaf node with no children) there's further optimizations possible:
<?php
$a = array(
'apple'=> array(
'new'=> array(array('id' => 1), array('id' => 2), array('id' => 5)),
'old'=> array(array('id' => 3), array('id' => 4, 'keyname' => 'keyvalue'))
),
);
// When true the complete path has been found.
$complete = false;
function get_path($a, $key, $value, &$path = null) {
global $complete;
// Initialize path array for first call
if (is_null($path)) $path = array();
foreach ($a as $k => $v) {
// Build current path being tested
array_push($path, $k);
// Check for key / value match
if ($k == $key && $v == $value) {
// Complete path found!
$complete= true;
// Remove last path
array_pop($path);
break;
} else if (is_array($v)) {
// **RECURSION** Step down into the next array
get_path($v, $key, $value, $path);
}
// When the complete path is found no need to continue loop iteration
if ($complete) break;
// Teardown current test path
array_pop($path);
}
return $path;
}
var_dump( get_path($a, 'id', 3) );
$complete = false;
var_dump( get_path($a, 'id', 2) );
$complete = false;
var_dump( get_path($a, 'id', 5) );
$complete = false;
var_dump( get_path($a, 'keyname', 'keyvalue') );
I tried this for my programming exercise.
<?php
$data = array(
'apple'=> array(
'new'=> array(array('id' => 1), array('id' => 2), array('id' => 5)),
'old'=> array(array('id' => 3), array('id' => 4))
),
);
####print_r($data);
function deepfind($data,$findfor,$depth = array() ){
foreach( $data as $key => $moredata ){
if( is_scalar($moredata) && $moredata == $findfor ){
return $depth;
} elseif( is_array($moredata) ){
$moredepth = $depth;
$moredepth[] = $key;
$isok = deepfind( $moredata, $findfor, $moredepth );
if( $isok !== false ){
return $isok;
}
}
}
return false;
}
$aaa = deepfind($data,3);
print_r($aaa);
If you create the array once and use it multiple times i would do it another way...
When building the initial array create another one
$id_to_info=array();
$id_to_info[1]=&array['apple']['new'][0];
$id_to_info[2]=&array['apple']['new'][2];

Categories