I need to calculate the average value for each column of data in an array of associative arrays. The result should be a flat, associative array of averages.
Sample array:
$array = [
[
"a" => 0.333,
"b" => 0.730,
"c" => 0.393
],
[
"a" => 0.323,
"b" => 0.454,
"c" => 0.987
],
[
"a" => 0.753,
"b" => 0.983,
"c" => 0.123
]
];
I am looking for a simpler way of processing all the array elements and producing a single array which has a mean value (average) of all the corresponding values.
My current code works, but I'm hoping for a more elegant approach.
$a = []; // Store all a values
$b = []; // Store all b values
$c = []; // Store all c values
for ( $i = 0; $i < count( $array ); $i ++ ) {
// For each array, store each value in it's corresponsing array
// Using variable variables to make it easy
foreach ( $array[ $i ] AS $key => $val ) {
$k = $key;
$$k[] = $val;
};
}
// Create single array with average of all
$fa = array(
'a' => array_sum($a) / count($a),
'b' => array_sum($b) / count($b),
'c' => array_sum($c) / count($c)
);
The desired result:
[
'a' => 0.4696666666666667,
'b' => 0.7223333333333333,
'c' => 0.501,
]
Assuming each sub-array has the same keys:
foreach(array_keys($array[0]) as $key) {
$result[$key] = array_sum($tmp = array_column($array, $key))/count($tmp);
}
Get the keys from the first sub-array
Loop, extract those values from the main array and calculate
Here is a functional-style approach which will work as #AbraCadaver's answer does.
Code: (Demo)
var_export(
array_reduce(
array_keys($array[0] ?? []),
fn($result, $k) => $result + [$k => array_sum($c = array_column($array, $k)) / count($c)],
[]
)
);
I used ?? [] as a safeguard for live applications where it is possible that the input array is empty (and does not contain an element at [0]).
Using PHP7.4's arrow function syntax allows the accessibility of the input array without a use() statement.
The + in the custom function is not performing addition; it is uniting the result array with the new associative array -- this serves to push the new element into the returned result array.
Related
I have two arrays like this:
$arr1 = ['a' => '1','b' => 2];
$arr2 = ['h' => 'c','j' => '3'];
And I want to merge them to this result:
$newArr = ['a' => '1','h'=>'c','b'=>2,'j' => '3'];
That means I want to merge them so that the global order of the entries is the same as in the source arrays. In other words, zip and flatten.
array_merge does not do this. Is there any solution?
Note that this solution will only work if the two arrays have the same length:
$arr1 = [ 'a' => '1', 'b' => 2 ];
$arr2 = [ 'h' => 'c', 'j' => '3' ];
$count = count($arr1);
$keys1 = array_keys($arr1);
$keys2 = array_keys($arr2);
$result = [];
for ($i = 0; $i < $count; $i++) {
$key1 = $keys1[$i];
$result[$key1] = $arr1[$key1];
$key2 = $keys2[$i];
$result[$key2] = $arr2[$key2];
}
print_r($result);
Output:
Array
(
[a] => 1
[h] => c
[b] => 2
[j] => 3
)
Edited based on mickmackusa's comment below.
First is a solution that will consume the input arrays in a loop while building the new structure. You can always cache separate copies of the input if you need them elsewhere.
All solutions below will work even if the two arrays have different lengths -- any remaining elements will be appended to the end of the result array after the loop.
Code: (Demo)
$result = [];
while ($arr1 && $arr2) {
$result += array_splice($arr1, 0, 1)
+ array_splice($arr2, 0, 1);
}
$result += $arr1 + $arr2;
var_export($result);
Another way without consuming the input arrays is to build lookup arrays:
Code: (Demo)
$max = max(count($arr1), count($arr2));
$keys1 = array_keys($arr1);
$keys2 = array_keys($arr2);
$result = [];
for ($x = 0; $x < $max; ++$x) {
if (isset($keys1[$x])) {
$result[$keys1[$x]] = $arr1[$keys1[$x]];
}
if (isset($keys2[$x])) {
$result[$keys2[$x]] = $arr2[$keys2[$x]];
}
}
var_export($result);
Or you could use array_slice() to isolate one element at a time from each array without damaging the input arrays, nor generating warnings.
Code: (Demo)
$result = [];
for ($i = 0, $count = count($arr1); $i < $count; ++$i) {
$result += array_slice($arr1, $i, 1)
+ array_slice($arr2, $i, 1);
}
$result += $arr1 + $arr2;
You can use array_merge() or array_merge_recursive().
Merges the elements of one or more arrays such that the values of one array are appended to the end of the previous one. The result of the function is a new array.
https://www.php.net/manual/ru/function.array-merge.php
The array_merge_recursive() function merges the elements of two or more arrays in such a way that the values of one array are appended to the end of the other. Returns the resulting array.
If the input arrays have the same string keys, then the values of those keys are merged into an array, and this is done recursively, so that if one of the values is an array, then the function merges it with the corresponding value in the other array. However, if the arrays have the same numeric keys, each successive value will not replace the original value, but will be added to the end of the array.
https://www.php.net/manual/ru/function.array-merge-recursive.php
you can use array_merge function,which uses to merge two or more arrays into single array.
$arr1 = ['a' => '1','b' => 2];
$arr2 = ['h' => 'c','j' => '3'];
$result=array_merge($arr1,$arr2);
var_dump($result);
//output:- array(4) { ["a"]=> string(1) "1" ["b"]=> int(2) ["h"]=> string(1) "c" ["j"]=> string(1) "3" }
Given an associative array like this, how can you shuffle the order of keys that have the same value?
array(a => 1,
b => 2, // make b or c ordered first, randomly
c => 2,
d => 4,
e => 5, // make e or f ordered first, randomly
f => 5);
The approach I tried was to turn it into a structure like this and shuffle the values (which are arrays of the original keys) and then flatten it back into the original form. Is there a simpler or cleaner approach? (I'm not worried about efficiency, this is for small data sets.)
array(1 => [a],
2 => [b, c], // shuffle these
4 => [d],
5 => [e, f]); // shuffle these
function array_sort_randomize_equal_values($array) {
$collect_by_value = array();
foreach ($array as $key => $value) {
if (! array_key_exists($value, $collect_by_value)) {
$collect_by_value[$value] = array();
}
// note the &, we want to modify the array, not get a copy
$subarray = &$collect_by_value[$value];
array_push($subarray, $key);
}
arsort($collect_by_value);
$reordered = array();
foreach ($collect_by_value as $value => $array_of_keys) {
// after randomizing keys with the same value, create a new array
shuffle($array_of_keys);
foreach ($array_of_keys as $key) {
array_push($reordered, $value);
}
}
return $reordered;
}
I rewrote the entire code, since I found another way which is a lot simpler and faster than the old one(If you are still interested in the old one see the revision):
old code (100,000 executions): Ø 4.4 sec.
new code (100,000 executions): Ø 1.3 sec.
Explanation
First we get all unique values from the array with array_flip(), since then the values are the keys and you can't have duplicate keys in an array we have our unique values. We also create an array $result for then storing our result in it and $keyPool for storing all keys for each value.
Now we loop through our unique values and get all keys which have the same value into an array with array_keys() and save it in $keyPool with the value as key. We can also right away shuffle() the keys array, so that they are already random:
foreach($uniqueValues as $value => $notNeeded){
$keyPool[$value] = array_keys($arr, $value, TRUE);
shuffle($keyPool[$value]);
}
Now we can already loop through our original array and get a key with array_shift() from the $keyPool for each value and save it in $result:
foreach($arr as $value)
$result[array_shift($keyPool[$value])] = $value;
Since we already shuffled the array the keys already have a random order and we just use array_shift(), so that we can't use the key twice.
Code
<?php
$arr = ["a" => 1, "b" => 1, "c" => 1, "d" => 1, "e" => 1, "f" => 2,
"g" => 1, "h" => 3, "i" => 4, "j" => 5, "k" => 5];
function randomize_duplicate_array_value_keys(array $arr){
$uniqueValues = array_flip($arr);
$result = [];
$keyPool = [];
foreach($uniqueValues as $value => $notNeeded){
$keyPool[$value] = array_keys($arr, $value, TRUE);
shuffle($keyPool[$value]);
}
foreach($arr as $value)
$result[array_shift($keyPool[$value])] = $value;
return $result;
}
$result = randomize_duplicate_array_value_keys($arr);
print_r($result);
?>
(possible) output:
Array (
[b] => 1
[g] => 1
[a] => 1
[e] => 1
[d] => 1
[f] => 2
[c] => 1
[h] => 3
[i] => 4
[k] => 5
[j] => 5
)
Footnotes
I used array_flip() instead of array_unique() to get the unique values from the array, since it's slightly faster.
I also removed the if statement to check if the array has more than one elements and needs to be shuffled, since with and without the if statement the code runs pretty much with the same execution time. I just removed it to make it easier to understand and the code more readable:
if(count($keyPool[$value]) > 1)
shuffle($keyPool[$value]);
You can also make some optimization changes if you want:
Preemptively return, if you get an empty array, e.g.
function randomize_duplicate_array_value_keys(array $arr){
if(empty($arr))
return [];
$uniqueValues = array_flip($arr);
$result = [];
//***
}
Preemptively return the array, if it doesn't have duplicate values:
function randomize_duplicate_array_value_keys(array $arr){
if(empty($arr))
return [];
elseif(empty(array_filter(array_count_values($arr), function($v){return $v > 1;})))
return [];
$uniqueValues = array_flip($arr);
$result = [];
//***
}
Here's another way that iterates through the sorted array while keeping track of the previous value. If the previous value is different than the current one, then the previous value is added to a new array while the current value becomes the previous value. If the current value is the same as the previous value, then depending on the outcome of rand(0,1) either the previous value is added to the new list as before, or the current value is added to the new list first:
<?php
$l = ['a' => 1,'b' => 2, 'c' => 2,
'd' => 4,'e' => 5,'f' => 5];
asort($l);
$prevK = key($l);
$prevV = array_shift($l); //initialize prev to 1st element
$shuffled = [];
foreach($l as $k => $v) {
if($v != $prevV || rand(0,1)) {
$shuffled[$prevK] = $prevV;
$prevK = $k;
$prevV = $v;
}
else {
$shuffled[$k] = $v;
}
}
$shuffled[$prevK] = $prevV;
print_r($shuffled);
This question already has answers here:
Get min and max value in PHP Array
(9 answers)
Closed 2 years ago.
The Problem
I have a multidimensional array similar to the one below. What I'm trying to achieve is a way to find and retrieve from the array the one with the highest "Total" value, now I know there's a function called max but that doesn't work with a multidimensional array like this.
What I've thought about doing is creating a foreach loop and building a new array with only the totals, then using max to find the max value, which would work, the only issue would then be retrieving the rest of the data which relates to that max value. I'm not sure that's the most efficient way either.
Any ideas?
Array
(
[0] => Array
(
[Key1] => Key1
[Total] => 13
)
[1] => Array
(
[Key2] => Key2
[Total] => 117
)
[2] => Array
(
[Key3] => Key3
[Total] => 39
)
)
Since PHP 5.5 you can use array_column to get an array of values for specific key, and max it.
max(array_column($array, 'Total'))
Just do a simple loop and compare values or use array_reduce. # is an error suppressor; it hides the fact that $a['total'] is not declared before it is accessed on the first iteration. Demo
$data = array_reduce($data, function ($a, $b) {
return #$a['Total'] > $b['Total'] ? $a : $b ;
});
print_r($data);
// Array( [Key2] => Key2 [Total] => 117 )
It could also be written with arrow function syntax which has been avaiable since PHP7.4. Demo
var_export(
array_reduce(
$data,
fn($result, $row) =>
$result['Total'] > $row['Total']
? $result
: $row,
['Key1' => null, 'Total' => PHP_INT_MIN]
)
);
// array ('Key2' => 'Key2', 'Total' => 117,)
It's so basic algorithm.
$max = -9999999; //will hold max val
$found_item = null; //will hold item with max val;
foreach($arr as $k=>$v)
{
if($v['Total']>$max)
{
$max = $v['Total'];
$found_item = $v;
}
}
echo "max value is $max";
print_r($found_item);
Working demo
I know this question is old, but I'm providing the following answer in response to another question that pointed here after being marked as a duplicate. This is another alternative I don't see mentioned in the current answers.
I know there's a function called max but that doesn't work with a multidimensional array like this.
You can get around that with array_column which makes getting the maximum value very easy:
$arr = [['message_id' => 1,
'points' => 3],
['message_id' => 2,
'points' => 2],
['message_id' => 3,
'points' => 2]];
// max value
$max = max(array_column($arr, 'points'));
Getting the associative key is where it gets a little more tricky, considering that you might actually want multiple keys (if $max matches more than one value). You can do this with an anonymous function inside array_map, and use array_filter to remove the null values:
// keys of max value
$keys = array_filter(array_map(function ($arr) use ($max) {
return $arr['points'] == $max ? $arr['message_id'] : null;
}, $arr));
Output:
array(1) {
[0]=>
int(1)
}
If you do end up with multiples keys but are only interested in the first match found, then simply reference $keys[0].
another simple method will be
$max = array_map( function( $arr ) {
global $last;
return (int)( ( $arr["Total"] > $last ) ? $arr["Total"] : $last );
}, $array );
print_r( max( $max ) );
<?php
$myarray = array(
0 => array(
'Key1' => 'Key1',
'Total' => 13,
),
1 => array(
'Key2' => 'Key2',
'Total' => 117,
),
2 => array(
'Key2' => 'Key3',
'Total' => 39,
),
);
$out = array();
foreach ($myarray as $item) {
$out[] = $item['Total'];
}
echo max($out); //117
unset($out, $item);
Can be done using array_walk(array_walk_recursive if needed)
$arr is the array you want to search in
$largestElement = null;
array_walk($arr, function(&$item, $key) use (&$largestElement) {
if (!is_array($largestElement) || $largestElement["Total"] < $item["Total"]) {
$largestElement = $item;
}
});
You can use php usort function:
http://php.net/manual/en/function.usort.php
A pretty illustrative example is given here:
<?php
function cmp($a, $b)
{
return strcmp($a["fruit"], $b["fruit"]);
}
$fruits[0]["fruit"] = "lemons";
$fruits[1]["fruit"] = "apples";
$fruits[2]["fruit"] = "grapes";
usort($fruits, "cmp");
while (list($key, $value) = each($fruits)) {
echo "\$fruits[$key]: " . $value["fruit"] . "\n";
}
?>
So it will sort the max value to the last array index.
Output:
$fruits[0]: apples
$fruits[1]: grapes
$fruits[2]: lemons
This example is given on aforementioned link
array_reduce accepts a 3rd "initial" parameter. Use this to avoid the bad practice of using "#" error suppression :
$data = array_reduce($data, function ($a, $b) {
return $a['Total'] > $b['Total'] ? $a : $b ;
},['Total' => 0]);
print_r($data);
PHP 7.4
$data = array_reduce($data, fn(a,b) => $a['Total'] > $b['Total'] ? $a : $b, ['Total' => 0]);
Basically I need to take two arrays, merge them with unique values and sum one of columns. It makes more sense when written out below:
$a = [
['ID' => 1, 'Count' => 2],
];
$b = [
['ID' => 1, 'Count' => 4],
['ID' => 2, 'Count' => 3]
];
and I need the final product to be:
$a_plus_b = [
['ID' => 1, 'Count' => 6],
['ID' => 2, 'Count' => 3]
];
I have been playing with different variations of array_merge() and array_unique(), but I can't find an efficient way to do what I need. I know I can always do nested loops, but I was hoping for something easier. Any ideas?
This should do the trick
Note: This solution requires PHP >= 5.3. There is a PHP < 5.3 solution below.
$input = array($a, $b);
// add as many result arrays to $input as you want; e.g.,
// $input = array($a, $b, $c, $d);
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
function($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
},
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
Output
Array
(
[1] => 6
[2] => 3
)
Note the array keys above are ID values. The array values are Count values.
If you're running PHP < 5.2 you won't be able to use the inline closure with array_fill. You have to define it as a separate function.
$input = array($a, $b);
function _fill($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
}
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
'_fill',
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
From here, converting the output to your desired format is a trivial task.
Please don't over-engineer such a basic task. Iterate both array with a single loop and assign temporary keys using ID values. If encountering a respective ID key more than once, just add the new Count value to the stored value.
Code: (Demo)
$result = [];
foreach (array_merge($a, $b) as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'ID' => 1,
'Count' => 6,
),
1 =>
array (
'ID' => 2,
'Count' => 3,
),
)
Functional programming can be used as well to achieve the same result -- array_reduce() is ideal since the number of elements in the output will be equal to or less than the number of elements in the input data.
Code: (Demo)
var_export(
array_values(
array_reduce(
array_merge($a, $b),
function ($result, $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
return $result;
},
[]
)
)
);
If the ID values in the first array are guaranteed to be unique, you can avoid the array_merge() call by porting the $a array to the result array and assigning temporary keys using the ID values. (Demo)
$result = array_column($a, null, 'ID');
foreach ($b as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
I have an associative array of the form:
$input = array("one" => <class object1>,
"two" => <class object2,
... //and so on);
The keys of $input are guaranteed to be unique. I also have a method called moveToHead($key) which moves the $input[$key] element to the 0th location of this associative array. I have few questions:
Is it possible to determine the index of an associative array?
How to move the array entry for corresponding $key => $value pair to the index 0 and retaining the $key as is?
What could be the best possible way to achieve both of the above points?
I was thinking to do array_flip for 2nd point (a sub solution), but later found out that array_flip can only be done when array elements are int and string only. Any pointers?
With a function called array_keys you can determine the index of a key:
$keys = array_flip(array_keys($input));
printf("Index of '%s' is: %d\n", $key, $keys[$key]);
To insert an array at a certain position (for example at the beginning), there is the array_splice function. So you can create the array to insert, remove the value from the old place and splice it in:
$key = 'two';
$value = $input[$key];
unset($input[$key]);
array_splice($input, 0, 0, array($key => $value));
Something similar is possible with the array union operator, but only because you want to move to the top:
$key = 'two';
$value = $input[$key];
unset($input[$key]);
$result = array($key => $value) + $input;
But I think this might have more overhead than array_splice.
The "index" of an associative array is the key. In a numerically indexed array, the "key" is the index number.
EDIT: I take it back. PHP's associative arrays are ordered (like Ordered Maps in other languages).
But perhaps what you really want is an ordered array of associative arrays?
$input = array(array("one" => <class object1>),
array("two" => <class object2>)
//...
);
Here's what I came up with:
$arr = array('one' => 'Value1', 'two' => 'Value2', 'three' => 'Value3');
function moveToHead($array,$key) {
$return = $array;
$add_val = array($key => $return[$key]);
unset($return[$key]);
return $add_val + $return;
}
print_r(moveToHead($arr,'two'));
results in
Array
(
[two] => Value2
[one] => Value1
[three] => Value3
)
http://codepad.org/Jcb6ebxZ
You can use internal array pointer functions to do what you want:
$input = array(
"one" => "element one",
"two" => "element two",
"three" => "element three",
);
reset($input); // Move to first element.
echo key($input); // "one"
You can unset the element out and put it in the front:
$input = array(
"one" => "element one",
"two" => "element two",
"three" => "element three",
);
$key = "two";
$element = $input[$key];
unset($input[$key]);
// Complicated associative array unshift:
$input = array_reverse($input);
$input[$key] = $element;
$input = array_reverse($input);
print_r($input);