I have some data which looks like this (reduced)
Array
(
[datasets] => Array
(
[0] => Array
(
[label] => NEW
[backgroundColor] => #37fdfd
[data] => Array
(
[0] => 0
[1] => 0
[2] => 5
[3] => 0
)
)
[1] => Array
(
[label] => Grade A
[backgroundColor] => #76ef76
[data] => Array
(
[0] => 8
[1] => 12
[2] => 11
[3] => 0
)
)
[2] => Array
(
[label] => Grade B
[backgroundColor] => #f9f96d
[data] => Array
(
[0] => 1
[1] => 6
[2] => 5
[3] => 3
)
)
[3] => Array
(
[label] => Grade C
[backgroundColor] => #f3ca36
[data] => Array
(
[0] => 3
[1] => 0
[2] => 1
[3] => 4
)
)
[4] => Array
(
[label] => Grade D
[backgroundColor] => #f3ca36
[data] => Array
(
[0] => 3
[1] => 0
[2] => 1
[3] => 0
)
)
)
[labels] => Array
(
[0] => User 0
[1] => User 1
[2] => User 2
[3] => User 3
)
)
Here is a JSON string of the data (not reduced, numbers may differ slightly)
{"datasets":[{"label":"NEW","backgroundColor":"#37fdfd","data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"label":"Grade A","backgroundColor":"#76ef76","data":[9,14,12,0,4,17,13,0,10,0,18,18,12,13,13,4]},{"label":"Grade B","backgroundColor":"#f9f96d","data":[1,6,5,0,6,5,2,0,1,0,2,1,4,3,1,15]},{"label":"Grade C","backgroundColor":"#f3ca36","data":[3,0,1,0,2,0,0,0,0,0,1,1,0,0,0,0]},{"label":"Grade C","backgroundColor":"#f3ca36","data":[3,0,1,0,2,0,0,0,0,0,1,1,0,0,0,0]}],"labels":["User 0","User 1","User 2","User 3","User 4","User 5","User 6","User 7","User 8","User 9","User 10","User 11","User 12","User 13","User 14","User 15"]}
Each dataset has an array of data which has keys that directly relates to a key in the labels array. This is currently sorted in alphabetical order by the label.
This data structure is the structure required for Chart.js, which I am using to display a stacked bar chart on my webpage.
Essentially what I need to accomplish is to sort the data array for every user in the labels array based on the sum of each data set for that user. I also need to sort the labels array to be in the same order.
My original idea on how to achieve this is to create a temporary array, loop through all the data sets and add them to this temporary array in the order necessary, but I got stuck after calculating the total for each user. Here is my attempt:
$return = [];
foreach($calculated['labels'] as $key => &$name) {
$total = 0;
foreach($calculated['datasets'] as $dataset) {
$total += $dataset['data'][$key];
}
echo "$name - $total<br>";
}
How can I sort my data and labels in descending order based on the total for each user from all datasets.
Here is my expected output for the reduced data above
Array
(
[datasets] => Array
(
[0] => Array
(
[label] => NEW
[backgroundColor] => #37fdfd
[data] => Array
(
[2] => 5
[1] => 0
[0] => 0
[3] => 0
)
)
[1] => Array
(
[label] => Grade A
[backgroundColor] => #76ef76
[data] => Array
(
[2] => 11
[1] => 12
[0] => 8
[3] => 0
)
)
[2] => Array
(
[label] => Grade B
[backgroundColor] => #f9f96d
[data] => Array
(
[2] => 5
[1] => 6
[0] => 1
[3] => 3
)
)
[3] => Array
(
[label] => Grade C
[backgroundColor] => #f3ca36
[data] => Array
(
[2] => 1
[1] => 0
[0] => 3
[3] => 4
)
)
[4] => Array
(
[label] => Grade D
[backgroundColor] => #f3ca36
[data] => Array
(
[2] => 1
[1] => 0
[0] => 3
[3] => 0
)
)
)
[labels] => Array
(
[2] => User 2 //23 total across all data sets
[1] => User 1 //18 total across all data sets
[0] => User 0 //15 total across all data sets
[3] => User 3 //7 total across all data sets
)
)
The key in the labels array acts as a unique identifier for each user in each dataset data array.
Notice how each set of data inside of each dataset is in the same order, as is the labels array. Each set should be ordered by the total amount from all sets for each user, not necessarily the highest number in each dataset.
For clarification, each set of data in each dataset contains a list of values, the key for each value is directly related to the key for each user in the labels array. So in my example, we have User 0 who has the key "0". This user has a total of 23 from adding up the values from each dataset with the key "0".
Complete solution:
// get array
$a = json_decode('{"datasets":[{"label":"NEW","backgroundColor":"#37fdfd","data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{"label":"Grade A","backgroundColor":"#76ef76","data":[9,14,12,0,4,17,13,0,10,0,18,18,12,13,13,4]},{"label":"Grade B","backgroundColor":"#f9f96d","data":[1,6,5,0,6,5,2,0,1,0,2,1,4,3,1,15]},{"label":"Grade C","backgroundColor":"#f3ca36","data":[3,0,1,0,2,0,0,0,0,0,1,1,0,0,0,0]},{"label":"Grade C","backgroundColor":"#f3ca36","data":[3,0,1,0,2,0,0,0,0,0,1,1,0,0,0,0]}],"labels":["User 0","User 1","User 2","User 3","User 4","User 5","User 6","User 7","User 8","User 9","User 10","User 11","User 12","User 13","User 14","User 15"]}', true);
// get array of arrays with `data` key from each data set
$users = array_column($a['datasets'], 'data');
// tricky code to sum arrays
$sums = array_map('array_sum', array_map(null, ...$users));
// sort array with keeping keys
arsort($sums);
// we need flip so as `array_replace` will work as expected
$keys = array_flip(array_keys($sums));
// "sorting" `data` subarrays
foreach ($a['datasets'] as &$item) {
$item['data'] = array_replace($keys, $item['data']);
}
// "sorting" `labels` subarray
$a['labels'] = array_replace($keys, $a['labels']);
// see the result
print_r($a);
Fiddle here https://3v4l.org/a7rPL
I see this task as a perfect candidate for array_multisort(). Your synchronously sorted subarrays don't need to retain their initial keys like in u_mulder's output.
The first parameter must be the array of columnar sums, then the descending sort flag as the second parameter, then the labels subarray as a reference, then the dynamic number of data subarrays as references to the original array.
Code: (Demo)
$params = [[], SORT_DESC, &$array['labels']];
foreach ($array['datasets'] as ['data' => &$data]) {
foreach ($data as $i => $d) {
$params[0][$i] = ($params[0][$i] ?? 0) + $d;
}
$params[] = &$data;
}
array_multisort(...$params);
var_export($array);
This looks like a typical job for map(reduce).sort: map each element to an object with id, so you can preserve "which user this used to be" information, and total, the result of reducing data. Then sort with a custom sort function (a,b) => a.total - b.total.
E.g.
function map_total($user, $pos) {
return array(
"id" => $pos,
"total" => array_sum($user.data)
);
}
function cmp_total($a, $b) {
return $a["total"] - $b["total"];
}
$mapped = array_map("map_total", $thing.dataset, array_keys($thing.dataset));
$sorted = usort($mapped, "cmp_total");
I am having some difficulty looping through an array and calculating fields. Here is the array $iroom:
Array
(
[num_rooms] => 2
[adults] => Array
(
[0] => 2
[1] => 2
)
[prices] => Array
(
[0] => 44.5
[1] => 44.5
)
[roomTotalPrice] => Array
(
[0] => 89
[1] => 89
)
[price] => 178
)
I want to (adults*prices)+(adults*$asup)+(chidern*$csup)+$ssup and pu the answer into the roomTotalPrice. So far the outer forloop sets the roomTotalPrice price but I cannot get the inner loops to calculate the price. The $sup are extra supplement prices.
The code I got so far:
foreach($iroom['roomTotalPrice'] as &$irt){
foreach($iroom['adults'] as $ira){
}
$irt = ;
}
CODE WRAPPED IN FUNCTION, TO HANDLE NEW ARRAY FORMAT
/*
Note that this function may not be 100% correct. I notice you have removed
the 'supp' key from the array, and that your current spec doesn't do anything
with the 'price' key. I suspect you may want the line
+ ((isset($array['supp'])) ? $array['supp'] : 0);
to read
+ ((isset($array['price'])) ? $array['price'] : 0);
*/
function calculateTotalPrices ($array, $asup = 10, $csup = 10) {
if (!is_array($array) || !isset($array['num_rooms']) || !$array['num_rooms']) return FALSE; // make sure data is valid
for ($i = 0; $i < $array['num_rooms']; $i++) { // Loop num_rooms times
$array['roomTotalPrice'][$i] =
((isset($array['adults'][$i],$array['prices'][$i])) ? ($array['adults'][$i] * $array['prices'][$i]) + ($array['adults'][$i] * $asup) : 0) // Calculate price for adults
+ ((isset($array['childern'][$i])) ? ($array['childern'][$i] * $csup) : 0) // Calculate price for children
+ ((isset($array['supp'])) ? $array['supp'] : 0); // Add the supplement
}
// Get a total price for adults + children + supplements for all rooms
$array['grandTotal'] = array_sum($array['roomTotalPrice']);
return $array;
}
$iroom = array (
'num_rooms' => 2,
'adults' => array (
0 => 2,
1 => 3
),
'childern' => array (
0 => 1,
1 => 2
),
'prices' => array (
0 => 44.5,
1 => 44.5
),
'price' => 178,
);
print_r(calculateTotalPrices($iroom));
/* With the above array, outputs
Array
(
[num_rooms] => 2
[adults] => Array
(
[0] => 2
[1] => 3
)
[childern] => Array
(
[0] => 1
[1] => 2
)
[prices] => Array
(
[0] => 44.5
[1] => 44.5
)
[price] => 178
[roomTotalPrice] => Array
(
[0] => 119
[1] => 183.5
)
[grandTotal] => 302.5
)
*/
print_r(calculateTotalPrices($iroom,20,25));
/* With your sample array, outputs
Array
(
[num_rooms] => 2
[adults] => Array
(
[0] => 2
[1] => 3
)
[childern] => Array
(
[0] => 1
[1] => 2
)
[prices] => Array
(
[0] => 44.5
[1] => 44.5
)
[price] => 178
[roomTotalPrice] => Array
(
[0] => 154
[1] => 243.5
)
[grandTotal] => 397.5
)
*/
CODE UPDATED WITH ADDITIONAL CHECKS
foreach ($iroom as $k1 => $v1) { // Loop outer array
if (is_array($v1)) { // Make sure element is an array
foreach ($v1 as $k2 => $v2) { // Loop inner array
if (is_array($v2)) { // Make sure element is an array
for ($i = 0; $i < $v2['num_rooms']; $i++) { // Loop num_rooms times
$iroom[$k1][$k2]['roomTotalPrice'][$i] =
((isset($v2['adults'][$i],$v2['prices'][$i])) ? ($v2['adults'][$i] * $v2['prices'][$i]) + ($v2['adults'][$i] * $asup) : 0) // Calculate price for adults
+ ((isset($v2['childern'][$i])) ? ($v2['childern'][$i] * $csup) : 0) // Calculate price for children
+ $v2['supp']; // Add the supplement
}
// Get a total price for adults + children + supplements for all rooms
$iroom[$k1][$k2]['grandTotal'] = array_sum($iroom[$k1][$k2]['roomTotalPrice']);
}
}
}
}
print_r($iroom);
EDIT
Using the exact code above, feeding in the array above, and setting $asup = $csup = 10; at the top, I get no errors, and this output:
Array
(
[10] => Array
(
[12] => Array
(
[num_rooms] => 2
[adults] => Array
(
[0] => 2
[1] => 3
)
[childern] => Array
(
[0] => 1
[1] => 2
)
[prices] => Array
(
[0] => 44.5
[1] => 44.5
)
[price] => 178
[supp] => 0
[roomTotalPrice] => Array
(
[0] => 119
[1] => 183.5
)
[grandTotal] => 302.5
)
)
)
Note that the first result comes out at 119, not 129 as you state in the comment above - this is because in your example array, supp is 0 and not 10, as you have used in your calculation.
I have also tested with more complex arrays (with more elements at the first and second levels) and it works fine.
I'm guessing if you are getting "invalid argument supplied for foreach" errors it's because in your actual array, you highest level has some non-array memebers. This can easily be overcome by changing
foreach ($v1 as $k2 => $v2) {
to
if (is_array($v1)) foreach ($v1 as $k2 => $v2) {
I have an array that output's this:
Array (
[0] => Array ( [week] => 1 )
[1] => Array ( [user] => 1 )
[2] => Array ( [score] => 6 )
[3] => Array ( [week] => 1 )
[4] => Array ( [user] => 15 )
[5] => Array ( [score] => 6 )
[6] => Array ( [week] => 2 )
[7] => Array ( [user] => 1 )
[8] => Array ( [score] => 5 )
[9] => Array ( [week] => 2 )
[10] => Array ( [user] => 15 )
[11] => Array ( [score] => 7 )
How do I drop the lowest score for user 1 (which would be 5 taken in week 2) from the array? I only want to drop the lowest number from the array for user 1 and then want to do the same thing for user 15 (which would be 6 in week 1).
Then, how would I sum the remaining scores in the array by user? There will be more weeks data to follow and I always want to drop the lowest per user for any week.
So thus far the total score for User 1 would be 6 thru 2 weeks and the total score for User 15 would be 7 thru week 2. If week 3 has lower scores for both users then their totals would be: User 1 = 11 and User 15 = 13.
Thanks in advance!!
Assuming that all records are in groups of 3:
$array = array(/* your data */);
$count = count($array) / 3;
$lowest = 0;
$min = PHP_INT_MAX;
for($i = 0; $i < $count; $i++)
{
if($array[$i + 1]['user'] == 1)
{
if($array[$i + 2]['score'] < $min)
{
$min = $array[$i + 2]['score'];
$lowest = $i;
}
}
}
unset($array[$lowest]);
unset($array[$lowest + 1]);
unset($array[$lowest + 2]);
Should at least give you the idea.
Your array structure is bizarre. Why is every array element itself a singleton array? Something like the following would make more sense:
$array_better = array (
[0] => array (
['week'] => 1,
['user'] => 1,
['score'] => 6
),
[1] => array (
['week'] => 1,
['user'] => 15,
['score'] => 6
),
[2] => array (
['week'] => 2,
['user'] => 1,
['score'] => 5
),
[3] => array (
['week'] => 2,
['user'] => 15,
['score'] => 7
)
)
But for that matter, what would make still more sense would be a $array[$week][$user] structure, like this:
$array_wu = array (
[1] => array (
[1] => 6,
[15] => 6
),
[2] => array (
[1] => 5,
[15] => 7
)
)
(If you do not understand what I have done here, please ask me about it.)
Alternatively again, you could have a $array[$user][$week] structure:
$array_uw = array (
[1] => array (
[1] => 6,
[2] => 5
),
[15] => array (
[1] => 6,
[2] => 7
)
)
This in particular would make it very easy to do what you are trying to do - in fact it would take only one line of code statement:
$sum_excl_lowest = (array_key_exists(1, $array_uw) and count($array_uw[1])) ?
array_sum($array_uw[1]) - min($array_uw[1]) :
0;