Get the maximum value from an element in a multidimensional array? [duplicate] - php

This question already has answers here:
Get min and max value in PHP Array
(9 answers)
Closed 7 months ago.
I'm trying to select the maximum value for a particular key in a multidimensional array. I'm having trouble "getting to" the key in question...
So, the array (which is much more lengthy than what I'm posting here)
[0] => stdClass Object
(
[id] => 70
[cust] => 4
[dnum] => 1
[upper] => Array
(
[0] => 66
)
)
[1] => stdClass Object
(
[id] => 43
[cust] => 42
[dnum] => 2
[upper] => Array
(
[0] => 77
)
)
[2] => stdClass Object
(
[id] => 12
[cust] => 3
[dnum] => 0
[upper] => Array
(
[0] => 99
)
)
I'm trying to find the maximum "dnum" value across the entire array, so in this example, $max = 2. I know that the max function allows me to do this, but I'm not sure how to reference the dnum element without putting the whole thing in a foreach loop, and if I do that, then max wouldn't be the function to use, right?
So, I can't exactly do this:
$max = max($myarray[]->dnum);
Is there a way for me to do this without having to recreate the entire array?

In PHP 5.2 the only way to do this is to loop through the array.
$max = null;
foreach ($arr as $item) {
$max = $max === null ? $item->dnum : max($max, $item->dnum);
}
Note: I've seeded the result with 0 because if all the dnum values are negative then the now accepted solution will produce an incorrect result. You need to seed the max with something sensible.
Alternatively you could use array_reduce():
$max = array_reduce($arr, 'max_dnum', -9999999);
function max_denum($v, $w) {
return max($v, $w->dnum);
}
In PHP 5.3+ you can use an anonymous function:
$max = array_reduce($arr, function($v, $w) {
return max($v, $w->dnum);
}, -9999999);
You can use array_map() too:
function get_dnum($a) {
return $a->dnum;
}
$max = max(array_map('get_dnum', $arr));

$max = 0;
foreach($array as $obj)
{
if($obj->dnum > $max)
{
$max = $obj->dnum;
}
}
That function would work correctly if your highest number is not negative (negatives, empty arrays, and 0s will return the max as 0).
Because you are using an object, which can have custom properties/structures, I don't believe there are really any 'predefined' functions you can use to get it. Might as well just use a foreach loop.
You really can't get away from a foreach loop, as even internal functions use a foreach loop, it is just behind the scenes.
Another solution is
$numbers = array();
foreach($array as $obj)
{
$numbers[] = $obj->dnum;
}
$max = max($numbers);

The simplest way is probably your initial thought, which is to loop your array once, and pull out all the dnum keys into a separate array, then call max() on that:
$out = array();
foreach($myarray as $obj) {
$out[] = $obj->dnum;
}
echo max($out);
You could do it without creating a separate array, but you'll end up calling max() a lot more often. Performance/memory usage will be different between the two, you could always benchmark it:
$first = $myarray[0]; //assume zero start index
$max = $first->dnum;
foreach($myarray as $obj) {
$max = max($max,$obj->dnum);
}
echo $max;
The only other way you could go about it would be to sort your array using usort() and a custom sorting function based on the object dnum properties. This is probably going to be much slower than just looping your array however, so I don't think I'd recommend it unless you needed the array sorted as a side effect.

If you like oneliners
$max = max( array_map(function( $row ){ return $row->dnum; }, $myarray) );

For anyone still interested, try this...
$min = min(array_column($myarray, 'dnum'));
$max = max(array_column($myarray, 'dnum'));

Related

Eliminate PHP Loop

I have a post array and need to create a new array format from this to store in database with batch insert. I have achieved it with the following code. But want a better solution (if any) to achieve my array. I wanted to eliminate the inner loop but did not get any solution. Please provide any suggestion on how can I achieve this.
Code to parse array:
if ($this->input->post()) {
foreach ($this->input->post() as $key => $value) {
$i = 0;
/* need to eliminate this loop */
foreach ($value as $k => $v) {
$postData[$i][$key] = $v;
$i++;
}
}
}
Input array:
Array
(
[category_id] => Array
(
[0] => 1
[1] => 4
)
[pay_type_id] => Array
(
[0] => 2
[1] => 5
)
[frequency_id] => Array
(
[0] => 3
[1] => 6
)
)
Output array;
Array
(
[0] => Array
(
[category_id] => 1
[pay_type_id] => 2
[frequency_id] => 3
)
[1] => Array
(
[category_id] => 4
[pay_type_id] => 5
[frequency_id] => 6
)
)
If you really want to, you can do this without loops at all:
$input = $this->input->post();
$keys = array_keys($input);
$postData = [
array_combine($keys, array_column($input, 0)),
array_combine($keys, array_column($input, 1)),
];
This will give the same $postData output as your example, assuming that the input only has keys 0 and 1 in the inner arrays, as it does in your example. If the number of possible elements in the inner arrays is unknown, then you may need to introduce a loop on that, but the secondary loop can still be avoided.
I had to use array_combine() as well as array_column() as array_column() on it's own does not preserve the named keys the your top level of your array.
Other solutions using array_map() or array_walk() may also exist.
However, while it's short and concise, it isn't exactly clear for a reader to understand what it's doing, so unless you document it clearly, you'll be creating a maintenance headache for yourself in the long term.
The double-loop is a more readily understandable solution, pretty standard, and won't cause you any issues. So while I've given you a solution, I would actually recommend just using the code you've got.
Because you have two arrays, a:"records" and b:"fields" in any case, theoretically, you need to have at least two loops to populate the "records" and "fields" inside a record. And, basically, this is not a bad approach or a problem.
If you really want just one loop, because of faith reasons, you need to put a hard-coded list of field assignments in first loop that populate the records.
The only way I can see to do it without a second array is like this:
$arr = array("category_id" => array(1, 4), "pay_type_id" => array(2, 5), "frequency_id" => array(3, 6));
foreach ($arr as $key => $value) {
$postData[0][$key] = $value[0];
$postData[1][$key] = $value[1];
}
print_r($postData);
But you lose flexibility with this approach, because you have to know in advance how many indexes there will be in the inner arrays. The only way to make it generic enough to cope with changes in data is to use an inner loop similar to how you did it originally.
There's nothing much wrong with your original code, it's a pretty standard and reasonable approach to changing the array format in this scenario. It shouldn't give you any particular performance issues, even with fairly large arrays, and there's not really any neater way to approach it.
As per your ouput you need second loop also. But yes you can eliminate use of $i you can use $k instead.
You can change your code as:
if ($this->input->post()) {
foreach ($this->input->post() as $key => $value) {
foreach ($value as $k => $v) {
$postData[$k][$key] = $v;
}
}
}
DEMO

How to sum values in multi dimensional arrays as in a given format [duplicate]

I have this:
Array (
[0] => Array ( [f_count] => 1 [uid] => 105 )
[1] => Array ( [f_count] => 0 [uid] => 106 )
[2] => Array ( [f_count] => 2 [uid] => 107 )
[3] => Array ( [f_count] => 0 [uid] => 108 )
[4] => Array ( [f_count] => 1 [uid] => 109 )
[5] => Array ( [f_count] => 0 [uid] => 110 )
[6] => Array ( [f_count] => 3 [uid] => 111 )
)
What I need is: 7", which is the the sum of the f_count column.
I've been trying to figure this out for a couple hours. I thought array_sum() would work, but not with a multidimensional array. So, I've tried figuring out how to isolate the f_counts by unset() or splicing or anything else, but every solution seems to involve a foreach loop. I've messed with array_map, array_walk, and others to no avail. I haven't found a function that works well with multidimensional arrays.
I'm running PHP 5.4.
Can someone please show me how to sum that column without a foreach loop?
If it helps, the f_count values will never be higher than 100, and the uid values will always be greater than 100.
Alternatively, if there's a way to run my query differently such that the array is not multidimensional, that would obviously work as well.
$query = "SELECT f_count, uid FROM users WHERE gid=:gid";
...
$array = $stmt->fetchAll();
I'm using PDO.
In php 5.5+ you can just used array_column and array_sum like so:
$value = array_sum(array_column($arr,'f_count'));
You need to couple it with array_map() to select the f_count column first:
array_sum(array_map(function($item) {
return $item['f_count'];
}, $arr));
Nowadays you can replace the inner function with array_column():
array_sum(array_column($arr, 'f_count'));
Of course, internally, this performs a double loop; it's just that you don't see it inside the code. You could use array_reduce() to get rid of one loop:
array_reduce($arr, function(&$res, $item) {
return $res + $item['f_count'];
}, 0);
However, if speed is the only interest, foreach remains the fastest:
$sum = 0;
foreach ($arr as $item) {
$sum += $item['f_count'];
}
This is thanks to the "locality" of the variables that you're using, i.e. there are no function calls used to calculate the final sum.
for these type of situation foreach loop is the best possible option but if u have to do this for multiple times on a single page then u should put foreach loop in a function so that your code remain clean
function sum_index($arr, $col_name){
$sum = 0;
foreach ($arr as $item) {
$sum += $item[$col_name];
}
return $sum;
}
Edit
After searching alot I found that array_column function exist in php 5.5 + to get all values of single column as an array.
For users having lower version You can use following function and call it with array sum if you want sum of all values as well.
//to get all values of single column from multidimensional array, this function exist in php 5.5 or greater.
if(!function_exists("array_column"))
{
function array_column($array,$column_name)
{
return array_map(function($element) use($column_name){return $element[$column_name];}, $array);
}
}
If you want to get all values of single column then call above function like this:
$newArray = array_column($array,$column_name);
If you want sum of all values of single column then use following code:
$newArraySum = array_sum(array_column($array,$column_name));
Alternatively, if there's a way to run my query differently such that
the array is not multidimensional, that would obviously work as well.
$query = "SELECT f_count, uid FROM users WHERE gid=:gid";
...
$array = $stmt->fetchAll();
I'm using PDO.
Yes, there is another way to run your query differently. You could use the aggregate function SUM to get the sum of all the f_count directly in your query. You would combine this with $stmt->fetchColumn() to get the value of the first column (which would be the sum) of the first row (which will be the only row since we used an aggregate function).
Here is an example of how you could do this:
$query = "SELECT SUM(f_count) FROM users WHERE gid=:gid";
$stmt = $pdo->prepare($query);
$stmt->execute(array(':gid' => 'yoursearchvalue'));
$sum = $stmt->fetchColumn();
Since you are using PHP 5.4 with PDO, there could be one more way to do it (available in PHP 5.1+):
use the PDO fetchAll() function with the PDO::FETCH_COLUMN as the fetch_style, and the 0-indexed column number as the fetch_argument.
As per your example, it could be:
$query = "SELECT f_count, uid FROM users WHERE gid=:gid";
...
$array_fcount = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
Now use the array_sum function to sum the values of the array:
$fcount_sum = array_sum($array_fcount);
You could also consult the PHP documentation at http://php.net/manual/en/pdostatement.fetchall.php
Hope this helps, and the performance could be better than looping! As already mentioned in other answers, in PHP 5.5 onwards you have the array_column() function to get $array_fcount directly from another array.

Two PHP arrays, unset positions that are only present in one array

I have two arrays (in PHP):
ArrayA
(
[0] => 9
[1] => 1
[2] => 2
[3] => 7
)
ArrayB
(
[0] => 1
[1] => 1
[3] => 8
)
I want to make two new arrays, where I have only the elements declared in both of the arrays, like the following:
ArrayA
(
[0] => 9
[1] => 1
[3] => 7
)
ArrayB
(
[0] => 1
[1] => 1
[3] => 8
)
In this example ArrayA[2] doesn't exist, so ArrayB[2] has been unset.
I wrote this for loop:
for ($i = 0, $i = 99999, $i++){
if (isset($ArrayA[$i]) AND isset($ArrayB[$i]) == FALSE)
{
unset($ArrayA[$i],$ArrayB[$i]);
}
}
But it's not great because it tries every index between 0 and a very big number (99999 in this case). How can I improve my code?
The function you're looking for is array_intersect_key:
array_intersect_key() returns an array containing all the entries of array1 which have keys that are present in all the arguments.
Since you want both arrays, you'll have to run it twice, with the parameters in opposite orders, as it only keeps keys from the first array. An example:
$arrayA_filtered = array_intersect_key($arrayA, $arrayB);
$arrayB_filtered = array_intersect_key($arrayB, $arrayA);
Also, although a for loop wasn't ideal in this case, in other cases where you find yourself needing to loop through sparse array (one where not every number is set), you can use a foreach loop:
foreach($array as $key => $value) {
//Do stuff
}
One very important thing to note about PHP arrays is that they are associative. You can't simply use a for loop, as the indices are not necessarily a range of integers. Consider what would happen if you applied this algorithm twice! You'd get out of bounds errors as $arrayA[2] and $arrayB[2] no longer exist!
I would iterate through the arrays using nested foreach statements. I.e.
$outputA = array();
$outputB = array();
foreach ($arrayA as $keyA => $itemA) {
foreach ($arrayB as $keyB => $itemB) {
if ($keyA == $keyB) {
$outputA[$keyA] = $itemA;
$outputB[$keyB] = $itemB;
}
}
This should give you two arrays, $outputA and $outputB, which look just like $arrayA and $arrayB, except they only include key=>value pairs if the key was present in both original arrays.
foreach($arrayA as $k=>$a)
if (!isset($arrayB[$k]))
unset($arrayA[$k];
Take a look to php : array_diff
http://docs.php.net/manual/fr/function.array-diff.php

Sum values of multidimensional array by key without loop

I have this:
Array (
[0] => Array ( [f_count] => 1 [uid] => 105 )
[1] => Array ( [f_count] => 0 [uid] => 106 )
[2] => Array ( [f_count] => 2 [uid] => 107 )
[3] => Array ( [f_count] => 0 [uid] => 108 )
[4] => Array ( [f_count] => 1 [uid] => 109 )
[5] => Array ( [f_count] => 0 [uid] => 110 )
[6] => Array ( [f_count] => 3 [uid] => 111 )
)
What I need is: 7", which is the the sum of the f_count column.
I've been trying to figure this out for a couple hours. I thought array_sum() would work, but not with a multidimensional array. So, I've tried figuring out how to isolate the f_counts by unset() or splicing or anything else, but every solution seems to involve a foreach loop. I've messed with array_map, array_walk, and others to no avail. I haven't found a function that works well with multidimensional arrays.
I'm running PHP 5.4.
Can someone please show me how to sum that column without a foreach loop?
If it helps, the f_count values will never be higher than 100, and the uid values will always be greater than 100.
Alternatively, if there's a way to run my query differently such that the array is not multidimensional, that would obviously work as well.
$query = "SELECT f_count, uid FROM users WHERE gid=:gid";
...
$array = $stmt->fetchAll();
I'm using PDO.
In php 5.5+ you can just used array_column and array_sum like so:
$value = array_sum(array_column($arr,'f_count'));
You need to couple it with array_map() to select the f_count column first:
array_sum(array_map(function($item) {
return $item['f_count'];
}, $arr));
Nowadays you can replace the inner function with array_column():
array_sum(array_column($arr, 'f_count'));
Of course, internally, this performs a double loop; it's just that you don't see it inside the code. You could use array_reduce() to get rid of one loop:
array_reduce($arr, function(&$res, $item) {
return $res + $item['f_count'];
}, 0);
However, if speed is the only interest, foreach remains the fastest:
$sum = 0;
foreach ($arr as $item) {
$sum += $item['f_count'];
}
This is thanks to the "locality" of the variables that you're using, i.e. there are no function calls used to calculate the final sum.
for these type of situation foreach loop is the best possible option but if u have to do this for multiple times on a single page then u should put foreach loop in a function so that your code remain clean
function sum_index($arr, $col_name){
$sum = 0;
foreach ($arr as $item) {
$sum += $item[$col_name];
}
return $sum;
}
Edit
After searching alot I found that array_column function exist in php 5.5 + to get all values of single column as an array.
For users having lower version You can use following function and call it with array sum if you want sum of all values as well.
//to get all values of single column from multidimensional array, this function exist in php 5.5 or greater.
if(!function_exists("array_column"))
{
function array_column($array,$column_name)
{
return array_map(function($element) use($column_name){return $element[$column_name];}, $array);
}
}
If you want to get all values of single column then call above function like this:
$newArray = array_column($array,$column_name);
If you want sum of all values of single column then use following code:
$newArraySum = array_sum(array_column($array,$column_name));
Alternatively, if there's a way to run my query differently such that
the array is not multidimensional, that would obviously work as well.
$query = "SELECT f_count, uid FROM users WHERE gid=:gid";
...
$array = $stmt->fetchAll();
I'm using PDO.
Yes, there is another way to run your query differently. You could use the aggregate function SUM to get the sum of all the f_count directly in your query. You would combine this with $stmt->fetchColumn() to get the value of the first column (which would be the sum) of the first row (which will be the only row since we used an aggregate function).
Here is an example of how you could do this:
$query = "SELECT SUM(f_count) FROM users WHERE gid=:gid";
$stmt = $pdo->prepare($query);
$stmt->execute(array(':gid' => 'yoursearchvalue'));
$sum = $stmt->fetchColumn();
Since you are using PHP 5.4 with PDO, there could be one more way to do it (available in PHP 5.1+):
use the PDO fetchAll() function with the PDO::FETCH_COLUMN as the fetch_style, and the 0-indexed column number as the fetch_argument.
As per your example, it could be:
$query = "SELECT f_count, uid FROM users WHERE gid=:gid";
...
$array_fcount = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
Now use the array_sum function to sum the values of the array:
$fcount_sum = array_sum($array_fcount);
You could also consult the PHP documentation at http://php.net/manual/en/pdostatement.fetchall.php
Hope this helps, and the performance could be better than looping! As already mentioned in other answers, in PHP 5.5 onwards you have the array_column() function to get $array_fcount directly from another array.

How to convert a 2-dim array of strings into 1-dim array of trimmed, unique values?

I have an array whose values are all arrays of a specific format that looks like this:
Array
(
[0] => Array
(
[0] => '8227'
[1] => ' 8138'
)
[1] => Array
(
[0] => '8227'
[1] => ' 8138'
[2] => ' 7785'
)
)
and I would like to have this:
Array
(
[0] => 8227
[1] => 8138
[2] => 7785
)
How can I do this ?
$result = array();
foreach ($input as $sub) { // Loop outer array
foreach ($sub as $val) { // Loop inner arrays
$val = trim($val);
if (!in_array($val, $result)) { // Check for duplicates
$result[] = $val; // Add to result array
}
}
}
$result = array();
foreach($array as $arr){
$result = array_merge($result, $arr);
}
$result = array_unique($result);
array_merge_recursive() can be used to flatten the array. Then, array_unique() to get the unique values, with array_values() to "reindex" the resultant array.
$flat = call_user_func_array('array_merge_recursive', $subject);
$uniq = array_values(array_unique($flat));
<?php
$array = array(
0 => array(
0 => 8227,
1 => 8138
),
1 => array(
0 => 8227,
1 => 8138,
2 => 7785
)
);
$newArray = array();
array_walk_recursive($array, function($item, $key) use(&$newArray) {
if(!in_array($item, $newArray)) {
$newArray[] = $item;
}
});
print_r($newArray);
?>
I don't like the idea of iterated calls of in_array() since it can cause a bit of drag on big arrays.
Now, my methods to follow are probably not going to set any speed records (I didn't bother to benchmark), but I thought I would post a couple of unorthodox approaches in case they may inspire future readers.
Method #1:
convert the multi-dimensional array to a json string
split the string on all non-digital substrings (this also trims the values)
eliminate duplicates using array_flip()
re-index the resultant array using array_keys() (output values are integer-type)
Method #2:
convert the multi-dimensional array to a json string
extract the words (which in this case include numbers and there aren't any letters to worry about)
eliminate duplicates using array_flip()
reindex the resultant array using array_keys() (output values are integer-type)
Code: (Demo)
$array = [['8227', '8138'], [' 8227', ' 8138', ' 7785']];
echo "regex method: ";
var_export(
array_keys(
array_flip(
preg_split(
'/\D+/',
json_encode($array),
0,
PREG_SPLIT_NO_EMPTY
)
)
)
);
echo "\n\nnon-regex method: ";
var_export(
array_keys(
array_flip(
str_word_count(
json_encode($array),
1,
'0..9'
)
)
)
);
Output:
regex method: array (
0 => 8227,
1 => 8138,
2 => 7785,
)
non-regex method: array (
0 => 8227,
1 => 8138,
2 => 7785,
)
If either of these methods perform well, it will be because they don't make iterated function calls.
p.s. Okay, because I was curious, I just did a very small / unofficial speed test on my two methods and DaveRandom's method (only 1000 iterations using the OP's data - I didn't want to abuse 3v4l.org) and...
Method #1 ran about as fast as Method #2
Both Method #1 and Method #2 ran faster than DaveRandom's method.
Again, I'll state that fabricated tests for micro-optimization may be pointless and real tests should be done on your REAL data IF it is actually important. If you merely prefer the logical flow of another answer on this page, I totally respect that.

Categories