$inventory = array(
array("fruit"=>"orange", "price"=>3),
array("fruit"=>"kiwi", "price"=>2),
array("fruit"=>"apple", "price"=>3),
array("fruit"=>"apple", "price"=>3),
array("fruit"=>"apple", "price"=>3),
array("fruit"=>"orange", "price"=>3),
array("fruit"=>"banana", "price"=>10),
array("fruit"=>"banana", "price"=>10),
);
// what I wish to do is loop through this array and add all of the 'prices' for each
// unique key 'fruit' and then sort them afterwards
// ex. the output I wish to achieve would be an array as such:
$sum_array = array("banana"=>"20", "apple"=>"9", "orange"=>"6", "kiwi"=>"2");
Well, just group them by fruit and then sort the end-result:
function groupFruits(&$result, $item)
{
$key = $item['fruit'];
#$result[$key] += $item['price'];
return $result;
}
$grouped = array_reduce($inventory, 'groupFruits', array());
arsort($grouped);
print_r($grouped);
Demo
See also: array_reduce() arsort()
Update
You will see some crazy results when you look at this code in different versions of PHP.
Related
I have an array of the form:
class anim {
public $qs;
public $dp;
public $cg;
public $timestamp;
}
$animArray = array();
$myAnim = new anim();
$myAnim->qs = "fred";
$myAnim->dp = "shorts";
$myAnim->cg = "dino";
$myAnim->timestamp = 1590157029399;
$animArray[] = $myAnim;
$myAnim = new anim();
$myAnim->qs = "barney";
$myAnim->dp = "tshirt";
$myAnim->cg = "bird";
$myAnim->timestamp = 1590133656330;
$animArray[] = $myAnim;
$myAnim = new anim();
$myAnim->qs = "fred";
$myAnim->dp = "tshirt";
$myAnim->cg = "bird";
$myAnim->timestamp = 1590117032286;
$animArray[] = $myAnim;
How do I create a new array containing only the non-duplicates (and the latest entry where duplicates are found) of $animArray, where a duplicate is defined as:
one where $myAnim->dp has the same value as that of another array element's $myAnim->dp AND the $myAnim->cg from the first and the $myAnim->cg from the second have the same value as each other.
In the example above, only the first element is unique by that definition.
I'm hoping there's an elegant solution. I've been through all the array functions in the PHP manual but can't see how it could be achieved.
I could loop through each array element checking if $myAnim->dp has the same value as that of another array element's $myAnim->dp, saving the matches into a new array and then looping through that new array, checking for its $myAnim->cg matching the $myAnim->cg of any other element in that new array.
A more elegant solution would allow me to to change which combination of key-value pairs determine whether there's a duplicate, without having to recast much code.
Does such a solution exist?
Thanks for helping this novice :)
While there is nothing built-in that can be used directly out of the box, there isn't a lot of code necessary to handle an arbitrary number of properties to consider for uniqueness. By keeping track of each unique property in a lookup array, we can build an array where the leaf nodes (i.e. the ones that isn't arrays themselves) are the objects.
We do this by keeping a reference (&) to the current level in the array, then continue building our lookup array for each property.
function find_uniques($list, $properties) {
$lookup = [];
$unique = [];
$last_idx = count($properties) - 1;
// Build our lookup array - the leaf nodes will be the items themselves,
// located on a level that matches the number of properties to look at
// to consider a duplicate
foreach ($list as $item) {
$current = &$lookup;
foreach ($properties as $idx => $property) {
// last level, keep object for future reference
if ($idx == $last_idx) {
$current[$item->$property] = $item;
break;
} else if (!isset($current[$item->$property])) {
// otherwise, if not already set, create empty array
$current[$item->$property] = [];
}
// next iteration starts on this level as its current level
$current = &$current[$item->$property];
}
}
// awr only calls the callback for leaf nodes - i.e. our items.
array_walk_recursive($lookup, function ($item) use (&$unique) {
$unique[] = $item;
});
return $unique;
}
Called with your data above, and the requirement being that uniques and the last element of duplicates being returned, we get the following result:
var_dump(find_uniques($animArray, ['dp', 'cg']));
array(2) {
[0] =>
class anim#1 (4) {
public $qs =>
string(4) "fred"
public $dp =>
string(6) "shorts"
public $cg =>
string(4) "dino"
public $timestamp =>
int(1590157029399)
}
[1] =>
class anim#3 (4) {
public $qs =>
string(4) "fred"
public $dp =>
string(6) "tshirt"
public $cg =>
string(4) "bird"
public $timestamp =>
int(1590117032286)
}
}
Which maps to element [0] and element [2] in your example. If you instead want to keep the first object for duplicates, add an isset that terminates the inner loop if property value has been seen already:
foreach ($properties as $idx => $property) {
if ($idx == $last_idx) {
if (isset($current[$item->$property])) {
break;
}
$current[$item->$property] = $item;
} else {
$current[$item->$property] = [];
}
// next iteration starts on this level as its current level
$current = &$current[$item->$property];
}
It's important to note that this has been written with the assumption that the array you want to check for uniqueness doesn't contain arrays themselves (since we're looking up properties with -> and since we're using array_walk_recursive to find anything that isn't an array).
This was fun:
array_multisort(array_column($animArray, 'timestamp'), SORT_DESC, $animArray);
$result = array_intersect_key($animArray,
array_unique(array_map(function($v) { return $v->dp.'-'.$v->cg; }, $animArray)));
First, extract the timestamp and sort that array descending, thereby sorting the original array.
Then, map to create a new array using the dp and cg combinations.
Next, make the combination array unique which will keep the first duplicate encountered (that's why we sorted descending).
Finally, get the intersection of keys of the original array and the unique one.
In a function with dynamic properties:
function array_unique_custom($array, $props) {
array_multisort(array_column($array, 'timestamp'), SORT_DESC, $array);
$result = array_intersect_key($array,
array_unique(array_map(function($v) use ($props) {
return implode('-', array_map(function($p) use($v) { return $v->$p; }, $props));;
},
$array)));
return $result;
}
$result = array_unique_custom($animArray, ['dp', 'cg']);
Another option would be to sort it ascending and then build an array with a dp and cg combination as the key, which will keep the last duplicate:
array_multisort(array_column($animArray, 'timestamp'), SORT_ASC, $animArray);
foreach($animArray as $v) {
$result[$v->dp.'-'.$v->cg] = $v;
}
In a function with dynamic properties:
function array_unique_custom($array, $props) {
array_multisort(array_column($array, 'timestamp'), SORT_ASC, $array);
foreach($array as $v) {
$key = implode(array_map(function($p) use($v) { return $v->$p; }, $props));
$result[$key] = $v;
}
return $result;
}
$result = array_unique_custom($animArray, ['dp', 'cg']);
//Create an array with dp and cg values only
$new_arr = [];
foreach($animArray as $key=>$item) {
$new_arr[] = $item->dp.','.$item->cg;
}
$cvs = array_count_values($new_arr);
$final_array = [];
foreach($cvs as $cvs_key=>$occurences) {
if ($occurences == 1) {
$filter_key = array_keys($new_arr, $cvs_key)[0];
$final_array[$filter_key] = $animArray[$filter_key];
}
}
The final result would be (from your example) in $final_array:
[0] => anim Object
(
[qs] => fred
[dp] => shorts
[cg] => dino
[timestamp] => 1590157029399
)
Some explanation:
//Create a new array based on your array of objects with the attributes dp and cg
//with a comma between them
$new_arr = [];
foreach($animArray as $key=>$item) {
$new_arr[] = $item->dp.','.$item->cg;
}
/*
$new_arr now contains:
[0] => shorts,dino
[1] => tshirt,bird
[2] => tshirt,bird
*/
//Use builtin-function array_count_values to get the nr of occurences for
//each item in an array
$cvs = array_count_values($new_arr);
/*
$cvs would contain:
(
[shorts,dino] => 1
[tshirt,bird] => 2
)
*/
//Iterate through the $cvs array.
//Where there are only one occurence (no duplicates)
//create a final array $final_array
$final_array = [];
foreach($cvs as $cvs_key=>$occurences) {
if ($occurences == 1) {
/*
array_keys with second argument $csv_key searches for key with
with the key from $cvs-key
so basically search for:
shorts,dino and retrieve the key 0 (first element)
*/
$filter_key = array_keys($new_arr, $cvs_key)[0];
/*
Add a new item to the $final_array based on the key in
the original array $animArray
if you don't want the original key in the new array
you could just do $final_array[] instead of
$final_array[$filter_key]
*/
$final_array[$filter_key] = $animArray[$filter_key];
}
}
You said you would like to have some kind of functionality test different attributes. I believe it would just be making a function/method where you pass in two values to the arguments $attr1 ('dp'?), $attr2('cg'?) or similar.
UPDATE
I had not grasped that you wanted the last value as well. This actually seemed as an easier task. Maybe I am missing something but it was fun to come up with a different approach than other answers :-)
//Create an array with dp and cg values only
$new_arr = [];
foreach($animArray as $key=>$item) {
$new_arr[] = $item->dp.','.$item->cg;
}
//Sort keys descending order
krsort($new_arr);
//Because of sending order of keys above, the unique values would return the
//last item of the duplicates
$new_arr2 = array_unique($new_arr);
//Switch order of keys back to normal (ascending)
ksort($new_arr2);
//Create a new array based on the keys set in $new_arr2
//
$final_arr = [];
foreach($new_arr2 as $key=>$item) {
$final_arr[] = $animArray[$key];
}
The output of $final_arr[] would be (in your example)
Array
(
[0] => anim Object
(
[qs] => fred
[dp] => shorts
[cg] => dino
[timestamp] => 1590157029399
)
[1] => anim Object
(
[qs] => fred
[dp] => tshirt
[cg] => bird
[timestamp] => 1590117032286
)
)
I'm initializing an array in my abstract class.
And I want to be sort this associative array in dynamically since the array index position has an effect when I export this as an excel sheet file.
public function setExportDetailsData() {
$headers['barcode'] = Lang::get('core::label.barcode');
$headers['stockNo'] = Lang::get('core::label.stock.no');
$headers['productName'] = Lang::get('core::label.product.name');
$headers['chineseName'] = Lang::get('core::label.chinese.name');
$headers['category'] = Lang::get('core::label.category');
$headers['discount2'] = Lang::get('core::label.discount.2');
$headers['discount3'] = Lang::get('core::label.discount.3');
$headers['discount4'] = Lang::get('core::label.discount.4');
$headers['amount'] = Lang::get('core::label.amount');
$headers['remarks'] = Lang::get('core::label.remarks');
}
I need to change the order it depending on the page/module sorting.
So, example, the order of sorting for page/module 1:
$sorting = [
'stockNo',
'barcode',
'chineseName',
'productName',
'category',
'discount2',
'discount3',
'discount4',
'amount',
'remarks'
];
And for other page/module has different page sorting.
I just want to sort this associative array dynamically depending on the page/module.
Use array_flip to create an array that has the score for each column:
$scores = array_flip($sorting);
Then use sortBy to sort by that score:
return collect($headers)->sortBy(fn ($value, $key) => $scores[$key])->all();
I need to sort an array in a particular order, sorting by the keys. I know I need to do something like the below but have tried many different variations of the it and cannot get the result I need.
Can anyone help?
An example of the array is below and th results I am looking for for this would be: Connectivity | Contact Centres | Cloud & Hosting | Business Continuity
$solutions = Array ( [Business Continuity] => business-continuity
[Connectivity] => connectivity [Cloud & Hosting] => cloud-hosting
[Contact Centres] => contact-centres )
function reorder_solutions($a, $b){
$custom = array('Lines & Calls', 'Mobile', 'Connectivity', 'Wifi', 'LAN', 'UC&C', 'Contact Centres', 'Cloud & Hosting', 'Managed Services', 'Security', 'Business Continuity');
foreach ($custom as $k => $v){
if ($k == $b) {
return 0;
}
return ($k < $b) ? -1 : 1;
}
}
uasort($solutions, "reorder_solutions");
Here's one way to do it:
// loop over the $custom array
foreach ($custom as $key) {
// add each key found in $solutions into a $sorted array
// (they'll be added in custom order)
if (isset($solutions[$key])) {
$sorted[$key] = $solutions[$key];
// remove the item from $solutions after adding it to $sorted
unset($solutions[$key]);
}
}
// merge the $sorted array with any remaining items in $solutions
$solutions = array_merge($sorted, $solutions);
Another way:
// create an array using the values of $custom as keys with all empty values
$custom = array_fill_keys($custom, null);
// merge that array with $solutions (same keys will be overwritten with values
// from $solutions, then remove the empty values with array_filter
$solutions = array_filter(array_merge($custom, $solutions));
You can make it a one-liner if you like.
$solutions = array_filter(array_merge(array_fill_keys($custom, null), $solutions));
So I have my query, its returning results as expect all is swell, except today my designer through in a wrench. Which seems to be throwing me off my game a bit, maybe its cause Im to tired who knows, anyway..
I am to create a 3 tier array
primary category, sub category (which can have multiples per primary), and the item list per sub category which could be 1 to 100 items.
I've tried foreach, while, for loops. All typically starting with $final = array(); then the loop below that.
trying to build arrays like:
$final[$row['primary]][$row['sub']][] = $row['item]
$final[$row['primary]][$row['sub']] = $row['item]
I've tried defining them each as there own array to use array_push() on. And various other tactics and I am failing horribly. I need a fresh minded person to help me out here. From what type of loop would best suit my need to how I can construct my array(s) to build out according to plan.
The Desired outcome would be
array(
primary = array
(
sub = array
(
itemA,
itemB,
itemC
),
sub = array
(
itemA,
itemB,
itemC
),
),
primary = array
(
sub = array
(
itemA,
itemB,
itemC
),
sub = array
(
itemA,
itemB,
itemC
),
),
)
Something like this during treatment of your request :
if (!array_key_exists($row['primary'], $final)) {
$final[$row['primary']] = array();
}
if (!array_key_exists($row['sub'], $final[$row['primary']])) {
$final[$row['primary']][$row['sub']] = array();
}
$final[$row['primary']][$row['sub']][] = $row['item'];
Something like this....
$final =
array(
'Primary1'=>array(
'Sub1'=>array("Item1", "Item2"),
'Sub2'=>array("Item3", "Item4")
),
'Primary2'=>array(
'Sub3'=>array("Item5", "Item6"),
'Sub4'=>array("Item7", "Item8")
),
);
You can do it using array_push but it's not that easy since you really want an associative array and array_push doesn't work well with keys. You could certainly use it to add items to your sub-elements
array_push($final['Primary1']['Sub1'], "Some New Item");
If I understand you correctly, you want to fetch a couple of db relations into an PHP Array.
This is some example code how you can resolve that:
<?php
$output = array();
$i = 0;
// DB Query
while($categories) { // $categories is an db result
$output[$i] = $categories;
$ii = 0;
// DB Query
while($subcategories) { // $subcategories is an db result
$output[$i]['subcategories'][$ii] = $subcategories;
$iii = 0;
// DB Query
while($items) { // $items is an db result
$output[$i]['subcategories'][$ii]['items'][$iii] = $items;
$iii++;
}
$ii++;
}
$i++;
}
print_r($output);
?>
I have some arrays that I'd like to unset based on a key.
For example, let's say I have this array:
$array = array(
'one' => array('item' => '1'),
'two' => array('item' => '2')
);
If I want to unset the nested array with key 'two', I could do:
unset($array['two'])
or if I wanted to unset just the item array for key 'two', I could do:
unset($array['two']['item'])
I want to dynamically delete array items based on known keys. So for example, I know I want to delete ['two']['item'].
How do I pass those two arguments to a method, that can then be appended to an array?
Example:
//This works fine if it's only the first item in the array
function deleteArray($keys)
{
unset($this->array[$keys]);
}
But when we want to delete nested items, this would not work. I could pass in the keys as an array such as array('two', 'item') and build the index off of this, but not sure how....
Any help would be great! thank you!
You can use this function:
function delete(&$array, $keys)
{
$key = array_shift($keys);
if (count($keys) == 0)
unset($array[$key]);
else
delete($array[$key], $keys);
}
Try with a recursive function:
function deleteArray(&$array, $keys) {
if ( count($keys) == 1 )
unset( $array[$keys[0]] );
else
{
$k = array_shift($keys);
deleteArray($array[$k],$keys);
}
}
deleteArray($this->arr, array("three","item","blabla")); // This erase $this->array["three"]["item"]["blabla"]
function deleteArray($keys)
{
$keyarray = explode($keys, " ");
unset($this->array[$keyarray[0]][$keyarray[1]]);
}
I edited it a bit (won't work!) maybe somebody could continue that. Maybe it is possible with a while() ...