Most efficient way to delimit key - php

Say I have a string of 16 numeric characters (i.e. 0123456789012345) what is the most efficient way to delimit it into sets like : 0123-4567-8901-2345, in PHP?
Note: I am rewriting an existing system that is painfully slow.

Use str_split():
$string = '0123456789012345';
$sets = str_split($string, 4);
print_r($sets);
The output:
Array
(
[0] => 0123
[1] => 4567
[2] => 8901
[3] => 2345
)
Then of course to insert hyphens between the sets you just implode() them together:
echo implode('-', $sets); // echoes '0123-4567-8901-2345'

If you are looking for a more flexible approach (for e.g. phone numbers), try regular expressions:
preg_replace('/^(\d{4})(\d{4})(\d{4})(\d{4})$/', '\1-\2-\3-\4', '0123456789012345');
If you can't see, the first argument accepts four groups of four digits each. The second argument formats them, and the third argument is your input.

This is a bit more general:
<?php
// arr[string] = strChunk(string, length [, length [...]] );
function strChunk() {
$n = func_num_args();
$str = func_get_arg(0);
$ret = array();
if ($n >= 2) {
for($i=1, $offs=0; $i<$n; ++$i) {
$chars = abs( func_get_arg($i) );
$ret[] = substr($str, $offs, $chars);
$offs += $chars;
}
}
return $ret;
}
echo join('-', strChunk('0123456789012345', 4, 4, 4, 4) );
?>

Related

Find all the occurrence points of a letter within a string

I have the following code:
<?php
$word = "aeagle";
$letter = "e";
$array = strposall($aegle, $letter);
print_r($array);
function strposall($haystack, $needle) {
$occurrence_points = array();
$pos = strpos($haystack, $needle);
if ($pos !== false) {
array_push($occurrence_points, $pos);
}
while ($pos = strpos($haystack, $needle, $pos + 1)) {
array_push($occurrence_points, $pos);
}
return $occurrence_points;
}
?>
As in the example, if I have aegle as my word and I'm searching for e within it, the function should return an array with the values 1 and 4 in it.
What's wrong with my code?
Why not trying instead
$word = "aeagle";
$letter = "e";
$occurrence_points = array_keys(array_intersect(str_split($word), array($letter)));
var_dump($occurrence_points);
I think you're passing the wrong parameters, shouild be $word instead of $aegle
Little bit more literal than the other answer:
function charpos($str, $char) {
$i = 0;
$pos = 0;
$matches = array();
if (strpos($str, $char) === false) {
return false;
}
while (!!$str) {
$pos = strpos($str, $char);
if ($pos === false) {
$str = '';
} else {
$i = $i + $pos;
$str = substr($str, $pos + 1);
array_push($matches, $i++);
}
}
return $matches;
}
https://ignite.io/code/511ff26eec221e0741000000
Using:
$str = 'abc is the place to be heard';
$positions = charpos($str, 'a');
print_r($positions);
while ($positions) {
$i = array_shift($positions);
echo "$i: $str[$i]\n";
}
Which gives:
Array (
[0] => 0
[1] => 13
[2] => 25
)
0: a
13: a
25: a
Other's have pointed out you're passing the wrong parameters. But you're also reinventing the wheel. Take a look at php's regular expression match-all (whoops, had linked the wrong function), it will already return an array of all matches with offsets, when used with the following flag.
flags
flags can be the following flag:
PREG_OFFSET_CAPTURE
If this flag is passed, for every occurring match the appendant string offset will also be returned. Note that this changes the value of matches into an array where every element is an array consisting of the matched string at offset 0 and its string offset into subject at offset 1.
Use a single letter pattern for the search term $letter = '/e/' and you should get back an array with all your positions as the second element of each result array, which you can then finagle into the output format you're looking for.
Update: Jared points out that you do get the capture of the pattern back, but with the flag set, you also get the offset. As a direct answer to the OP's question, try this code:
$word = "aeagle";
$pattern = "/e/";
$matches = array();
preg_match_all($pattern, $word, $matches, PREG_OFFSET_CAPTURE);
print_r($matches);
It has the following ouput:
Array
(
// Matches of the first pattern: /e/
[0] => Array
(
// First match
[0] => Array
(
// Substring of $word that matched
[0] => e
// Offset into $word where previous substring starts
[1] => 1
)
[1] => Array
(
[0] => e
[1] => 5
)
)
)
The results are 3D instead of 2D because preg_match_all can match multiple patterns at once. The hits are for the first (and in this case: only) pattern supplied and are thus in the first array.
And unlike the OP originally stated, 1 and 5 are the correct indexes of the letter e in the string 'aeagle'
aeagle
012345
^ ^
1 5
Performance wise, the customized version of strposall would probably be faster than a regular expression match. But learning to use an in-built function is almost always faster than developing, testing, supporting and maintaining your own code. And 9 times out of 10, that's the most expensive part of programming.

PHP: How should I convert this inconsistent string into arrays?

I have this string: $delims = ',^.;:\t' (which is used in a preg_replace() call).
I need two arrays .. one with those delimiters as the index ($delimCount), and another with those delimiters as the values ($delimiters).
The former will get values assigned from within a loop on the latter .. as such:
foreach ($delimiters as $delim) {
if( strpos($char, $delim) !== false) { // if the char is the delim ...
$delimCount[$delim]++; // ... increment
}
}
The way I have it now is messy, and I'd like to simply break that string into the two arrays ... but I'm getting tripped up on the \t delimiter (because it's the only one with two characters).
How can/should I handle that?
How I would handle your \t delimiter
$delims = ',^.;:\t';
$i = 0;
while($i < strlen($delims)) {
if($delims[$i] == chr(92)) {
$delimiters[] = $delims[$i] . $delims[$i+1];
$i = $i + 2;
}
else {
$delimiters[] = $delims[$i];
$i++;
}
}
Output of $delimiters
Array
(
[0] => ,
[1] => ^
[2] => .
[3] => ;
[4] => :
[5] => \t
)
As far as an array with the delimiters as an index
foreach($delimiters as $key=>$val) {
$delimCount[$val] = $val;
}
Output of $delimCount
Array
(
[,] => ,
[^] => ^
[.] => .
[;] => ;
[:] => :
[\t] => \t
)
Hope this helps.
I need two arrays .. one with those delimiters as the index ($delimCount), and another with those delimiters as the values ($delimiters).
Well, where does $delimiters come from? Did you write it yourself? If so, I'd argue you'd be better off making $delimiters an array in any case, and using implode when you use it in preg_replace.
If you have a problem with tabulator, then you can use ordinal number of character as the key inplace of character itself: $delim = "\t"; $delimCount[ ord($delim) ]++;
Handy complementary functions: ord() and chr().

Split string only once between sequence of letters and sequence of numbers

I want to extract two substrings from a predictably formatted string.
Each string is comprised of letters followed by numbers.
Inputs & Outputs:
MAU120 => MAU and 120
MAUL345 => MAUL and 345
MAUW23 => MAUW and 23
$matches = array();
if ( preg_match('/^([A-Z]+)([0-9]+)$/i', 'MAUL345', $matches) ) {
echo $matches[1]; // MAUL
echo $matches[2]; // 345
}
If you require the MAU you can do:
/^(MAU[A-Z]*)([0-9]+)$/i
Removing i modifier at the end will make the regex case-sensitive.
Try this regular expression:
/(\D*)(\d*)/
PHP code:
$matches = array();
var_dump( preg_match('/(\D*)(\d*)/', 'MAUL345', $matches) );
var_dump( $matches );
Taken literally from your examples:
<?php
$tests = array('MAU120', 'MAUL345', 'MAUW23', 'bob2', '?##!123', 'In the MAUX123 middle.');
header('Content-type: text/plain');
foreach($tests as $test)
{
preg_match('/(MAU[A-Z]?)(\d+)/', $test, $matches);
$str = isset($matches[1]) ? $matches[1] : '';
$num = isset($matches[2]) ? $matches[2] : '';
printf("\$str = %s\n\$num = %d\n\n", $str, $num);
}
?>
Produces:
$test = MAU120
$str = MAU
$num = 120
$test = MAUL345
$str = MAUL
$num = 345
$test = MAUW23
$str = MAUW
$num = 23
$test = bob2
$str =
$num = 0
$test = ?##!123
$str =
$num = 0
$test = In the MAUX123 middle.
$str = MAUX
$num = 123
When you can guarantee that there will be one or more non-numbers and then one or more numbers, you can call upon sscanf() to parse the string.
The native function has multiple advantages over preg_match().
It doesn't return the fullstring match.
It will allow you to type cast substrings depending on the format placeholder you use.
It can return its array or create reference variables -- depending on the number of parameters you feed it.
Code: (Demo)
$tests = [
'MAU120',
'MAUL345',
'MAUW23',
];
foreach ($tests as $test) {
sscanf($test, '%[^0-9]%d', $letters, $numbers);
var_export([$letters, $numbers]);
echo "\n";
}
Output: (notice that the numbers are cast as integer type)
array (
0 => 'MAU',
1 => 120,
)
array (
0 => 'MAUL',
1 => 345,
)
array (
0 => 'MAUW',
1 => 23,
)
If your numbers might start with zero(s) and you want to retain them, you can use %s instead of %d to capture the non-whitespaces substring. If you use %s, then the digits will be cast as a string instead of int-type.
Alternative syntax: (Demo)
foreach ($tests as $test) {
var_export(sscanf($test, '%[^0-9]%d'));
echo "\n";
}

"Unfolding" a String

I have a set of strings, each string has a variable number of segments separated by pipes (|), e.g.:
$string = 'abc|b|ac';
Each segment with more than one char should be expanded into all the possible one char combinations, for 3 segments the following "algorithm" works wonderfully:
$result = array();
$string = explode('|', 'abc|b|ac');
foreach (str_split($string[0]) as $i)
{
foreach (str_split($string[1]) as $j)
{
foreach (str_split($string[2]) as $k)
{
$result[] = implode('|', array($i, $j, $k)); // more...
}
}
}
print_r($result);
Output:
$result = array('a|b|a', 'a|b|c', 'b|b|a', 'b|b|c', 'c|b|a', 'c|b|c');
Obviously, for more than 3 segments the code starts to get extremely messy, since I need to add (and check) more and more inner loops. I tried coming up with a dynamic solution but I can't figure out how to generate the correct combination for all the segments (individually and as a whole). I also looked at some combinatorics source code but I'm unable to combine the different combinations of my segments.
I appreciate if anyone can point me in the right direction.
Recursion to the rescue (you might need to tweak a bit to cover edge cases, but it works):
function explodinator($str) {
$segments = explode('|', $str);
$pieces = array_map('str_split', $segments);
return e_helper($pieces);
}
function e_helper($pieces) {
if (count($pieces) == 1)
return $pieces[0];
$first = array_shift($pieces);
$subs = e_helper($pieces);
foreach($first as $char) {
foreach ($subs as $sub) {
$result[] = $char . '|' . $sub;
}
}
return $result;
}
print_r(explodinator('abc|b|ac'));
Outputs:
Array
(
[0] => a|b|a
[1] => a|b|c
[2] => b|b|a
[3] => b|b|c
[4] => c|b|a
[5] => c|b|c
)
As seen on ideone.
This looks like a job for recursive programming! :P
I first looked at this and thought it was going to be a on-liner (and probably is in perl).
There are other non-recursive ways (enumerate all combinations of indexes into segments then loop through, for example) but I think this is more interesting, and probably 'better'.
$str = explode('|', 'abc|b|ac');
$strlen = count( $str );
$results = array();
function splitAndForeach( $bchar , $oldindex, $tempthread) {
global $strlen, $str, $results;
$temp = $tempthread;
$newindex = $oldindex + 1;
if ( $bchar != '') { array_push($temp, $bchar ); }
if ( $newindex <= $strlen ){
print "starting foreach loop on string '".$str[$newindex-1]."' \n";
foreach(str_split( $str[$newindex - 1] ) as $c) {
print "Going into next depth ($newindex) of recursion on char $c \n";
splitAndForeach( $c , $newindex, $temp);
}
} else {
$found = implode('|', $temp);
print "Array length (max recursion depth) reached, result: $found \n";
array_push( $results, $found );
$temp = $tempthread;
$index = 0;
print "***************** Reset index to 0 *****************\n\n";
}
}
splitAndForeach('', 0, array() );
print "your results: \n";
print_r($results);
You could have two arrays: the alternatives and a current counter.
$alternatives = array(array('a', 'b', 'c'), array('b'), array('a', 'c'));
$counter = array(0, 0, 0);
Then, in a loop, you increment the "last digit" of the counter, and if that is equal to the number of alternatives for that position, you reset that "digit" to zero and increment the "digit" left to it. This works just like counting with decimal numbers.
The string for each step is built by concatenating the $alternatives[$i][$counter[$i]] for each digit.
You are finished when the "first digit" becomes as large as the number of alternatives for that digit.
Example: for the above variables, the counter would get the following values in the steps:
0,0,0
0,0,1
1,0,0 (overflow in the last two digit)
1,0,1
2,0,0 (overflow in the last two digits)
2,0,1
3,0,0 (finished, since the first "digit" has only 3 alternatives)

PHP string manipulation, inside the string

I have string:
ABCDEFGHIJK
And I have two arrays of positions in that string that I want to insert different things to.
Array
(
[0] => 0
[1] => 5
)
Array
(
[0] => 7
[1] => 9
)
Which if I decided to add the # character and the = character, it'd produce:
#ABCDE=FG#HI=JK
Is there any way I can do this without a complicated set of substr?
Also, # and = need to be variables that can be of any length, not just one character.
You can use string as array
$str = "ABCDEFGH";
$characters = preg_split('//', $str, -1);
And afterwards you array_splice to insert '#' or '=' to position given by array
Return the array back to string is done by:
$str = implode("",$str);
This works for any number of characters (I am using "#a" and "=b" as the character sequences):
function array_insert($array,$pos,$val)
{
$array2 = array_splice($array,$pos);
$array[] = $val;
$array = array_merge($array,$array2);
return $array;
}
$s = "ABCDEFGHIJK";
$arr = str_split($s);
$arr_add1 = array(0=>0, 1=>5);
$arr_add2 = array(0=>7, 1=>9);
$char1 = '#a';
$char2 = '=b';
$arr = array_insert($arr, $arr_add1[0], $char1);
$arr = array_insert($arr, $arr_add1[1] + strlen($char1), $char2);
$arr = array_insert($arr, $arr_add2[0]+ strlen($char1)+ strlen($char2), $char1);
$arr = array_insert($arr, $arr_add2[1]+ strlen($char1)+ strlen($char2) + strlen($char1), $char2);
$s = implode("", $arr);
print_r($s);
There is an easy function for that: substr_replace. But for this to work, you would have to structure you array differently (which would be more structured anyway), e.g.:
$replacement = array(
0 => '#',
5 => '=',
7 => '#',
9 => '='
);
Then sort the array by keys descending, using krsort:
krsort($replacement);
And then you just need to loop over the array:
$str = "ABCDEFGHIJK";
foreach($replacement as $position => $rep) {
$str = substr_replace($str, $rep, $position, 0);
}
echo $str; // prints #ABCDE=FG#HI=JK
This works by inserting the replacements starting from the end of string. And it would work with any replacement string without having to determine the length of that string.
Working DEMO

Categories