How to output variance in very similar strings into an array? - php

I have something like this array of strings, as seen they are very similar, with the exception of the same one place in the string:
$strings = [
'This is +1% better than last time!',
'This is +2% better than last time!',
'This is +3% better than last time!',
'This is +4% better than last time!',
'This is +5% better than last time!',
...
];
// Psuedo code
From this I'd like to end up with
$array = [
'1',
'2',
'3',
'4',
'5',
...
];
// And preferrably
$string = 'This is +%s% better than last time!';
Via a function that takes any array of similar strings and outputs what is actually different in them.

If you know that your array of similar strings will only differ in one place than you can compare them from the beginning until they differ and from the end until they differ and record those offsets. Then extract the string beginning at the first difference until the last difference from each string in your array.

function myfunc($strings) {
// if the array is empty we don't have anything to do
if (count($strings) == 0) return "";
// count how many words are in a string (use the first one)
$num_tokens = 0;
$tok = strtok($strings[0], " ");
while ($tok !== false) {
$num_tokens++;
$tok = strtok(" ");
}
$output = "";
$tokens = [];
// iterate over each word of the string
for ($w = 0; $w < $num_tokens; $w++) {
// iterate over each string
for ($s = 0; $s < count($strings); $s++) {
// extract the same word of each string
$tokens[$s] = strtok($strings[$s], " ");
// remove that word from the string so it
// will not be extracted again
$strings[$s] = trim(substr($strings[$s], strlen($tokens[$s])));
}
$first_token = true;
$tmp = "";
// If all words we have extracted are equal, we add that
// word to the $output string. Otherwise we add '+%s%'.
for ($s = 0; $s < count($strings); $s++) {
// In the first iteration we just extract the word. We
// will start comparing from the next iteration.
if ($first_token) {
$tmp = $tokens[$s];
$first_token = false;
} else {
// If the words are not the same, we will add '+%s%' and
// exit the loop.
if ($tokens[$s] != $tmp) {
$tmp = "+%s%";
break;
}
}
}
// Add the word to the $output string. If it's the first
// word we don't add a white space before it.
if ($output == "") {
$output .= $tmp;
} else {
$output .= " $tmp";
}
}
return $output;
}
Example of usage:
$strings = [
'This is +1% better than last time!',
'This is +2% better than last time!',
'This is +3% better than last time!',
'This is +4% better than last time!',
'This is +5% better than last time!',
];
echo myfunc($strings);

Warning for a very long post with a lot of code!
Thanks a lot for the help everybody, all of which gave me very good hints of where to go with this. Here is my solution / class to solve this problem, which is an extension of the code of Vicente Olivert Riera's answer and the method FlyingFoX explained in his/her answer:
Link to GitHub repo
class StringDiff
{
/**
* The unformatted string to be used in the
* vsprintf call
* #var string
*/
protected $unformattedString = '';
/**
* Array with arguments replacing the %s in the
* unformatted string
* #var array
*/
protected $args = [];
/**
* Returns the arguments to be used for a vsprintf
* call along with the format string
* #return array
*/
public function getArgs()
{
return $this->args;
}
/**
* Returns the unformatted string to be used in
* a vsprint call along with the arguments
* #return string
*/
public function getUnformattedString()
{
return $this->unformattedString;
}
/**
* Takes an array argument of very similarly formatted
* strings and fills in the $unformattedString and $args
* variables from the data provided
* #param array $strings Group of very similar strings
* #return void
*/
public function run(array $strings)
{
// If there are no strings, return nothing
if (count($strings) == 0) return '';
// Replacing % with %% so the vsprintf call doesn't
// bug the arguments
$strings = str_replace('%', '%%', $strings);
$num_words = 0;
// Explodes each string into many smaller containing
// only words
foreach($strings as $key => $string)
{
$strings[$key] = explode(' ', $string);
}
$num_words = count($strings[0]);
// Array containing the indices of the substrings
// that are different
$sub_str_nr = [];
// Loops through all the words in each string
for ($n = 0; $n < $num_words; $n++)
{
// First round only sets the string to be compared with
$first_round = true;
for ($s = 0; $s < count($strings); $s++)
{
if ($first_round)
{
$first_round = false;
$tmp[0] = $strings[$s][$n];
}
else
{
$tmp[1] = $strings[$s][$n];
if ($tmp[0] == $tmp[1])
{
$tmp[0] = $tmp[1];
}
else
{
if (!in_array($n, $sub_str_nr))
{
$sub_str_nr[] = $n;
}
}
}
}
}
// Array to hold the arguments, i.e. all the strings
// that differ from each other. From these the differences
// will be deduced and put into the $this->args variable
$args = [];
foreach($sub_str_nr as $nr)
{
$tmpArgs = [];
for ($a = 0; $a < count($strings); $a++)
{
$tmpArgs[] = $strings[$a][$nr];
}
$args[] = $tmpArgs;
}
foreach($args as $key => $arg)
{
// Offset from the beginning of the string that is still the same
$front_offset = 0;
// If the offset from the beginning has been maxed
$front_flag = true;
// Offset from the end of the string that is still the same
$back_offset = 0;
// Id the offset from the end has been maxed
$back_flag = true;
// The string to be compared against is the first in line
$tmp = $arg[0];
while ($front_flag || $back_flag)
{
// Flag 1 & 2 limits to only one increase of offset per loop
$flag1 = true;
$flag2 = true;
for ($a = 1; $a < count($strings); $a++)
{
// The two following if statements compare substring
// to substring of length one
if ($front_flag && $flag1)
{
if (substr($tmp, $front_offset, 1) != substr($arg[$a], $front_offset, 1) || is_numeric(substr($arg[$a], $front_offset, 1)))
{
$front_flag = false;
}
else
{
$front_offset++;
$flag1 = false;
}
}
if ($back_flag && $flag2)
{
if (substr($tmp, strlen($tmp) - $back_offset - 1, 1) != substr($arg[$a], strlen($arg[$a]) - $back_offset - 1, 1) || is_numeric(substr($arg[$a], strlen($arg[$a]) - $back_offset - 1, 1)))
{
$back_flag = false;
}
else
{
$back_offset++;
$flag2 = false;
}
}
}
}
// Sets the $this->args variable with the found arguments
foreach($arg as $arkey => $ar)
{
$this->args[$arkey][$key] = (float)substr($arg[$arkey], $front_offset, strlen($arg[$arkey]) - $back_offset - $front_offset);
}
// Changes the strings for the unformatted string, switches
// out the varying part to %s
$strings[0][$sub_str_nr[$key]] = substr($arg[0], 0, $front_offset) . '%s' . substr($arg[0], strlen($arg[0]) - $back_offset, $back_offset);
}
// Creates the unformatted string from the array of
// words, which originates from the original long string
$unformattedString = '';
foreach($strings[0] as $string)
{
$unformattedString.= ' ' . $string;
}
// Trim whitespaces in the beginning and end of the
// formatted string
$this->unformattedString = trim($unformattedString);
return;
}
}
How to use:
$stringd = new StringDiff;
$test_array = [
"Your Cooldown Reduction cap is increased to 41% and you gain 1% Cooldown Reduction",
"Your Cooldown Reduction cap is increased to 42% and you gain 2% Cooldown Reduction",
"Your Cooldown Reduction cap is increased to 43% and you gain 3% Cooldown Reduction",
"Your Cooldown Reduction cap is increased to 44% and you gain 4% Cooldown Reduction",
"Your Cooldown Reduction cap is increased to 45% and you gain 5% Cooldown Reduction",
];
$stringd->run($test_array);
foreach($stringd->getArgs() as $arg)
{
echo vsprintf($stringd->getUnformattedString(), $arg) . '<br>';
}
Outputs:
Your Cooldown Reduction cap is increased to 41% and you gain 1% Cooldown Reduction
Your Cooldown Reduction cap is increased to 42% and you gain 2% Cooldown Reduction
Your Cooldown Reduction cap is increased to 43% and you gain 3% Cooldown Reduction
Your Cooldown Reduction cap is increased to 44% and you gain 4% Cooldown Reduction
Your Cooldown Reduction cap is increased to 45% and you gain 5% Cooldown Reduction
array(5) {
[0]=>
array(2) {
[0]=>
float(41)
[1]=>
float(1)
}
[1]=>
array(2) {
[0]=>
float(42)
[1]=>
float(2)
}
[2]=>
array(2) {
[0]=>
float(43)
[1]=>
float(3)
}
[3]=>
array(2) {
[0]=>
float(44)
[1]=>
float(4)
}
[4]=>
array(2) {
[0]=>
float(45)
[1]=>
float(5)
}
}
Your Cooldown Reduction cap is increased to %s%% and you gain %s%% Cooldown Reduction
And yes, if you are wondering, this is related to the Riot API.
If you have suggestions for improvement or any changes at all, feel free to comment down below :)

Related

Best way to search misspelled words in MYSQL [duplicate]

I was wondering if anyone knows of any library, script, or service that can spell check a string and return a suggestion of the properly spelled word or suggestions if more that one properly spelled word that it could be written in PHP.
I would prefer if there wasn't a limit on the amount of queries I could do, so not like Google's APIs.
It would be great if it could function like this:
// string to be spell checked stored in variable
$misspelledString = "The quick brown lama jumped over the lazy dog.";
//pass that variable to function
//function returns suggestion or suggestions as an array of string or strings
$suggestion = spellCheck($misspelledString);
echo "Did you mean ".$suggestion[0];
You can try the included Pspell functions:
http://php.net/manual/en/ref.pspell.php
Or an external plugin, like this one:
http://www.phpspellcheck.com/
Check this SO question for an example.
Not quite as nice an API as in your example, but Pspell would be an option. It may already be included with your system copy of PHP. You'll need aspell libraries for each language you want to check.
http://php.net/manual/en/book.pspell.php
On my debian based machine, it's included in the system repositories as a separate package, php5-pspell.
You need to have "pspell" PHP extension, you can install it on Linux using CLI:
sudo apt-get install php-pspell;
sudo service apache2 restart;
The code is very simple:
if ($word = $_GET['word']) {
$spellLink = pspell_new("en");
if (!pspell_check($spellLink, $word)) {
$suggestions = pspell_suggest($spellLink, $word);
echo '<p>Did you mean: <i>"'.$suggestions[0].'"</i>?</p>';
}
}
I attempted to create a class that takes a list of phrases and compares that to the user inputs. What I was trying to do is get things like Porshre Ceyman to correct to Porsche Cayman for example.
This class requires an array of correct terms $this->full_model_list , and an array of the user input $search_terms. I took out the contruct so you will need to pass in the full_model_list. Note, this didn't fully work so I decided to scrap it, it was adapted from someone looking to correct large sentences ...
You would call it like so:
$sth = new SearchTermHelper;
$resArr = $sth->spellCheckModelKeywords($search_terms)
Code (VERY BETA) :
<?php
/*
// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
//
// FUNCTION: Search Term Helper Class
// PURPOSE: Handles finding matches and such with search terms for keyword searching.
// DETAILS: Functions below build search combinations, find matches, look for spelling issues in words etc.
//
// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
*/
class SearchTermHelper
{
public $full_model_list;
private $inv;
// --------------------------------------------------------------------------------------------------------------
// -- return an array of metaphones for each word in a string
// --------------------------------------------------------------------------------------------------------------
private function getMetaPhone($phrase)
{
$metaphones = array();
$words = str_word_count($phrase, 1);
foreach ($words as $word) {
$metaphones[] = metaphone($word);
}
return $metaphones;
}
// --------------------------------------------------------------------------------------------------------------
// -- return the closest matching string found in $this->searchAgainst when compared to $this->input
// --------------------------------------------------------------------------------------------------------------
public function findBestMatchReturnString($searchAgainst, $input, $max_tolerance = 200, $max_length_diff = 200, $min_str = 3, $lower_case = true, $search_in_phrases = true)
{
if (empty($searchAgainst) || empty($input)) return "";
//weed out strings we thing are too small for this
if (strlen($input) <= $min_str) return $input;
$foundbestmatch = -1;
if ($lower_case) $input = strtolower($input);
//sort list or else not best matches may be found first
$counts = array();
foreach ($searchAgainst as $s) {
$counts[] = strlen($s);
}
array_multisort($counts, $searchAgainst);
//get the metaphone equivalent for the input phrase
$tempInput = implode(" ", $this->getMetaPhone($input));
$list = array();
foreach ($searchAgainst as $phrase) {
if ($lower_case) $phrase = strtolower($phrase);
if ($search_in_phrases) $phraseArr = explode(" ",$phrase);
foreach ($phraseArr as $word) {
//get the metaphone equivalent for each phrase we're searching against
$tempSearchAgainst = implode(' ', $this->getMetaPhone($word));
$similarity = levenshtein($tempInput, $tempSearchAgainst);
if ($similarity == 0) // we found an exact match
{
$closest = $word;
$foundbestmatch = 0;
echo "" . $closest . "(" . $foundbestmatch . ") <br>";
break;
}
if ($similarity <= $foundbestmatch || $foundbestmatch < 0) {
$closest = $word;
$foundbestmatch = $similarity;
//keep score
if (array_key_exists($closest, $list)) {
//echo "" . $closest . "(" . $foundbestmatch . ") <br>";
$list[$closest] += 1;
} else {
$list[$closest] = 1;
}
}
}
if ($similarity == 0 || $similarity <= $max_tolerance) break;
}
// if we find a bunch of a value, assume it to be what we wanted
if (!empty($list)) {
if ($most_occuring = array_keys($list, max($list)) && max($list) > 10) {
return $closest;
}
}
//echo "input:".$input."(".$foundbestmatch.") match: ".$closest."\n";
// disallow results to be all that much different in char length (if you want)
if (abs(strlen($closest) - strlen($input)) > $max_length_diff) return "";
// based on tolerance of difference, return if match meets this requirement (0 = exact only 1 = close, 20+ = far)
return ((int)$foundbestmatch <= (int)$max_tolerance) ? $closest : "";
}
// --------------------------------------------------------------------------------------------------------------
// -- Handles passing arrays instead of a string above ( could have done this in the func above )
// --------------------------------------------------------------------------------------------------------------
public function findBestMatchReturnArray($searchAgainst, $inputArray, $max_tolerance = 200, $max_length_diff = 200, $min_str = 3)
{
$results = array();
$tempStr = '';
foreach ($inputArray as $item) {
if ($tmpStr = $this->findBestMatchReturnString($searchAgainst, $item, $max_tolerance, $max_length_diff, $min_str))
$results[] = $tmpStr;
}
return (!empty($results)) ? $results : $results = array();
}
// --------------------------------------------------------------------------------------------------------------
// -- Build combos of search terms -- So we can check Cayman S or S Cayman etc.
// careful, this is very labor intensive ( O(n^k) )
// --------------------------------------------------------------------------------------------------------------
public function buildSearchCombinations(&$set, &$results)
{
for ($i = 0; $i < count($set); $i++) {
$results[] = $set[$i];
$tempset = $set;
array_splice($tempset, $i, 1);
$tempresults = array();
$this->buildSearchCombinations($tempset, $tempresults);
foreach ($tempresults as $res) {
$results[] = trim($set[$i]) . " " . trim($res);
}
}
}
// --------------------------------------------------------------------------------------------------------------
// -- Model match function -- Get best model match from user input.
// --------------------------------------------------------------------------------------------------------------
public function findBestSearchMatches($model_type, $search_terms, $models_list)
{
$partial_search_phrases = array();
if (count($search_terms) > 1) {
$this->buildSearchCombinations($search_terms, $partial_search_phrases); // careful, this is very labor intensive ( O(n^k) )
$partial_search_phrases = array_diff($partial_search_phrases, $search_terms);
for ($i = 0; $i < count($search_terms); $i++) $partial_search_phrases[] = $search_terms[$i];
$partial_search_phrases = array_values($partial_search_phrases);
} else {
$partial_search_phrases = $search_terms;
}
//sort list or else not best matches may be found first
$counts = array();
foreach ($models_list as $m) {
$counts[] = strlen($m);
}
array_multisort($counts,SORT_DESC,$models_list);
unset($counts);
//sort list or else not best matches may be found first
foreach ($partial_search_phrases as $p) {
$counts[] = strlen($p);
}
array_multisort($counts,SORT_DESC,$partial_search_phrases);
$results = array("exact_match" => '', "partial_match" => '');
foreach ($partial_search_phrases as $term) {
foreach ($models_list as $model) {
foreach ($model_type as $mt) {
if (strpos(strtolower($model), strtolower($mt)) !== false) {
if ((strtolower($model) == strtolower($term) || strtolower($model) == strtolower($mt . " " . $term))
) {
// echo " " . $model . " === " . $term . " <br>";
if (strlen($model) > strlen($results['exact_match']) /*|| strtolower($term) != strtolower($mt)*/
) {
$results['exact_match'] = strtolower($model);
return $results;
}
} else if (strpos(strtolower($model), strtolower($term)) !== false) {
if (strlen($term) > strlen($results['partial_match'])
|| strtolower($term) != strtolower($mt)
) {
$results['partial_match'] = $term;
//return $results;
}
}
}
}
}
}
return $results;
}
// --------------------------------------------------------------------------------------------------------------
// -- Get all models in DB for Make (e.g. porsche) (could include multiple makes)
// --------------------------------------------------------------------------------------------------------------
public function initializeFullModelList($make) {
$this->full_model_list = array();
$modelsDB = $this->inv->getAllModelsForMakeAndCounts($make);
foreach ($modelsDB as $m) {
$this->full_model_list[] = $m['model'];
}
}
// --------------------------------------------------------------------------------------------------------------
// -- spell checker -- use algorithm to check model spelling (could expand to include english words)
// --------------------------------------------------------------------------------------------------------------
public function spellCheckModelKeywords($search_terms)
{
// INPUTS: findBestMatchReturnArray($searchList, $inputArray,$tolerance,$differenceLenTolerance,$ignoreStringsOfLengthX,$useLowerCase);
//
// $searchList, - The list of items you want to get a match from
// $inputArray, - The user input value or value array
// $tolerance, - How close do we want the match to be 0 = exact, 1 = close, 2 = less close, etc. 20 = find a match 100% of the time
// $lenTolerance, - the number of characters between input and match allowed, ie. 3 would mean match can be +- 3 in length diff
// $ignoreStrLessEq, - min number of chars that must be before checking (i.e. if 3 ignore anything 3 in length to check)
// $useLowerCase - puts the phrases in lower case for easier matching ( not needed per se )
// $searchInPhrases - compare against every word in searchList (which could be groups of words per array item (so search every word past to function
$tolerance = 0; // 1-2 recommended
$lenTolerance = 1; // 1-3 recommended
$ignoreStrLessEq = 3; // may not want to correct tiny words, 3-4 recommended
$useLowercase = true; // convert to lowercase matching = true
$searchInPhrases = true; //match words not phrases, true recommended
$spell_checked_search_terms = $this->findBestMatchReturnArray($this->full_model_list, $search_terms, $tolerance, $lenTolerance, $ignoreStrLessEq, $useLowercase,$searchInPhrases);
$spell_checked_search_terms = array_values($spell_checked_search_terms);
// return spell checked terms
if (!empty($spell_checked_search_terms)) {
if (strpos(strtolower(implode(" ", $spell_checked_search_terms)), strtolower(implode(" ", $search_terms))) === false //&&
// strlen(implode(" ", $spell_checked_search_terms)) > 4
) {
return $spell_checked_search_terms;
}
}
// or just return search terms as is
return $search_terms;
}
}
?>

How to make uniqid shorter

I'd like to use php uniqid() in a smarty template for my small reservation system (for a product) to generate an UNIQUE value that would make for a reservation number.
Default uniqid() is a bit too long for my purpose, how can I make it like 5-6 characters?
tentative answer:
<?php
function toBase(/* positiv integer*/ $n, array $alphabet) {
$retval = '';
do {
$retval .= $alphabet[ $n%count($alphabet) ];
$n = intval( $n / count($alphabet) );
}
while( ($n=intval($n)) > 0);
return $retval;
}
function getCode() {
static $alphabet = null;
if( $alphabet==null) {
$alphabet = str_split('3479ACEFHJKLMNPRTUVWXY');
}
// get a random number
// and "encode" it using the alphabet
$code = toBase(mt_rand(), $alphabet);
// this might be both
// - too long
// - and too short*
// so first get the last 6 characters (if there are that much)
$code = substr($code, -6);
// and if there wasn't, pad them with 'zeros' (according to the alphabet that's a '3')
$code = str_pad($code, 6, $alphabet[0]);
return $code;
// *) the "too short" part could be avoided via mt_rand(22^6, ...)
// but I want to keep it in the range of a 32bit signed integer
}
getCode() gives you codes like
YFTRXA
MRMTMV
YC9HVN
VWCAUE
JEVXUF
WWMEYU
KLWAML
YCKE3V
37KJ3P
ME9EKU
I've tested getCode() (once) via
function testCodes() {
$codes = [];
for($i=0; $i<2000; $i++) {
$codes[] = getCode();
}
$withoutCollisions = array_unique($codes);
return count($codes)-count($withoutCollisions);
}
$collisions = [];
for($i=0; $i<5000; $i++) {
$c = testCodes();
if ( !isset($collisions[$c]) ) {
$collisions[$c] = 0;
}
$collisions[$c] += 1;
}
var_dump($collisions);
and the output was
array(3) {
[0]=>
int(4899)
[1]=>
int(100)
[2]=>
int(1)
}
So there are collisions (a set of 2000 codes having one or two doublets) but I'd say for what you're supposedly trying to achieve it's in the ball park. Collision rate is low enough so that you could even place a unique contraint in the database on that field and simply try again on a collison.
....BUT feel free to get over to https://security.stackexchange.com/ and have this algorithm shred to pieces ;-)

Parse string containing dots in php

I would parse the following string:
$str = 'ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1';
parse_str($str,$f);
I wish that $f be parsed into:
array(
'ProceduresCustomer.tipi_id' => '10',
'ProceduresCustomer.id' => '1'
)
Actually, the parse_str returns
array(
'ProceduresCustomer_tipi_id' => '10',
'ProceduresCustomer_id' => '1'
)
Beside writing my own function, does anybody know if there is a php function for that?
From the PHP Manual:
Dots and spaces in variable names are converted to underscores. For example <input name="a.b" /> becomes $_REQUEST["a_b"].
So, it is not possible. parse_str() will convert all periods to underscores. If you really can't avoid using periods in your query variable names, you will have to write custom function to achieve this.
The following function (taken from this answer) converts the names of each key-value pair in the query string to their corresponding hexadecimal form and then does a parse_str() on it. Then, they're reverted back to their original form. This way, the periods aren't touched:
function parse_qs($data)
{
$data = preg_replace_callback('/(?:^|(?<=&))[^=[]+/', function($match) {
return bin2hex(urldecode($match[0]));
}, $data);
parse_str($data, $values);
return array_combine(array_map('hex2bin', array_keys($values)), $values);
}
Example usage:
$data = parse_qs($_SERVER['QUERY_STRING']);
Quick 'n' dirty.
$str = "ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1";
function my_func($str){
$expl = explode("&", $str);
foreach($expl as $r){
$tmp = explode("=", $r);
$out[$tmp[0]] = $tmp[1];
}
return $out;
}
var_dump(my_func($str));
array(2) {
["ProceduresCustomer.tipi_id"]=> string(2) "10"
["ProceduresCustomer.id"]=>string(1) "1"
}
This quick-made function attempts to properly parse the query string and returns an array.
The second (optional) parameter $break_dots tells the parser to create a sub-array when encountering a dot (this goes beyond the question, but I included it anyway).
/**
* parse_name -- Parses a string and returns an array of the key path
* if the string is malformed, only return the original string as a key
*
* $str The string to parse
* $break_dot Whether or not to break on dots (default: false)
*
* Examples :
* + parse_name("var[hello][world]") = array("var", "hello", "world")
* + parse_name("var[hello[world]]") = array("var[hello[world]]") // Malformed
* + parse_name("var.hello.world", true) = array("var", "hello", "world")
* + parse_name("var.hello.world") = array("var.hello.world")
* + parse_name("var[hello][world") = array("var[hello][world") // Malformed
*/
function parse_name ($str, $break_dot = false) {
// Output array
$out = array();
// Name buffer
$buf = '';
// Array counter
$acount = 0;
// Whether or not was a closing bracket, in order to avoid empty indexes
$lastbroke = false;
// Loop on chars
foreach (str_split($str) as $c) {
switch ($c) {
// Encountering '[' flushes the buffer to $out and increments the
// array counter
case '[':
if ($acount == 0) {
if (!$lastbroke) $out[] = $buf;
$buf = "";
$acount++;
$lastbroke = false;
// In this case, the name is malformed. Return it as-is
} else return array($str);
break;
// Encountering ']' flushes rge buffer to $out and decrements the
// array counter
case ']':
if ($acount == 1) {
if (!$lastbroke) $out[] = $buf;
$buf = '';
$acount--;
$lastbroke = true;
// In this case, the name is malformed. Return it as-is
} else return array($str);
break;
// If $break_dot is set to true, flush the buffer to $out.
// Otherwise, treat it as a normal char.
case '.':
if ($break_dot) {
if (!$lastbroke) $out[] = $buf;
$buf = '';
$lastbroke = false;
break;
}
// Add every other char to the buffer
default:
$buf .= $c;
$lastbroke = false;
}
}
// If the counter isn't back to 0 then the string is malformed. Return it as-is
if ($acount > 0) return array($str);
// Otherwise, flush the buffer to $out and return it.
if (!$lastbroke) $out[] = $buf;
return $out;
}
/**
* decode_qstr -- Take a query string and decode it to an array
*
* $str The query string
* $break_dot Whether or not to break field names on dots (default: false)
*/
function decode_qstr ($str, $break_dots = false) {
$out = array();
// '&' is the field separator
$a = explode('&', $str);
// For each field=value pair:
foreach ($a as $param) {
// Break on the first equal sign.
$param = explode('=', $param, 2);
// Parse the field name
$key = parse_name($param[0], $break_dots);
// This piece of code creates the array structure according to th
// decomposition given by parse_name()
$array = &$out; // Reference to the last object. Starts to $out
$append = false; // If an empty key is given, treat it like $array[] = 'value'
foreach ($key as $k) {
// If the current ref isn't an array, make it one
if (!is_array($array)) $array = array();
// If the current key is empty, break the loop and append to current ref
if (empty($k)) {
$append = true;
break;
}
// If the key isn't set, set it :)
if (!isset($array[$k])) $array[$k] = NULL;
// In order to walk down the array, we need to first save the ref in
// $array to $tmp
$tmp = &$array;
// Deletes the ref from $array
unset($array);
// Create a new ref to the next item
$array =& $tmp[$k];
// Delete the save
unset($tmp);
}
// If instructed to append, do that
if ($append) $array[] = $param[1];
// Otherwise, just set the value
else $array = $param[1];
// Destroy the ref for good
unset($array);
}
// Return the result
return $out;
}
I tried to correctly handle multi-level keys. The code is a bit hacky, but it should work. I tried to comment the code, comment if you have any question.
Test case:
var_dump(decode_qstr("ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1"));
// array(2) {
// ["ProceduresCustomer.tipi_id"]=>
// string(2) "10"
// ["ProceduresCustomer.id"]=>
// string(1) "1"
// }
var_dump(decode_qstr("ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1", true));
// array(1) {
// ["ProceduresCustomer"]=>
// array(2) {
// ["tipi_id"]=>
// string(2) "10"
// ["id"]=>
// string(1) "1"
// }
// }
I would like to add my solution as well, because I had trouble finding one that did all I needed and would handle all circumstances. I tested it quite thoroughly. It keeps dots and spaces and unmatched square brackets (normally changed to underscores), plus it handles arrays in the input well. Tested in PHP 8.0.0 and 8.0.14.
const periodPlaceholder = 'QQleQPunT';
const spacePlaceholder = 'QQleQSpaTIE';
function parse_str_clean($querystr): array {
// without the converting of spaces and dots etc to underscores.
$qquerystr = str_ireplace(['.','%2E','+',' ','%20'], [periodPlaceholder,periodPlaceholder,spacePlaceholder,spacePlaceholder,spacePlaceholder], $querystr);
$arr = null ; parse_str($qquerystr, $arr);
sanitizeArr($arr, $querystr);
return $arr;
}
function sanitizeArr(&$arr, $querystr) {
foreach($arr as $key=>$val) {
// restore values to original
if ( is_string($val)) {
$newval = str_replace([periodPlaceholder,spacePlaceholder], ["."," "], $val);
if ( $val != $newval) $arr[$key]=$newval;
}
}
unset($val);
foreach($arr as $key=>$val) {
$newkey = str_replace([periodPlaceholder,spacePlaceholder], ["."," "], $key);
if ( str_contains($newkey, '_') ) {
// periode of space or [ or ] converted to _. Restore with querystring
$regex = '/&('.str_replace('_', '[ \.\[\]]', preg_quote($newkey, '/')).')=/';
$matches = null ;
if ( preg_match_all($regex, "&".urldecode($querystr), $matches) ) {
if ( count(array_unique($matches[1])) === 1 && $key != $matches[1][0] ) {
$newkey = $matches[1][0] ;
}
}
}
if ( $newkey != $key ) $arr = array_replace_key($arr,$key, $newkey);
if ( is_array($val)) {
sanitizeArr($arr[$newkey], $querystr);
}
}
}
function array_replace_key($array, $oldKey, $newKey): array {
// preserves order of the array
if( ! array_key_exists( $oldKey, $array ) ) return $array;
$keys = array_keys( $array );
$keys[ array_search( $oldKey, $keys ) ] = $newKey;
return array_combine( $keys, $array );
}
First replaces spaces and . by placeholders in querystring before coding before parsing, later undoes that within the array keys and values. This way we can use the normal parse_str.
Unmatched [ and ] are also replaced by underscores by parse_str, but these cannot be reliably replaced by a placeholder. And we definitely don't want to replace matched []. Hence we don't replace [ and ], en let them be replaced by underscores by parse_str. Then we restore the _ in the resulting keys and seeing in the original querystring if there was a [ or ] there.
Known bug: keys 'something]something' and almost identical 'something[something' may be confused. It's occurrence will be zero, so I left it.
Test:
var_dump(parse_str_clean("code.1=printr%28hahaha&code 1=448044&test.mijn%5B%5D%5B2%5D=test%20Roemer&test%20mijn%5B=test%202e%20Roemer"));
yields correctly
array(4) {
["code.1"]=>
string(13) "printr(hahaha"
["code 1"]=>
string(6) "448044"
["test.mijn"]=>
array(1) {
[0]=>
array(1) {
[2]=>
string(11) "test Roemer"
}
}
["test[mijn"]=>
string(14) "test 2e Roemer"
}
whereas the original parse_str only yields, with the same string:
array(2) {
["code_1"]=>
string(6) "448044"
["test_mijn"]=>
string(14) "test 2e Roemer"
}

Spell check and suggest proper word in PHP

I was wondering if anyone knows of any library, script, or service that can spell check a string and return a suggestion of the properly spelled word or suggestions if more that one properly spelled word that it could be written in PHP.
I would prefer if there wasn't a limit on the amount of queries I could do, so not like Google's APIs.
It would be great if it could function like this:
// string to be spell checked stored in variable
$misspelledString = "The quick brown lama jumped over the lazy dog.";
//pass that variable to function
//function returns suggestion or suggestions as an array of string or strings
$suggestion = spellCheck($misspelledString);
echo "Did you mean ".$suggestion[0];
You can try the included Pspell functions:
http://php.net/manual/en/ref.pspell.php
Or an external plugin, like this one:
http://www.phpspellcheck.com/
Check this SO question for an example.
Not quite as nice an API as in your example, but Pspell would be an option. It may already be included with your system copy of PHP. You'll need aspell libraries for each language you want to check.
http://php.net/manual/en/book.pspell.php
On my debian based machine, it's included in the system repositories as a separate package, php5-pspell.
You need to have "pspell" PHP extension, you can install it on Linux using CLI:
sudo apt-get install php-pspell;
sudo service apache2 restart;
The code is very simple:
if ($word = $_GET['word']) {
$spellLink = pspell_new("en");
if (!pspell_check($spellLink, $word)) {
$suggestions = pspell_suggest($spellLink, $word);
echo '<p>Did you mean: <i>"'.$suggestions[0].'"</i>?</p>';
}
}
I attempted to create a class that takes a list of phrases and compares that to the user inputs. What I was trying to do is get things like Porshre Ceyman to correct to Porsche Cayman for example.
This class requires an array of correct terms $this->full_model_list , and an array of the user input $search_terms. I took out the contruct so you will need to pass in the full_model_list. Note, this didn't fully work so I decided to scrap it, it was adapted from someone looking to correct large sentences ...
You would call it like so:
$sth = new SearchTermHelper;
$resArr = $sth->spellCheckModelKeywords($search_terms)
Code (VERY BETA) :
<?php
/*
// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
//
// FUNCTION: Search Term Helper Class
// PURPOSE: Handles finding matches and such with search terms for keyword searching.
// DETAILS: Functions below build search combinations, find matches, look for spelling issues in words etc.
//
// ---------------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
*/
class SearchTermHelper
{
public $full_model_list;
private $inv;
// --------------------------------------------------------------------------------------------------------------
// -- return an array of metaphones for each word in a string
// --------------------------------------------------------------------------------------------------------------
private function getMetaPhone($phrase)
{
$metaphones = array();
$words = str_word_count($phrase, 1);
foreach ($words as $word) {
$metaphones[] = metaphone($word);
}
return $metaphones;
}
// --------------------------------------------------------------------------------------------------------------
// -- return the closest matching string found in $this->searchAgainst when compared to $this->input
// --------------------------------------------------------------------------------------------------------------
public function findBestMatchReturnString($searchAgainst, $input, $max_tolerance = 200, $max_length_diff = 200, $min_str = 3, $lower_case = true, $search_in_phrases = true)
{
if (empty($searchAgainst) || empty($input)) return "";
//weed out strings we thing are too small for this
if (strlen($input) <= $min_str) return $input;
$foundbestmatch = -1;
if ($lower_case) $input = strtolower($input);
//sort list or else not best matches may be found first
$counts = array();
foreach ($searchAgainst as $s) {
$counts[] = strlen($s);
}
array_multisort($counts, $searchAgainst);
//get the metaphone equivalent for the input phrase
$tempInput = implode(" ", $this->getMetaPhone($input));
$list = array();
foreach ($searchAgainst as $phrase) {
if ($lower_case) $phrase = strtolower($phrase);
if ($search_in_phrases) $phraseArr = explode(" ",$phrase);
foreach ($phraseArr as $word) {
//get the metaphone equivalent for each phrase we're searching against
$tempSearchAgainst = implode(' ', $this->getMetaPhone($word));
$similarity = levenshtein($tempInput, $tempSearchAgainst);
if ($similarity == 0) // we found an exact match
{
$closest = $word;
$foundbestmatch = 0;
echo "" . $closest . "(" . $foundbestmatch . ") <br>";
break;
}
if ($similarity <= $foundbestmatch || $foundbestmatch < 0) {
$closest = $word;
$foundbestmatch = $similarity;
//keep score
if (array_key_exists($closest, $list)) {
//echo "" . $closest . "(" . $foundbestmatch . ") <br>";
$list[$closest] += 1;
} else {
$list[$closest] = 1;
}
}
}
if ($similarity == 0 || $similarity <= $max_tolerance) break;
}
// if we find a bunch of a value, assume it to be what we wanted
if (!empty($list)) {
if ($most_occuring = array_keys($list, max($list)) && max($list) > 10) {
return $closest;
}
}
//echo "input:".$input."(".$foundbestmatch.") match: ".$closest."\n";
// disallow results to be all that much different in char length (if you want)
if (abs(strlen($closest) - strlen($input)) > $max_length_diff) return "";
// based on tolerance of difference, return if match meets this requirement (0 = exact only 1 = close, 20+ = far)
return ((int)$foundbestmatch <= (int)$max_tolerance) ? $closest : "";
}
// --------------------------------------------------------------------------------------------------------------
// -- Handles passing arrays instead of a string above ( could have done this in the func above )
// --------------------------------------------------------------------------------------------------------------
public function findBestMatchReturnArray($searchAgainst, $inputArray, $max_tolerance = 200, $max_length_diff = 200, $min_str = 3)
{
$results = array();
$tempStr = '';
foreach ($inputArray as $item) {
if ($tmpStr = $this->findBestMatchReturnString($searchAgainst, $item, $max_tolerance, $max_length_diff, $min_str))
$results[] = $tmpStr;
}
return (!empty($results)) ? $results : $results = array();
}
// --------------------------------------------------------------------------------------------------------------
// -- Build combos of search terms -- So we can check Cayman S or S Cayman etc.
// careful, this is very labor intensive ( O(n^k) )
// --------------------------------------------------------------------------------------------------------------
public function buildSearchCombinations(&$set, &$results)
{
for ($i = 0; $i < count($set); $i++) {
$results[] = $set[$i];
$tempset = $set;
array_splice($tempset, $i, 1);
$tempresults = array();
$this->buildSearchCombinations($tempset, $tempresults);
foreach ($tempresults as $res) {
$results[] = trim($set[$i]) . " " . trim($res);
}
}
}
// --------------------------------------------------------------------------------------------------------------
// -- Model match function -- Get best model match from user input.
// --------------------------------------------------------------------------------------------------------------
public function findBestSearchMatches($model_type, $search_terms, $models_list)
{
$partial_search_phrases = array();
if (count($search_terms) > 1) {
$this->buildSearchCombinations($search_terms, $partial_search_phrases); // careful, this is very labor intensive ( O(n^k) )
$partial_search_phrases = array_diff($partial_search_phrases, $search_terms);
for ($i = 0; $i < count($search_terms); $i++) $partial_search_phrases[] = $search_terms[$i];
$partial_search_phrases = array_values($partial_search_phrases);
} else {
$partial_search_phrases = $search_terms;
}
//sort list or else not best matches may be found first
$counts = array();
foreach ($models_list as $m) {
$counts[] = strlen($m);
}
array_multisort($counts,SORT_DESC,$models_list);
unset($counts);
//sort list or else not best matches may be found first
foreach ($partial_search_phrases as $p) {
$counts[] = strlen($p);
}
array_multisort($counts,SORT_DESC,$partial_search_phrases);
$results = array("exact_match" => '', "partial_match" => '');
foreach ($partial_search_phrases as $term) {
foreach ($models_list as $model) {
foreach ($model_type as $mt) {
if (strpos(strtolower($model), strtolower($mt)) !== false) {
if ((strtolower($model) == strtolower($term) || strtolower($model) == strtolower($mt . " " . $term))
) {
// echo " " . $model . " === " . $term . " <br>";
if (strlen($model) > strlen($results['exact_match']) /*|| strtolower($term) != strtolower($mt)*/
) {
$results['exact_match'] = strtolower($model);
return $results;
}
} else if (strpos(strtolower($model), strtolower($term)) !== false) {
if (strlen($term) > strlen($results['partial_match'])
|| strtolower($term) != strtolower($mt)
) {
$results['partial_match'] = $term;
//return $results;
}
}
}
}
}
}
return $results;
}
// --------------------------------------------------------------------------------------------------------------
// -- Get all models in DB for Make (e.g. porsche) (could include multiple makes)
// --------------------------------------------------------------------------------------------------------------
public function initializeFullModelList($make) {
$this->full_model_list = array();
$modelsDB = $this->inv->getAllModelsForMakeAndCounts($make);
foreach ($modelsDB as $m) {
$this->full_model_list[] = $m['model'];
}
}
// --------------------------------------------------------------------------------------------------------------
// -- spell checker -- use algorithm to check model spelling (could expand to include english words)
// --------------------------------------------------------------------------------------------------------------
public function spellCheckModelKeywords($search_terms)
{
// INPUTS: findBestMatchReturnArray($searchList, $inputArray,$tolerance,$differenceLenTolerance,$ignoreStringsOfLengthX,$useLowerCase);
//
// $searchList, - The list of items you want to get a match from
// $inputArray, - The user input value or value array
// $tolerance, - How close do we want the match to be 0 = exact, 1 = close, 2 = less close, etc. 20 = find a match 100% of the time
// $lenTolerance, - the number of characters between input and match allowed, ie. 3 would mean match can be +- 3 in length diff
// $ignoreStrLessEq, - min number of chars that must be before checking (i.e. if 3 ignore anything 3 in length to check)
// $useLowerCase - puts the phrases in lower case for easier matching ( not needed per se )
// $searchInPhrases - compare against every word in searchList (which could be groups of words per array item (so search every word past to function
$tolerance = 0; // 1-2 recommended
$lenTolerance = 1; // 1-3 recommended
$ignoreStrLessEq = 3; // may not want to correct tiny words, 3-4 recommended
$useLowercase = true; // convert to lowercase matching = true
$searchInPhrases = true; //match words not phrases, true recommended
$spell_checked_search_terms = $this->findBestMatchReturnArray($this->full_model_list, $search_terms, $tolerance, $lenTolerance, $ignoreStrLessEq, $useLowercase,$searchInPhrases);
$spell_checked_search_terms = array_values($spell_checked_search_terms);
// return spell checked terms
if (!empty($spell_checked_search_terms)) {
if (strpos(strtolower(implode(" ", $spell_checked_search_terms)), strtolower(implode(" ", $search_terms))) === false //&&
// strlen(implode(" ", $spell_checked_search_terms)) > 4
) {
return $spell_checked_search_terms;
}
}
// or just return search terms as is
return $search_terms;
}
}
?>

PHP calculator with words as operators

I am stuck in resolving a PHP script.
I want to calculate a result from a set of instructions. Instructions comprise of a keyword and a number that are separated by a space per line. Instructions are loaded from file and results are output to the screen. Any number of Instructions can be specified. Instructions are operators (add, divide, subtract, multiply). The instructions will ignore mathematical precedence. The last instruction should be “apply” and a number (e.g., “apply 3”). The calculator is then initialised with that number and the previous instructions are applied to that number.
[Input]
add 2
multiply 3
apply 3
[Output]
15
[Explanation]
(3 + 2) * 3 = 15
example
[Input]
multiply 9
apply 5
[Output]
45
Can anyone help with some example logic or code ?
This is what I have tried so far:
class calculator {
public
function calculate($expression) {
$expression = str_replace(' ', '', $expression);
return $this - > exec($expression);
}
private
function exec($expression) {
switch ($expression['op']) {
case 'add':
$r = $expression[0] + $expression[1];
break;
case 'subtract':
$r = $expression[0] - $expression[1];
break;
case 'multiply':
$r = $expression[0] * $expression[1];
break;
case 'divide':
$r = $expression[0] / $expression[1];
break;
}
return $r;
}
private
function parseExpression($expression) {
$blankspace = preg_match('/\s/', $expression);
$counter = count($expression);
}
}
I would go this way:
class Calculator {
public $result = 0;
public $queue = Array();
public parseString($text) {
// parse input string
$cmds = explode(" ", $text);
foreach($cmds as $cmd) {
$cmd = trim($cmd);
if(!$cmd) continue; // blank or space, ignoring
$this->queue[] = $cmd;
}
// lets process commands
$command = false;
foreach($this->queue as $index => $cmd) {
if(is_number($cmd)) {
// if it's number fire previous command if exists
if(!$command || !method_exists($this, $command)) {
throw new Exception("Unknown command $command");
}
$this->$command($index, $cmd);
}else{
$command = $cmd;
}
}
}
public function apply($index, $number) {
// manipulate $result, $queue, $number
}
public function add($index, $number) {
// manipulate $result, $queue, $number
}
public function substract($index, $number) {
// manipulate $result, $queue, $number
}
}
$calculator = new Calculator();
$calculator->parseString('...');
It's just example how you can do it, but it's your job to do it :)
You gonna need to add some methods like clearQueue, clearResult, setResult etc...

Categories