Convert and reconvert a version to number to store in database - php

is there any algorithm to convert an string like 1.0.0 to a sortable number via PHP?
It should be able to convert to same string again. It's not possible to just remove dots. Also length of version is unknown, for example 1.0.0, 11.222.0, 0.8.1526

If you just want to sort versions, there is no need to convert.
<?php
$versions = array('1.0.0', '11.222.0', '0.8.1256');
usort($versions, 'version_compare');
var_dump($versions);
array(3) {
[0]=>
string(8) "0.8.1256"
[1]=>
string(5) "1.0.0"
[2]=>
string(8) "11.222.0"
}

If you want to compare versions numbers, you could just use the version_compare() function.
And if you have an array of versions that you need to sort, you could use a function such as usort() / uasort(), with a callback based on version_compare().

If you insist on an arbitrary length there is no way to uniquely map the numbers with at the same time maintaining the ordering criterion. Maybe you just want to sort the version numbers without conversion (see other answers)?

If you expect version segmentation with numbers like 12345 (eg. 0.9.12345.2), then you may be best off exploding the string and storing each segment in separate field in SQL.
That way you can sort it how ever you wish.

One option would be using explode:
function cmp($a, $b)
{
$a = explode('.', $a);
$b = explode('.', $b);
$m = min(count($a), count($b));
for ($i = 0; $i < $m; $i++) {
if (intval($a[$i]) < intval($b[$i]))
return -1;
else
return 1;
}
return 0;
}
EDIT: Didn't know about version_compare, that might be a better option if it works as you need.

Here are a couple of functions that convert version to string and vice-versa.
So you can store the strings in your database and be able to sort them. I've used a length of 5 char but you can adapt to your needs.
function version_to_str($version) {
$list = explode('.', $version);
$str = '';
foreach ($list as $element) {
$str .= sprintf('%05d', $element);
}
return $str;
}
function str_to_version($str) {
$version = array();
for ($i=0; $i<strlen($str); $i+=5) {
$version[] = intval(substr($str, $i, 5));
}
return implode('.', $version);
}
$versions = array('1.0.0', '11.222.0', '0.8.1526');
$versions = array_map("version_to_str", $versions);
sort($versions);
$versions = array_map("str_to_version", $versions);
print_r($versions);
output:
Array
(
[0] => 0.8.1526
[1] => 1.0.0
[2] => 11.222.0
)

Related

Finding the 3 most occurring substrings in a string in PHP

I wanted to go over the thought process of these since I am not sure how to improve this. I have a string that are separated by commas and they have reoccurring substrings and I want to find the 3 most occurring substrings.
I was going to explode the string by commas into an array.
Perform a substr_count in the original string for each element in the array and store it in a separate array to store the counts? (Not sure how to improve this since that would create duplicate counts for the same substring)
Perform a max on the array to find the first, second, and third most occurring substrings.
Return an array with the first, second, and third most occurring substrings.
I am guessing after I perform an explode, I can do a quick sort and go from there?
This is what I have tried so far:
$result = findThreeMostOccuringStrings("apple, apple, berry, cherry, cherry, cherry, dog, dog, dog");
var_dump($result);
function findThreeMostOccuringStrings($str){
$first = PHP_INT_MIN;
$second = PHP_INT_MIN;
$third = PHP_INT_MIN;
$arr = explode(",", $str);
for ($i = 0; $i < count($str); $i++){
$arrIdx[] = substr_count($arr[$i]);
}
$first = max($arrIdx);
$arrIdx[$first] = -1;
$second = max($arrIdx);
$arrIdx[$first] = -1;
$third = max($arrIdx);
$arrIdx[$first] = -1;
$threeMostOccuringStrings = array($first, $second, $third);
return $threeMostOccuringStrings;
}
If by substring you mean only the strings separated by commas and not substrings of these, use array_count_values after explode
If you're looking for an efficient way to solve substring search, the answer is a Trie, or prefix tree. This basically reduces the look up time to search for a substring as it creates a deterministic path for all prefixes (i.e. a prefix tree).
Consider the string "A cat does not categorize its food while among other cats." Here the substrings categorize and cats all share the same prefix of cat. So finding the most frequented substring in a prefix tree is easy if you count the number of EOW nodes stemming from each branch in the root.
Building the tree is also quite trivial.
function insertString($str, $trie) {
$str = strtolower($str); // normalize case
$node = $trie;
foreach(str_split($str) as $letter) {
if (!isset($node->$letter)) {
$node->$letter = new stdClass;
}
$node = $node->$letter;
}
$node->EOW = true; // place EOL node
}
function countNodes($branch) {
$n = 0;
foreach($branch as $e => $node) {
if ($node instanceof stdClass) {
$n += countNodes($node);
} elseif ($e === 'EOW') {
$n++;
}
}
return $n;
}
$trie = new stdClass;
$str = "A cat does not categorize its food while among other cats";
foreach(explode(' ', $str) as $word) {
insertString($word, $trie);
}
$s = [];
foreach($trie as $n => $rootNodes) {
$s[$n] = countNodes($rootNodes);
}
var_dump($s);
Which should give you...
array(8) {
["a"]=>
int(2)
["c"]=>
int(3)
["d"]=>
int(1)
["n"]=>
int(1)
["i"]=>
int(1)
["f"]=>
int(1)
["w"]=>
int(1)
["o"]=>
int(1)
}
From there you can see the root branch c has the highest number of substrings (which if walked match cat, cats, and categorize.
Based on your post and the comments, you actually want to tally terms in a comma delimited list of terms, and then rank them, so: let's just do that, using an associative array for tallying and arsort to sort that associative array by value, in reverse order (so that the highest counts are at the start of the array):
function rank($input) {
$terms = explode(',', $input);
$ranked = array();
foreach($terms as $word) {
$word = trim($word);
if (!isset($ranked[$word])) {
$ranked[$word] = 0;
}
$ranked[$word]++;
}
arsort($ranked);
return $ranked;
}
So if we run that through print_r(rank("apple, apple, berry, cherry, cherry, cherry, dog, dog, dog")) we get:
Array
(
[dog] => 3
[cherry] => 3
[apple] => 2
[berry] => 1
)
Splendid.

PHP Compress array of bits into shortest string possible

I have an array that contains values of 1 or 0 representing true or false values. e.g.
array(1,0,0,1,0,1,1,1,1);
I want to compress/encode this array into the shortest string possible so that it can be stored within a space constrained place such as a cookie. It also need to be able to be decoded again later. How do I go about this?
ps. I am working in PHP
Here is my proposal:
$a = array(1,0,0,1,0,1,1,1,1,1,0,0,1,0,1,1,1,1,1,0,0,1,0,1,1,1,1);
$compressed = base64_encode(implode('', array_map(function($i) {
return chr(bindec(implode('', $i)));
}, array_chunk($a, 8))));
var_dump($compressed); // string(8) "l8vlBw=="
So you get each 8 characters (which in fact is a binary 0..255), convert them to an integer, represent as an ASCII character, implode it to a string and convert to base64 to be able to save it as a string.
UPD:
the opposite is pretty straightforward:
$original = str_split(implode('', array_map(function($i) {
return decbin(ord($i));
}, str_split(base64_decode($compressed)))));
How exactly I wrote it (just in case anyone interesting how to write such unreadable and barely maintainable code):
I've written the $original = $compressed; and started reversing the right part of this expression step by step:
Decoded from base64 to a binary string
Split it to an array
Converted every character to its ASCII code
Converted decimal ASCII code to a binary
Joined all the binary numbers into a single one
Split the long binary string to an array
Dont use serialize. Just make a string of it:
<?php
$string = implode( '', $array );
?>
You are left with an string like this:
100101111
If you want to have an array again, just access it like an array:
$string = '100101111';
echo $string[1]; // returns "0"
?>
Of course you could also make it a decimal and just store the number. That's even shorter then the "raw" bits.
<?php
$dec = bindec( $string );
?>
How about pack and unpack
$arr = array(1,1,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,1,1,1);
$str = implode($arr);
$res = pack("h*", $str);
var_dump($res);
$rev = unpack("h*", $res);
var_dump($rev);
output:
string(10) # Not visible here
array(1) {
[1]=>
string(20) "11110011010011001111"
}
Here is my solution based on zerkms answer, this deals with the loss of leading 0's when converting decimals back into binary.
function compressBitArray(array $bitArray){
$byteChunks = array_chunk($bitArray, 8);
$asciiString = implode('', array_map(function($i) {
return chr(bindec(implode('', $i)));
},$byteChunks));
$encoded = base64_encode($asciiString).'#'.count($bitArray);
return $encoded;
}
//decode
function decompressBitArray($compressedString){
//extract origional length of the string
$parts = explode('#',$compressedString);
$origLength = $parts[1];
$asciiChars = str_split(base64_decode($parts[0]));
$bitStrings = array_map(function($i) {
return decbin(ord($i));
}, $asciiChars);
//pad lost leading 0's
for($i = 0; $i < count($bitStrings); $i++){
if($i == count($bitStrings)-1){
$toPad = strlen($bitStrings[$i]) + ($origLength - strlen(implode('', $bitStrings)));
$bitStrings[$i] = str_pad($bitStrings[$i], $toPad, '0', STR_PAD_LEFT);
}else{
if(strlen($bitStrings[$i]) < 8){
$bitStrings[$i] = str_pad($bitStrings[$i], 8, '0', STR_PAD_LEFT);
}
}
}
$bitArray = str_split(implode('', $bitStrings));
return $bitArray;
}

Randomize a PHP array with a seed?

I'm looking for a function that I can pass an array and a seed to in PHP and get back a "randomized" array. If I passed the same array and same seed again, I would get the same output.
I've tried this code
//sample array
$test = array(1,2,3,4,5,6);
//show the array
print_r($test);
//seed the random number generator
mt_srand('123');
//generate a random number based on that
echo mt_rand();
echo "\n";
//shuffle the array
shuffle($test);
//show the results
print_r($test);
But it does not seem to work. Any thoughts on the best way to do this?
This question dances around the issue but it's old and nobody has provided an actual answer on how to do it: Can i randomize an array by providing a seed and get the same order? - "Yes" - but how?
Update
The answers so far work with PHP 5.1 and 5.3, but not 5.2. Just so happens the machine I want to run this on is using 5.2.
Can anyone give an example without using mt_rand? It is "broken" in php 5.2 because it will not give the same sequence of random numbers based off the same seed. See the php mt_rand page and the bug tracker to learn about this issue.
Sorry, but accordingly to the
documentation the
shuffle function is seeded automatically.
Normally, you shouldn't try to come up with your own algorithms to randomize things since they are very likely to be biased. The Fisher-Yates algorithm is known to be both efficient and unbiased though:
function fisherYatesShuffle(&$items, $seed)
{
#mt_srand($seed);
for ($i = count($items) - 1; $i > 0; $i--)
{
$j = #mt_rand(0, $i);
$tmp = $items[$i];
$items[$i] = $items[$j];
$items[$j] = $tmp;
}
}
Example (PHP 5.5.9):
php > $original = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
php > $shuffled = (array)$original;
php > fisherYatesShuffle($shuffled, 0);
php > print_r($shuffled);
Array
(
[0] => 6
[1] => 0
[2] => 7
[3] => 2
[4] => 9
[5] => 3
[6] => 1
[7] => 8
[8] => 5
[9] => 4
)
php > $shuffled = (array)$original;
php > fisherYatesShuffle($shuffled, 0);
php > print_r($shuffled);
Array
(
[0] => 6
[1] => 0
[2] => 7
[3] => 2
[4] => 9
[5] => 3
[6] => 1
[7] => 8
[8] => 5
[9] => 4
)
You can use array_multisort to order the array values by a second array of mt_rand values:
$arr = array(1,2,3,4,5,6);
mt_srand('123');
$order = array_map(create_function('$val', 'return mt_rand();'), range(1, count($arr)));
array_multisort($order, $arr);
var_dump($arr);
Here $order is an array of mt_rand values of the same length as $arr. array_multisort sorts the values of $order and orders the elements of $arr according to the order of the values of $order.
The problem you have is that PHP comes with two random number generators built in.
The shuffle() command does not use the mt_rand() random number generator; it uses the older rand() random number generator.
Therefore, if you want shuffle() to use a seeded number sequence, you need to seed the older randomiser, using srand() rather than mt_srand().
In most other cases, you should use mt_rand() rather than rand(), since it is a better random number generator.
The main question involves two parts. One is about how to shuffle. The other is about how to add randomness to it.
A simple solution
This is probably the simplest answer to the main question. It is sufficient for most cases in PHP scripting. But not all (see below).
function /*array*/ seedShuffle(/*one dimentional array*/ $array, /*integer*/ $seed) {
$tmp = array();
for ($rest = $count = count($array);$count>0;$count--) {
$seed %= $count;
$t = array_splice($array,$seed,1);
$tmp[] = $t[0];
$seed = $seed*$seed + $rest;
}
return $tmp;
}
The above method will do, even though it doesn't produce true random shuffles for all possible seed-array combinations. However, if you really want it to be balanced and all, I guess PHP shuldn't be your choice.
A more useful solution for advanced programmers
As stated by André Laszlo, randomization is a tricky business. It is usually best to let a dedicated object handle it. My point is, that you shouldn't need to bother with the randomness when you write the shuffle function. Depending on what degree of ramdomness you would like in your shuffle, you may have a number of PseudoRandom objects to choose from. Thus the above could look like this:
abstract class PseudoRandom {
protected abstract function /*integer*/ nextInt();
public function /*integer*/ randInt(/*integer*/ $limit) {
return $this->nextInt()%$limit;
}
}
function /*array*/ seedShuffle($array, /*PseudoRandom Object*/ $rnd) {
$tmp = array();
$count = count($array);
while($count>0) {
$t = array_splice($array,$rnd->randInt($count--),1);
$tmp[] = $t[0];
}
return $tmp;
}
Now, this solution is the one I would vote for. It separates shuffle codes from randomization codes. Depending on what kind of random you need you can subclass PseudoRandom, add the needed methods and your preferred formulas.
And, as the same shuffle function may be used with many random algorithms, one random algorithm may be used in different places.
In recent PHP versions, seeding the PHP builtin rand() and mt_rand() functions will not give you the same results everytime. The reason for this is not clear to me (why would you want to seed the function anyway if the result is different every time.) Anyway, it seems like the only solution is to write your own random function
class Random {
// random seed
private static $RSeed = 0;
// set seed
public static function seed($s = 0) {
self::$RSeed = abs(intval($s)) % 9999999 + 1;
self::num();
}
// generate random number
public static function num($min = 0, $max = 9999999) {
if (self::$RSeed == 0) self::seed(mt_rand());
self::$RSeed = (self::$RSeed * 125) % 2796203;
return self::$RSeed % ($max - $min + 1) + $min;
}
}
Usage:
// set seed
Random::seed(42);
// echo 10 numbers between 1 and 100
for ($i = 0; $i < 10; $i++) {
echo Random::num(1, 100) . '<br />';
}
The code above will output the folowing sequence every time you run it:
76
86
14
79
73
2
87
43
62
7
Just change the seed to get a completely different "random" sequence
A variant that also works with PHP version 7.2, because the php function create_function is deprecated in the newest php version.
mt_srand($seed);
$getMTRand = function () {
return mt_rand();
};
$order = array_map($getMTRand, range(1, count($array)));
array_multisort($order, $array);
return $array;
I guess this will do the job :
function choose_X_random_items($original_array , $number_of_items_wanted = -1 , $seed = FALSE ){
//save the keys
foreach ($original_array as $key => $value) {
$original_array[$key]['key_memory'] = $key;
}
$original_array = array_values($original_array);
$results = array();
if($seed !== FALSE){srand($seed);}
$main_random = rand();
$random = substr($main_random,0,( $number_of_items_wanted == -1 ? count($original_array) : min($number_of_items_wanted,count($original_array)) ));
$random = str_split($random);
foreach ($random AS $id => $value){
$pick = ($value*$main_random) % count($original_array);
$smaller_array[] = $original_array[$pick];
unset($original_array[$pick]);
$original_array = array_values($original_array);
}
//retrieve the keys
foreach ($smaller_array as $key => $value) {
$smaller_array[$value['key_memory']] = $value;
unset($smaller_array[$value['key_memory']]['key_memory']);
unset($smaller_array[$key]);
}
return $smaller_array;
}
In order to not limit the resulting array, set $number_of_items_wanted to -1
In order to not use a seed, set $seed to FALSE
Seeded shuffle while maintaining the key index:
function seeded_shuffle(array &$items, $seed = false) {
mt_srand($seed ? $seed : time());
$keys = array_keys($items);
$items = array_values($items);
for ($i = count($items) - 1; $i > 0; $i--) {
$j = mt_rand(0, $i);
list($items[$i], $items[$j]) = array($items[$j], $items[$i]);
list($keys[$i], $keys[$j]) = array($keys[$j], $keys[$i]);
}
$items = array_combine($keys, $items);
}
A simple solution:
$pool = [1, 2, 3, 4, 5, 6];
$seed = 'foo';
$randomIndex = crc32($seed) % count($pool);
$randomElement = $pool[$randomIndex];
It might not be quite as random as the Fisher Yates shuffle, but I found it gave me more than enough entropy for what I needed it for.
Based on #Gumbo, #Spudley, #AndreyP answers, it works like that:
$arr = array(1,2,3,4,5,6);
srand(123); //srand(124);
$order = array_map(function($val) {return rand();}, range(1, count($arr)));
array_multisort($order, $arr);
var_dump($arr);
Home Made function, using crc32(note:also returns negative value(https://www.php.net/manual/en/function.crc32.php))
$sortArrFromSeed = function($arr, $seed){
$arrLen = count($arr);
$newArr = [];
$hash = crc32($seed); // returns hash (0-9 numbers)
$hash = strval($hash);
while(strlen($hash) < $arrLen){
$hash .= $hash;
}
for ($i=0; $i<$arrLen; $i++) {
$index = (int) $hash[$i] * (count($arr)/9); // because 0-9 range
$index = (int) $index; // remove decimal
if($index !== 0) $index--;
array_push($newArr, $arr[$index]);
unset($arr[$index]);
$arr = array_values($arr);
}
return $newArr;
};
// TESTING
$arr = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
$arr = $sortArrFromSeed($arr,'myseed123');
echo '<pre>'.print_r($arr,true).'</pre>';
This seems the easiest for me...
srand(123);
usort($array,function($a,$b){return rand(-1,1);});

Parsing Text File

I need an advice.
I need to scrap and parse text file (using for currency exchange rates). Here we are, a small snippet from this file:
c057z110323
h057z110323
a057z110323
b012z110323
c058z110324
h058z110324
a058z110324
c059z110325
h059z110325
a059z110325
c060z110328
h060z110328
a060z110328
c061z110329
h061z110329
a061z110329
c062z110330
h062z110330
a062z110330
b013z110330
c063z110331
h063z110331
a063z110331
c064z110401
h064z110401
a064z110401
c065z110404
h065z110404
a065z110404
c066z110405
h066z110405
a066z110405
c067z110406
h067z110406
a067z110406
b014z110406
c068z110407
h068z110407
a068z110407
c069z110408
h069z110408
a069z110408
As you may see there's a lot of lines (in original file there are about 80000 of lines (few lines per day are being added).
String format is as following:
A000112233
where
A - type
000 - number of the file (created this year)
11 - year
22 - month
33 - day
I'm getting 25 latest lines from from the file using following snippet:
$file = "http://www.nbp.pl/kursy/xml/dir.txt";
$data = file($file);
$count = count($data);
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$latest[] = $data[$i];
}
}
I need to get only lines starting with "a". The output array looks as following:
array(8) {
[0]=>
string(13) "a062z110330
"
[1]=>
string(13) "a063z110331
"
[2]=>
string(13) "a064z110401
"
[3]=>
string(13) "a065z110404
"
[4]=>
string(13) "a066z110405
"
[5]=>
string(13) "a067z110406
"
[6]=>
string(13) "a068z110407
"
[7]=>
string(13) "a069z110408
"
}
Now I need to compare every array element to get the latest item from the latest working day before current date. I'm acheiving it this way:
$i = 1;
foreach($latest as $row)
{
$plural = ($i > 1) ? 's' : null;
if( substr(trim($row), -6) === date("ymd", strtotime("-" . $i . " day" . $plural) )
{
$filename = $row;
break;
}
$i++;
}
It's working quite OK, however I'm facing one big problem. I'm unable to sort $latest array by the latest six characters. I tried doing this using sort(), rsort(). None of them worked good for me.
Can anybody help me with this issue or maybe has better approach to do what I'm looking for.
When you do
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$latest[] = $data[$i];
}
}
use date as a key in $latest array:
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$key = (int) substr($data[$i], -6);
$latest[$key] = $data[$i];
}
}
Then you can sort by key like:
ksort($latest);
You need to use a custom sort method. You can use usort to write your own comparison function : http://php.net/manual/en/function.usort.php
From the manual
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$a = array(3, 2, 5, 6, 1);
usort($a, "cmp");
The comparison function must return an
integer less than, equal to, or
greater than zero if the first
argument is considered to be
respectively less than, equal to, or
greater than the second.
Since you're only asking how to sort an array of strings by their last six characters:
Use usort:
function sortfunc($a, $b) {
return strcmp(substr($a, -6), substr($b, -6));
}
usort($latest, 'sortfunc');
You may need to trim() your lines first or the newlines and/or carriage return characters will be part of the last 6 characters.

php array needed

hi all i need a snippet i know can do it with a bit of coding
but i need a snippet
i want an array of say choosable length like getArray(50) gives me a array of size 50
like we declare na ?
array[50] in other languages
and i want to fill it with some random data.
I want some real cool methods!
Like this
array(
0=>"sds"
1=>"bds"
....
n=>"mds"
);
You can get an array of a specific length, that is pre-filled with a given value, using the array_fill() function. I don't think that there is an in-built function that will generate arrays with random contents, though.
$myArray = array_fill(0, 50, null);
What form exactly do you want the array elements to take? You want them to be a lowercase letter frollowed by "ds"?
$myArraySize = 50;
$myArray = array_fill(0, $myArraySize, '_ds');
for ($i=0; $i<$myArraySize; $i++) {
$myArray[$i][0] = chr(mt_rand(97, 122));
}
this can be used to get array's of specific length filled with 24 character long string. Use it according to your use.
<?php
function generate_array($num)
{
$input = array();
$result = array_pad($input, $num, 0);
foreach($result as $key=>$val)
{
$result[$key] = gen_rand_str_24();
}
return $result;
}
function gen_rand_str_24()
{
return pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand());
}
$result = generate_array(5);
print_r($result);
?>
Just do:
$arr = array();
$arr[] = "a";
$arr[] = "b";
Or similar:
$arr = array( 0 => "a",
1 => "b");
You don't need to set a length before filling it in PHP.

Categories