How to transform number values from array? Using php - php

I have a website (site-1) which import pages from other website (site-2).
These pages have an id number in site-2 and this number is copied to site-1 when the importing is done. So far so good.
Problem is that the id of site-2 are huge, e.g.: 32423201639, 3212450421639,... and the sistem in site-1 is no able to handle them. So I need to make these numbers smaller when the importing is done.
It is important:
to generate unique number values.
to be bigger than 3000 and smaller than 10000.
It can not use rand(). If we execute this several time the results must be the same
UPDATE: To keep in mind:
This importing is done every week, so I need to consider this: Let's say a first importing is done, and then in the second importing ONLY the first array value changes but the others remain then this one will be the only one to be changed and the other will keep the same value as in the first importing.
The first thing I thought was something like this (but the most important is missing):
$array_values_site1 = array("12345" , "123456", "1234567", "12345678", "123456789", "1234567890", "12345678901", "123456789012", "1234567890123", "12345678901234", "123456789012345", "1234567890123456");
$array_values_site2 = array();
foreach ($array_values_site1 as &$value) {
/* here I need to change the value of $value:
--- to be bigger than 3000 and smaller than 10000.
--- It can not use rand(). If we execute this several time the results must be the same
--- to be unique */
$new_value = "....";
$array_values_site2 [] = $new_value;
}

Looking at the comments, hashing the original ID appears best:
$hashids = new Hashids\Hashids('this is my salt');
$id = $hashids->encode(1);
$original = $hashids->decode($id);
To specify a minimum length (not a number, but a length) and characters to use in the result, include a second and third parameter:
$hashids = new Hashids\Hashids('this is my salt');
$id = $hashids->encode(1, 8, 'abcdefghij1234567890');
$original = $hashids->decode($id);
// $id = '514cdi42';
See hashids.org and github for info.

Related

Filter a php array to only elements that have a matching value to $name in $data

I have a php script getting all folders in a posts folder and making them into a list.
I have a $postinfo_str variable assigned to a json file for each folder which I am using to store post date and category/tag info etc in.
I also have a $pagetitle variable assigned to a title.php include file for each folder. So say I am on a "June 2018" archive page, the text in that file will be "June 2018". If I am on say a "Tutorials" category page, that will be the text in the title.php.
In the json file, I have:
{
"Arraysortdate": "YYYYMMDD",
"Month": "Month YYYY",
"Category": ["cat1", "cat2", "etc"]
}
I am ordering the array newest to oldest using krsort with Arraysortdate as key.
How do I filter the array using $pagetitle as input, finding if there is a match in $postinfo_str, and if there isn't, remove that folder from the array?
All I can seem to find regarding array sorting is where the info in the $pageinfo_str is basically the array and so by that, the $title is the input and the output is the matching text from the $postinfo_str, whereas I want the output to be the folders that only have the matching text in the $postinfo_str to what the input ($pagetitle) is.
Here is my code I have.. Keep in mind this is flat file, I do not want a database to achieve this. See comments if you want an explaination.
<?php
$BASE_PATH = '/path/to/public_html';
// initial array containing the dirs
$dirs = glob($BASE_PATH.'/testblog/*/posts/*', GLOB_ONLYDIR);
// new array with date as key
$dirinfo_arr = [];
foreach ($dirs as $cdir) {
// get current page title from file
$pagetitle = file_get_contents("includes/title.php");
// get date & post info from file
$dirinfo_str = file_get_contents("$cdir/includes/post-info.json");
$dirinfo = json_decode($dirinfo_str, TRUE);
// add current directory to the info array
$dirinfo['dir'] = $cdir;
// add current dir to new array where date is the key
$dirinfo_arr[$dirinfo['Arraysortdate']] = $dirinfo;
}
// now we sort the new array
krsort($dirinfo_arr);
foreach($dirinfo_arr as $key=>$dir) {
$dirpath = $dir['dir'];
$dirpath = str_replace('/path/to/public_html/', '', $dirpath);
?>
<!--HTML HERE SUCH AS--!>
TEXT <br>
<?php
};
?>
I have difficulties following your problem description. Your code example is slightly confusing. It appears to load the same global includes/title.php for each directory. Meaning, the value of $pagetitle should be the same every iteration. If this is intended, you should probably move that line right outside the loop. If the file contains actual php code, you should probably use
$pagetitle = include 'includes/title.php';
or something similar. If it doesn't, you should probably name it title.txt. If it is not one global file, you should probably add the path to the file_get_contents/include as well. (However, why wouldn't you just add the title in the json struct?)
I'm under the assumption that this happened by accident when trying to provide a minimal code example (?) ... In any case, my answer won't be the perfect answer, but it hopefully can be adapted once understood ;o)
If you only want elements in your array, that fulfill certain properties, you have essentially two choices:
don't put those element in (mostly your code)
foreach ($dirs as $cdir) {
// get current page title from file
$pagetitle = file_get_contents("includes/title.php");
// get date & post info from file
$dirinfo_str = file_get_contents("$cdir/includes/post-info.json");
$dirinfo = json_decode($dirinfo_str, TRUE);
// add current directory to the info array
$dirinfo['dir'] = $cdir;
// add current dir to new array where date is the key
// ------------ NEW --------------
$filtercat = 'cat1';
if(!in_array($filtercat, $dirinfo['Category'])) {
continue;
}
// -------------------------------
$dirinfo_arr[$dirinfo['Arraysortdate']] = $dirinfo;
array_filter the array afterwards, by providing a anonymous function
// ----- before cycling through $dirinfo_arr for output
$filtercat = 'cat1';
$filterfunc = function($dirinfo) use ($filtercat) {
return in_array($filtercat, $dirinfo['Category']));
}
$dirinfo_arr = array_filter($dirinfo_arr, $filterfunc);
you should read up about anonymous functions and how you provide local vars to them, to ease the pain. maybe your use case is bettersuited for array_reduce, which is similar, except you can determine the output of your "filter".
$new = array_filter($array, $func), is just a fancy way of writing:
$new = [];
foreach($array as $key => $value) {
if($func($value)) {
$new[$key] = $value;
}
}
update 1
in my code samples, you could replace in_array($filtercat, $dirinfo['Category']) with in_array($pagetitle, $dirinfo) - if you want to match on anything that's in the json-struct (base level) - or with ($pagetitle == $dirinfo['Month']) if you just want to match the month.
update 2
I understand, that you're probably just starting with php or even programming, so the concept of some "huge database" may be frightening. But tbh, the filesystem is - from a certain point of view - a database as well. However, it usually is quite slow in comparison, it also doesn't provide many features.
In the long run, I would strongly suggest using a database. If you don't like the idea of putting your data in "some database server", use sqlite. However, there is a learning curve involved, if you never had to deal with databases before. In the long run it will be time worth spending, because it simplifys so many things.

PHP: Separate Area Code from Phone number with MySQL Database

I want to separate the area code from a phone number string by using a area code mysql database.
For example the string is 0349152023.
The endresult should be 03491 52023.
To get the endresult, i want to split the string and search every digit in database.
For example 0 and then 3 and then 4 and then take the last found result.
The code i have at the moment is only to prepare the phone number string for futher actions:
$phone1 = preg_replace('/[oO]/', '0', $phone-string);
$phone2 = preg_replace("/[^0-9]/", "", $phone1);
Then i use str_split to cut the string in pieces:
$searchArray = str_split($phone2);
Thanks for your help.
You may build an array containing all the area codes.
Then you may write something like this:
foreach ($area_codes as $code) {
if (substr($phone, 0, strlen($code)) == $code) {
$phone_string = substr($phone, 0, strlen($code))." ".substr($phone, strlen($code));
}
}
You can obviously add a controller in order to verify if the area code was found or not.
step 1: select all area codes from db and put them into an array $areaCodes
step 2: iterate over $areaCodes as $code and check if the phonenumber starts with $code. if it does, create a string that has a whitespace between the code and the rest of the number
$phonenumber = '0349152023';
$preparedPhonenumber = '';
foreach($areaCodes as $code){
if(str_pos($phonenumber, $code) === 0){
// phonenumber starts with areacode
$phoneWithoutCode = substr($phonenumber, strlen($code));
$preparedPhonenumber = $code.' '.$phoneWithoutCode;
break;
}
}
// if one of the areaCodes was 0349,
// the variable $preparedPhonenumber is now '0349 152023'
edit: you can shorten the amount of returned area codes from db by selecting only those that start with a certain string.
Let's assume the shortest area code in germany is 3 digits long (which i think is correct).
$threeDigits = substr($phonenumber,0,3);
$query = "SELECT * from areacodes
WHERE code like '".$threeDigits."%'
ORDER BY CHAR_LENGTH(code) DESC";
this will drastically shrink down the probable area codes array, therefore making the script faster.
edit 2: added order by clause in query so the above code will check for longer areacodes first. (the break; in the foreach loop is now obligatory!)
Hi Leonardo Gugliotti and Cashbee
i sort the areaCodes to get a better match. The php scripts works fine, but takes to long time to handle 5000 MySQL entries. Is it possible to make the foreach search directly in mySQL?
<?php
$sample_area_codes = array( '0350', '034', '034915', '03491', '0348', '0349', '03491', '034916', '034917',);
sort($sample_area_codes);
$phone_string = '0349152023';
foreach ($sample_area_codes as $code) {
$subString = substr($phone_string, 0, strlen($code));
if ($subString == $code) {
$phone = $subString." ".substr($phone_string, strlen($code));
}
}
if (!empty($phone)) {
echo $phone;
}
else {
echo "No AreaCode found.";
}
?>
Output: 034915 2023, which is correct
A single probe (assuming INDEX(area_code)):
SELECT ...
FROM AreaCodes
WHERE area_code < ?
ORDER BY area_code DESC
LIMIT 1;
(Where you bind the $phone_number as a string into the ?)
I think you'd better split your database into a tree, making a table for each digit.
So the third digit could refer to the second, the fourth to the third, and so on until you reach the maximum lenght of the prefix. The last table should include the name of the area.
Following your example, supposing that the maximum lenght of the area code was five digits, the fifth_digit_table should have at least four fields like these:
ID
IDref
Number
Name
10 records may have the same IDref, corresponding to the number "2" at the fourth position, linked to the previous "021" through the fourth_digit_table, the three_digit_table and so on; only one among these records, that with the Number field filled with "9", should have the Name "Haan"; the others, if there aren't any, should have the Name "Solingen".
I hope you will manage to speed up your script.

Small scope/huge frustration mystery: buggy array behavior in php

So crazy! I have a bug that's 100% reproducible, it happens in only a few lines of code, yet I cannot for the life of me determine what the problem is.
My project is a workout maker, and the mystery involves two functions:
get_pairings: It makes a set of $together_pairs (easy) and $mixed_pairs (annoying), and combines them into $all_pairs, used to make the workout.
make_mixed_pairs: this has different logic depending on whether it's a partner vs solo workout. Both cases return a set of $mixed_pairs (in the same exact format), called by the function above.
The symptoms/clues:
The case of the solo workout is fine, $all_pairs will only contain $mixed_pairs (because as it's defined, $together_pairs are only for partner workouts)
In the case of the a partner workout, when I combine the two sets in get_pairings(), $all_pairs only successfully gets the first set I give it! (If I swap those lines at step 2 and add $together_pairs first, $all_pairs contains only those. If I do $mixed_pairs first, $all_pairs contains only that).
Then if I uncomment that second-to-last line in make_mixed_pairs() just for troubleshooting to see what happens, then $all_pairs does successfully include exercises from both sets!
That suggests the problem is something I'm doing wrong in making the arrays in make_mixed_pairs(), but I confirmed that the resulting format is identical in both cases.
Anyone see what else I could be missing? I've been narrowing down this bug for 4 hours so far- I can't make it any smaller, and I can't see what's wrong :(
Update: I updated the for loop in make_mixed_pairs() to stop at $mixed_pair_count - 1 (instead of just $mixed_pair_count), and now I sometimes get one single 'together_pair' mixed in the $all_pairs results; the same damn one each time, weirdly. Though it's not 'fixed', because again when I change the order that I add the two sets in get_pairings, when I add $together_pairs first, then $all_pairs is ENTIRELY those- it's so strange...
Here are the functions: first get_pairings (relevant part is right before and after step 2):
/**
* Used in make_workout.php: take the user's available resources, and return valid exercises
*/
function get_pairings($exercises, $count, $outdoor_partner_workout)
{
// 1. Prep our variables, and put exercises into the appropriate buckets
$mixed_exercises = array();
$together_pairs = array();
$mixed_pairs = array();
$all_pairs = array();
$selected_pairs = array();
// Sort the valid exercises: self_pairing exercises go as they are, with extra
// array for consistent formatting. Mixed ones go into $mixed_exercises array
// for more specialized pairing in make_mixed_pairs
foreach($exercises as $exercise)
{
if ($exercise['self_pairing'])
{
$pair = array($exercise);
array_push($together_pairs, [$pair]);
}
else
{
$this_exercise = array($exercise);
array_push($mixed_exercises, $this_exercise);
}
}
// Now get the mixed_pairs
$mixed_pairs = make_mixed_pairs($mixed_exercises, $outdoor_partner_workout);
// 2. combine together into one set, and select random pairs for the workout
// Add both sets to the array of all pairs (to pick from afterward)
$all_pairs += $mixed_pairs;
$all_pairs += $together_pairs;
// Now let's choose at random our desired # of pairs, and save them in $selected_pairs
$pairing_keys = array_rand($all_pairs, $count);
foreach($pairing_keys as $key)
{
array_push($selected_pairs, $all_pairs[$key]);
}
// Finally, shuffle it so we don't always see the self-pairs first
shuffle($selected_pairs);
return $selected_pairs;
}
And the other one- make_mixed_pairs: there are two cases, the first is complicated (and shows the bug) and the second is simple (and works):
/**
* Used by get_pairings: in case of a partner workout that has open space (where
* one person can travel to a point while the other does an exercise til they return)
* we'll pair exercises in a special way. (If not, fine to grab random pairs)
*/
function make_mixed_pairs($mixed_exercises, $outdoor_partner_workout)
{
$mixed_pairs = array();
// When it's an outdoor partner workout, we want to pair travelling with stationary
// put them into arrays and then we'll make pairs using one from each
if ($outdoor_partner_workout)
{
$mixed_travelling = array();
$mixed_stationary = array();
foreach($mixed_exercises as $exercise)
{
if ($exercise[0]['travelling'])
{
array_push($mixed_travelling, $exercise);
}
else
{
array_push($mixed_stationary, $exercise);
}
}
shuffle($mixed_travelling);
shuffle($mixed_stationary);
// determine the smaller set, and pair exercises that many times
$mixed_pair_count = min(count($mixed_travelling), count($mixed_stationary));
for ($i=0; $i < $mixed_pair_count; $i++)
{
$this_pair = array($mixed_travelling[$i], $mixed_stationary[$i]);
array_push($mixed_pairs, $this_pair); // problem is adding them here- we get only self_pairs
}
}
// Otherwise we can just grab pairs from mixed_exercises
else
{
// shuffle the array so it's in random order, then chunk it into pairs
shuffle($mixed_exercises);
$mixed_pairs = array_chunk($mixed_exercises, 2);
}
// $mixed_pairs = array_chunk($mixed_exercises, 2); // when I replace it with this, it works
return $mixed_pairs;
}
Oh for Pete's sake: I mentioned this to a friend, who told me that union is flukey in php, and that I should use array_merge instead.
I replaced these lines:
$all_pairs += $together_pairs;
$all_pairs += $mixed_pairs;
with this:
$all_pairs = array_merge($together_pairs, $mixed_pairs);
And now it all works

PHP: check user input against text file?

I have the following street names and house numbers in a text file:
Albert Dr: 4116-4230, 4510, 4513-4516
Bergundy Pl: 1300, 1340-1450
David Ln: 3400, 4918, 4928, 4825
Garfield Av: 5000, 5002, 5004, 5006, 8619-8627, 9104-9113
....
This data represents the boundary data for a local neighborhood (i.e., what houses are inside the community).
I want to make a PHP script that will take a user's input (in the form of something like "4918 David Lane" or "3000 Bergundy") search this list, and return a yes/no response whether that house exists within the boundaries.
What would be an efficient way to parse the input (regex?) and compare it to the text list?
Thanks for the help!
It's better to store this info in a database so that you don't have to parse out the data from a text file. Regexes are also not generally applicable to find a number in a range so a general purpose language is advised as well.
But... if you want to do it with regexes (and see why it's not a good idea)
To lookup the numbers for a street use
David Ln:(.*)
To then get the numbers use
[^,]*
You could simply import the file into a string. After this is done, breack each line of the file in an array so Array(Line 1=> array(), Line 2=> array(), etc. After this is done, you can explode using :. After, you'll simply need to search in the array. Not the fastest way, but it may be faster then regex.
You should sincerely consider using a database or re-think how your file are.
Try something like this, put your street names inside test.txt.. Now that you are able to get the details inside the text file, just compare it with the values that you submit in your form.
$filename = 'test.txt';
if(file_exists($filename)) {
if($handle = fopen($filename, 'r')) {
$name = array();
while(($file = fgets($handle)) !==FALSE) {
preg_match('#(.*):(.*)#', $file, $match);
$array = explode(',', $match[2]);
foreach($array as $val) {
$name[$match[1]][] = $val;
}
}
}
}
As mentioned, using a database to store street numbers that are relational to your street names would be ideal. I think a way you could implement this with your text file though is to create a a 2D array; storing the street names in the first array and the valid street numbers in their respective arrays.
Parse the file line by line in a loop. Parse the street name and store in array, then use a nested loop to parse all of the numbers (for ones in a range like 1414-1420, you can use an additional loop to get each number in the range) and build the next array in the initial street name array element. When you have your 2D array, you can do a simple nested loop to check it for a match.
I will try to make a little pseudo-code for you..
pseudocode:
$addresses = array();
$counter = 0;
$line = file->readline
while(!file->eof)
{
$addresses[$counter] = parse_street_name($line);
$numbers_array = parse_street_numbers($line);
foreach($numbers_array as $num)
$addresses[$counter][] = $num;
$line = file->readline
$counter++;
}
It's better if you store your streets in a separate table with IDs, and store numbers in separate table one row for each range or number and street id.
For example:
streets:
ID, street
-----------
1, Albert Dr
2, Bergundy Pl
3, David Ln
4, Garfield Av
...
houses:
street_id, house_min, house_max
-----------------
1, 4116, 4230
1, 4510, 4510
1, 4513, 4516
2, 1300, 1300
2, 1340, 1450
...
In the rows, where no range but one house number, you set both min and max to the same value.
You can write a script, that will parse your txt file and save all data to db. That should be as easy as several loops and explode() with different parameters and some insert queries too.
Then with first query you get street id
SELECT id FROM streets WHERE street LIKE '%[street name]%'
After that you run second query and get answer, is there such house number on that street
SELECT COUNT(*)
FROM houses
WHERE street_id = [street_id]
AND [house_num] BETWEEN house_min AND house_max
Inside [...] you put real values, dont forget to escape them to prevent sql injections...
Or you even can run just one query using JOIN.
Also you should make sure that your given house number is integer, not float.

PHP/mysql array search algorithm

I'd like to be able to use php search an array (or better yet, a column of a mysql table) for a particular string. However, my goal is for it to return the string it finds and the number of matching characters (in the right order) or some other way to see how reasonable the search results are, so then I can make use of that info to decide if I want to display the top result by default or give the user options of the top few.
I know I can do something like
$citysearch = mysql_query(" SELECT city FROM $table WHERE city LIKE '$city' ");
but I can't figure out a way to determine how accurate it is.
The goal would be:
a) find "Milwaukee" if the search term were "milwakee" or something similar.
b) if the search term were "west", return things like "West Bend" and "Westmont".
Anyone know a good way to do this?
You should check out full text searching in MySQL. Also check out Zend's port of the Apache Lucene project, Zend_Search_Lucene.
More searching led me to the Levenshtein distance and then to similar_text, which proved to be the best way to do this.
similar_text("input string", "match against this", $pct_accuracy);
compares the strings and then saves the accuracy as a variable. The Levenshtein distance determines how many delete, insert, or replace functions on a single character it would need to do to get from one string to the other, with an allowance for weighting each function differently (eg. you can make it cost more to replace a character than to delete a character). It's apparently faster but less accurate than similar_text. Other posts I've read elsewhere have mentioned that for strings of fewer than 10000 characters, there's no functional difference in speed.
I ended up using a modified version of something I found to make it work. This ends up saving the top 3 results (except in the case of an exact match).
$input = $_POST["searchcity"];
$accuracy = 0;
$runner1acc = 0;
$runner2acc = 0;
while ($cityarr = mysql_fetch_row($allcities)) {
$cityname = $cityarr[1];
$cityid = $cityarr[0];
$city = strtolower($cityname);
$diff = similar_text($input, $city, $tempacc);
// check for an exact match
if ($tempacc == '100') {
// closest word is this one (exact match)
$closest = $cityname;
$closestid = $cityid;
$accuracy = 100;
break;
}
if ($tempacc >= $accuracy) { // more accurate than current leader
$runner2 = $runner1;
$runner2id = $runner1id;
$runner2acc = $runner1acc;
$runner1 = $closest;
$runner1id = $closestid;
$runner1acc = $accuracy;
$closest = $cityname;
$closestid = $cityid;
$accuracy = $tempacc;
}
if (($tempacc < $accuracy)&&($tempacc >= $runner1acc)) { // new 2nd place
$runner2 = $runner1;
$runner2id = $runner1id;
$runner2acc = $runner1acc;
$runner1 = $cityname;
$runner1id = $cityid;
$runner1acc = $tempacc;
}
if (($tempacc < $runner1acc)&&($tempacc >= $runner2acc)) { // new 3rd place
$runner2 = $cityname;
$runner2id = $cityid;
$runner2acc = $tempacc;
}
}
echo "Input word: $input\n<BR>";
if ($accuracy == 100) {
echo "Exact match found: $closestid $closest\n";
} elseif ($accuracy > 70) { // for high accuracies, assumes that it's correct
echo "We think you meant $closestid $closest ($accuracy)\n";
} else {
echo "Did you mean:<BR>";
echo "$closestid $closest? ($accuracy)<BR>\n";
echo "$runner1id $runner1 ($runner1acc)<BR>\n";
echo "$runner2id $runner2 ($runner2acc)<BR>\n";
}
This can be very complicated, and I am not personally aware of any good 3rd party libraries although I'm sure they exist. Others may be able to suggest some canned solutions, though.
I have written something similar from scratch a few times in the past. If you go down that route, it is probably not something you'd want to do in PHP by itself as every query would involve getting all of the records and performing your calculations on them. It will almost certainly involve creating a set of index tables that meet your specifications.
For instance, you would have to come up with rules for how you imagine that "Milwaukee" could end up spelled "milwakee." My solution to this was to do vowel compression and duplication compression (not sure if these are actually search terms). So, milwaukee would be indexed as:
milwaukee
m_lw__k__
m_lw_k_
When the search query came in for "milwaukee", I would run the same process on the text input, and then run a search on the index table for:
SELECT cityId,
COUNT(*)
FROM myCityIndexTable
WHERE term IN ('milwaukee', 'm_lw__k__', 'm_lw_k_')
When the search query came in for "milwakee", I would run the same process on the text input, and then run a search on the index table for:
SELECT cityId,
COUNT(*)
FROM myCityIndexTable
WHERE term IN ('milwaukee', 'm_lw_k__', 'm_lw_k_')
In the case of Milwaukee (spelled correctly), it would return "3" for the count.
In the case of Milwakee (spelled incorrectly) ,it would return "2" for the count (since it would not match the m_lw__k__ pattern as it only had one vowel in the middle).
If you sort the results based on the count, you would end up meeting one of your rules, that "Milwaukee" would end up being sorted higher as a possible match than "Milwakee."
If you want to build this system in a generic way (as hinted by your use of $table in the query) then you'd probably need another mapping table somewhere in there to map your terms to the appropriate table.
I'm not suggesting this is the best (or even a good) way to go about this, just something I've done in the past that might prove useful to you if you plan to try and do this without a third party solution.
Most maddening result with LIKE is this one "%man" this will return all woman in file!
In case of listing perhaps a not too bad solution is to keep on shortening the searching needle. In your case a match will come up when your searching $ is as short as "milwa".

Categories