How can I improve this algorithm? - php

What I am trying to accomplish is to take a US Currency amount, and break it down into how many of each bill and coin it takes to make that amount using the least of each type.
I wrote this in a hurry, and it works, but I feel like it could be improved. Also, I had to round the remainder because I was getting a strange result once it got to something like (0.13 - (1 * 0.1) instead of 0.3 it would come out to 0.299999995
The code below does seem to work.
function moneybreak ($amount, $sizebill) {
// get units of sizecurrency
$numbills = floor($amount / $sizebill);
$remainder = $amount - ($numbills * $sizebill);
$remainder = round($remainder, 2);
$ret['bills'] = $numbills;
$ret['remain'] = $remainder;
return $ret;
}
$amount = 1999.99;
$money = array();
$tmoney = moneybreak($amount, 500);
$money['fivehundred'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 100);
$money['onehundred'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 20);
$money['twenty'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 10);
$money['ten'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 5);
$money['five'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 1);
$money['one'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 0.25);
$money['quarter'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 0.1);
$money['dime'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 0.05);
$money['nickle'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
$tmoney = moneybreak($tmoney['remain'], 0.01);
$money['penny'] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;

Your question is a perfect example of why you shouldn't use floating-point arithmetic to represent currency.
First, you'll need to avoid floating-point numbers like you owe them a lot of money (though actually it might be the opposite). To that end, we'll do our calculations with cents instead of dollars. Any amount you might have needs to be multiplied by 100.
You can then rather easily get away by listing all the money units you want to use ($100, $50, $20, ...) into an array, and sort it in descending order so the biggest ones come out first.
<?php
// money units, in cents
// (reflecting common Canadian currency)
$units = array(10000, 5000, 2000, 1000, 500, 200, 100, 25, 10, 5, 1);
function moneyBreak($amount, $units)
{
// ensure the array is sorted in descending order
rsort($units);
$itemsOfEach = array();
// loop through all the money units
foreach ($units as $unit)
{
// you'll try to use this money unit for the largest possible
// amount of money
$itemsOfEach[$unit] = intval($amount / $unit);
// and once you're done, you'll continue with the remainder
$amount %= $unit;
}
return $itemsOfEach;
}
?>
And here is an example:
$amount = 32347; // $323.47
$result = moneyBreak($amount, $units);
print_r($result);
/* prints this:
Array
(
[10000] => 3
[5000] => 0
[2000] => 1
[1000] => 0
[500] => 0
[200] => 1
[100] => 1
[25] => 1
[10] => 2
[5] => 0
[1] => 2
)
*/

A quick way to do what you want if I understood the question correctly.
$amount = 1999;
$values = array(500, 100, 20, 10, 5, 1);
$result = array();
foreach($values as $value) {
$result[$value] = floor($amount / $value);
$amount = $amount % $value;
}
Just work in the smaller sums too as you see fit.
Edit: Actually, come to think of it the floor part will ofc break when it goes below 1 so you need to add a check for when $amount < 1

Well, since you're doing the same thing over and over again, you could do something like this:
$denominations = array(
'fivehundred'=>500,
'onehundred'=>100,
'twenty'=>20,
'etc'=>'etc' //The rest of the denominations
);
$amount = 1999.99;
$money = array();
foreach($denominations as $word => $num)
{
$tmoney = moneybreak($amount, $num);
$money[$word] = ($tmoney['bills'] > 0) ? $tmoney['bills'] : 0.00;
}
Though, zneak's answer is nice. If combined with the array which includes the keys for the denominations in mine, and modifying his foreach($units as $units) to foreach($units as $key => $unit), you could get an array that has the proper key for each unit ("onehundred","fives", etc.).

Related

How to determine if data is increasing or decreasing in PHP

Let's say we have the following data in an array:
$data1 = [3,5,7,6,8,9,13,14,17,15,16,16,16,18,22,20,21,20];
$data2 = [23,18,17,17,16,15,16,14,15,10,11,7,4,5];
As with $data1 we can say the data is increasing while in $data2 it is decreasing.
Using PHP, how do you know the data is increasing or decreasing, and is there a way on how to measure
know the rate of increasing as well as decreasing i.e in terms of percentage.
Edit
From the comments I received I got an idea and here is what I have tried.
What I want to achieve;
I want to know if the trend of the data coming in is upwards or downwards.
Want also to know the rate at which the data is rising or droping. For example $data1 = [1,3,5]; is not the same as $data2 = [1, 20, 55];. You can see $data1 rate of increase is not the same as $data2.
function increaseOrDecrease($streams = []) : array
{
$streams = [3,5,7,6,8,9,13,14,17,15,16,16,16,18,22,20,21,20]; // For the increasing
//$streams = [23,18,17,17,16,15,16,14,15,10,11,7,4,5]; // For the decreasing
$first = 0;
$diff = [];
foreach ($streams as $key => $number) {
if ($key != 0) {
$diff[] = $number - $first;
}
$first = $number;
}
$avgdifference = array_sum($diff)/count($diff); //Get the average
$side = $avgdifference > 0 ? 'UP' : 'DOWN';
$avgsum = array_sum($streams)/count($streams);
$percentage = abs($avgdifference)/$avgsum * 100;
if ($side == 'UP') {
$data = [
'up' => true,
'percent' => $percentage,
];
}else {
$data = [
'up' => false,
'percent' => $percentage,
];
}
return $data;
}
I would like some help to refactor this code or the best approach to solve the issue.
There are several ways to analyze data and extract a trend. The most classical method is called
least squares. It's a way of fitting a line
through the data. The method computes the slope and the intercept of the line. The trend is just the slope.
The formulas are given here.
A PHP implementation is the following:
function linearRegression($x, $y)
{
$x_sum = array_sum($x);
$y_sum = array_sum($y);
$xy_sum = 0;
$x2_sum = 0;
$n = count($x);
for($i=0;$i<$n;$i++)
{
$xy_sum += $x[$i] * $y[$i];
$x2_sum += $x[$i] * $x[$i];
}
$beta = ($n * $xy_sum - $x_sum * $y_sum) / ($n * $x2_sum - $x_sum * $x_sum);
$alpha = $y_sum / $n - $beta * $x_sum / $n;
return ['alpha' => $alpha, 'beta' => $beta];
}
function getTrend($data)
{
$x = range(1, count($data)); // [1, 2, 3, ...]
$fit = linearRegression($x, $data);
return $fit['beta']; // slope of fitted line
}
Examples:
echo getTrend([1, 2, 3]); // 1
echo getTrend([1, 0, -1]); // -1
echo getTrend([3,5,7,6,8,9,13,14,17,15,16,16,16,18,22,20,21,20]); // 1.065
echo getTrend([23,18,17,17,16,15,16,14,15,10,11,7,4,5]); // -1.213
You are asking for a type of data structure that can represent ascending as well as descending data. PHP got SplMinHeap and SplMaxHeap for this purpose. These built in classes make life easer when dealing with ascending or descending datasets.
A quick example ...
<?php
declare(strict_types=1);
namespace Marcel;
use SplMinHeap;
$numbers = [128, 32, 64, 8, 256];
$heap = new SplMinHeap();
foreach ($numbers as $number) {
$heap->insert($number);
}
$heap->rewind();
while($heap->valid()) {
// 8, 32, 64, 128, 256
echo $heap->current() . PHP_EOL;
$heap->next();
}
The SplMinHeap class keeps the minimum automatically on the top. So just use heaps instead of arrays that have no structure. Same goes for SplMaxHeap that keeps the highest value on the top.
Finding the differences
If you want to iterate all data and finding the differences between one to the next, you just have to iterate the heap. It 's ordered anyway.
$heap->rewind();
$smallest = $heap->current();
while($heap->valid()) {
// 8, 32, 64, 128, 256
$current = $heap->current();
echo $current . PHP_EOL;
// 0 (8 - 8), 24 (32 - 8), 32 (64 - 32), 64 (128 - 64), 128 (256 - 128)
echo "difference to the value before: " . ($current - $smallest) . PHP_EOL;
$smallest = $current;
$heap->next();
}
I would do simple things like this
$data1 = [3,5,7,6,8,9,13,14,17,15,16,16,16,18,22,20,21,20];
$data2 = [23,18,17,17,16,15,16,14,15,10,11,7,4,5];
getTrend($data1) //Returns up
getTrend($data2) // Returns down
function getTrend($arr)
{
$up = 0;
$down = 0;
$prev = "";
foreach($arr as $val)
{
if($prev != "" && $val > $prev)
{
$up = $val-$prev;
}
else if($prev != "" && $val < $prev)
{
$down = $prev-$val ;
}
$prev = $val);
}
if($up > $down)
{
return "up";
}
else if($down > $up)
{
return "down";
}
else {
return "flat";
}
}

JSON Array values to always total up to 100%

I have a wheel of fortune that spins, and has probability set for winning each prize that adds up to 100%. Once the quantity of a prize is met, ie, all of that prize is gone from stock, it sets the probability to 0 so you can't win it again. My problem then, is that the other values don't add up to 100. They always have to add up to 100 otherwise the wheel doesn't work.
I've got to the point of dividing all of the values by the total, rounding to two decimals and then adding them together, but i get 99 instead of 100. I've tried a few other ways of doing it and can never get to 100.
edit: My main problem seems to be that there is a 0.01% chance of winning 2 of the prizes. Dividing them by the total drops them to 0.000234234% or something similar, so when i multiply that by 100 then round that up to 2 decimals i'm getting 0.00% - but i can't think of any other way around it...
$jsonString = file_get_contents('wp-content/themes/supertheme/wheel_data.json');
$data = json_decode($jsonString, true);
echo '<pre>';
//print_r($data['segmentValuesArray'][0]['probability']);
echo '</pre>';
if ($prize1 <= 5){
$prize1before = $data['segmentValuesArray'][0]['probability'];
$data['segmentValuesArray'][0]['probability'] = 0;
}
if ($prize2 >= 5){
$data['segmentValuesArray'][1]['probability'] = 0;
}
if ($prize3 >= 300){
$data['segmentValuesArray'][2]['probability'] = 0;
}
if ($prize4 >= 500){
$data['segmentValuesArray'][3]['probability'] = 0;
}
if ($prize5 >= 10){
$data['segmentValuesArray'][4]['probability'] = 0;
}
$prize1total = $data['segmentValuesArray'][0]['probability'];
$prize2total = $data['segmentValuesArray'][1]['probability'];
$prize3total = $data['segmentValuesArray'][2]['probability'];
$prize4total = $data['segmentValuesArray'][3]['probability'];
$prize5total = $data['segmentValuesArray'][4]['probability'];
$total = $prize1total + $prize2total + $prize3total + $prize4total + $prize5total;
echo'<pre>';
print_r($total);
echo'<pre>';
$a = 0;
foreach($data['segmentValuesArray'] as $prize_array){
if($prize_array['probability'] > 0){
$a++;
}
}
$integer = $prize1before / $a;
$divided1 = $prize1total / $total;
$rounded1 = number_format((float)$divided1, 2, '.', '');
$full1 = $rounded1 * 100;
$divided2 = $prize2total / $total;
$rounded2 = number_format((float)$divided2, 2, '.', '');
$full2 = $rounded2 * 100 + $integer;
$divided3 = $prize3total / $total;
$rounded3 = number_format((float)$divided3, 2, '.', '');
$full3 = $rounded3 * 100 + $integer;
$divided4 = $prize4total / $total;
$rounded4 = number_format((float)$divided4, 2, '.', '');
$full4 = $rounded4 * 100 + $integer;
$divided5 = $prize5total / $total;
$rounded5 = number_format((float)$divided5, 2, '.', '');
$full5 = $rounded5 * 100 + $integer;
$newtotal = $full1 + $full2 + $full3 + $full4 + $full5;
echo'<pre>';
print_r($newtotal);
echo'<pre>';
$newJsonString = json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
file_put_contents('wp-content/themes/supertheme/wheel_data.json', $newJsonString);

PHP round value to it's own size (ten, hunderd, thousand)

I want to make a function that round up to its own size of the value.
Example:
5 => 10
51 => 60
100 => 100/200 // i don't mind which it will become
121 => 200
999 => 1000
1001 => 2000
How can i do this within PHP. I know this can be done using ceil/round but i don't know how.
May be You mean this
function Myround(array $numbers)
{
$new_numbers = [];
foreach ($numbers as $num) {
$rozriad = strlen($num)-1;
$new_numbers[] = round(ceil($num/pow(10, $rozriad))*pow(10, $rozriad), -$rozriad);
};
return $new_numbers;
};
$dozens = [5, 51, 100, 121, 999, 1001];
print_r(Myround($dozens));
You can achieve this with the following function:
/**
* by Johannes Schuh, Pineapple Developer
*/
function roundIt($number) {
$amount = 0;
do {
$number = $number / 10;
$amount++;
} while($number >= 10);
$result = 0;
$result = $number - fmod($number, 1);
$result++;
$result = $result * pow(10, $amount);
return $result;
}

How do I get box plot key numbers from an array in PHP?

Say I have an array with values like:
$values = array(48,30,97,61,34,40,51,33,1);
And I want the values to be able to plot a box plot like follows:
$box_plot_values = array(
'lower_outlier' => 1,
'min' => 8,
'q1' => 32,
'median' => 40,
'q3' => 56,
'max' => 80,
'higher_outlier' => 97,
);
How would I do this in PHP?
function box_plot_values($array)
{
$return = array(
'lower_outlier' => 0,
'min' => 0,
'q1' => 0,
'median' => 0,
'q3' => 0,
'max' => 0,
'higher_outlier' => 0,
);
$array_count = count($array);
sort($array, SORT_NUMERIC);
$return['min'] = $array[0];
$return['lower_outlier'] = $return['min'];
$return['max'] = $array[$array_count - 1];
$return['higher_outlier'] = $return['max'];
$middle_index = floor($array_count / 2);
$return['median'] = $array[$middle_index]; // Assume an odd # of items
$lower_values = array();
$higher_values = array();
// If we have an even number of values, we need some special rules
if ($array_count % 2 == 0)
{
// Handle the even case by averaging the middle 2 items
$return['median'] = round(($return['median'] + $array[$middle_index - 1]) / 2);
foreach ($array as $idx => $value)
{
if ($idx < ($middle_index - 1)) $lower_values[] = $value; // We need to remove both of the values we used for the median from the lower values
elseif ($idx > $middle_index) $higher_values[] = $value;
}
}
else
{
foreach ($array as $idx => $value)
{
if ($idx < $middle_index) $lower_values[] = $value;
elseif ($idx > $middle_index) $higher_values[] = $value;
}
}
$lower_values_count = count($lower_values);
$lower_middle_index = floor($lower_values_count / 2);
$return['q1'] = $lower_values[$lower_middle_index];
if ($lower_values_count % 2 == 0)
$return['q1'] = round(($return['q1'] + $lower_values[$lower_middle_index - 1]) / 2);
$higher_values_count = count($higher_values);
$higher_middle_index = floor($higher_values_count / 2);
$return['q3'] = $higher_values[$higher_middle_index];
if ($higher_values_count % 2 == 0)
$return['q3'] = round(($return['q3'] + $higher_values[$higher_middle_index - 1]) / 2);
// Check if min and max should be capped
$iqr = $return['q3'] - $return['q1']; // Calculate the Inner Quartile Range (iqr)
if ($return['q1'] > $iqr) $return['min'] = $return['q1'] - $iqr;
if ($return['max'] - $return['q3'] > $iqr) $return['max'] = $return['q3'] + $iqr;
return $return;
}
Lilleman's code is brilliant. I really appreciate his way to deal with median and q1/q3. If I were answering this first, I would be cope with odd and even amount of values in a harder but unnecessary way. I mean useing if 4 times for 4 different situationgs of mode( count( values ) , 4 ). But his way is just neat and tidy. I really admires his work.
I would like to make some improvemenets about max, min, higher_outliers and lower_outliers. Because q1 - 1.5*IQR is only the lower bound, we should find the least value that greater than this bound as the 'min'. This is the same for 'max'. Also, there might be more than one outliers. So I would like to make some changes based on Lilleman's work. Thanks.
function box_plot_values($array)
{
$return = array(
'lower_outlier' => 0,
'min' => 0,
'q1' => 0,
'median' => 0,
'q3' => 0,
'max' => 0,
'higher_outlier' => 0,
);
$array_count = count($array);
sort($array, SORT_NUMERIC);
$return['min'] = $array[0];
$return['lower_outlier'] = array();
$return['max'] = $array[$array_count - 1];
$return['higher_outlier'] = array();
$middle_index = floor($array_count / 2);
$return['median'] = $array[$middle_index]; // Assume an odd # of items
$lower_values = array();
$higher_values = array();
// If we have an even number of values, we need some special rules
if ($array_count % 2 == 0)
{
// Handle the even case by averaging the middle 2 items
$return['median'] = round(($return['median'] + $array[$middle_index - 1]) / 2);
foreach ($array as $idx => $value)
{
if ($idx < ($middle_index - 1)) $lower_values[] = $value; // We need to remove both of the values we used for the median from the lower values
elseif ($idx > $middle_index) $higher_values[] = $value;
}
}
else
{
foreach ($array as $idx => $value)
{
if ($idx < $middle_index) $lower_values[] = $value;
elseif ($idx > $middle_index) $higher_values[] = $value;
}
}
$lower_values_count = count($lower_values);
$lower_middle_index = floor($lower_values_count / 2);
$return['q1'] = $lower_values[$lower_middle_index];
if ($lower_values_count % 2 == 0)
$return['q1'] = round(($return['q1'] + $lower_values[$lower_middle_index - 1]) / 2);
$higher_values_count = count($higher_values);
$higher_middle_index = floor($higher_values_count / 2);
$return['q3'] = $higher_values[$higher_middle_index];
if ($higher_values_count % 2 == 0)
$return['q3'] = round(($return['q3'] + $higher_values[$higher_middle_index - 1]) / 2);
// Check if min and max should be capped
$iqr = $return['q3'] - $return['q1']; // Calculate the Inner Quartile Range (iqr)
$return['min'] = $return['q1'] - 1.5*$iqr; // This ( q1 - 1.5*IQR ) is actually the lower bound,
// We must compare every value in the lower half to this.
// Those less than the bound are outliers, whereas
// The least one that greater than this bound is the 'min'
// for the boxplot.
foreach( $lower_values as $idx => $value )
{
if( $value < $return['min'] ) // when values are less than the bound
{
$return['lower_outlier'][$idx] = $value ; // keep the index here seems unnecessary
// but those who are interested in which values are outliers
// can take advantage of this and asort to identify the outliers
}else
{
$return['min'] = $value; // when values that greater than the bound
break; // we should break the loop to keep the 'min' as the least that greater than the bound
}
}
$return['max'] = $return['q3'] + 1.5*$iqr; // This ( q3 + 1.5*IQR ) is the same as previous.
foreach( array_reverse($higher_values) as $idx => $value )
{
if( $value > $return['max'] )
{
$return['higher_outlier'][$idx] = $value ;
}else
{
$return['max'] = $value;
break;
}
}
return $return;
}
I wish this could be helpful for those who would be interested in this issue. And Pls add comment to me if there is a better way to know which values are the outliers. Thanks!
I have a different solution for calculating the lower and higher whiskers. Like ShaoE's solution it finds the least value greater or equal than the lower bound (Q1 - 1.5 * IQR) and vice versa for the higher bound.
I use array_filter which iterates over an array, passing values to a callback function and returns an array with only the values for which the callback gives true (see php.net's array_filter manual). In this case values greater than the lower bound are returned and used as input for min which itself returns the least value.
// get lower whisker
$whiskerMin = min(array_filter($array, function($value) use($quartile1, $iqr) {
return $value >= $quartile1 - 1.5 * $iqr;
} ));
// get higher whisker vice versa
$whiskerMax = max(array_filter($array, function($value) use($quartile3, $iqr) {
return $value <= $quartile3 + 1.5 * $iqr;
} ));
Note that it ignores outliers and I only tested it with positive values.

Break down number into thousands,hundreds,etc

I'm currently putting together a cheque printing solution for my company. When printing the cheque you need to print the number of millions,hundred thousands,ten thousands,thousands, hundreds,tens and units (pounds/dollars/euros etc ) from the amount being paid.
In the case of 111232.23 the following is correctly output from the code I have written below. I cant help feeling that there is a more efficient or reliable method of doing this? Does anyone know of a library/class math technique that does this?
float(111232.23)
Array
(
[100000] => 1
[10000] => 1
[1000] => 1
[100] => 2
[10] => 3
[1] => 2
)
<?php
$amounts = array(111232.23,4334.25,123.24,3.99);
function cheque_format($amount)
{
var_dump($amount);
#no need for millions
$levels = array(100000,10000,1000,100,10,1);
do{
$current_level = current($levels);
$modulo = $amount % $current_level;
$results[$current_level] = $div = number_format(floor($amount) / $current_level,0);
if($div)
{
$amount -= $current_level * $div;
}
}while($modulo && next($levels));
print_r($results);
}
foreach($amounts as $amount)
{
cheque_format($amount);
}
?>
I think you just re-wrote the number_format function that PHP has. My suggestion is to use the PHP function rather than to re-write it.
<?php
$number = 1234.56;
// english notation (default)
$english_format_number = number_format($number);
// 1,235
// French notation
$nombre_format_francais = number_format($number, 2, ',', ' ');
// 1 234,56
$number = 1234.5678;
// english notation without thousands separator
$english_format_number = number_format($number, 2, '.', '');
// 1234.57
?>
I'm not sure exactly what the PHP script would be for this, but if you have 10000, 1000, 100, 10, 1 as the things you need the amounts of. How many 10,000's in amount $dollar?
floor($dollar/10000)
how many thousands?
floor(($dollar%10000)/1000)
etc.
This is not the answer for the question, but the following also break down the decimals.
function cheque_format($amount, $decimals = true, $decimal_seperator = '.')
{
var_dump($amount);
$levels = array(100000, 10000, 1000, 100, 10, 5, 1);
$decimal_levels = array(50, 20, 10, 5, 1);
preg_match('/(?:\\' . $decimal_seperator . '(\d+))?(?:[eE]([+-]?\d+))?$/', (string)$amount, $match);
$d = isset($match[1]) ? $match[1] : 0;
foreach ( $levels as $level )
{
$level = (float)$level;
$results[(string)$level] = $div = (int)(floor($amount) / $level);
if ($div) $amount -= $level * $div;
}
if ( $decimals ) {
$amount = $d;
foreach ( $decimal_levels as $level )
{
$level = (float)$level;
$results[$level < 10 ? '0.0'.(string)$level : '0.'.(string)$level] = $div = (int)(floor($amount) / $level);
if ($div) $amount -= $level * $div;
}
}
print_r($results);
}

Categories