php - map a value using fromRange and toRange? - php

I'm trying to figure out how to map a number between 1 and 1000 to a number between 1 and 5.
For example:
I have a database of 1000 records and I want to assign an id number between 1 and 5 to each record. I don't want it to be random, thats easy enough with rand(1,5).
In the Arduino language it has a function that I'm hoping PHP has:
result = map(value, fromLow, fromHigh, toLow, toHigh)
The purpose of this is I don't want to store that mapped value in the database, I need a php function that I can call and if say the db record is 100 no matter how often the function is called it will always return the same mapped value.
Thanks!

The function you're looking for maps ranges by using different scales. So that's easy to do:
function map($value, $fromLow, $fromHigh, $toLow, $toHigh) {
$fromRange = $fromHigh - $fromLow;
$toRange = $toHigh - $toLow;
$scaleFactor = $toRange / $fromRange;
// Re-zero the value within the from range
$tmpValue = $value - $fromLow;
// Rescale the value to the to range
$tmpValue *= $scaleFactor;
// Re-zero back to the to range
return $tmpValue + $toLow;
}
So basically, it'll re-base the number within the range. Now, note that there is no error checking if value is within either range. The reason is that it maps scales, not ranges. So you can use it for base conversion:
$feet = map($inches, 0, 12, 0, 1);
And you can map "ranges" as well since it re-bases the number (moves it along the number line):
5 == map(15, 10, 20, 0, 10);
So for from range (0, 1000) and to range (0, 5), the following table will hold true:
-200 | -1
0 | 0
1 | 0.005
100 | 0.5
200 | 1
400 | 2
600 | 3
800 | 4
1000 | 5
2000 | 10
3000 | 15
And to show the re-basing, if we map (0, 1000) to (5, 10):
-200 | 4
0 | 5
1 | 5.005
100 | 5.5
200 | 6
400 | 7
600 | 8
800 | 9
1000 | 10
2000 | 15
3000 | 20

Have you considered: $mappedValue = ($value % 5) + 1;? Will return the remainder after dividing the value by 5 (i.e. 0-4) then adds one.

Related

According to days change homepage or results

i have a two tables, days and posts.
Days table
id | day
----------
1 | Day1
2 | Day2
3 | Day3
4 | Day4
5 | Day5
6 | Day6
7 | Day7
8 | Day8
9 | Day9
10 | Day10
11 | Day11
12 | Day12
Posts table
id | day | posts
---------------------------
1 | 1 | Day1Text
2 | 1 | Day1Text
1 | 1 | Day1Text
2 | 2 | Day2Text
1 | 2 | Day2Text
2 | 3 | Day3Text
1 | 4 | Day4Text
2 | 5 | Day5Text
I have a 12 days and each days have a different posts, every day at 10.00 am this change, example Today(Day1) and only 1st day posts will appear. Tomorrow at 10.00 am 2nd day posts will appear. When the 12th day ends, this loop will return to the beginning and start again 1st day. How can i do this?
First off you want to know how to cycle over the posts as days go by. As input we need at least 12 different days, so we can use the current day of the month or year. If we take the day of the year:
Carbon::now()->dayOfYear //e.g. int(137)
We can now calculate its mod against the number of posts, which will always return a number between 0 and 11. If we add 1 we'll have a number between 1 - 12. E.g:
137 % 12 = 5 // 5 + 1 = 6
120 % 12 = 0 // 0 + 1 = 1
347 % 12 = 11 // 11 + 1 = 12
At this point, we know what post we need to display each day, but we're not controlling the hour it should start displaying.
To fix this, one way would be to consider the days change at the time you want the posts to change, aligning both makes things easier. As we don't want to offset the server timezone, we can just offset the date we pick by 10h meaning that a new day will begin once the current day gets to 10AM:
Carbon::now()->subHours(10)->dayOfYear
Now we can use the resulting mod value to build the necessary query to display the post.
From the looks of it you don't really need the if statement so for this you could either use the englishDayOfWeek getter or just use php's date format
i.e.
$results = Post::where('Day', now()->englishDayOfWeek)->get()
or
$results = Post::where('Day', now()->format('l'))->get()

PHP- Query MySQLi results nearest a given number

I am trying to search for an invoice by the amount. So, I would like to search all invoices +/- 10% of the amount searched, and order by the result closest to the given number:
$search = 100.00
$lower = $search * 0.9; // 90
$higher = $search * 1.1 // 110
$results = $db->select("SELECT ID from `invoices` WHERE Amount >= `$lower` && Amount >= `$higher`");
So, I am not sure how to order these. Let's say this query gives me the following results:
108, 99, 100, 103, 92
I want to order the results, starting with the actual number searched (since it's an exact match), and working out from there, so:
100, 99, 103, 92, 108
You could do this as follows:
$search = 100.00
$deviation = 0.10;
$results = $db->select("
SELECT ID, Amount, ABS(1 - Amount/$search) deviation
FROM invoices
WHERE ABS(1 - Amount/$search) <= $deviation
ORDER BY ABS(1 - Amount/$search)
");
Output is:
+----+--------+-----------+
| id | Amount | deviation |
+----+--------+-----------+
| 3 | 100 | 0 |
| 2 | 99 | 0.01 |
| 4 | 103 | 0.03 |
| 1 | 108 | 0.08 |
| 5 | 92 | 0.08 |
+----+--------+-----------+
Here is an SQL fiddle
This way you let SQL calculate the deviation, by dividing the actual amount by the "perfect" amount ($search). This will be 1 for a perfect match. By subtracting this from 1, the perfect match is represented by the value 0. Any deviation is non-zero. By taking the absolute value of that, you get the exact deviation as a fractional number (representing a percentage), like for example 0.02 (which is 2%).
By comparing this deviation to a given maximum deviation ($deviation), you get what you need. Of course, ordering is then easily done on this calculated deviation.
Try this:
$search = 100.00
$lower = $search * 0.9; // 90
$higher = $search * 1.1 // 110
$results = $db->select("SELECT ID from `invoices`
WHERE Amount >= `$lower` && Amount <= `$higher`
ORDER BY ABS(Amount - $search)
");
The ABS function returns the absolute value of its argument (=> it basically removes the minus from negative numbers). Therefore ABS(Amount - $search) returns the distance from the $search value.
Besides that you should consider using prepared statements. Otherwise your application could be vulnerable to sql injection.

Splitting website traffic by percentage

I need to split up traffic to multiple sources based on an assigned percentage. I figure I need a log table like this:
Table:
+--------+------+----------------------+
| Source | hits | allocated percentage |
+--------+------+----------------------+
| path1 | 50 | 50 |
| path2 | 40 | 40 |
| path3 | 10 | 10 |
+--------+------+----------------------+
I figure the logic needs to loop through all the paths and calculate the current percentage and then determine which one is furthest from the "allocated percentage" and then update the table hits=hits+1. I'm having trouble with the last compare part.
$overall_hits = $db->getall('Select sum(total_hits) from table');
$source = $db->getall('Select * from table');
foreach($source as $row){
$current_percentage = ($row['total_hits']/$overall_hits)*100;
//how should I compare? what if they are equal?
if($current_percentage < $row['allocated_percentaged'])
{
$chosen_path = $row['source'];
$db->sql("Update table set total_hits=total_hits+1 where source='".$chosen_path."'");
break;
}else{
continue;
}
}
Am I even on the right track here?
Presuming I understand what you're trying to do, you can do all of the logic checks in your SQL.
Using the following data as an example:
CREATE TABLE t (
source TEXT,
hits INT,
percentage INT
);
INSERT INTO t (source, hits, percentage)
VALUES
('path1', 41, 50),
('path2', 27, 40),
('path3', 3, 10)
You can simply run a query against the entire table, to calculate what percentage each of the paths is at:
SELECT
source,
hits,
percentage,
(hits / percentage) * 100
AS current
FROM t
ORDER BY current ASC;
Which will give you the following results
SOURCE HITS PERCENTAGE CURRENT
path1 3 10 30
path2 27 40 67.5
path3 41 50 82
You can then simply add LIMIT 1 to the end of your query, to only obtain 1 result. This will give you the path with the lowest number of hits : allocated ratio.
SOURCE HITS PERCENTAGE CURRENT
path1 3 10 30
You can see it in action on SQLFiddle here.

Integer division in PHP produces zero - how to cast it to float?

In a Russian card game I'm trying to keep statistics of how often a player swears (says "bad words" - and we do have a lot of them in Russian language) in the following PostgreSQL table:
# select * from pref_chat order by swear desc;
id | swear | lines
-------------------------+-------+-------
OK194281930260 | 3 | 153
OK350321778615 | 2 | 127
DE12770 | 2 | 339
OK122898831181 | 2 | 63
OK349829847011 | 2 | 126
OK215240745969 | 1 | 66
OK459742722980 | 1 | 96
And I need to generate an integer number of this data - between 1 and 100 (overflowing is okay) - so that I can create the following "swear'o'meter:
So I'm trying (with PHP 5.3 at CentOS 6.2):
$sth = $db->prepare('select swear, lines from pref_chat where id=?');
$sth->execute(array($id));
if ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
$quotient = 100 * floor(20 * $row['swear'] / (1 + $row['lines']));
print('<p><img src="https://chart.googleapis.com/chart?chs=700x100&cht=gom&chco=00FF00,FFFF00,FF0000&chxt=x&chxl=0:|Swearometer&chd=t:' . $quotient . '" width=700 height=100 alt="Swearometer"></p>)';
}
Unfortunately I get zero - because PHP is probably doing an "integer division".
I've tried to prepend floor() to $row['swear'] and $row['lines'] to "cast" them to float - but this didn't help.
UPDATE:
Sorry, I had a typo in my original question... The $quotient is really 0, I print it out. I've also tried the following, but still zero:
$quot = 100 * floor(20 * $row['swear'] / (.1 + $row['lines']));
Well, $row['swear'] / (1 + $row['lines']) would always be < .05 given the numbers you list. Therefore, when you multiply by 20 and then floor you will very correctly get 0.
#AlexanderFarber: No, sorry integer division in PHP is not giving zero may be you are missing something.Just use your usual division.

How could I detect if a character close to another character on a QWERTY keyboard?

I'm developing a spam detection system and have been alerted to find that it can't detect strings like this - "asdfsdf".
My solution to this involves detecting if the previous keys were near the other keys on the keyboard. I am not getting the input (to detect spam from) from the keyboard, I'm getting it in the form of a string.
All I want to know is whether a character is one key, two keys or more than two keys away from another character.
For example, on a modern QWERTY keyboard, the characters 'q' and 'w' would be 1 key away. Same would the chars 'q' and 's'. Humans can figure this out logically, how could I do this in code?
You could simply create a two-dimensional map for the standard qwerty keyboard.
Basically it could look something like this:
map[0][0] = 'q';
map[0][1] = 'a';
map[1][0] = 'w';
map[1][1] = 's';
and so on.
When you get two characters, you simply need to find their x, and y in the array 'map' above, and can simply calculate the distance using pythagoras. It would not fill the requirement you had as 'q' and 's' being 1 distance away. But rather it would be sqrt(1^2 + 1^2) approx 1.4
The formula would be:
Characters are c1 and c2
Find coordinates for c1 and c2: (x1,y1) and (x2,y2)
Calculate the distance using pythagoras: dist = sqrt((x2-x1)^2 + (y2-y1)^2).
If necessary, ceil or floor the result.
For example:
Say you get the characters c1='q', and c2='w'. Examine the map and find that 'q' has coordinates (x1,y1) = (0, 0) and 'w' has coordinates (x2,y2) = (1, 0). The distance is
sqrt((1-0)^2 + (0-0)^2) = sqrt(1) = 1
Well, let's see. That's a tough one. I always take the brute-force method and I stay away from advanced concepts like that guy Pythagoras tried to foist on us, so how about a two-dimensional table? Something like this. maybe:
+---+---+---+---+---+---+---
| | a | b | c | d | f | s ...
+---+---+---+---+---+---+---
| a | 0 | 5 | 4 | 2 | 4 | 1 ...
| b | 5 | 0 | 3 | 3 | 2 | 4 ...
| c | 4 | 3 | 0 | 1 | 2 | 2 ...
| d | 2 | 3 | 1 | 0 | 1 | 1 ...
| f | 3 | 2 | 2 | 1 | 0 | 2 ...
| s | 1 | 4 | 2 | 1 | 2 | 0 ...
+---+---+---+---+---+---+---
Could that work for ya'? You could even have negative numbers to show that one key is to the left of the other. PLUS you could put a 2-integer struct in each cell where the second int is positive or negative to show that the second letter is up or down from the first. Get my patent attorney on the phone, quick!
Build a map from keys to positions on an idealized keyboard. Something like:
'q' => {0,0},
'w' => {0,1},
'a' => {1,0},
's' => {1,1}, ...
Then you can take the "distance" as the mathematical distance between the two points.
The basic idea is to create a map of characters and their positions on the keyboard. You can then use a simple distance formula to determine how close they are together.
For example, consider the left side of the keyboard:
1 2 3 4 5 6
q w e r t
a s d f g
z x c v b
Character a has the position [2, 0] and character b has the position [3, 4]. The formula for their distance apart is:
sqrt((x2-x1)^2 + (y2-y1)^2);
So the distance between a and b is sqrt((4 - 0)^2 + (3 - 2)^2)
It'll take you a little bit of effort to map the keys into a rectangular grid (my example isn't perfect, but it gives you the idea). But after that you can build a map (or dictionary), and lookup is simple and fast.
I developed a function for the same purpose in PHP because I wanted to see whether I can use it to analyse strings to figure out whether they're likely to be spam.
This is for the QWERTZ keyboard, but it can easily be changed. The first number in the array $keys is the approximate distance from the left and the second is the row number from top.
function string_distance($string){
if(mb_strlen($string)<2){
return NULL;
}
$keys=array(
'q'=>array(1,1),
'w'=>array(2,1),
'e'=>array(3,1),
'r'=>array(4,1),
't'=>array(5,1),
'z'=>array(6,1),
'u'=>array(7,1),
'i'=>array(8,1),
'o'=>array(9,1),
'p'=>array(10,1),
'a'=>array(1.25,2),
's'=>array(2.25,2),
'd'=>array(3.25,2),
'f'=>array(4.25,2),
'g'=>array(5.25,2),
'h'=>array(6.25,2),
'j'=>array(7.25,2),
'k'=>array(8.25,2),
'l'=>array(9.25,2),
'y'=>array(1.85,3),
'x'=>array(2.85,3),
'c'=>array(3.85,3),
'v'=>array(4.85,3),
'b'=>array(5.85,3),
'n'=>array(6.85,3),
'm'=>array(7.85,3)
);
$string=preg_replace("/[^a-z]+/",'',mb_strtolower($string));
for($i=0;$i+1<mb_strlen($string);$i++){
$char_a=mb_substr($string,$i,1);
$char_b=mb_substr($string,$i+1,1);
$a=abs($keys[$char_a][0]-$keys[$char_b][0]);
$b=abs($keys[$char_a][1]-$keys[$char_b][1]);
$distance=sqrt($a^2+$b^2);
$distances[]=$distance;
}
return array_sum($distances)/count($distances);
}
You can use it the following way.
string_distance('Boat'); # output 2.0332570942187
string_distance('HDxtaBQrGkjny'); # output 1.4580596252044
I used multibyte functions because I was thinking about extending it for other characters. One could extend it by checking the case of characters as well.

Categories