String number approximation - php

I was thinking about a "number approximation" function that takes an integer and returns a string, similar to the following:
45 => "some"
100 => "1 hundred"
150 => "over 1 hundred"
1,386 => "over 1 thousand"
15,235,742 => "over 15 million"
797,356,264,255 => "over 700 billion"
I was hoping to use it for, for example, saying how many rows in a database table in an approximate manner.
I couldn't think how to describe such a thing so searching for it has been somewhat tricky.
Does any body know of an existing function (preferably in PHP) that does this, or could anybody describe/point to an algo to get me started on rolling my own?

Take a look at this package: http://pear.php.net/package-info.php?package=Numbers_Words
The following code explained in comments would do it
I've given two options. One only with words. The second that one you have exactly said in your answer. The first one is easier because you don't need to preconvert words to numbers again.
<?php
require_once "Numbers/Words.php";
$number = new Numbers_Words();
$input = "797,356,264,255";
$input = str_replace(',', '',$input); // removing the comas
$output = $input[0]; // take first char (7)
$output2 = $input[0].'00'; //7 + appended 00 = 700 (for displaying 700 instead of 'seven hundred')
for ($i = 1; $i<strlen($input); $i++) {
$output .= '0';
}
$words = $number->toWords($output); //seven hundred billion
$output3 = explode(' ', $words);
$word = $output3[count($output3)-1]; // billion
echo "Over ". $words; // Over seven hundred billion
#####################
echo "Over " . $output2 . ' ' . $word; // Over 700 billion

What do you want to do is so subjective. That's why you cannot find any function to do that.
For your algorithm, you can define some strings which will match with patterns. For example: over ** million matches with a number of 8 digits. You can find the firsts 2 digits and replace ** in the string.
Then you can use math function like round, floor, ceil (it depends of what you want), and find the string corresponding to your pattern.

After a little bit of fiddling I have come up with this:
function numberEstimate($number) {
// Check for some special cases.
if ($number < 1) {
return "zero";
} else if ($number< 1000) {
return "less than 1 thousand";
}
// Define the string suffixes.
$sz = array("thousand", "million", "billion", "trillion", "gazillion");
// Calculate.
$factor = floor((strlen($number) - 1) / 3);
$number = floor(($number / pow(1000, $factor)));
$number = floor(($number / pow(10, strlen($number) - 1))) * pow(10, strlen($number) - 1);
return "over ".$number." ".#$sz[$factor - 1];
}
which outputs something like this:
0 => "zero"
1 => "less than 1 thousand"
10 => "less than 1 thousand"
11 => "less than 1 thousand"
56 => "less than 1 thousand"
99 => "less than 1 thousand"
100 => "less than 1 thousand"
101 => "less than 1 thousand"
465 => "less than 1 thousand"
890 => "less than 1 thousand"
999 => "less than 1 thousand"
1,000 => "over 1 thousand"
1,001 => "over 1 thousand"
1,956 => "over 1 thousand"
56,123 => "over 50 thousand"
99,213 => "over 90 thousand"
168,000 => "over 100 thousand"
796,274 => "over 700 thousand"
999,999 => "over 900 thousand"
1,000,000 => "over 1 million"
1,000,001 => "over 1 million"
5,683,886 => "over 5 million"
56,973,083 => "over 50 million"
964,289,851 => "over 900 million"
769,767,890,753 => "over 700 billion"
7,687,647,652,973,863 => "over 7 gazillion"
It may not be the prettiest solution, or the most elegant, but it seems to work and does a good job so I will probably go along with this.
I thank everyone for the pointers and suggestions!

Related

PHP - Map a 16 digit number pattern to binary chunks

I am working on a step sequencer program for drum sounds. It takes a 16 bit binary pattern example: '1010010100101001' and then it breaks the binary pattern into chunks like so: 10, 100, 10, 100, 10, 100, 1. It then assigns each chunk a time value based on how many digits. Reason why, is some drum sample sounds ring out longer than the length of 1 beat, so the chunking solves this part. (for example if the beat was 60bpm 1 digit = 1 second) '10' = 2 seconds, '100' = 3 seconds, '1' = seconds. (allowing me to trim the sounds to the proper length in the pattern and concat it into a final wav using ffmpeg) Also 1 = drum hit / 0 = silent hit..... This method works great for my needs.
Now I can make perfect beat loops.... and I want to add a velocity pattern layer on top of this to allow ghost notes / add human feel / dynamics to my drum patterns. I have decided to use a 0,1,2,3,4 value system for the velocity patterns. '0' = 0% volume, '1' = 25% volume, '2' = 50% volume, '3' = 75% volume, and '4' = %100 volume. (0 volume so I can add open hi hat / cymbal crash hard stops that a 0 in binary pattern wouldn't do) So along with the '1111111111111111' pattern you would see a velocity pattern layer, say '4242424242424242' (That velocity pattern alternates 100% hit and 50% hit and sounds good with hi hats / like a real drummer)
Using PHP I am breaking 16 bit binary patterns into an array of chunks. '1001110011110010' would be
['100','1','1','100','1','1','1','100','10']
Now via a loop, I need to map another 16 digit number layer pattern of 0,1,2,3,4 digits to first digit of each chunk.
Example 1:
Velocity Pattern: '4242424242424242'
Binary Pattern: '1001110011110010'
Array = ['100','1','1','100','1','1','1','100','10']
'100' = 4 (1st digit in 4242424242424242 pattern)
'1' = 2 (4th digit in 4242424242424242 pattern)
'1' = 4 (5th digit in 4242424242424242 pattern)
'100' = 2 (6th digit in the 4242424242424242 pattern)
'1' = 4 (9th digit in the 4242424242424242 pattern)
'1' = 2 (10th digit in the 4242424242424242 pattern)
'1' = 4 (11th digit in the 4242424242424242 pattern)
'100' = 2 (12th digit in the 4242424242424242 pattern)
'10' = 4 (15th digit in the 4242424242424242 pattern)
Example 2:
Velocity Pattern: '4242424242424242'
Binary Pattern: '1111111111111111'
Array = ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1']
'1' = 4 (n1 digit in 4242424242424242 pattern)
'1' = 2 (n2 digit in 4242424242424242 pattern)
'1' = 4 (n3 digit in 4242424242424242 pattern)
'1' = 2 (n4 digit in 4242424242424242 pattern)
'1' = 4 (n5 digit in 4242424242424242 pattern)
'1' = 2 (n6 digit in 4242424242424242 pattern)
'1' = 4 (n7 digit in 4242424242424242 pattern)
'1' = 2 (n8 digit in 4242424242424242 pattern)
'1' = 4 (n9 digit in 4242424242424242 pattern)
'1' = 2 (n10 digit in 4242424242424242 pattern)
'1' = 4 (n11 digit in 4242424242424242 pattern)
'1' = 2 (n12 digit in 4242424242424242 pattern)
'1' = 4 (n13 digit in 4242424242424242 pattern)
'1' = 2 (n14 digit in 4242424242424242 pattern)
'1' = 4 (n15 digit in 4242424242424242 pattern)
'1' = 2 (n16 digit in 4242424242424242 pattern)
Example 3:
Velocity Pattern: '4231423142314231'
Binary Pattern: '0001000100010001'
Array = ['0','0','0','1000','1000','1000','1']
'0' = 4 (1st digit in 4231423142314231 pattern)
'0' = 2 (2nd digit in 4231423142314231 pattern)
'0' = 3 (3rd digit in 4231423142314231 pattern)
'1000' = 1 (4th digit in 4231423142314231 pattern)
'1000' = 1 (8th digit in 4231423142314231 pattern)
'1000' = 1 (12th digit in 4231423142314231 pattern)
'1' = 1 (16th digit in 4231423142314231 pattern)
The patterns will vary, so I need a method that works even if the pattern starts with 0, ect.
a pattern of 111111111111111 would be easy since each 1 is already split into a group by itself.
I tried using a counter called "$v_count" to map find the position in the pattern but its not working like expected.
$v_count = 0;
$beat_pattern = '1001110011110010';
$velocity_pattern = '4242424242424242';
preg_match_all('/10*|0/', $beat_pattern, $m);
$c_count = count($m, COUNT_RECURSIVE) - 1;
for ($z = 0; $z < $c_count; $z++) {
$z2 = $z;
${"c" . $z} = $m[0][$z];
${"cl" . $z} = strlen($m[0][$z]);
if (${"cl" . $z} == 1 & $m[0][$z] == "0") {
$v_count = $v_count + 1;
echo 'the position of this chunk is: '.$v_count.' in the velocity_pattern<br>';
};
if (${"cl" . $z} == 1 & $m[0][$z] == "1") {
$v_count = $v_count + 1;
echo 'the position of this chunk is: '.$v_count.' in the velocity_pattern<br>';
};
if (${"cl" . $z} > 1) {
if ($z == 1)
{
$v_count = 1;
}
if ($z > 1)
{
$v_count = $v_count + 1;
}
echo ' - the velocity position of this chunk is: '.$v_count.' in the pattern<br>';
$v_count = $v_count + ${"cl" . $z} + 1;
};
}
From the example you've given, it seems that you need the corresponding value from the velocity array and the duration between the 1's in the beat array.
This code first extracts the 1's by splitting it into an array and then filtering out the 0's. So
$beat_pattern = '1001110011110010';
$velocity_pattern = '4242424242424242';
$beat = array_filter(str_split($beat_pattern));
would give in $beat...
Array
(
[0] => 1
[3] => 1
[4] => 1
[5] => 1
[8] => 1
[9] => 1
[10] => 1
[11] => 1
[14] => 1
)
it then takes each entry in turn, works out the length by looking at the next key and subtract the two, also using the index to get the corresponding velocity.
To account for the starting with 0, you can loop up to the first instance of 1 and output the velocity pattern for the same element...
$beat_pattern = '1001110011110010';
$velocity_pattern = '4242424242424242';
$beat = array_filter(str_split($beat_pattern));
$beatKeys = array_keys($beat);
// For the leading 0's
for( $i = 0; $i < $beatKeys[0]; $i++ ) {
echo "1-". $velocity_pattern[$i] . PHP_EOL;
}
for ( $i = 0; $i < count($beatKeys); $i++ ) {
echo ($beatKeys[$i+1] ?? strlen($beat_pattern)) - $beatKeys[$i] . "-".
$velocity_pattern[$beatKeys[$i]] . PHP_EOL;
}
gives (length-velocity)...
3-4
1-2
1-4
3-2
1-4
1-2
1-4
3-2
2-4
Assuming your two input strings:
$binary = '0001000110101001';
$velocity = '4231423142314231';
If you analyse the pattern with a regex, you can obtain all the component parts in one operation, including pauses at the start of the pattern (which are essentially 0% volume beats).
$index = 0;
preg_match_all('/^0+|10*/', $binary, $parts);
foreach ($parts[0] as $part) {
$duration = strlen($part); // How many beats
$volume = $part[0] ? $velocity[$index] : 0; // The corresponding volume number
$index += $duration;
}
To develop this further, it seems to me that it would be practical to produce a proper array of data for the pattern, and you could package up this functionality if you so wanted:
function drumPattern($binary, $velocity) {
$output = [];
$index = 0;
preg_match_all('/^0+|10*/', $binary, $parts);
foreach ($parts[0] as $part) {
$duration = strlen($part);
$output[] = [
'duration' => $duration,
'volume' => $part[0] ? $velocity[$index] : 0
];
$index += $duration;
}
return $output;
}
Example
drumPattern($binary, $velocity);
Produces the following output
Array
(
[0] => Array
(
[duration] => 3
[volume] => 0
)
[1] => Array
(
[duration] => 4
[volume] => 1
)
[2] => Array
(
[duration] => 1
[volume] => 1
)
[3] => Array
(
[duration] => 2
[volume] => 4
)
[4] => Array
(
[duration] => 2
[volume] => 3
)
[5] => Array
(
[duration] => 3
[volume] => 4
)
[6] => Array
(
[duration] => 1
[volume] => 1
)
)

PHP: Seperate a number to 10er array with a minimum and maximum value

I do not know how to form this the right way so here is the concept.
I have for example the 93.5 and 35 as numbers.
What i would like to do is to get from the minimum value the previous 10er number, in this case the 30 and get from the maximum value the next 10er number which in this case is 100.
The next step is to get an array from each 10er number between those two numbers. It would look like this:
array(8 items)
0 => 30
1 => 40
2 => 50
3 => 60
4 => 70
5 => 80
6 => 90
7 => 100
So the question is, how do i achieve that with PHP
After some coding i have found the solution.
First step: Get the minimum and maximum numbers and set them to the previous and next 10er number:
$minSize = floor(35 / 10) * 10; /* Gives back 30 */
$maxSize = ceil( 93.5 / 10) * 10; /* Gives back 100*/
Now get the number from the $maxSize didvided by 10.
$devidedNumber = $maxSize / 10; /* Gives back 10 */
After that, a for iteration is to be created using the $devidedNumber in order to decide how many times this iteration should run.
$sizedArray = array();
for ($i = $minSize/10; $i <= $devidedNumber; $i++)
{
$sizedArray[] = $i * 10;
}
The $i starts from 3 because we need the value that starts from 30 to the target value.
And the result:
array(8 items)
0 => 30
1 => 40
2 => 50
3 => 60
4 => 70
5 => 80
6 => 90
7 => 100
EDIT:
After #Nigel Ren answer, another way to do it is the following. After rounding the numbers, the for iteration could look like this:
for ($i = $minSize; $i <= $maxSize; $i+=10)
{
$sizedArray[] = $l;
}
In this case, the code is simplified by removing the line
$devidedNumber = $maxSize / 10; /* Gives back 10 */
and get directly the between values from the rounded.
Thanks for the nice suggestion

How do i calculate characters in string without if conditions?

I have the code example:
$str = '123456789101112131415';
if(strlen($str) <9 )
{
echo "the string is 10";
}
elseif(strlen($str) > 9) {
echo 'the string is 11';
}
elseif(strlen($str) > 21) {
echo 'the string is 30';
}
elseif(strlen($str) > 31) {
echo 'the string is 40';
}
My problem is how do I sort out when the string is huge and not specified in the if conditions
Calculate it!
call strlen()
divide by 10
call floor() to remove decimals
multiply by 10
check if less than 10
Here is a battery of tests:
Code: (Demo)
$strings=[9=>'123456789',10=>'1234567890',11=>'12345678901',19=>'1234567890123456789',20=>'12345678901234567890',21=>'123456789012345678901'];
foreach($strings as $k=>$str){
if(($group=floor(strlen($str)/10)*10)<10){ // use arithmetic to find the group, declare and check group in one step
echo "$k => $str is not upto 10";
}else{
echo "$k => $str is in group $group";
}
echo "\n";
}
Output:
9 => 123456789 is not upto 10
10 => 1234567890 is in group 10
11 => 12345678901 is in group 10
19 => 1234567890123456789 is in group 10
20 => 12345678901234567890 is in group 20
21 => 123456789012345678901 is in group 20
After OP's update:
Code: (Demo)
$strings=[9=>'123456789',10=>'1234567890',11=>'12345678901',19=>'1234567890123456789',20=>'12345678901234567890',21=>'123456789012345678901'];
foreach($strings as $k=>$str){
echo "$k => $str is in group ",(floor(strlen($str)/10)+1)*10,"\n";
}
Output:
9 => 123456789 is in group 10
10 => 1234567890 is in group 20
11 => 12345678901 is in group 20
19 => 1234567890123456789 is in group 20
20 => 12345678901234567890 is in group 30
21 => 123456789012345678901 is in group 30
Here is another method that delivers EXACTLY what the OP is asking for, but I bet it's "not right" either.
http://sandbox.onlinephpfunctions.com/code/77effe7f68c389bafb1d104a49b7055873e7b038

Adjust function for pyramid-like distribution

In this question I got help to write a PHP function which gives a pyramid-like distribution:
function getRandomStrength($min, $max) {
$ln_low = log($min, M_E);
$ln_high = log($max, M_E);
$scale = $ln_high-$ln_low;
$rand = (mt_rand()/mt_getrandmax())*$scale+$ln_low;
$value = round(pow(M_E, $rand), 1);
return $value;
}
getRandomStrenth(1.1, 9.9);
// output could be: 1.4 or 8.3 or 9.8 or 7.2 or 2.9 or ...
When I run 50,000 iterations and check how often the numbers from 1 to 9 appear, I get the following list:
1 » 26%
2 » 19%
3 » 14%
4 » 10%
5 » 9%
6 » 7%
7 » 6%
8 » 6%
9 » 4%
This is what I wanted to have. But now I would like to adjust this function a bit. The smaller values should appear more often and the big values should appear less often - so that I get a list like this:
1 » 28%
2 » 20%
3 » 15%
4 » 11%
5 » 9%
6 » 6%
7 » 5%
8 » 5%
9 » 2%
As you can see, I just need a slight modification. But what can I change so that my function behaves as expected?
I tried several things (e.g. changing the base of the logarithm) but this did not change anything.
You can use pow on the random number.
$rand = pow( mt_rand()/mt_getrandmax(), 1.2 )*$scale+$ln_low;
By playing with the exponent value, you can get less or more small value.
Reducing the $scale of your function by a small (constant) amount seems to generate results pretty close to what you're looking for. You can achieve more accurate results by making this reduction of $scale a function of the randomly generated number from mt_rand(), which would require saving (mt_rand()/mt_getrandmax()) to a variable and performing some additional math on $scale.
Here are my tests, you can run it yourself: http://codepad.viper-7.com/ssblbQ
function getRandomStrength($min, $max)
{
$ln_low = log($min, M_E);
$ln_high = log($max, M_E);
$scale = $ln_high-$ln_low - .05; // Subtract a small constant, vary between .05 and .08
$rand = (mt_rand()/mt_getrandmax())*$scale+$ln_low;
$value = round(pow(M_E, $rand), 1);
return $value;
}
$values = array_fill(1, 9, 0);
for( $i = 0; $i < 50000; $i++)
{
$values[ intval( getRandomStrength(1.1, 9.9)) ]++;
}
for( $i = 1; $i <= 9; $i++)
{
$values[ $i] /= 500; // / 50000 * 100 to get a percent
}
var_dump( $values);
Output
Run #1 - Constant = 0.5
array(9) {
[1] => float(26.626) // Should be 28
[2] => float(19.464) // Should be 20
[3] => float(13.476) // Should be 15
[4] => float(10.41) // Should be 11
[5] => float(8.616) // Should be 9
[6] => float(7.198) // Should be 6
[7] => float(6.258) // Should be 5
[8] => float(5.52) // Should be 5
[9] => float(2.432) // Should be 2
}
Run #2 - Constant = 0.65
array(9) {
[1] => float(26.75) // Should be 28
[2] => float(19.466) // Should be 20
[3] => float(13.872) // Should be 15
[4] => float(10.562) // Should be 11
[5] => float(8.466) // Should be 9
[6] => float(7.222) // Should be 6
[7] => float(6.454) // Should be 5
[8] => float(5.554) // Should be 5
[9] => float(1.654) // Should be 2
}
Run #3 - Constant = 0.70
array(9) {
[1] => float(26.848) // Should be 28
[2] => float(19.476) // Should be 20
[3] => float(13.808) // Should be 15
[4] => float(10.764) // Should be 11
[5] => float(8.67) // Should be 9
[6] => float(7.148) // Should be 6
[7] => float(6.264) // Should be 5
[8] => float(5.576) // Should be 5
[9] => float(1.446) // Should be 2
}
For n in {0..1}, y=(x^n)-1, y will range from 0 to x-1. That curve is then easily mapped from 0 to some max value by multiplying by the range and dividing by (x-1). If you change the value x to something near one, the curve will be nearly linear, and at large values, the curve becomes more like a hockey-stick, but will still fall in the same range.
My initial sample value of three won't be precisely what you expressed, but you can adjust it to get the distribution curve you're looking for.
function getCustomStrength($min, $max, $x_val, $base) {
$logmax = $base-1;
$range = $max-$min;
return (pow($base,$x_val)-1)*($range/($base-1))+$min;
}
function getRandomStrength($min, $max) {
$rand = mt_rand()/mt_getrandmax();
$base = 3.0;
return getCustomStrength($min, $max, $rand, $base);
}
getRandomStrength(1.1, 9.9);

Best way to get N numbers from range betwen 2 (big) numbers

Hello I need to get N numbers from range between 2 big numbers, without the start and end numbers.
The (N) numbers must be on equal intervals... I will try to explain with small numbers:
<?php
$rangeStart = 0;
$rangeEnd = 100;
$n = 9;
In this example i need to get 10,20,30,40,50,60,70,80,90
I have try with 'for loop' but it is veeery slow, because I'm using range like 1207083600 ~ 1275512399
Will appreciate any help.
=====
This is what I call slow http://jsfiddle.net/pbF7N/1/
The start and end are timestamps and I need to extract 10 dates...
range() with its optional 3rd parameter to specify the step size...
range(10, 90, 10);
$range = range(10, 90, 10);
print_r($range);
Array
(
[0] => 10
[1] => 20
[2] => 30
[3] => 40
[4] => 50
[5] => 60
[6] => 70
[7] => 80
[8] => 90
)
Something like this maybe:
function nrange($num, $start, $end)
{
$out = array(); $i = 0;
$interval = floor(($end - $start) / ($num + 1));
while ($i++ < $num )
$out[] = $start + $i * $interval;
return $out;
}
Consider first your example case. Your numbers broke up the range [0..100) into 10 equal intervals, [0..10), [10, 20), etc. up to [90..100).
Notice that the number of intervals is $n+1. So you see that each interval is of length ($rangeEnd - $rangeStart) / $n.
Using this information, you can use range to step across $interval numbers at a time, i.e.,
$interval = ($rangeEnd - $rangeStart) / $n;
$range = range($rangeStart, $rangeEnd, $interval);

Categories