Percent limit with remainder distribution on PHP - php

There is a list of percentages. The sum of the numbers is 100.
$percent = [20.88, 14.93, 14.14, 13.29, 5.06, 4.43, 4.24, 4.22, 2.57, 2.51, 2.38, 2.18, 1.94, 1.80, 1.34, 1.21, 0.81, 0.63, 0.50, 0.48, 0.30, 0.16];
I need to limit the maximum number to a specific value, and distribute the remainder over the rest of the numbers. If possible, keeping the proportions of the remaining percentages.
Tried the following solution:
$limit = 8;
// Calculating the remainder of numbers exceeding $limit.
$rest = array_reduce($percent, function ($a, $b) use ($limit) {
return $limit < $b ? $a += ($b - $limit) : $a;
});
// Number of numbers not exceeding $limit.
$small = array_reduce($percent, function ($a, $b) use ($limit) {
return $limit > $b ? ++$a : $a;
});
// Percent calc with limit up to $limit and addition of the remainder ($rest / $small).
array_walk($percent, function(&$value) use ($limit, $rest, $small) {
$value = $limit < $value ? min($value, $limit) : $value + ($rest / $small);
});
print_r($percent);
print(array_sum($percent)) . "\n";
This code works correctly:
Array
(
[0] => 8
[1] => 8
[2] => 8
[3] => 8
[4] => 6.7955555555556
[5] => 6.1655555555556
[6] => 5.9755555555556
[7] => 5.9555555555556
[8] => 4.3055555555556
[9] => 4.2455555555556
[10] => 4.1155555555556
[11] => 3.9155555555556
[12] => 3.6755555555556
[13] => 3.5355555555556
[14] => 3.0755555555556
[15] => 2.9455555555556
[16] => 2.5455555555556
[17] => 2.3655555555556
[18] => 2.2355555555556
[19] => 2.2155555555556
[20] => 2.0355555555556
[21] => 1.8955555555556
)
100
But if I change the variable $limit to the value 6, the limit is exceeded due to the addition of the remainder:
Array
(
[0] => 6
[1] => 6
[2] => 6
[3] => 6
[4] => 7.24
[5] => 6.61
[6] => 6.42
[7] => 6.4
[8] => 4.75
[9] => 4.69
[10] => 4.56
[11] => 4.36
[12] => 4.12
[13] => 3.98
[14] => 3.52
[15] => 3.39
[16] => 2.99
[17] => 2.81
[18] => 2.68
[19] => 2.66
[20] => 2.48
[21] => 2.34
)
100
I can't find an algorithm.

You can try an iterative approch.
limit the maximum value
while the sum is not 100:
compute the missing value for each item under the limit
add this value to item under the limit, without exceeding this limit
Code: (demo)
$percent = [20.88, 14.93, 14.14, 13.29, 5.06, 4.43, 4.24, 4.22, 2.57, 2.51, 2.38, 2.18, 1.94, 1.80, 1.34, 1.21, 0.81, 0.63, 0.50, 0.48, 0.30, 0.16];
$limit = 6;
// check if a solution is possible
if ($limit * count($percent) < 100) die("Not possible");
// 1. limit the maximum value
$limited = array_map(fn($value) => min($value, $limit), $percent);
// 2. while the sum is not 100
while (($remain = 100 - array_sum($limited)) > 0)
{
// get the "non-full" values
$filtered = array_filter($limited, fn($v) => $v < $limit);
// get the value to add for each items
$evenly = $remain / count($filtered);
// add the value (maximum as possible)
$limited = array_map(fn($value) => min($value + $evenly, $limit), $limited);
}
print_r($limited);
print_r(array_sum($limited));
Output:
Array
(
[0] => 6
[1] => 6
[2] => 6
[3] => 6
[4] => 6
[5] => 6
[6] => 6
[7] => 6
[8] => 4.9407142857143
[9] => 4.8807142857143
[10] => 4.7507142857143
[11] => 4.5507142857143
[12] => 4.3107142857143
[13] => 4.1707142857143
[14] => 3.7107142857143
[15] => 3.5807142857143
[16] => 3.1807142857143
[17] => 3.0007142857143
[18] => 2.8707142857143
[19] => 2.8507142857143
[20] => 2.6707142857143
[21] => 2.5307142857143
)
100
See this demo for PHP version prior to 7.4.

Related

How to create a looping random number generator

I need to create a looping random number generator so each loops pumps out a different set of random numbers.
e.g result would be:
9463216549
6541335466
6749746326
6546879994
Code I have so far is:
<?php
$limit1 = 10;
$counter1 = 1;
$limit2 = 4;
$counter2 = 1;
while ($counter2 <= $limit2) {
while ($counter1 <= $limit1) {
$rayndom = mt_rand(0,6);
$counter1++;
}
$counter2++;
}
?>
If you are using php version > 7 you can use inbuilt function random_int():
Generates cryptographically secure pseudo-random integers
Usage of random_int():
random_int(0, 1000); // 0 is min value and 1000 is max
random_int() is always safe alternative to rand() and mt_rand()
If you are using PHP version < 7.0 then you can take a look at userland implementation of random_int i.e. random_compat.
Here is the code hope this helps
<?php
$waves = array(
array(),
array(),
array(),
array()
);
foreach($waves as $wave) {
for($counter = 1; $counter <= 10; $counter++) {
$num = mt_rand(0,6);
array_push($wave, $num);
}
print_r($wave);
}
?>
Your array will look like this
Array
(
[0] => 0
[1] => 2
[2] => 1
[3] => 0
[4] => 2
[5] => 1
[6] => 2
[7] => 6
[8] => 5
[9] => 0
)
Array
(
[0] => 4
[1] => 2
[2] => 2
[3] => 5
[4] => 5
[5] => 5
[6] => 6
[7] => 0
[8] => 4
[9] => 2
)
Array
(
[0] => 5
[1] => 4
[2] => 2
[3] => 5
[4] => 4
[5] => 3
[6] => 0
[7] => 3
[8] => 5
[9] => 2
)
Array
(
[0] => 2
[1] => 1
[2] => 4
[3] => 5
[4] => 0
[5] => 2
[6] => 4
[7] => 4
[8] => 4
[9] => 6
)
each array will be one wave

How do i check for unique elements in an array and total an amount assoicated with that element

I'm querying a database to get this array. The output is an account number followed by the amount for that account number. As you can see there are duplicate instances of the account number. This array will always be the same, ie account number and then amount. How do I get a total of a single account number? For example the total amount for 1B2 is $10119.59.
Array
(
[0] => 1B2
[1] => 1970.40
[2] => 1B2
[3] => 1493.60
[4] => 1B2
[5] => 1400.25
[6] => J014 1
[7] => 1423.20
[8] => J014 1
[9] => 2179.20
[10] => J014 1
[11] => 1432.00
[12] => J014 1
[13] => 711.60
[14] => 1B2
[15] => 298.72
[16] => 1B2
[17] => 1568.80
[18] => 1B2
[19] => 1822.62
[20] => 1B2
[21] => 1493.60
[22] => J014 1
[23] => 1400.25
[24] => 1B2
[25] => 711.60
[26] => J014 1
[27] => 1194.88
[28] => J014 1
[29] => 1493.60
)
Here is one way to do it using a bunch of array functions.
$your_account = '1B2';
$total = array_sum(array_column(array_filter(
array_chunk($array, 2), function($v, $k) use ($your_account) {
return $v[0] == $your_account;
}, ARRAY_FILTER_USE_BOTH), 1));
Working from inside to outside:
array_chunk creates a two dimensional array of account entries
array_filter filters the array to only entries for your account
array_column gets only the amount column
array_sum totals the
amounts
You can use the following code:
$data = [
0 => '1B2',
1 => '1970.40',
2 => '1B2',
3 => '1493.60',
4 => '1B2',
5 => '1400.25',
6 => 'J014 1',
7 => '1423.20',
8 => 'J014 1',
9 => '2179.20',
10 => 'J014 1',
11 => '1432.00',
12 => 'J014 1',
13 => '711.60',
14 => '1B2',
15 => '298.72',
16 => '1B2',
17 => '1568.80',
18 => '1B2',
19 => '1822.62',
20 => '1B2',
21 => '1493.60',
22 => 'J014 1',
23 => '1400.25',
24 => '1B2',
25 => '711.60',
26 => 'J014 1',
27 => '1194.88',
28 => 'J014 1',
29 => '1493.60',
];
$temp = array();
$i = 0;
$account_key = '';
foreach($data as $key => $value) {
if ($i % 2 == 0) { //This is the account number
$account_key = $value;
} else { //This is the amount
$temp[$account_key][] = $value;
}
$i++;
}
foreach($temp as $key => $value) { //For each account number sum the amount
echo $key.': '.array_sum($value).'<br />';
}
Result:
1B2: 10759.59
J014 1: 9834.73

How to find minimums of array ( local minimums graphic)

i need to find minimums of graphic.
Code :
$mindots = array();
for ($i = 0; $i<=sizeof($chart); $i++ ) {
if ( $chart[$i] >=0 ) {
$mindots[1] = $i;
if ($chart[$i+1] >= $chart[$i] && $chart[$i+2] >= $chart[$i] ) {
break;
}
}
}
for ($i = $mindots[1]+2; $i<=sizeof($chart); $i++ ) {
if ( $chart[$i] >=0 ) {
$mindots[2] = $i;
if ($chart[$i+1] >= $chart[$i] && $chart[$i+2] >= $chart[$i]) {
break;
}
}
}
for ($i = $mindots[2]+2; $i<=sizeof($chart); $i++ ) {
if ( $chart[$i] >=0 ) {
$mindots[3] = $i;
if ($chart[$i+1] >= $chart[$i] && $chart[$i+2] >= $chart[$i] ) {
break;
}
}
}
Here you can see graphics :
as result i have :
array(3) { [1]=> int(0) [2]=> int(6) [3]=> int(8) }
but i need numbers like ~ 6 22 36 .
My graph size ( x coord max ) in range 35-45 numbs.
My values :
0.0;2.0
1.0;4.0
2.0;16.0
3.0;18.0
4.0;7.0
5.0;4.0
6.0;2.0
7.0;2.0
8.0;5.0
9.0;7.0
10.0;10.0
11.0;10.0
12.0;12.0
13.0;7.0
14.0;5.0
15.0;9.0
16.0;10.0
17.0;11.0
18.0;12.0
19.0;6.0
20.0;2.0
21.0;0.0
22.0;2.0
23.0;6.0
24.0;11.0
25.0;11.0
26.0;12.0
27.0;11.0
28.0;6.0
29.0;7.0
30.0;11.0
31.0;10.0
32.0;11.0
33.0;10.0
34.0;2.0
35.0;0.0
36.0;0.0
37.0;2.0
38.0;4.0
39.0;14.0
40.0;15.0
41.0;7.0
42.0;4.0
Or in array form ( print )
Array ( [0] => 2 [1] => 4 [2] => 16 [3] => 18 [4] => 7 [5] => 4 [6] => 2 [7] => 2 [8] => 5 [9] => 7 [10] => 10 [11] => 10 [12] => 12 [13] => 7 [14] => 5 [15] => 9 [16] => 10 [17] => 11 [18] => 12 [19] => 6 [20] => 2 [21] => 0 [22] => 2 [23] => 6 [24] => 11 [25] => 11 [26] => 12 [27] => 11 [28] => 6 [29] => 7 [30] => 11 [31] => 10 [32] => 11 [33] => 10 [34] => 2 [35] => 0 [36] => 0 [37] => 2 [38] => 4 [39] => 14 [40] => 15 [41] => 7 [42] => 4 )
If someone dont understand graph , i made another (with another values )
How can i do it?
If I'm understand your question right, then next code might help you:
$localMinimums = array ();
for ($i = 1; $i<count($chart)-1; $i++) {
if ($chart[$i-1] > $chart[$i] && $chart[$i+1] >= $chart[$i]) {
$localMinimums[$i] = $chart[$i];
}
}
asort($localMinimums);
$result = array_slice($localMinimums, 0, 3); //I think that you need only 3 minimum elements
Or maybe you need slice by thresold?
Sort your array:
// Sorts by value..
asort($chart);
then just get min:
// Smallest
$smallest = $chart[0];

Replace value in multi dimension array

I have array format like:
Array
(
[Australia] => Array
(
[0] => [1990,0.01],
[1] => [1991,0.02],
[2] => [1992,0.02],
[3] => [1993,0.02],
[4] => [1994,0.02],
[5] => [1995,0.02],
[6] => [1996,0.02],
[7] => [1997,0.02],
[8] => [1998,0.02],
[9] => [1999,0.02],
[10] => [2000,0.02],
[11] => [2001,0.02],
[12] => [2002,0.02],
[13] => [2003,0.02],
[14] => [2004,0.02],
[15] => [2005,0.02],
[16] => [2006,0.02],
[17] => [2007,0.02],
[18] => [2008,0.02],
[19] => [2009,empty],
[20] => [2010,empty],
[21] => [2011,empty],
[22] => [2012,empty],
[23] => [2013,empty],
[24] => [2014,empty],
[25] => [2015,empty]
)
[Pakistan] => Array
(
[0] => [1990,0.00],
[1] => [1991,0.00],
[2] => [1992,0.00],
[3] => [1993,0.00],
[4] => [1994,0.00],
[5] => [1995,0.00],
[6] => [1996,0.00],
[7] => [1997,0.00],
[8] => [1998,0.00],
[9] => [1999,0.00],
[10] => [2000,0.00],
[11] => [2001,0.00],
[12] => [2002,0.00],
[13] => [2003,0.00],
[14] => [2004,0.01],
[15] => [2005,0.01],
[16] => [2006,0.00],
[17] => [2007,0.00],
[18] => [2008,0.00],
[19] => [2009,empty],
[20] => [2010,empty],
[21] => [2011,empty],
[22] => [2012,empty],
[23] => [2013,empty],
[24] => [2014,empty],
[25] => [2015,empty]
)
)
and i want to replace 'empty' with 0 without change the array structure and elements position. I stuck how to do..
You can use array_walk_recursive function:
function replace_empty(&$item, $key) {
$item = str_replace('empty', '0', $item);
}
array_walk_recursive($your_array, 'replace_empty');
You could use the array_walk_recursive function, with a callback function that would replace empty by 0.
For example, considering your array is declared this way :
$myArray[0] = array(23, empty, 43, 12);
$myArray[1] = array(empty, empty, 53, 19);
Note : I supposed you made a typo, and your arrays are not containing only a string, but several sub-elements.
You could use this kind of code :
array_walk_recursive($myArray, 'replacer');
var_dump($myArray);
With the following callback functon :
function replacer(& $item, $key) {
if ($item === empty) {
$item = 0;
}
}
Note that :
the first parameter is passed by reference !
which means modifying it will modify the corresponding value in your array
I'm using the === operator for the comparison
And you'd get the following output :
array(
0 =>
array
0 => int 23
1 => int 0
2 => int 43
3 => int 12
1 =>
array
0 => int 0
1 => int 0
2 => int 53
3 => int 19)
I would foreach in both indices (not tested):
foreach($array as $country){
foreach($country as &$field){
if($field[1] == 'empty'){
$field[1] = 0;
}
}
}
(I assume empty is a string)
EDIT:
If this [1990,0.00] is not an array but a string, you could use str_replace instead
foreach($array as $country){
foreach($country as &$field){
$field = str_replace('empty', '0.00', $field);
}
}
}

Php, printing a 3d array into a table

Basically i need to sort some data into the top 5 best sales.
I've managed to group the 3 arrays i've used using.
$c = array_combine($PriceArray,$namesArray,$ProductArray);
krsort($c);
Price array being the total. (Bit of a silly name, i just realised)
namesarray is the array of names
and product array is the list of product codes
I need to print it in a table like
"|£3.45|Jelly Beans | 120|"
so they have their own column, but at the moment it's printing it like
| 3.45| array | array|
and i use
echo '<td>'.$ProductArray[$i].'</td>'.'<td>'.$year.'</td>'.'<td>'.array_keys($c,$c[$i]).'<td>'. $PriceArray[$i].'</td>';
to print it.
Thanks in advance
Array
(
[0] => 77358.47
[1] => 111004.98
[2] => 227194.9
[3] => 84645.75
[4] => 29693.58
[5] => 198867.2
[6] => 110779.5
[7] => 210530.62
[8] => 102916.79
[9] => 186630.75
[10] => 140143.24
[11] => 48984.48
[12] => 74759.34
[13] => 204793.14
[14] => 82192.5
[15] => 167402.7
[16] => 58232.72
[17] => 216302.32
[18] => 26353.92
[19] => 149993.1
)
Array
(
[0] => Jelly beans
[1] => Banana milkshake powder
[2] => Edam Cheese
[3] => Hairnet
[4] => Aubergine jam
[5] => White bread
[6] => Brown bread
[7] => Purple bread
[8] => Plain flour
[9] => Striped flour
[10] => Soft tissues
[11] => Anti personnel mines
[12] => Chicken fillets
[13] => Beef cubes
[14] => Marshmallows
[15] => Fizzy carrot juice
[16] => Low fat lard
[17] => Suet dumpling mix
[18] => Gravy powder
[19] => Cherry pie filling
)
Array
(
[0] => 121
[1] => 122
[2] => 123
[3] => 124
[4] => 125
[5] => 126
[6] => 127
[7] => 128
[8] => 129
[9] => 130
[10] => 131
[11] => 132
[12] => 133
[13] => 134
[14] => 135
[15] => 136
[16] => 137
[17] => 138
[18] => 139
[19] => 140
)
Product Code Year Name Sales Total
Zip function blatantly stolen from: zip function from SO
function zip() {
$args = func_get_args();
$zipped = array();
$n = count($args);
for ($i=0; $i<$n; ++$i) {
reset($args[$i]);
}
while ($n) {
$tmp = array();
for ($i=0; $i<$n; ++$i) {
if (key($args[$i]) === null) {
break 2;
}
$tmp[] = current($args[$i]);
next($args[$i]);
}
$zipped[] = $tmp;
}
return $zipped;
}
function cmp($a, $b)
{
if($a[0] == $b[0]){
return 0;
}
return ($a[0] < $b[0]) ? -1 : 1;
}
$PriceArray = array( 4.56, 1.23, 7.89 );
$namesArray = array( 'ab', 'cd', 'ef' );
$ProductArray = array( '11', '22', '33' );
$c = zip($PriceArray, $namesArray, $ProductArray);
usort($c, 'cmp');
foreach($c as $prices)
{
//$prices[0] == price
//$prices[1] == name
//$prices[2] == code
echo "{$prices[0]}|{$prices[1]}|{$prices[2]}\n";
prints:
1.23|cd|22
4.56|ab|11
7.89|ef|33
I am using the zip-function that is available natively in Python to combine N-arrays and "zip" them together.
So take index 0 of all given arrays, and make that a new array entry. Do that for all indices available.
The cmp function takes two variables, in this case two arrays, where index-0 = price, 1 = name and 2 = code. You obviously want to sort by ascending by price thus we are comparing the price index. This results in a new/sorted array based on price.
You can also substitude the usort($c, 'cmp'); call with the following:
usort($c, function($a, $b){
if($a[0] == $b[0]){
return 0;
}
return ($a[0] < $b[0]) ? -1 : 1;
});
However, this is only available in PHP version >= 5.3
Just put the keys and values into new arrays.
$NewNamesArray = array_values($c); $NewPriceArray = array_keys($c)

Categories