Splitting string by fixed length - php

I am looking for ways to split a string of a unicode alpha-numeric type to fixed lenghts.
for example:
992000199821376John Smith 20070603
and the array should look like this:
Array (
[0] => 99,
[1] => 2,
[2] => 00019982,
[3] => 1376,
[4] => "John Smith",
[5] => 20070603
)
array data will be split like this:
Array[0] - Account type - must be 2 characters long,
Array[1] - Account status - must be 1 character long,
Array[2] - Account ID - must be 8 characters long,
Array[3] - Account settings - must be 4 characters long,
Array[4] - User Name - must be 20 characters long,
Array[5] - Join Date - must be 8 characters long.

Or if you want to avoid preg:
$string = '992000199821376John Smith 20070603';
$intervals = array(2, 1, 8, 4, 20, 8);
$start = 0;
$parts = array();
foreach ($intervals as $i)
{
$parts[] = mb_substr($string, $start, $i);
$start += $i;
}

$s = '992000199821376Николай Шмидт 20070603';
if (preg_match('~(.{2})(.{1})(.{8})(.{4})(.{20})(.{8})~u', $s, $match))
{
list (, $type, $status, $id, $settings, $name, $date) = $match;
}

Using the substr function would do this quite easily.
$accountDetails = "992000199821376John Smith 20070603";
$accountArray = array(substr($accountDetails,0,2),substr($accountDetails,2,1),substr($accountDetails,3,8),substr($accountDetails,11,4),substr($accountDetails,15,20),substr($accountDetails,35,8));
Should do the trick, other than that regular expressions (as suggested by akond) is probably the way to go (and more flexible). (Figured this was still valid as an alternate option).

It is not possible to split a unicode string in a way you ask for.
Not possible without making the parts invalid.
Some code points have no way of standing out, for example: שׁ is 2 code points (and 4 bytes in UTF-8 and UTF-16) and you cannot split it because it is undefined.
When you work with unicode, "character" is a very slippery term. There are code points, glyphs, etc. See more at http://www.utf8everywhere.org, the part on "length of a string"

Related

How to effectively convert positive number in base -2 to a negative one in base - 2?

Old question name: How to effectively split a binary string in a groups of 10, 0, 11?
I have some strings as an input, which are binary representation of a number.
For example:
10011
100111
0111111
11111011101
I need to split these strings (or arrays) into groups of 10, 0, and 11 in order to replace them.
10 => 11
0 => 0
11 => 10
How to do it? I have tried these options but don't work.
preg_match('/([10]{2})(0{1})([11]{2})/', $S, $matches);
It should be [10] [0], [11] for 10011 input.
And it should be 11010 when replaced.
UPD1.
Actually, I'm trying to do a negation algorithm for converting a positive number in a base -2 to a negative one in a base -2.
It could be done with an algorithm from Wikipedia with a loop. But byte groups replacing is a much faster. I have implemented it already and just trying to optimize it.
For this case 0111111 it's possible to add 0 in the end. Then rules will be applied. And we could remove leading zeros in a result. The output will be 101010.
UPD2.
#Wiktor Stribiżew proposed an idea how to do a replace immediately, without splitting bytes into groups first.
But I have a faster solution already.
$S = strtr($S, $rules);
The meaning of this question isn't do a replacement, but get an array of desired groups [11] [0] [10].
UPD3.
This is a solution which I reached with an idea of converting binary groups. It's faster than one with a loop.
function solution2($A)
{
$S = implode('', $A);
//we could add leading 0
if (substr($S, strlen($S) - 1, 1) == 1) {
$S .= '0';
}
$rules = [
'10' => '11',
'0' => '0',
'11' => '10',
];
$S = strtr($S, $rules);
$arr = str_split($S);
//remove leading 0
while ($arr[count($arr) - 1] == 0) {
array_pop($arr);
}
return $arr;
}
But the solution in #Alex Blex answer is faster.
You may use a simple /11|10/ regex with a preg_replace_callback:
$s = '10011';
echo preg_replace_callback("/11|10/", function($m) {
return $m[0] == "11" ? "10" : "11"; // if 11 is matched, replace with 10 or vice versa
}, $s);
// => 11010
See the online PHP demo.
Answering the question
algorithm for converting a positive number in a base -2 to a negative one in a base -2
I believe following function is more efficient than a regex:
function negate($negabin)
{
$mask = 0xAAAAAAAAAAAAAAA;
return decbin((($mask<<1)-($mask^bindec($negabin)))^$mask);
}
Parameter is a positive int60 in a base -2 notation, e.g. 11111011101.
The function converts the parameter to base 10, negate it, and convert it back to base -2 as described in the wiki: https://en.wikipedia.org/wiki/Negative_base#To_negabinary
Works on 64bit system, but can easily adopted to work on 32bit.

Php: String indexing inconsistant?

I have created a function which randomly generates a phrase from a hardcoded list of words. I have a function get_words() which has a string of hardcoded words, which it turns into an array then shuffles and returns.
get_words() is called by generate_random_phrase(), which iterates through get_words() n times, and on every iteration concatenates the n word into the final phrase which is destined to be returned to the user.
My problem is, for some reason PHP keeps giving me inconsistent results. It does give me words which are randomized, but it gives inconsistent number of words. I specify 4 words as the default and it gives me phrases ranging from 1-4 words instead of 4. This program is so simple it is almost unbelievable I can't pinpoint the exact issue. It seems like the broken link in the chain is the $words array which is being indexed, it seems like for some reason sometimes the indexing fails. I am unfamiliar with PHP, can someone explain this to me?
<?php
function generate_random_phrase() {
$words = get_words();
$number_of_words = get_word_count();
$phrase = "";
$symbols = "!##$%^&*()";
echo print_r($phrase);
for ($i = 0;$i < $number_of_words;$i++) {
$phrase .= " ".$words[$i];
}
if (isset($_POST['include_numbers']))
$phrase = $phrase.rand(0, 9);
if (isset($_POST['include_symbols']))
$phrase = $phrase.$symbols[rand(0, 9)];
return $phrase;
}
function get_word_count() {
if ($_POST['word_count'] < 1 || $_POST['word_count'] > 9)
$word_count = 4; #default
else
$word_count = $_POST['word_count'];
return $word_count;
}
function get_words() {
$BASE_WORDS = "my sentence really hope you
like narwhales bacon at midnight but only
ferver where can paper laptops spoon door knobs
head phones watches barbeque not say";
$words = explode(' ', $BASE_WORDS);
shuffle($words);
return $words;
}
?>
In $BASE_WORDS your tabs and new lines are occupying a space in the exploded array that's why. Remove the newlines and tabs and it'll generate the correct answer. Ie:
$BASE_WORDS = "my sentence really hope you like narwhales bacon at midnight but only ferver where can paper laptops spoon door knobs head phones watches barbeque not say";
Your function seems a bit inconsistent since you also include spaces inside the array, thats why when you included them, you include them in your loop, which seems to be 5 words (4 real words with one space index) is not really correct. You could just filter spaces also first, including whitespaces.
Here is the visual representation of what I mean:
Array
(
[0] => // hello im a whitespace, i should not be in here since im not really a word
[1] => but
[2] =>
[3] => bacon
[4] => spoon
[5] => head
[6] => barbeque
[7] =>
[8] =>
[9] => sentence
[10] => door
[11] => you
[12] =>
[13] => watches
[14] => really
[15] => midnight
[16] =>
So when you loop it, you include spaces, in this case. If you got a number of words of 5, you really dont get those 5 words, index 0 - 4 it will look like you only got 3 (1 => but, 3 => bacon, 4 => spoon).
Here is a modified version:
function generate_random_phrase() {
$words = get_words();
$number_of_words = get_word_count();
$phrase = "";
$symbols = "!##$%^&*()";
$words = array_filter(array_map('trim', $words)); // filter empty words
$phrase = implode(' ', array_slice($words, 0, $number_of_words)); // no need for a loop
// this simply gets the array from the first until the desired number of words (0,5 or 0,9 whatever)
// and then implode, just glues all the words with space
// so this ensure its always according to how many words you want
if (isset($_POST['include_numbers']))
$phrase = $phrase.rand(0, 9);
if (isset($_POST['include_symbols']))
$phrase = $phrase.$symbols[rand(0, 9)];
return $phrase;
}
Inconsistent spacing in your words list is the issue.
Here is a fix:
function get_words() {
$BASE_WORDS = "my|sentence|really|hope|you|
|like|narwhales|bacon|at|midnight|but|only|
|ferver|where|can|paper|laptops|spoon|door|knobs|
|head|phones|watches|barbeque|not|say";
$words = explode('|', $BASE_WORDS);
shuffle($words);
return $words;
}

Converting large numbers into letters (and back again)

Is there a term for the idea of storing large numbers as letters? For example let's say I have the (relatively small) number 138201162401719 and I want to shrink the number of characters (I know this does not help with saving disk space) to the fewest possible number of characters. There are 26 letters in the English alphabet (but i count them as 25 since we need a zero letter). If I start splitting up my large number into pieces that are each 25 or less I get:
13, 8, 20, 11, 6, 24, 0, 17, 19
If I then count the numbers of the alphabet a=0, b=1, c=2, d=3... I can convert this to:
NIULGYART
So I went from 15 digits long (138201162401719) to 9 characters long (NIULGYART). This could of course be easily converted back to the original number as well.
So...my first question is "Does this have a name" and my second "Does anyone have PHP code that will do the conversion (in both directions)?"
I am looking for proper terminology so that I can do my own research in Google...though working code examples are cool too.
This only possible if you're considering to store your number before processing as a string. Because you can't store huge number as integers. You will lost the precision (13820116240171986468445 will be stored as 1.3820116240172E+22) so the alot of digits are lost.
If you're considering storing the number as a string this will be your answer:
Functions used: intval, chr and preg_match_all.
<?php
$regex = '/(2[0-5])|(1[0-9])|([0-9])/';
$numberString = '138201162401719';
preg_match_all($regex, $numberString, $numberArray, PREG_SET_ORDER);
echo($numberString . " -> ");
foreach($numberArray as $value){
$character = chr (intval($value[0]) + 65);
echo($character);
}
?>
Demo
This is the result:
138201162401719 -> NIULGYART
Here's how I would do it:
Store the big number as a string and split it into an array of numbers containing one digit each
Loop through the array extract 2-digit chunks using substr()
Check if the number is less than 26 (in which case, it is an alphabet) and add them to an array
Use array_map() with chr() to create a new array of characters from the above array
Implode the resulting array to get the cipher
In code:
$str = '138201162401719';
$arr = str_split($str);
$i = 0; // starting from the left
while ($i < count($arr)) {
$n = substr($str, $i, 2);
$firstchar = substr($n, 0, 1);
if ($n < 26 && $firstchar != 0) {
$result[] = substr($str, $i, 2);
$i += 2; // advance two characters
} else {
$result[] = substr($str, $i, 1);
$i++; // advance one character
}
}
$output = array_map(function($n) {
return chr($n+65);
}, $result);
echo implode($output); // => NIULGYART
Demo.
As an alternative, you could convert the input integer to express it in base 26, instead of base 10. Something like (pseudocode):
func convertBase26(num)
if (num < 0)
return "-" & convertBase26(-num) // '&' is concatenate.
else if (num = 0)
return "A"
endif
output = "";
while (num > 0)
output <- ('A' + num MOD 26) & output // Modulus operator.
num <- num DIV 26 // Integer division.
endwhile
return output
endfunc
This uses A = 0, B = 1, up to Z = 25 and standard place notation: 26 = BA. Obviously a base conversion is easily reversible.
strtr() is a magnificent tool for this task! It replaces the longest match as is traverses the string.
Code: (Demo)
function toAlpha ($num) {
return strtr($num, range("A", "Z"));
}
$string = toAlpha("138201162401719");
echo "$string\n";
$string = toAlpha("123456789012345");
echo "$string\n";
$string = toAlpha("101112131415161");
echo "$string\n";
$string = toAlpha("2625242322212019");
echo "$string";
Output:
NIULGYART
MDEFGHIJAMDEF
KLMNOPQB
CGZYXWVUT
Just flip the lookup array to reverse the conversion: https://3v4l.org/YsFZu
Merged: https://3v4l.org/u3NQ5
Of course, I must mention that there is a vulnerability with converting a sequence of letters to numbers and back to letters. Consider BB becomes 11 then is mistaken for eleven which would traslate to L when converted again.
There are ways to mitigate this by adjusting the lookup array, but that may not be necessary/favorable depending on program requirements.
And here is another consideration from CodeReview.
I have been trying to do the same thing in PHP without success.
Assuming I'm using the 26 letters of the English alphabet, starting with A = 0 down to Z as 25:
I find the highest power of 26 lower than the number I am encoding. I divide it by the best power of 26 I found. Of the result I take away the integer, convert it to a letter and multiply the decimals by 26. I keep doing that until I get a whole number. It's ok to get a zero as it's an A, but if it has decimals it must be multiplied.
For 1 billion which is DGEHTYM and it's done in 6 loops obviously. Although my answer demonstrates how to encode, I'm afraid it does not help doing so on PHP which is what I'm trying to do myself. I hope the algorithm helps people out there though.

Credit card Formatting function in Yii

I want to format the credit cards like below when i display it,
Eg:
1234 4567 9874 1222
as
1xxx xxxx xxx 1222
Is there any formatting function like this in Yii ?
No - but there's nothing wrong with using straight PHP.
If you always want the 1st and the last 4 chars you can do something like this:
$last4 = substr($cardNum, -4);
$first = substr($cardNum, 0, 1);
$output = $first.'xxx xxxx xxxx '.$last4;
There are many ways to do this, nothing Yii specific
You could do it using str_split (untested):
$string = "1234 4567 1234 456";
$character_array = str_split($string);
for ($i = 1; $i < count($character_array) - 4; $i++) {
if ($character_array[$i] != " "){
$character_array[$i] = "x";
}
}
echo implode($character_array);
So we are creating an array of characters from the string called
$character_array.
We are then looping thru the characters (starting from position 1,
not 0, so the first character is visible).
We loop until the number of entries in the array minus 4 (so the last
4 characters are not replaced) We replace each character in the loop
with an 'x' (if it's not equal to a space)
We the implode the array back into a string
And you could also use preg_replace :
$card='1234 4567 9874 1222';
$xcard = preg_replace('/^([0-9])([- 0-9]+)([0-9]{4})$/', '${1}xxx xxxx xxxx ${3}', $card);
This regex will also take care of hyphens.
There is no in-built function in Yii.

Adding leading 0 in php

I have
tutorial 1 how to make this
tutorial 21 how to make this
tutorial 2 how to make this
tutorial 3 how to make this
and i need
tutorial 01 how to make this
tutorial 21 how to make this
tutorial 02 how to make this
tutorial 03 how to make this
so i can order them properly. (adding leading 0 when single digit is found)
What would be a php method to convert?
thanks in advance
note-please make sure that it identifies the single digit numbers only first and then add the leading zero
str_pad()
echo str_pad($input, 2, "0", STR_PAD_LEFT);
sprintf()
echo sprintf("%02d", $input);
If it is coming from a DB, this is the way to do it on a sql query:
lpad(yourfield, (select length(max(yourfield)) FROM yourtable),'0') yourfield
This is will get the max value in the table and place the leading zeros.
If it's hardcoded (PHP), use str_pad()
str_pad($yourvar, $numberofzeros, "0", STR_PAD_LEFT);
This is a small example of what I did on a online php compiler, and it works...
$string = "Tutorial 1 how to";
$number = explode(" ", $string); //Divides the string in a array
$number = $number[1]; //The number is in the position 1 in the array, so this will be number variable
$str = ""; //The final number
if($number<10) $str .= "0"; //If the number is below 10, it will add a leading zero
$str .= $number; //Then, add the number
$string = str_replace($number, $str, $string); //Then, replace the old number with the new one on the string
echo $string;
If your goal is to do natural ordering, the way a human being would, why not just use strnatcmp
$arr = [
'tutorial 1 how to make this',
'tutorial 21 how to make this',
'tutorial 2 how to make this',
'tutorial 3 how to make this',
];
usort($arr, "strnatcmp");
print_r($arr);
The above example will output:
Array
(
[0] => tutorial 1 how to make this
[1] => tutorial 2 how to make this
[2] => tutorial 3 how to make this
[3] => tutorial 21 how to make this
)

Categories