Evaluate multiple conditions stored as a string - php

I have a few strings stored in a database which contain specific rules which must be met. The rules are like this:
>25
>25 and < 82
even and > 100
even and > 10 or odd and < 21
Given a number and a string, what is the best way to evaluate it in PHP?
eg. Given the number 3 and the string "even and > 10 or odd and < 21" this would evaluate to TRUE
Thanks
Mitch

As mentioned in the comments, the solution to this can be very simple or very complex.
I've thrown together a function that will work with the examples you've given:
function ruleToExpression($rule) {
$pattern = '/^( +(and|or) +(even|odd|[<>]=? *[0-9]+))+$/';
if (!preg_match($pattern, ' and ' . $rule)) {
throw new Exception('Invalid expression');
}
$find = array('even', 'odd', 'and', 'or');
$replace = array('%2==0', '%2==1', ') && ($x', ')) || (($x');
return '(($x' . str_replace($find, $replace, $rule) . '))';
}
function evaluateExpr($expr, $val) {
$x = $val;
return eval("return ({$expr});");
}
This supports multiple clauses separated by and and or, with no parentheses and the and always being evaluated first. Each clause can be even, odd, or a comparison to a number, allowing >, <, >=, and <= comparisons.
It works by comparing the entire rule against a regular expression pattern to ensure its syntax is valid and supported. If it passes that test, then the string replacements that follow will successfully convert it to an executable expression hard-coded against the variable $x.
As an example:
ruleToExpression('>25');
// (($x>25))
ruleToExpression('>25 and < 82');
// (($x>25 ) && ($x < 82))
ruleToExpression('even and > 100');
// (($x%2==0 ) && ($x > 100))
ruleToExpression('even and > 10 or odd and < 21');
// (($x%2==0 ) && ($x > 10 )) || (($x %2==1 ) && ($x < 21))
evaluateExpr(ruleToExpression('even and >25'), 31);
// false
evaluateExpr(ruleToExpression('even and >25'), 32);
// true
evaluateExpr(ruleToExpression('even and > 10 or odd and < 21'), 3);
// true

Why don't you translate the string even to maths? If you use mods you can write it like that $number % 2 == 0. In that case, your example will be:
if(($number % 2 == 0 && $number > 10 ) || ($number % 2 != 0 && $number < 21)){
//Then it is true!
}

Related

What is the best / fastest way to validate this string format "A000AA00" in PHP?

I need to verify that my string $path contains exactly 8 characters and that $path is in the following pattern: A000AA00 where A is any letter A-Z and 0 is any number 0-9.
The first thing I did was that I used strlen to get the string length.
if (strlen($path) !== 8) { die('Bad string length'); }
Next I used ctype_alpha and ctype_digit to check if the string is in the format I want based on what I expect $path[0-7] to be.
if (ctype_alpha($path[0]) && ctype_digit($path[1]) && ctype_digit($path[2]) && ctype_digit($path[3]) && ctype_alpha($path[4]) && ctype_alpha($path[5]) && ctype_digit($path[6]) && ctype_digit($path[7])) { // We good }
Could I improve this code somehow?
Are there any faster alternatives?
If you plan to use a regex to validate such a pattern, you may try
preg_match('~^[A-Z]\d{3}[A-Z]{2}\d{2}\z~', $s)
The regex approach is very readable: start with an uppercase letter, 3 digits, 2 uppercase letters, 2 digits, end of the string.
Now,
$path = "A000AA00";
$startA = microtime(true);
for($i = 0; $i < 100000; $i++)
{
if (strlen($path) !== 8) { die('Bad string length'); }
if (ctype_alpha($path[0]) && ctype_digit($path[1]) && ctype_digit($path[2]) && ctype_digit($path[3]) && ctype_alpha($path[4]) && ctype_alpha($path[5]) && ctype_digit($path[6]) && ctype_digit($path[7])) { // We good
}
}
$endA = microtime(true);
echo $endA-$startA;
yields 0.02226710319519 (PHP 7.3.2), and the regex based solution yields 0.0064888000488281.

Time complexity of an algorithm: find length of a longest palindromic substring

I've written a small PHP function to find a length of a longest palindromic substring of a string. To avoid many loops I've used a recursion.
The idea behind algorithm is, to loop through an array and for each center (including centers between characters and on a character), recursively check left and right caret values for equality. Iteration for a particular center ends when characters are not equal or one of the carets is out of the array (word) range.
Questions:
1) Could you please write a math calculations which should be used to explain time complexity of this algorithm? In my understanding its O(n^2), but I'm struggling to confirm that with a detailed calculations.
2) What do you think about this solution, any improvement suggestions (considering it was written in 45 mins just for practice)? Are there better approaches from the time complexity perspective?
To simplify the example I've dropped some input checks (more in comments).
Thanks guys, cheers.
<?php
/**
* Find length of the longest palindromic substring of a string.
*
* O(n^2)
* questions by developer
* 1) Is the solution meant to be case sensitive? (no)
* 2) Do phrase palindromes need to be taken into account? (no)
* 3) What about punctuation? (no)
*/
$input = 'tttabcbarabb';
$input2 = 'taat';
$input3 = 'aaaaaa';
$input4 = 'ccc';
$input5 = 'bbbb';
$input6 = 'axvfdaaaaagdgre';
$input7 = 'adsasdabcgeeegcbgtrhtyjtj';
function getLenRecursive($l, $r, $word)
{
if ($word === null || strlen($word) === 0) {
return 0;
}
if ($l < 0 || !isset($word[$r]) || $word[$l] != $word[$r]) {
$longest = ($r - 1) - ($l + 1) + 1;
return !$longest ? 1 : $longest;
}
--$l;
++$r;
return getLenRecursive($l, $r, $word);
}
function getLongestPalSubstrLength($inp)
{
if ($inp === null || strlen($inp) === 0) {
return 0;
}
$longestLength = 1;
for ($i = 0; $i <= strlen($inp); $i++) {
$l = $i - 1;
$r = $i + 1;
$length = getLenRecursive($l, $r, $inp); # around char
if ($i > 0) {
$length2 = getLenRecursive($l, $i, $inp); # around center
$longerOne = $length > $length2 ? $length : $length2;
} else {
$longerOne = $length;
}
$longestLength = $longerOne > $longestLength ? $longerOne : $longestLength;
}
return $longestLength;
}
echo 'expected: 5, got: ';
var_dump(getLongestPalSubstrLength($input));
echo 'expected: 4, got: ';
var_dump(getLongestPalSubstrLength($input2));
echo 'expected: 6, got: ';
var_dump(getLongestPalSubstrLength($input3));
echo 'expected: 3, got: ';
var_dump(getLongestPalSubstrLength($input4));
echo 'expected: 4, got: ';
var_dump(getLongestPalSubstrLength($input5));
echo 'expected: 5, got: ';
var_dump(getLongestPalSubstrLength($input6));
echo 'expected: 9, got: ';
var_dump(getLongestPalSubstrLength($input7));
Your code doesn't really need to be recursive. A simple while loop would do just fine.
Yes, complexity is O(N^2). You have N options for selecting the middle point. The number of recursion steps goes from 1 to N/2. The sum of all that is 2 * (N/2) * (n/2 + 1) /2 and that is O(N^2).
For code review, I wouldn't do recursion here since it's fairly straightforward and you don't need the stack at all. I would replace it with a while loop (still in a separate function, to make the code more readable).

How to check if a number (float or integer) is within a range (0 - 100)

I'm looking for the fastest way to get this test.
So functions, operands and everything else is allowed.
I tried with the following regex (I'm not an expert):
0\.[0-9]+|100\.0+|100|[1-9]\d{0,1}\.{0,1}[0-9]+
It works except that it erroneously accept 0.0 or 0.000000 and so on.
Also it's not the most appropriated and fastest way.
(if anybody wants to fix the regex to don't allow those 0.00 values it would be appreciated)`
No need for regex:
if (is_numeric($val) && $val > 0 && $val <= 100)
{
echo '$val is number (int or float) between 0 and 100';
}
Demo
Update
It turns out you're getting the numeric values from a string. In that case, it would be best to extract all of them using a regex, something like:
if (preg_match_all('/\d+\.?\d*/', $string, $allNumbers))
{
$valid = [];
foreach ($allNumbers[0] as $num)
{
if ($num > 0 && $num <= 100)
$valid[] = $num;
}
}
You can leave out the is_numeric check, because the matched strings are guaranteed to be numeric anyway...
Use bccomp
This is a perfect use case for BCMath functions.
function compare_numberic_strings($number) {
if (
is_numeric($number) &&
bccomp($number, '0') === 1 &&
bccomp($number, '100') === -1
) {
return true;
}
return false;
}
echo compare_numberic_strings('0.00001');
//returns true
echo compare_numberic_strings('50');
//returns true
echo compare_numeric_strings('100.1');
//returns false
echo compare_numeric_strings('-0.1');
//returns false
From the manual:
Returns 0 if the two operands are equal, 1 if the left_operand is
larger than the right_operand, -1 otherwise.
I think your regex pattern should look like this:
^\d{1,2}$|(100)
Demo

Ranking document based on searched terms

How can I implement this
tf-idf(WORD) = occurrences(WORD,DOCUMENT) / number-of-words(DOCUMENT) * log10 ( documents(ALL) / ( 1 + documents(WORD, ALL) ) )
into my PHP codings for ranking search results?
Can refer here for the current codings:
https://stackoverflow.com/a/8574651/1107551
I only understand part of what you are asking for, but I think I can help you with the occurrences(WORD,DOCUMENT) / number-of-words(DOCUMENT) part:
<?php
function rank($word, $document)
{
// Swap newlines for spaces, you'll see why
$document = str_replace("\n",' ',$document);
// Remove special characters except '-' from the string
for($i = 0; $i <= 127; $i++)
{
// Space is allowed, Hyphen is a legitimate part of some words. Also allow range for 0-9, A-Z, and a-z
// Extended ASCII (128 - 255) is purposfully excluded from this since it isn't often used
if($i != 32 && $i != 45 && !($i >= 48 && $i <=57) && !($i >= 65 && $i <= 90) && !($i >= 97 && $i <= 122))
$document = str_replace(chr($i),'',$document);
}
// Split the document on spaces. This gives us individual words
$tmpDoc = explode(' ',trim($document));
// Get the number of elements with $word in them
$occur = count(array_keys($tmpDoc,$word));
// Get the total number of elements
$numWords = count($tmpDoc);
return $occur / $numWords;
}
?>
I'm sure there are more efficient ways to do this, but there are surely also much worse ways.
Note: I did not test the PHP code

Best way to check for positive integer (PHP)?

I need to check for a form input value to be a positive integer (not just an integer), and I noticed another snippet using the code below:
$i = $user_input_value;
if (!is_numeric($i) || $i < 1 || $i != round($i)) {
return TRUE;
}
I was wondering if there's any advantage to using the three checks above, instead of just doing something like so:
$i = $user_input_value;
if (!is_int($i) && $i < 1) {
return TRUE;
}
Not sure why there's no suggestion to use filter_var on this. I know it's an old thread, but maybe it will help someone out (after all, I ended up here, right?).
$filter_options = array(
'options' => array( 'min_range' => 0)
);
if( filter_var( $i, FILTER_VALIDATE_INT, $filter_options ) !== FALSE) {
...
}
You could also add a maximum value as well.
$filter_options = array(
'options' => array( 'min_range' => 0,
'max_range' => 100 )
);
Learn more about filters.
the difference between your two code snippets is that is_numeric($i) also returns true if $i is a numeric string, but is_int($i) only returns true if $i is an integer and not if $i is an integer string. That is why you should use the first code snippet if you also want to return true if $i is an integer string (e.g. if $i == "19" and not $i == 19).
See these references for more information:
php is_numeric function
php is_int function
The best way for checking for positive integers when the variable can be INTEGER or STRING representing the integer:
if ((is_int($value) || ctype_digit($value)) && (int)$value > 0 ) { // int }
is_int() will return true if the value type is integer. ctype_digit() will return true if the type is string but the value of the string is an integer.
The difference between this check and is_numeric() is that is_numeric() will return true even for the values that represent numbers that are not integers (e.g. "+0.123").
It's definitely heading towards the land of micro-optimisation, but hey: the code I'm working on chews through millions of items every day and it's Friday. So I did a little bit of experimenting...
for ($i = 0; $i < 1000000; $i++) {
// Option 1: simple casting/equivalence testing
if ((int) $value == $value && $value > 0) { ... }
// Option 2: using is_int() and ctype_digit(). Note that ctype_digit implicitly rejects negative values!
if ((is_int($value) && $value > 0) || ctype_digit($value)) { ... }
// Option 3: regular expressions
if (preg_match('/^\d+$/', $value)) { ... }
}
I then ran the above tests for both integer and string values
Option 1: simple casting/equivalence testing
Integer: 0.3s
String: 0.4s
Option 2: using is_int() and ctype_digit()
Integer: 0.9s
String: 1.45s
Option 3: regular expressions
Integer: 1.83s
String: 1.60s
Perhaps unsurprisingly, option 1 is by far the quickest, since there's no function calls, just casting. It's also worth noting that unlike the other methods, option 1 treats the string-float-integer value "5.0" as an integer:
$valList = array(5, '5', '5.0', -5, '-5', 'fred');
foreach ($valList as $value) {
if ((int) $value == $value && $value > 0) {
print "Yes: " . var_export($value, true) . " is a positive integer\n";
} else {
print "No: " . var_export($value, true) . " is not a positive integer\n";
}
}
Yes: 5 is a positive integer
Yes: '5' is a positive integer
Yes: '5.0' is a positive integer
No: -5 is not a positive integer
No: '-5' is not a positive integer
No: 'fred' is not a positive integer
Whether or not that's a good thing for your particular use-case is left as an exercise for the reader...
The other best way to check a Integer number is using regular expression. You can use the following code to check Integer value. It will false for float values.
if(preg_match('/^\d+$/',$i)) {
// valid input.
} else {
// invalid input.
}
It's better if you can check whether $i > 0 too.
preg_match('{^[0-9]*$}',$string))
and if you want to limit the length:
preg_match('{^[0-9]{1,3}$}',$string)) //minimum of 1 max of 3
So pisitive int with a max length of 6:
if(preg_match('{^[0-9]{1,6}$}',$string)) && $string >= 0)
You don't really need to use all three check and if you want a positive integer you might want to do the opposite of what is in your code:
if(is_numeric($i) && $i >= 0) { return true; }
Check Sören's answer for more information concerning the difference between is_int() and is_numeric()
if(preg_match('/^[1-9]\d*$/',$i)) {
//Positive and > 0
}
Rather than checking for int OR string with multiple conditions like:
if ( ctype_digit($i) || ( is_int($i) && $i > 0 ) )
{
return TRUE;
}
you can simplify this by just casting the input to (string) so that the one ctype_digit call will check both string and int inputs:
if( ctype_digit( (string)$i ) )
{
return TRUE;
}
In addition to all the other answers: You are probably looking for ctype_digit. It looks for a string containing only digits.
Definition:
!A = !is_numeric($i)
B = $i < 1
!C = $i != round($i)
Then...
!is_numeric($i) || $i < 1 || $i != round($i) is equal to
!A || B || !C
So:
!A || B || !C = !A || !C || B
Now, using the deMorgan theorem, i.e. (!A || !C) = (A && C), then:
!A || !C || B = (A && C) || B
Now, note that A && C = is_numeric($i) && $i == round($i), but if $i == round($i) is TRUE, then is_numeric($i) is TRUE as well, so we can simplify A && C = C so,
(A && C) || B = C || B =
$i == round($i) || $i < 1
So you just need to use:
$i = $user_input_value;
if ($i == round($i) || $i < 1) {
return TRUE;
}
Laravel 4.2 Validation rule for positive number
It takes only positive numbers including float values.
public static $rules = array(
'field_name' => 'required|regex:/^\d*\.?\d*$/'
);
e.g:20,2.6,06
The first example is using round to verify that the input is an integer, and not a different numeric value (ie: a decimal).
is_int will return false if passed a string. See the PHP manual examples for is_int
To check for positive integer use:
$i = $user_input_value;
if (is_int($i) && $i > 0) {
return true; //or any other instructions
}
OR
$i = $user_input_value;
if (!is_int($i) || $i < 1) {
return false; //or any other instructions
}
Use the one that fits your purpose as they are the same. The following examples demonstrate the difference between is_numeric() and is_int():
is_numeric(0); // returns true
is_numeric(7); // returns true
is_numeric(-7); // returns true
is_numeric(7.2); // returns true
is_numeric("7"); // returns true
is_numeric("-7"); // returns true
is_numeric("7.2"); // returns true
is_numeric("abc"); // returns false
is_int(0); // returns true
is_int(7); // returns true
is_int(-7); // returns true
is_int(7.2); // returns false
is_int("7"); // returns false
is_int("-7"); // returns false
is_int("7.2"); // returns false
is_int("abc"); // returns false
All these answers overlook the fact that the requestor may checking form input.
The is_int() will fail because the form input is a string.
is_numeric() will be true also for float numbers.
That is why the $i == round($i) comes in as it checks for the input being a whole number.
Ok, I know this thread is really old but I share #Jeffrey Vdovjak's opinion: since I was able to find it, it might still help someone else out there.
php's gmp_sign() might be another easy way to check. It works for integer and numeric strings, and returns 1 if a is positive, -1 if a is negative, and 0 if a is zero.
So:
// positive
echo gmp_sign("500") . "\n";
// negative
echo gmp_sign("-500") . "\n";
// zero
echo gmp_sign("0") . "\n";
will output:
1
-1
0
See function manual at http://php.net/manual/en/function.gmp-sign.php
P.S. You'll need to have php_gmp.dll enabled in your .ini file.
This's my solution, hope helpful :
if (is_numeric($i) && (intval($i) == floatval($i)) && intval($i) > 0)
echo "positive integer";
i check if string is numeric, second check to sure it's integer and third to sure it positive
If you use "is_int" the variable must be integer, so it can't be a float value. (no round needed).
if(isset($i) && is_int($i) && $i >= 0){ //0 is technically a postive integer I suppose
return TRUE; //or FALSE I think in your case.
}
I would do something like this:
if ((int) $i > 0) {
// this number is positive
}
The number gets typecast to a positive or negative number depending on the minus sign being at the front. Then compares the typecast number to being greater than 0 to determine if the number is positive.

Categories