I want to calculate blending ratios, but I have runtime problems,
It seems 7 components with a precision of 4% are the limit in PHP ...
So I try to find a way to avoid superfluous loops, to calculate faster.
I have ingredients and a limit (here 6). I want find all combinations
lower than 6. In a secound step (not shown here) I just order these
hits after price. I would like to find the cheapest combination with
ingredient_x lower than 6.
I try two ways, to do this, just simple sum the ingredients, and step by step, to abort the loops early (to safe loops).
<?php
$beginn = microtime(true);
$loops = 0;
$hits = 0;
$m = array();
// 1. TEST
$component[0]['ingredient_x'] = 6.95;
$component[1]['ingredient_x'] = 65.7;
$component[2]['ingredient_x'] = '';
$component[3]['ingredient_x'] = 2;
$component[4]['ingredient_x'] = '';
$component[5]['ingredient_x'] = '';
$component[6]['ingredient_x'] = '';
/*
Results:
With abort Loops
Loops: 285.188
Hits: 285.077
Seconds: 1,707 sec.
Without:
Loops: 736.281
Hits: 285.077
Seconds: 6,582 sec.
*/
// 2. TEST
$component[0]['ingredient_x'] = 6.95;
$component[1]['ingredient_x'] = 6.7;
$component[2]['ingredient_x'] = '';
$component[3]['ingredient_x'] = 2;
$component[4]['ingredient_x'] = '';
$component[5]['ingredient_x'] = '';
$component[6]['ingredient_x'] = '';
/*
Results:
With abort Loops
Loops: 735.244
Hits: 735.167
Seconds: 4,467 sec.
Without:
Loops: 736.281
Hits: 735.167
Seconds: 3,191 sec.
*/
$abort_loops = 1;
for ($m[0] = 0; $m[0] <= 100; $m[0] += 4) {
for ($m[1] = 0; $m[1] <= (100 - $m[0]); $m[1] += 4) {
for ($m[2] = 0; $m[2] <= (100 - $m[0] - $m[1]); $m[2] += 4) {
for ($m[3] = 0; $m[3] <= (100 - $m[0] - $m[1] - $m[2]); $m[3] += 4) {
for ($m[4] = 0; $m[4] <= (100 - $m[0] - $m[1] - $m[2] - $m[3]); $m[4] += 4) {
for ($m[5] = 0; $m[5] <= (100 - $m[0] - $m[1] - $m[2] - $m[3] - $m[4]); $m[5] += 4) {
$m[6] = (100 - $m[0] - $m[1] - $m[2] - $m[3] - $m[4] - $m[5]);
$loops++;
if ($abort_loops) {
$r = 0;
$do_break = 0;
// Checking ingredient_x sum, component by component
for ($i = 0; $i <= 6; $i++) {
$r += ($m[$i] * ($component[$i]['ingredient_x'] / 100));
// If Limit is reached, end all following loops
if ($r > 6) {
$m[$i] = 100;
// Cant break here, because of inner loop ...
$do_break = 1;
}
}
// ... so do it outside
if ($do_break) {
break;
}
$hits++;
} else {
// just sum ingredient_x
$r = ($m[0] * ($component[0]['ingredient_x'] / 100)) +
($m[1] * ($component[1]['ingredient_x'] / 100)) +
($m[2] * ($component[2]['ingredient_x'] / 100)) +
($m[3] * ($component[3]['ingredient_x'] / 100)) +
($m[4] * ($component[4]['ingredient_x'] / 100)) +
($m[5] * ($component[5]['ingredient_x'] / 100)) +
($m[6] * ($component[6]['ingredient_x'] / 100));
if ($r <= 6) {
$hits++;
}
}
}
}
}
}
}
}
print('Loops: ' . number_format($loops, 0, '', '.') . '<br>');
print('Hits: ' . number_format($hits, 0, '', '.') . '<br>');
print('Seconds: ' . number_format((microtime(true) - $beginn), 3, ',', '') . ' sec.');
?>
But this depends very on the ingredients. As you can see in Test 1
(abort loops is 5 times faster) and Test 2 (the simple sum way is faster).
Is it possible to make these loop faster? Better "abort loop" code?
Is there a faster way to find the cheapest combination?
One approach is to use simple math to improve your summing up:
// just sum ingredient_x
$r = (
$m[0] * $component[0]['ingredient_x'] +
$m[1] * $component[1]['ingredient_x'] +
$m[2] * $component[2]['ingredient_x'] +
$m[3] * $component[3]['ingredient_x'] +
$m[4] * $component[4]['ingredient_x'] +
$m[5] * $component[5]['ingredient_x'] +
$m[6] * $component[6]['ingredient_x']
);
;
if ($r <= 600) {
$hits++;
}
Allt thos divisons by 100 can be completely dropped if you simply raise your limit to 100 times your limit.
a0 / 100 + a1 / 100 + a2/100 + ... a6/100
is the same as
(a0 + a1 + a2 + ... + a6) /100
This will just need one division instead of 7.
Even this division can be avoided if you adjust your limit to 100 * your limit.
Speed improvement on my machine is about 10-15%.
Related
I need to validate that an inputted number is a valid number based on my stepping rules and round up to the nearest valid number if not. These numbers will change but one example would be:
$min = 0.25;
$step = 0.1
$qty = 0.75 // user input
so these would be valid inputs:
0.75
0.85
0.95
But these should round:
0.76 (to 0.85)
0.80 (to 0.85)
I thought I could use modulus somehow but not getting the calculation correct.
if (($qty % min) / $step == 0)) {
echo "good";
}
I've tried some variations of math that are likely very wrong
$step = 0.1;
$min = 0.25;
$qty = .85;
$h = ($qty / $min) / $step;
echo $h;
$j = mround($qty, $min-$step);
echo $j;
function mround($num, $parts) {
if ($parts <= 0) { $parts = 1; }
$res = $num * (1/$parts);
$res = round($res);
return $res /(1/$parts);
}
I think you can use fmod to do this.
$new = $original + ($step - fmod($original - $minimum, $step));
Example on 3v4l.org
I need to return a result of (1 / n!) * (1! + 2! + 3! + ... + n!), n>=1.
This is a CodeWars challenge! The code below returns 1.146652 for n = 8, but the correct result is 1.1466510000000001 or 1.146651.
How can I truncate this number correctly?
function factorial($val){
$factor = 1;
for($i=1;$i<=$val;$i++){
$factor *= $i;
}
return $factor;
}
function going($n) {
$val = 1/factorial($n);
$somatorio = 0;
for($i=1;$i<=$n;$i++){
$somatorio += factorial($i);
}
return round($val * $somatorio,6);
}
I have some cases as follows.
1? 2?
?2? ??3
? ?
?5 ?0
Now what I am supposed to do is to find some values in place of question marks, that would give produce the minimum possible difference between the 2 numbers.
Answers Should be like
19 20
023 023
0 0
05 00
Note : the number which will be produced after the minimum absolute difference between the 2 values must be smallest. As in, the last case could be 15 and 10 with the absolute difference to be 5 but it is invalid.
I tried some permutation combination ideas for replacing the question marks for both numbers individually and then find out the number but the length of the number could go up to 18 digits per number. Hence I believe it wouldn't be a good idea.
Then I tried to search for similar questions but that didn't help.
I still think that regex could be helpful to solve this question but am stuck with how to do it.
Any help is welcome!! Thanx!
The language shall be Php.. I am working with Php.
Okay, I got a solution.
Explanation:
Uses regex to grab the two numbers, then compares them in pairs from left to right, starting with the assumption they're equal. Meaning they both resolve to the same number wherever possible, or 0 if they are both ?.
After there is a pair of numbers that aren't equal, it starts setting the lower ones ?'s to 9, and the higher ones ?'s to 0, to make them as close as possible.
Here is an example of it in action.
function minDiff($str) {
preg_match("/([\d\?]+) ([\d\?]+)/", $str, $matches);
$first = $matches[1];
$second = $matches[2];
$biggest = 0; // -1 = first, 0 = none, 1 = second
$firstResult = 0;
$secondResult = 0;
for ($i = 0; $i < strlen($first); $i++) {
$powerValue = strlen($first) - $i - 1;
if ($biggest != 0) { // not equal
if (!strcmp($first[$i], '?') && !strcmp($second[$i], '?')) {
if ($biggest > 0) { // second is biggest
$firstResult += 9 * pow(10, $powerValue);
} else { // first is biggest
$secondResult += 9 * pow(10, $powerValue);
}
} elseif (!strcmp($first[$i], '?')) {
if ($biggest > 0) { // second is biggest
$firstResult += 9 * pow(10, $powerValue);
}
$secondResult += $second[$i] * pow(10, $powerValue);
} elseif (!strcmp($second[$i], '?')) {
if ($biggest < 0) { // first is biggest
$secondResult += 9 * pow(10, $powerValue);
}
$firstResult += $first[$i] * pow(10, $powerValue);
} else {
$firstResult += $first[$i] * pow(10, $powerValue);
$secondResult += $second[$i] * pow(10, $powerValue);
}
} else { // both equal (so far)
if (!strcmp($first[$i], '?')) {
$firstResult += $second[$i] * pow(10, $powerValue);
$secondResult += $second[$i] * pow(10, $powerValue);
} elseif (!strcmp($second[$i], '?')) {
$firstResult += $first[$i] * pow(10, $powerValue);
$secondResult += $first[$i] * pow(10, $powerValue);
} else {
if (intval($first[$i]) > intval($second[$i])) {
$biggest = -1;
} elseif (intval($first[$i]) < intval($second[$i])) {
$biggest = 1;
}
$firstResult += $first[$i] * pow(10, $powerValue);
$secondResult += $second[$i] * pow(10, $powerValue);
}
// Find if next number will change
if (($i + 1) < strlen($first) && strcmp($first[$i + 1], '?') && strcmp($second[$i + 1], '?')) {
$diff = preg_replace('/\?/', '0', substr($first, $i + 1)) - preg_replace('/\?/', '0', substr($second, $i + 1));
echo "$diff\n";
// Check to see if you need to add 1 to the value for this loop
if ($diff > pow(10, $powerValue) / 2) {
$secondResult += pow(10, $powerValue);
$biggest = 1;
} elseif ($diff < pow(10, $powerValue) / -2) {
$firstResult += pow(10, $powerValue);
$biggest = -1;
}
}
}
}
echo "first: ".str_pad($firstResult, strlen($first), "0", STR_PAD_LEFT)."\n";
echo "second: ".str_pad($secondResult, strlen($second), "0", STR_PAD_LEFT)."\n\n";
}
I am trying to implement the levenshtein algorithm with a little addon. I want to prioritize values that have consecutive matching letters. I've tried implementing my own form of it using the code below:
function levenshtein_rating($string1, $string2) {
$GLOBALS['lvn_memo'] = array();
return lev($string1, 0, strlen($string1), $string2, 0, strlen($string2));
}
function lev($s1, $s1x, $s1l, $s2, $s2x, $s2l, $cons = 0) {
$key = $s1x . "," . $s1l . "," . $s2x . "," . $s2l;
if (isset($GLOBALS['lvn_memo'][$key])) return $GLOBALS['lvn_memo'][$key];
if ($s1l == 0) return $s2l;
if ($s2l == 0) return $s1l;
$cost = 0;
if ($s1[$s1x] != $s2[$s2x]) $cost = 1;
else $cons -= 0.1;
$dist = min(
(lev($s1, $s1x + 1, $s1l - 1, $s2, $s2x, $s2l, $cons) + 1),
(lev($s1, $s1x, $s1l, $s2, $s2x + 1, $s2l - 1, $cons) + 1),
(lev($s1, $s1x + 1, $s1l - 1, $s2, $s2x + 1, $s2l - 1, $cons) + $cost)
);
$GLOBALS['lvn_memo'][$key] = $dist + $cons;
return $dist + $cons;
}
You should note the $cons -= 0.1; is the part where I am adding a value to prioritize consecutive values. This formula will be checking against a large database of strings. (As high as 20,000 - 50,000) I've done a benchmark test with PHP's built in levenshtein
Message Time Change Memory
PHP N/A 9300128
End PHP 1ms 9300864
End Mine 20ms 9310736
Array
(
[0] => 3
[1] => 3
[2] => 0
)
Array
(
[0] => 2.5
[1] => 1.9
[2] => -1.5
)
Benchmark Test Code:
$string1 = "kitten";
$string2 = "sitter";
$string3 = "sitting";
$log = new Logger("PHP");
$distances = array();
$distances[] = levenshtein($string1, $string3);
$distances[] = levenshtein($string2, $string3);
$distances[] = levenshtein($string3, $string3);
$log->log("End PHP");
$distances2 = array();
$distances2[] = levenshtein_rating($string1, $string3);
$distances2[] = levenshtein_rating($string2, $string3);
$distances2[] = levenshtein_rating($string3, $string3);
$log->log("End Mine");
echo $log->status();
echo "<pre>" . print_r($distances, true) . "</pre>";
echo "<pre>" . print_r($distances2, true) . "</pre>";
I recognize that PHP's built in function will probably always be faster than mine by nature. But I am wondering if there is a way to speed mine up?
So the question: Is there a way to speed this up? My alternative here is to run levenshtein and then search through the highest X results of that and prioritize them additionally.
Based on Leigh's comment, copying PHP's built in form of Levenhstein lowered the time down to 3ms. (EDIT: Posted the version with consecutive character deductions. This may need tweaked, by appears to work.)
function levenshtein_rating($s1, $s2, $cons = 0, $cost_ins = 1, $cost_rep = 1, $cost_del = 1) {
$s1l = strlen($s1);
$s2l = strlen($s2);
if ($s1l == 0) return $s2l;
if ($s2l == 0) return $s1l;
$p1 = array();
$p2 = array();
for ($i2 = 0; $i2 <= $s2l; ++$i2) {
$p1[$i2] = $i2 * $cost_ins;
}
$cons = 0;
$cons_count = 0;
$cln = 0;
$tbl = $s1;
$lst = false;
for ($i1 = 0; $i1 < $s1l; ++$i1) {
$p2[0] = $p1[0] + $cost_del;
$srch = true;
for($i2 = 0; $i2 < $s2l; ++ $i2) {
$c0 = $p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep);
if ($srch && $s2[$i2] == $tbl[$i1]) {
$tbl[$i1] = "\0";
$srch = false;
$cln += ($cln == 0) ? 1 : $cln * 1;
}
$c1 = $p1[$i2 + 1] + $cost_del;
if ($c1 < $c0) $c0 = $c1;
$c2 = $p2[$i2] + $cost_ins;
if ($c2 < $c0) $c0 = $c2;
$p2[$i2 + 1] = $c0;
}
if (!$srch && $lst) {
$cons_count += $cln;
$cln = 0;
}
$lst = $srch;
$tmp = $p1;
$p1 = $p2;
$p2 = $tmp;
}
$cons_count += $cln;
$cons = -1 * ($cons_count * 0.1);
return $p1[$s2l] + $cons;
}
I think the major slowdown in your function is the fact that it's recursive.
As I've said in my comments, PHP function calls are notoriously heavy work for the engine.
PHP itself implements levenshtein as a loop, keeping a running total of the cost incurred for inserts, replacements and deletes.
I'm sure if you converted your code to a loop as well you'd see some massive performance increases.
I don't know exactly what your code is doing, but I have ported the native C code to PHP to give you a starting point.
define('LEVENSHTEIN_MAX_LENGTH', 12);
function lev2($s1, $s2, $cost_ins = 1, $cost_rep = 1, $cost_del = 1)
{
$l1 = strlen($s1);
$l2 = strlen($s2);
if ($l1 == 0) {
return $l2 * $cost_ins;
}
if ($l2 == 0) {
return $l1 * $cost_del;
}
if (($l1 > LEVENSHTEIN_MAX_LENGTH) || ($l2 > LEVENSHTEIN_MAX_LENGTH)) {
return -1;
}
$p1 = array();
$p2 = array();
for ($i2 = 0; $i2 <= $l2; $i2++) {
$p1[$i2] = $i2 * $cost_ins;
}
for ($i1 = 0; $i1 < $l1; $i1++) {
$p2[0] = $p1[0] + $cost_del;
for ($i2 = 0; $i2 < $l2; $i2++) {
$c0 = $p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep);
$c1 = $p1[$i2 + 1] + $cost_del;
if ($c1 < $c0) {
$c0 = $c1;
}
$c2 = $p2[$i2] + $cost_ins;
if ($c2 < $c0) {
$c0 = $c2;
}
$p2[$i2 + 1] = $c0;
}
$tmp = $p1;
$p1 = $p2;
$p2 = $tmp;
}
return $p1[$l2];
}
I did a quick benchmark comparing yours, mine, and PHPs internal functions, 100,000 iterations each, time is in seconds.
float(12.954766988754)
float(2.4660499095917)
float(0.14857912063599)
Obviously it hasn't got your tweaks in it yet, but I'm sure they wont slow it down that much.
If you really need more of a speed boost, once you have worked out how to change this function, it should be easy enough to port your changes back into C, make a copy of PHPs function definitions, and implement your own native C version of your modified function.
There's lots of tutorials out there on how to make PHP extensions, so you shouldn't have that much difficulty if you decide to go down that route.
Edit:
Was looking at ways to improve it further, I noticed
$c0 = $p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep);
$c1 = $p1[$i2 + 1] + $cost_del;
if ($c1 < $c0) {
$c0 = $c1;
}
$c2 = $p2[$i2] + $cost_ins;
if ($c2 < $c0) {
$c0 = $c2;
}
Is the same as
$c0 = min(
$p1[$i2 + 1] + $cost_del,
$p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep),
$c2 = $p2[$i2] + $cost_ins
);
Which I think directly relates to the min block in your code. However, this slows down the code quite significantly. (I guess its the overhead of the extra function call)
Benchmarks with the min() block as the second timing.
float(2.484846830368)
float(3.6055288314819)
You were right about the second $cost_ins not belonging - copy/paste fail on my part.
I want to generate alphanumeric unique numbers but the format should be like this
that should be starts from AA001 to AA999 after that AB001 to AB999 .... BA001 to BA999 end with ZZ999. if i give the input is
1 = result AA001
999 = result AA999
1000 = result AB001
any one can help this ?
Complete solution (see it running):
function formatNum1000($num) {
$tail = $num % 1000;
$head = (int)($num / 1000);
$char1 = chr(ord('A') + (int)($head / 26));
$char2 = chr(ord('A') + ($head % 26));
return sprintf('%s%s%03d', $char1, $char2, $tail);
}
function formatNum999($num) {
$tail = (($num - 1 ) % 999) + 1;
$head = (int)(($num - $tail) / 999);
$char1 = chr(ord('A') + (int)($head / 26));
$char2 = chr(ord('A') + ($head % 26));
return sprintf('%s%s%03d', $char1, $char2, $tail);
}
$ns = array(1, 500, 999, 1000, 1998, 1999, 2000, 25974, 25975, 25999, 26000, 675324, 675999);
foreach($ns as $n) {
$formatted1000 = formatNum1000($n);
$formatted999 = formatNum999 ($n);
echo "Num: $n => $formatted1000 / $formatted999\n";
}
Note: you need to make sure that the input number is within the valid range (0...675999 when including 000-numbers, 1...675324 otherwise)
Note: answer revised, missed the point earlier that 000 is not allowed
How about:
$start = 'AA997';
for($i = 0; $i < 5; $i++) {
$start++;
if (substr($start, 2) == '000') continue;
echo $start,"\n";
}
output:
AA998
AA999
AB001
AB002