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.
Related
A have a list with many full names (>20000) and it increases with each new registration. I need create a seven digits identification number to every register in alphabetical order, so that the conversion start in 0100000 and finish in 9999999. This number must be based on the full name and your order.
When adding new names and that they are merged in the existing base, also generate new numbers merged too.
I have not yet been able to develop a good algorithm that solves this. Then I'll need to create a PHP script for this.
It is a conversion of names to numbers, but with a defined range.
For example
Anthony Felder : 0.459.789
Bianca Mall : 0.989.432
Danton Bishop : 2.986.999
Mario Cortez: 7.883.120
Paul Rudd: 8.788.454
Zeta Jones: 9.987.001
A new name inserted:
Augustus Novell : 0.589.223
Frederic Francis Ford Copolla : 3.765.453
You are going to run into problems, because eventually you are adding to many records that August zzzzzperson will get number 0.989.432 and that already exists.
If you don't expect TOO many new people being added, what you could do:
If Augustus Novell is added to your database - find out between which two names he should be placed (alphabetically).
Anthony Felder : 0.459.789
Bianca Mall : 0.989.432
Grab their numbers and get a number right in the middle of the two:
roundUp((0.459.789 + 0.989.432) / 2) = 0.724.611
As long as you leave a significant gap between each record at the start. In this example with this gap you can only do this 20 times when you keep adding a new name between Anthony Felder and the latest added name. Increasing the gap, increases the amount of times you can do this. But you have to DOUBLE the gap, just to get one additional name in there.
The limit of 20 is only if keep using the same name 20 times as the upper or lower limit. Would love to hear if there is a smarter algorithm, but I doubt it, without rebuilding indices. Taking the middle of two numbers makes sure you always have the biggest gap between two numbers. (not taking predictive models into account).
I don't like my solution of taking the average, but I think it may be the best solution. In other words, unless someone comes up with a better algo, I would try to sort your situation differently. For example, letting go of the need to make the numbers sequential to the alphabetical order of the names (I wonder why this is needed anyway)
EDIT: One other option. Map their name to a number
a = 01, b = 02, c = 03... z = 26, space = 27
Optional, space is a dot, but you can also put a dot every 3 letters (6 numbers)
That means 2 people with the same name would get the same number. You can solve this by having the first two numbers telling you which person it is.
So the first Anthony Felder would start with 01, second Anthony Felder with 02, third Anthony Felder with 03 etc and then start mapping the A (=01).
You have to define how to deal with other characters such as é .
This leads to numbers with variable lengths
This can lead to LONG numbers.
The limit is 99 people with the same name (or 100 if you start with 00)
Actually we can create your idea to code
but it need more time (You say that there are 20000 records)
$value = 100000;
$n= 100000 + $total_db_row; //find total name count from your db table
for( $i=100000; $i < $n; $i++ ) {
$a = str_pad($i, 7, '0', STR_PAD_LEFT);
$a = substr_replace( $a, '.', 1, 0 );
$id[] = substr_replace( $a, '.', 5, 0 );
}
//var_dump($id);
then ,after each insertion you should do...
mysql> SELECT * FROM ordered_names;
+----+-----------+----------+------------+
| id | firstname | lastname | sort_order |
+----+-----------+----------+------------+
| 1 | pamela | edwards | NULL |
| 2 | rolando | edwards | NULL |
| 3 | diamond | edwards | NULL |
+----+-----------+----------+------------+
3 rows in set (0.00 sec)
mysql>
Next, let's populate the sort order column:
SET #x = 0;
UPDATE ordered_names SET sort_order = (#x:=#x+1) ORDER BY lastname,firstname;
SELECT * FROM ordered_names;
Let's run that:
mysql> SET #x = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE ordered_names SET sort_order = (#x:=#x+1) ORDER BY lastname,firstname;
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> SELECT * FROM ordered_names;
+----+-----------+----------+------------+
| id | firstname | lastname | sort_order |
+----+-----------+----------+------------+
| 1 | pamela | edwards | 2 |
| 2 | rolando | edwards | 3 |
| 3 | diamond | edwards | 1 |
+----+-----------+----------+------------+
3 rows in set (0.00 sec)
Then update php created array with where condition 'sort_order'.
foreach($i=0; $i < count($id); i++ )
{
$sql = "UPDATE ordered_names SET sort_order=.$id[$i]. WHERE sort_order=.$i.";
$db->query($sql);
}
it will update sort_order col with 0.100.100 to .....
But i repeat it need more execution time for larger records
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.
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.
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.
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.