php regex negative lookahead - php

I have a dictionary of 4 letter words. I want to write a regex to go through the dictionary and matches all words given a set of letters.
Suppose I pass in a,b,l,l. I want to find all words with exactly those letters.
I know I could do /[abl]{4}/ but that will also match words with 2 a's or 2 b's.
I feel like I need to do a negative look ahead. Something like:
[l|(ab)(?!\1)]{4}
The attempt here is that I want a word that starts with l or a or b and not followed by a or b.

First thing you need to anchor your pattern to describe where the string begins and ends:
for a whole string (^ start of the string, $ end of the string):
^[abl]{4}$
or to find words in a larger text, use word-boundaries (limit between a character from [A-Za-z0-9_] and something else):
\b[abl]{4}\b
Then you need to say that l must occur two times (or that a and b must occurs only one time, but it's more complicated):
for a whole string:
^(?=.*l.*l)[abl]{4}$
in a larger text:
\b(?=\w*l\w*l)[abl]{4}\b
To avoid two a or b, you can use an other lookahead:
for a whole string:
^(?=.*l.*l)(?=l*al*b|l*bl*a)[abl]{4}$
in a larger text:
\b(?=\w*l\w*l)(?=l*al*b|l*bl*a)[abl]{4}\b
About [l|(ab)(?!\1)]: in a character class, special regex characters or sequence of characters loose their special meaning and all characters are seen as literals. So [l|(ab)(?!\1)] is the same than [)(!|?1abl] for example. (Since \1 is an unknown escape sequence in a character class, the backslash is ignored.)
Note that with several constraints the pattern becomes quickly ugly. You should consider an other approach that consists to catch all words with \b[abl]{4}\b and to filter them in a second time (using count_chars for example).
$str ='abll labl ball aabl lblabla 1234';
$dict = 'abll';
$count = count_chars($dict);
$result = [];
if (preg_match_all('~\b[abl]{4}\b~', $str, $matches)) {
$result = array_filter($matches[0], function ($i) use ($count) {
return $count == count_chars($i);
});
}
print_r($result);

If you want specify letters dynamically and then generate regexp that will do all work - this will be a very expensive work.
Simple approach: you can generate simple regexp like /^[abl]{4}$/, get all words from dictionary that match him and then validate each word separately - check letters quantity.
More efficient approach: you can index your words in dictionary with sorted list of letters like this:
word: apple | index: aelpp
word: pale | index: aelp
And so on. To get all words from list of letters you simply should sort this letters and find exact match with "index" value.

Edit: So for 47 letters it would be
\b(?:((?(1)(?!))l1)|((?(2)(?!))l2)|...|((?(47)(?!))l47)){47}\b
Letters can be duplicates, say 4 a's and 15 r's (but no more), etc ...
( immune to permutations )
To match out of order items only once,
use a conditional to allow each item to match once,
but no more.
It's not complicated, and is immune to permutations.
Works every time !
\b(?:((?(1)(?!))a)|((?(2)(?!))b)|((?(3)(?!))l)|((?(4)(?!))l)){4}\b
Expanded
\b
(?:
( # (1)
(?(1)(?!))
a
)
|
( # (2)
(?(2)(?!))
b
)
|
( # (3)
(?(3)(?!))
l
)
|
( # (4)
(?(4)(?!))
l
)
){4}
\b

Related

split a value into two and then reverse the value in php

I have a value like this 73b6424b. I want to split value into two parts. Like 73b6 and 424b. Then the two split value want to reverse. Like 424b and 73b6. And concatenate this two value like this 424b73b6. I have already done this like way
$substr_device_value = 73b6424b;
$first_value = substr($substr_device_value,0,4);
$second_value = substr($substr_device_value,4,8);
$final_value = $second_value.$first_value;
I am searching more than easy way what I have done. Is it possible?? If yes then approach please
You may use
preg_replace('~^(.{4})(.{4})$~', '$2$1', $s)
See the regex demo
Details
^ - matches the string start position
(.{4}) - captures any 4 chars into Group 1 ($1)
(.{4}) - captures any 4 chars into Group 2 ($2)
$ - end of string.
The '$2$1' replacement pattern swaps the values.
NOTE: If you want to pre-validate the data before swapping, you may replace . pattern with a more specific one, say, \w to only match word chars, or [[:alnum:]] to only match alphanumeric chars, or [0-9a-z] if you plan to only match strings containing digits and lowercase ASCII letters.

PHP Regexp capturing repeating group of chars, e.g. hahaha jajajaja hihihi

As title, is there a way in PHP, with preg_match_all to catch all the repetitions of chars group?
For instante catch
hahahaha
jajajaj
hihihi
It's fine to catch repetition of any char, like abababab, acacacacac.
Also, is there a way to count the number of repetition?
The idea is to catch all this "forms" of smiling on social media.
I figured out that there are also other cases, such as misspelled instances like ahahhahaah (where you have two consecutive a or h). Any ideas?
How about this:
preg_match_all('/((?i)[a-z])((?i)[a-z])(\1\2)+/', $str, $m);
$matches = $m[0]; //$matches will contain an array of matches
A bit complicated, but it does work. To explain, the first subpattern (((?i)[a-z])) matches any character between a and z, no matter the case. The second subpattern (((?i)[a-z])) does the same thing. The third subpattern ((\1\2)+) matches one or more repetitions of the first two letters, in the same case as they were originally put. This regular expression also assumes that there's an even number of repetitions. If you don't want that, you can add \1? at the end, meaning that (as long as it contains one or more repetitions), it can end with the first character (for instance, hahah and ikikikik would both be valid, but not asa).
To retrieve the number of repetitions for a specific match, you can do:
$numb = strlen($matches[$index])/2 - 1; //-1 because the first two letters aren't repetitions
For the shortest repetition (e.g. ha gets repeated multiple times in hahahaha):
(.+?)\1+
See demo.
For the longest repetition (e.g. haha gets repeated in hahahaha):
(.+)\1+
Counting Repetitions
The non-regex solution is to compare the lengths of Group 1 (the repteated token) and the overall match.
With pure regex, in .NET, you could simply do (.+?)(\1)+ and look at the number of captures in the Group 1 CaptureCollection object.
In PHP, that's not possible, but there are some hacks. See, for instance, this question about matching a line number—it's the same technique. This is for "study purposes" only—you wouldn't want to use that in real life.

Search a String for Alpha Numeric Characters in a Pattern

I have a string that contains 5 words. In the string one of the words is a Ham Radio Call Sign and can be anyone of the thousands of call signs in the US. In order to extract the Call Sign from the string I need to utilize the below pattern. The Call Sign I need to extract can be in any of the 5 positions in the string. The number is never the first character and the number is never the last character. The string is actually put together from an Array since it is originally read from a text file.
$string = $word[1] $word[2] $word[3] etc....
So the search can be either done on the whole string or each piece of the array.
Patterns:
1 Number and 3 Letters Example: AB4C A4BC
1 Number and 4 Letters Example: A4BCD
1 Number and 5 Letters Example: AB4CDE
I have tried everything I can think of and search till I cant search no more. I am sure I am over thinking this.
A two-step regular expression like this would do it:
$str = "hello A4AB there BC5AD";
$signs = array();
preg_match_all('/[A-Z][A-Z\d]{1,3}[A-Z]/', $str, $possible_signs);
foreach($possible_signs[0] as $possible_sign)
if (preg_match('/^\D+\d\D+$/', $possible_sign))
array_push($signs, $possible_sign);
print_r($signs); //Array ([0] => A4AB [1] => BC5AD)
Explanation
This is a regular expression approach, using two patterns. I don't think it could be done with one and still satisfy the exact requirements of the matching rules.
The first pattern enforces the following requirements:
substring starts and ends with a capital letter
substring contains only other capital letters or numbers between the first and last letter
substring is, overall, not more than 6 characters long
What I can't do in that same pattern, for complex REGEX reasons I won't go into (unless someone knows a way and can correct me), is enforce that only one number is contained.
#jeroen's answer does enforce this in a single pattern, but in turn does not enforce the correct length of the substring. Either way, we need a second pattern.
So after grabbing the initial matches, we loop over the results. We then apply each to a second pattern that enforces simply that there is only one number in the substring.
If so, we green-light the substring and it's added to the $signs array.
Hope this helps.
It depends on what the other words can contain, but you could use a regular expression like:
#\b[a-z]+\d[a-z]+\b#i
^ case insensitive
^^ a word boundary
^^^^^^ One or more letters
^^ One number
You can make it more restrictive by using {1,3} instead of + for the letters so that you have a sequence of 1 to 3 letters.
The complete expression would be something like:
$success = preg_match('#\b[a-z]+\d[a-z]+\b#i', $input_string, $matches);
where $matches[0] will contain the matched value, see the manual.

Filter array of numeric PIN code strings which may be in the format "######" or "### ###"

I have a PHP array of strings. The strings are supposed to represent PIN codes which are of 6 digits like:
560095
Having a space after the first 3 digits is also considered valid e.g. 560 095.
Not all array elements are valid. I want to filter out all invalid PIN codes.
Yes you can make use of regex for this.
PHP has a function called preg_grep to which you pass your regular expression and it returns a new array with entries from the input array that match the pattern.
$new_array = preg_grep('/^\d{3} ?\d{3}$/',$array);
Explanation of the regex:
^ - Start anchor
\d{3} - 3 digits. Same as [0-9][0-9][0-9]
? - optional space (there is a space before ?)
If you want to allow any number of any whitespace between the groups
you can use \s* instead
\d{3} - 3 digits
$ - End anchor
Yes, you can use a regular expression to make sure there are 6 digits with or without a space.
A neat tool for playing with regular expressions is RegExr... here's what RegEx I came up with:
^[0-9]{3}\s?[0-9]{3}$
It matches the beginning of the string ^, then any three numbers [0-9]{3} followed by an optional space \s? followed by another three numbers [0-9]{3}, followed by the end of the string $.
Passing the array into the PHP function preg_grep along with the Regex will return a new array with only matching indeces.
If you just want to iterate over the valid responses (loop over them), you could always use a RegexIterator:
$regex = '/^\d{3}\s?\d{3}$/';
$it = new RegexIterator(new ArrayIterator($array), $regex);
foreach ($it as $valid) {
//Only matching items will be looped over, non-matching will be skipped
}
It has the benefit of not copying the entire array (it computes the next one when you want it). So it's much more memory efficient than doing something with preg_grep for large arrays. But it also will be slower if you iterate multiple times (but for a single iteration it should be faster due to the memory usage).
If you want to get an array of the valid PIN codes, use codaddict's answer.
You could also, at the same time as filtering only valid PINs, remove the optional space character so that all PINs become 6 digits by using preg_filter:
$new_array = preg_filter('/^(\d{3}) ?(\d{3})$/D', '$1$2', $array);
The best answer might depend on your situation, but if you wanted to do a simple and low cost check first...
$item = str_replace( " ", "", $var );
if ( strlen( $item ) !== 6 ){
echo 'fail early';
}
Following that, you could equally go on and do some type checking - as long as valid numbers did not start with a 0 in which case is might be more difficult.
If you don't fail early, then go on with the regex solutions already posted.

Regex: Rename Files

I am trying to rename a bunch of image files.
They are named inconsistently however there is some logic to it
They all start with an Id number
After the Id there may be some of the following (Items To Be Removed):
a space
2 letters
a dash -
These will appear in various orders and sometimes more than once, for the space or dash.
The filenames may have any of these items but not necessarily all of them.
Some filenames do have all 3 items.
They may have an additional _ after this
Then they may have a number {Index}
Finally they end in .ext where ext = jpg|png|gif...
Here are some example filenames:
1227.jpg
1227_1.jpg
2200 WH-1.jpg
2200WH 2.jpg
2200 WH2.jpg
2201_BK 1.png
2203 RD_1.jpg
I am trying to remove/replace the mentioned items so the filenames are as follows:
ID.ext or ID_{index}.ext
So the above list would turn into:
1227.jpg
1227_1.jpg
2200_1.jpg
2200_2.jpg
2201_1.png
2203_1.jpg
I have tried writing a few expressions but am a little stumped on this one.
I am working on a PHP project though other languages would be fine for this script.
Pattern: /^\d+\K[-a-z_ ]+/i
Replace: _
(Pattern Demo)
Basically only match when there are one or more characters between the id and the index. Simple.
/ #pattern delimiter
^ #start of string
\d+ #one or more digits
\K #restart fullstring match so that the fullstring match is replaced
[-a-z_ ]+ #match one or more hyphens, letters, underscores, or spaces
/ #pattern delimiter
i #make the pattern case-insensitive
Code: (Demo)
$images=['1227.jpg','1227_1.jpg','2200 WH-1.jpg','2200WH 2.jpg','2200 WH2.jpg','2201_BK 1.png','2203 RD_1.jpg'];
var_export(preg_replace('/^\d+\K[-a-z_ ]+/i','_',$images));
Output:
array (
0 => '1227.jpg',
1 => '1227_1.jpg',
2 => '2200_1.jpg',
3 => '2200_2.jpg',
4 => '2200_2.jpg',
5 => '2201_1.png',
6 => '2203_1.jpg',
)
Question extension solution: (Demo) (Demo)
You can do it with two patterns and replacements on a single preg_replace() call or you can use preg_replace() then str_replace() to mop up the dangling underscores. This will come down to personal coding preference. (It could also be done with a preg_replace_callback() that checks if there is an index number in the image name before adding the underscore, but that will make a more convoluted snippet.)
Codes:
$images=['1227.jpg','1227_1.jpg','2200 WH-1.jpg','2200WH 2.jpg','2200 WH2.jpg','2201_BK 1.png','2203 RD_1.jpg','2200 WH.jpg','3000_01.jpg'];
foreach($images as $image){
echo str_replace('_.','.',preg_replace('/^\d+\K[-a-z_ ]+0*/i','_',$image)),"\n";
}
Or
$images=['1227.jpg','1227_1.jpg','2200 WH-1.jpg','2200WH 2.jpg','2200 WH2.jpg','2201_BK 1.png','2203 RD_1.jpg','2200 WH.jpg','3000_01.jpg'];
foreach($images as $image){
echo preg_replace(['~^\d+\K[-a-z_ ]+0*~i','~_\.~'],['_','.'],$image),"\n";
}
I would do it with the following pattern:
(\d{4})([^0-9.]*)(\d\.)
And with a substitution of $1_$3.
Step by step:
(\d{4}) - Check for the 1st 4 digits.
([^0-9.]*) - Check for everything that is not a number or a period after the ID.
(\d\.) - Check for ending number and period before extension (This is so we can properly place the underscore)
Adding the substitution means that the 4 digit number will be added to the beginning, all non-number (or period) characters will be removed, and an underscore will be added between the $1 and whatever is left. If there is nothing after the ID, no underscore will be added, then the period is added inside the substitution as well.
You can view this on Regex101 for a very detailed step-by-step of what is going on.
In PHP this would be:
preg_replace("/(\d{4})([^0-9.]*)(\d)\./", "$1_", $string);
Output:
1227.jpg
1227_1.jpg
2200_1.jpg
2200_2.jpg
2200_2.jpg
2201_1.png
2203_1.jpg
Not a PHP person but the regular expression I would use is:
/(\d+).*?(\d?)\.(.*)/
This will capture the first set of numbers, skip the middle part, capture the number on the end if present, then capture the file extension.
Then in ruby I would do the following:
id, index, extension = my_file_name.match(/(\d+).*?(\d?)\.(.*)/)
new_name = id.to_s
new_name += "_#{index}" unless index.empty?
new_name += ".#{extension}"

Categories