I wonder if there is a faster way to sum i.e. the weight of each item by qty.
$items = [
[
'qty' => 1,
'weight' => 1,
],
[
'qty' => 2,
'weight' => 1,
],
[
'qty' => 3,
'weight' => 1,
],
];
$totalWeight = 0.0;
foreach ($items as $item) {
$totalWeight += $item['weight'] * $item['qty'];
}
echo $totalWeight . PHP_EOL;
If i would not need the qty offset, i just could use
array_sum(array_column($items, 'weight'))
But this wont work ofc in this example.
Anybody has an idea if and how this could be done faster?
Thanks
/cottton
EDIT
Test script:
$items = [];
for ($i = 0; $i < 1000; $i++) {
$items[] = [
'foo' => 1,
'qty' => $i,
'bar' => 2,
'weight' => $i,
'baz' => 3,
];
}
$totalWeight = 0.0;
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$totalWeight = 0.0;
foreach ($items as $item) {
$totalWeight += $item['weight'] * $item['qty'];
}
}
$elapsed = sprintf('%f', microtime(true) - $start);
echo "Elapsed: {$elapsed}\r\n";
echo "Total weight: {$totalWeight}\r\n";
// Elapsed: 0.744311
// Total weight: 332833500
Use https://www.w3schools.com/php/func_array_reduce.asp
<?php
$items = [
[
'qty' => 1,
'weight' => 1,
],
[
'qty' => 2,
'weight' => 1,
],
[
'qty' => 3,
'weight' => 1,
],
];
$totalWeight = array_reduce($items,
function($acc, $e) { return $acc + ($e['weight'] * $e['qty']); });
echo $totalWeight . PHP_EOL;
?>
You can map array_product over the inner arrays and array_sum the resulting array of products.
$totalWeight = array_sum(array_map('array_product', $items));
(This may not work if the example you showed was simplified for the the question here and the inner arrays include other fields that aren't shown.)
Here is a method that loops the unique weights and finds the sum of qty of each weights and multiply it with the weight.
In your case it makes one loop.
$weights = array_column($items, 'weight');
$result =0;
Foreach(array_unique($weights) as $weight){
// Finds the items with the weight 1 for example
$weightitems = preg_grep("/". $weight . "/", $weights);
// Sum quantity of items with the weight of 1 and multiply with weight (1).
$result += array_sum(array_column(array_intersect_key($items, $weightitems), "qty")) * $weight;
}
Echo $result;
Notice I added a "5" just to see that it worked correct in my example code.
https://3v4l.org/V293P
A slightly more optimized code, but still not faster than a simple loop as far as I can see.
I can make it 4-ish times slower depending on the input but I can't get it closer than that.
$weights =array_unique(array_column($items, 'weight'));
$result =[];
Foreach($weights as $weight){
$weightitems = array_filter($items, function ($var) use ($weight) {
return ($var['weight'] == $weight);
});
$result[] = array_sum(array_column($weightitems, "qty")) * $weight;
}
Echo array_sum($result) ."\n";
https://3v4l.org/aB7d9
How accurate does it have to be?
This is faster than looping but is off by 1 on 3000 items and a total of 17992.
Is that within error margin, then this is faster.
I calculate the average of the sumproduct of the two columns using array_sum, array_column and count.
$count = count($items);
$weights = array_sum(array_column($items, 'weight'));
$qty = array_sum(array_column($items, 'qty'));
Echo ($qty * $weights)/$count ."\n";
// 17991 (should be 17992)
https://3v4l.org/c3CIg
I think I have tried all possible solutions now.
Alright so Im trying to compare what array has bigger value but I want this code shorter, not running two foreach loops.
$one = array("test", "100");
$two = array("something", "200");
$distance1;
$distance2;
foreach($one as $val => $key) {
$distance1 = $val;
}
foreach($two as $val => $key) {
$distance2 = $val;
}
if($distance1 > $distance2)
I'm not entirely sure what you want to do, and it's been a while since I last wrote PHP, but I love this question and I would like to suggest 4 possible approaches:
1: use the arrays that you supplied (I believe this may be what you mean)
$one = array("test", "100");
$two = array("something", "200");
$distance1 = $one[1];
$distance2 = $two[1];
if($distance1 > $distance2)
2: associative arrays, and numbers for the values
$one = array(
"name" = > "test",
"distance" => 100
);
$two = array(
"name" => "something",
"distance" => 200
);
if ($one["distance"] > $two["distance"])
3: Using a foreach loop with $key and $val
$distances = array(
"test" => 100,
"something" => 200
);
$highestDistance = 0;
$highest = null;
foreach ($distances as $key => $val) {
if ($val >= highestDistance) {
$highestDistance = $val;
$highest = $key;
}
}
if ($highest === 'test')
4: objects and sorting:
$distances = array(
(object) array(
"name" => "test",
"distance" => 100
),
(object) array(
"name" => "something",
"distance" => 200
)
);
usort($distances, function($a, $b) { return $a - $b; });
if ($distances[0]->name === 'test')
Note that the conditions of the final if statements evaluate to false, as I believe is your intention (because 100 is not greater than 200).
You Can combine the 2 arrays
<?php
$one = array("test", "100");
$two = array("something", "200");
$result = array_merge($one , $two);
foreach($result as $val => $key) {
echo $val;
}
?>
I want to get generate histogram data for the following array. array_count_values() would be great to use, except it just counts values for an exact value match. How can I elegantly do the same, but bundle values by a given step or interval?
$dataArray = array(385,515,975,1136,2394,2436,4051,4399,4484,4768,4768,4849,4856,4954,5020,5020,5020,5020,5020,5020,5020,5020,5020,5052,5163,5200,5271,5421,5421,5442,5746,5765,5903,5992,5992,6046,6122,6205,6208,6239,6310,6360,6416,6512,6536,6543,6581,6609,6696,6699,6752,6796,6806,6855,6859,6886,6906,6911,6923,6953,7016,7072,7086,7089,7110,7232,7278,7293,7304,7309,7348,7367,7378,7380,7419,7453,7454,7492,7506,7549,7563,7721,7723,7731,7745,7750,7751,7783,7791,7813,7813,7814,7818,7833,7863,7875,7886,7887,7902,7907,7935,7942,7942,7948,7973,7995,8002,8013,8013,8015,8024,8025,8030,8038,8041,8050,8056,8060,8064,8071,8081,8082,8085,8093,8124,8139,8142,8167,8179,8204,8214,8223,8225,8247,8248,8253,8258,8264,8265,8265,8269,8277,8278,8289,8300,8312,8314,8323,8328,8334,8363,8369,8390,8397,8399,8399,8401,8436,8442,8456,8457,8471,8474,8483,8503,8511,8516,8533,8560,8571,8575,8583,8592,8593,8626,8635,8635,8644,8659,8685,8695,8695,8702,8714,8715,8717,8729,8732,8740,8743,8750,8756,8772,8772,8778,8797,8828,8840,8840,8843,8856,8865,8874,8876,8878,8885,8887,8893,8896,8905,8910,8955,8970,8971,8991,8995,9014,9016,9042,9043,9063,9069,9104,9106,9107,9116,9131,9157,9227,9359,9471);
// if array_count_values accepted a step value I could do this:
print_r(array_count_values($dataArray,1000));
// expected result:
// array(1000 => 3, 2000 => 1, ... 10000 => 15);
// ^0-1000 ^[385,515,975]
What do you recommend?
In the event I have to loop through all values manually, is there an elegant way to round all values to a given interval?
$step = 1000;
$result = array_count_values(
array_map(
function ($value) use ($step) {
return (int) ceil($value / $step) * $step;
},
$dataArray
)
);
var_dump($result);
The rounding solution seems pretty straight forward:
$step_size = 10;
$data = array(10, 20, 24, 30, 35, 50);
foreach ($data as $index => $value) {
$data[$index] = round($value / $step_size) * $step_size;
}
// array(10, 20, 20, 30, 40, 50);
You can build the output directly to avoid mapping the entire data array just to make use of array_count_values(); below is a more generic implementation that allows the mapping to be done outside of the function itself:
function array_count_values_callback(array $data, callable $fn)
{
$freq = [];
foreach ($data as $item) {
$key = $fn($item);
$freq[$key] = isset($freq[$key]) ? $freq[$key] + 1 : 1;
}
return $freq;
}
print_r(array_count_values_callback($dataArray, function($item) {
return ceil($item / 1000) * 1000;
}));
Here is a simple solution where you loop through your $dataArray,
$step_size = 1000;
$histogramArray = array();
foreach ($dataArray as $v) {
$k = (int)ceil($v / $step_size) * $step_size;
if (!array_key_exists($k, $histogramArray)) $histogramArray[$k] = 0;
$histogramArray[$k]++;
}
And the output would be,
Array
(
[1000] => 3
[2000] => 1
[3000] => 2
[5000] => 8
[6000] => 21
[7000] => 25
[8000] => 46
[9000] => 110
[10000] => 15
)
So i've got an array that looks something like of:
Array ( [Safari] => 13 [Firefox] => 5 )
How do i a make a new array that looks like :
Array ( [Safari] => 72.2% [Firefox] => 27.7% )
using a neat php function?
thanks in advance.
You can use array_sum() to get the total, then iterate over the values returning the new value.
$total = array_sum($share);
foreach($share as &$hits) {
$hits = round($hits / $total * 100, 1) . '%';
}
CodePad.
If you have >= PHP 5.3 it can be a tad more elegant with an anonymous function.
$total = array_sum($share);
$share = array_map(function($hits) use ($total) {
return round($hits / $total * 100, 1) . '%';
}, $share);
CodePad.
$array=array ( 'Safari' => 13 ,'Firefox' => 5 );
$tot=array_sum($array);
foreach($array as $key=>$value){
$result[$key]=number_format(($value/$total)*100,1).'%';
}
print_r($result); //Array ( [Safari] => 72.2% [Firefox] => 27.7% )
Try this:
$array = Array ( 'Safari' => 13, 'Firefox' => 5 );
$total = array_sum($array); # total sum of all elements
$onePercent = $total / 100; # we want to know what value represents 1 percent
array_walk($array, create_function('&$v', '$v /= '.$onePercent.'; $v .= " %";')); # we walk through the array changing numbers to percents
var_export($array);
If you want to have your result in second array leaving $array not touched, you can use array_map instead of array_walk
You also might want to use sprintf to set precision of float values that represent percent, because my code would output:
array (
'Safari' => '72.2222222222 %',
'Firefox' => '27.7777777778 %',
)
It's not hard to figure out how to do it?
For PHP 5.3, you can do it like this:
$browsers = array('Safari' => 13, 'Firefox' => 5);
$browsers_proportioned = proportionalize($browsers);
function proportionalize ($arr) {
$total = array_sum($arr);
$names = array_map(
function($number) use ($total) { return number_format($number / $total * 100, 1) .'%'; },
$arr
);
return $names;
}
I personnally like when numbers add up to exactly 100 (not almost 100 like 99.99). So with the solution below, the last item uses the rest instead of being calculated like other items:
public static function getPercentagesByKeyValue(array $arr)
{
$ret = [];
$nbItems = count($arr);
$i = 1;
$sum = array_sum($arr);
foreach ($arr as $key => $number) {
$percent = round($number / $sum * 100, 2);
if ($i === $nbItems) {
$percent = 100 - (array_sum($ret));
}
$ret[$key] = $percent;
$i++;
}
return $ret;
}
I have an array like this:
$arr = array(1, 1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3);
I found the function array_count_values(), but it will group all of the same values and count the occurrences without respecting breaks in the consecutive sequences.
$result[1] = 5
$result[2] = 4
$result[3] = 3
How can I group each set of consecutive values and count the length of each sequence? Notice there are two sets of sequences for the numbers 1, 2, and 3.
The data that I expect to generate needs to resemble this:
[1] = 3;
[2] = 2;
[3] = 2;
[1] = 2;
[2] = 2;
[3] = 1;
It can be done simply manually:
$arr = array(1,1,1,2,2,3,3,1,1,2,2,3);
$result = array();
$prev_value = array('value' => null, 'amount' => null);
foreach ($arr as $val) {
if ($prev_value['value'] != $val) {
unset($prev_value);
$prev_value = array('value' => $val, 'amount' => 0);
$result[] =& $prev_value;
}
$prev_value['amount']++;
}
var_dump($result);
My suggestion is to extract&remove the first value from the array prior to entering the loop and use a temporary array ($carry) to track whether each new value matches the key in the carry array. If so, increment it. If not, push the completed sequence count into the result array and overwrite the carry with the new value and set the counter to 1. When the loop finishes, push the lingering carry into the result set. My snippet does not check if the input array is empty; if necessary, add that condition to your project.
Code: (Demo)
$array = [1,1,1,2,2,3,3,1,1,2,2,3];
$result = [];
$carry = [array_shift($array) => 1];
foreach ($array as $value) {
if (isset($carry[$value])) {
++$carry[$value];
} else {
$result[] = $carry;
$carry = [$value => 1];
}
}
$result[] = $carry;
print_r($result);
Output: (condensed to reduce page bloat)
[
[1 => 3],
[2 => 2],
[3 => 2],
[1 => 2],
[2 => 2],
[3 => 1],
]
If you'd rather implement a zerkms-style, modify-by-reference style technique, the following snippet provides the same result as the above snippet.
Effectively, it pushes every newly encountered value as an associative, single-element array into the indexed result array. Because the pushed subarray is declared as a variable ($carry) then assigned-by-reference (= &) to the result array, incrementation of $carry will be applied to the deeply nested value in the result array. The output array requires the additional depth in its structure so that a given value which occurs multiple times can be reliably stored.
Code: (Demo)
$result = [];
$carry = [];
foreach ($array as $value) {
if ($carry && key($carry) === $value) {
++$carry[$value];
} else {
unset($carry);
$carry = [$value => 1];
$result[] = &$carry;
}
}
unset($carry);
print_r($result);
Unsetting the reference variable $carry after the loop may not be necessary, but if there is any potential re-use of that variable within the variable's scope, it will be important to uncouple the reference with unset().
And just for fun, here is a hideous regex-infused approach that works with the sample data: Demo
What about PHP's array_count_values function?
<?php
$array = array(1, "hello", 1, "world", "hello");
print_r(array_count_values($array));
?>
output:
Array
(
[1] => 2
[hello] => 2
[world] => 1
)
function findRepetitions($times, $array) {
$values = array_unique($array);
$counts = [];
foreach($values as $value) {
$counts[] = ['value' => $value, 'count' => 0];
}
foreach ($array as $value) {
foreach ($counts as $key => $count) {
if ($count['value'] === $value) {
$counts[$key]['count']++;
}
}
}
$repetitions = [];
foreach ($counts as $count) {
if ($count['count'] === $times) {
$repetitions[] = $count['value'];
}
}
return $repetitions;
}
$current = null;
foreach($your_array as $v) {
if($v == $current) {
$result[count($result)-1]++;
} else {
$result[] = 1;
$current = $v;
}
}
var_dump($result);
Here is the way that I would do it:
function SplitIntoGroups($array)
{
$toReturnArray = array();
$currentNumber = $array[0];
$currentCount = 1;
for($i=1; $i <= count($array); $i++)
{
if($array[$i] == $currentNumber)
{
$currentCount++;
}
else
{
$toReturnArray[] = array($currentNumber, $currentCount);
$currentNumber = $array[$i];
$currentCount = 1;
}
}
return $toReturnArray;
}
$answer = SplitIntoGroups(array(1,1,1,2,2,3,3,1,1,2,2,3));
for($i=0; $i<count($answer); $i++)
{
echo '[' . $answer[$i][0] . '] = ' . $answer[$i][1] . '<br />';
}