Calculate with regex group matches? - php

Is it possible to calculate with regex group matches?
String:
(00) Bananas
...
(02) Apples (red ones)
...
(05) Oranges
...
(11) Some Other Fruit
...
If the difference between the numbers at the beginning of each row is 3 or less, remove the "..." inbetween. So the string should be returned like this:
(00) Bananas
(02) Apples (red ones)
(05) Oranges
...
(11) Some Other Fruit
Regex:
$match = '/(*ANYCRLF)\((\d+)\) (.+)$
\.{3}
\((\d+)\) (.+)/m';
Now the tricky part is how to grab the matches and add some to a condition like
if($3-$1 >= 3) {
//replace
}
Test: http://codepad.viper-7.com/f6iI4m
Thanks!

Here's how you could do it with preg_replace_callback().
$callback = function ($match) {
if ($match[3] <= $match[2] + 3) {
return $match[1];
} else {
return $match[0];
}
};
$newtxt = preg_replace_callback('/(^\((\d+)\).+$)\s+^\.{3}$(?=\s+^\((\d+)\))/m', $callback, $txt);
/(^\((\d+)\).+$)\s+^\.{3}$(?=\s+^\((\d+)\))/m
Here's the pattern in pieces:
(^\((\d+)\).+$) # subpattern 1, first line; subpattern 2, the number
\s+^\.{3}$ # newline(s) and second line ("...")
(?=\s+^\((\d+)\)) # lookahead that matches another numbered line
# without consuming it; contains subpattern 3, next number
Thus, the entire pattern's match is the first two lines (i.e., numbered line and '...' line).
If the difference in numbers is greater than 3, replace with original text in $match[0] (i.e., no change). If difference is less than or equal to 3, replace with first line only (found in $match1]).

You could employ preg_replace_callback and use any php code to return the replacement string, the callback receives the captures. However for you output you would have to get overlapping matches for replacement:
compare (00) Bananas vs (02) Apples -> 2-0=2 replace
compare (02) Apples vs (05) Oranges -> 5-2=3 replace
...
But since the input's (02) Apples part have been used for a previous match you it won't be picked up for the second time.
edit:
Here's a regexp based solutinon with lookaheads, credit goes to Wiseguy:
$s = "(00) Bananas
...
(02) Apples (red ones)
...
(05) Oranges
...
(11) Some Other Fruit
...";
$match = '/(*ANYCRLF)\((\d+)\) (.+)$
\.{3}
(?=\((\d+)\) (.+))/m';
// php5.3 anonymous function syntax
$s = preg_replace_callback($match, function($m){
if ($m[3] - $m[1] <= 3) {
print preg_replace("/[\r\n]+.../", '', $m[0]);
} else {
print $m[0];
}
}, $s);
echo $s;
Here is my first take, based on the logic "find the dots then see the previous / next lines":
$s = "(00) Bananas
...
(02) Apples (red ones)
...
(05) Oranges
...
(11) Some Other Fruit
...
(18) Some Other Fruit
...
(19) Some Other Fruit
...
";
$s = preg_replace("/[\r\n]{2}/", "\n", $s);
$num_pattern = '/^\((?<num>\d+)\)/';
$dots_removed = 0;
preg_match_all('/\.{3}/', $s, $m, PREG_OFFSET_CAPTURE);
foreach ($m[0] as $i => $dots) {
$offset = $dots[1] - ($dots_removed * 4); // fix offset of changing input
$prev_line_end = $offset - 2; // -2 since the offset is pointing to the first '.', prev char is "\n"
$prev_line_start = $prev_line_end; // start the search for the prev line's start from its end
while ($prev_line_start > 0 && $s[$prev_line_start] != "\n") {
--$prev_line_start;
}
$next_line_start = $offset + strlen($dots[0]) + 1;
$next_line_end = strpos($s, "\n", $next_line_start);
$next_line_end or $next_line_end = strlen($s);
$prev_line = trim(substr($s, $prev_line_start, $prev_line_end - $prev_line_start));
$next_line = trim(substr($s, $next_line_start, $next_line_end - $next_line_start));
if (!$next_line) {
break;
}
// get the numbers
preg_match($num_pattern, $prev_line, $prev);
preg_match($num_pattern, $next_line, $next);
if (intval($next['num']) - intval($prev['num']) <= 3) {
// delete the "..." line
$s = substr_replace($s, '', $offset-1, strlen($dots[0]) + 1);
++$dots_removed;
}
}
print $s;

Related

Using preg_replace() To Increment a Digit in a Phrase [duplicate]

I have a string formed up by numbers and sometimes by letters.
Example AF-1234 or 345ww.
I have to get the numeric part and increment it by one.
how can I do that? maybe with regex?
You can use preg_replace_callback as:
function inc($matches) {
return ++$matches[1];
}
$input = preg_replace_callback("|(\d+)|", "inc", $input);
Basically you match the numeric part of the string using the regex \d+ and replace it with the value returned by the callback function which returns the incremented value.
Ideone link
Alternatively this can be done using preg_replace() with the e modifier as:
$input = preg_replace("|(\d+)|e", "$1+1", $input);
Ideone link
If the string ends with numeric characters it is this simple...
$str = 'AF-1234';
echo $str++; //AF-1235
That works the same way with '345ww' though the result may not be what you expect.
$str = '345ww';
echo $str++; //345wx
#tampe125
This example is probably the best method for your needs if incrementing string that end with numbers.
$str = 'XXX-342';
echo $str++; //XXX-343
Here is an example that worked for me by doing a pre increment on the value
$admNo = HF0001;
$newAdmNo = ++$admNo;
The above code will output HF0002
If you are dealing with strings that have multiple number parts then it's not so easy to solve with regex, since you might have numbers overflowing from one numeric part to another.
For example if you have a number INV00-10-99 which should increment to INV00-11-00.
I ended up with the following:
for ($i = strlen($string) - 1; $i >= 0; $i--) {
if (is_numeric($string[$i])) {
$most_significant_number = $i;
if ($string[$i] < 9) {
$string[$i] = $string[$i] + 1;
break;
}
// The number was a 9, set it to zero and continue.
$string[$i] = 0;
}
}
// If the most significant number was set to a zero it has overflowed so we
// need to prefix it with a '1'.
if ($string[$most_significant_number] === '0') {
$string = substr_replace($string, '1', $most_significant_number, 0);
}
Here's some Python code that does what you ask. Not too great on my PHP, but I'll see if I can convert it for you.
>>> import re
>>> match = re.match(r'(\D*)(\d+)(\D*)', 'AF-1234')
>>> match.group(1) + str(int(match.group(2))+1) + match.group(3)
'AF-1235'
This is similar to the answer above, but contains the code inline and does a full check for the last character.
function replace_title($title) {
$pattern = '/(\d+)(?!.*\d)+/';
return preg_replace_callback($pattern, function($m) { return ++$m[0]; }, $title);
}
echo replace_title('test 123'); // test 124
echo replace_title('test 12 3'); // test 12 4
echo replace_title('test 123 - 2'); // test 123 - 3
echo replace_title('test 123 - 3 - 5'); // test 123 - 3 - 6
echo replace_title('123test'); // 124test

adding an increment to a variable [duplicate]

I have a string formed up by numbers and sometimes by letters.
Example AF-1234 or 345ww.
I have to get the numeric part and increment it by one.
how can I do that? maybe with regex?
You can use preg_replace_callback as:
function inc($matches) {
return ++$matches[1];
}
$input = preg_replace_callback("|(\d+)|", "inc", $input);
Basically you match the numeric part of the string using the regex \d+ and replace it with the value returned by the callback function which returns the incremented value.
Ideone link
Alternatively this can be done using preg_replace() with the e modifier as:
$input = preg_replace("|(\d+)|e", "$1+1", $input);
Ideone link
If the string ends with numeric characters it is this simple...
$str = 'AF-1234';
echo $str++; //AF-1235
That works the same way with '345ww' though the result may not be what you expect.
$str = '345ww';
echo $str++; //345wx
#tampe125
This example is probably the best method for your needs if incrementing string that end with numbers.
$str = 'XXX-342';
echo $str++; //XXX-343
Here is an example that worked for me by doing a pre increment on the value
$admNo = HF0001;
$newAdmNo = ++$admNo;
The above code will output HF0002
If you are dealing with strings that have multiple number parts then it's not so easy to solve with regex, since you might have numbers overflowing from one numeric part to another.
For example if you have a number INV00-10-99 which should increment to INV00-11-00.
I ended up with the following:
for ($i = strlen($string) - 1; $i >= 0; $i--) {
if (is_numeric($string[$i])) {
$most_significant_number = $i;
if ($string[$i] < 9) {
$string[$i] = $string[$i] + 1;
break;
}
// The number was a 9, set it to zero and continue.
$string[$i] = 0;
}
}
// If the most significant number was set to a zero it has overflowed so we
// need to prefix it with a '1'.
if ($string[$most_significant_number] === '0') {
$string = substr_replace($string, '1', $most_significant_number, 0);
}
Here's some Python code that does what you ask. Not too great on my PHP, but I'll see if I can convert it for you.
>>> import re
>>> match = re.match(r'(\D*)(\d+)(\D*)', 'AF-1234')
>>> match.group(1) + str(int(match.group(2))+1) + match.group(3)
'AF-1235'
This is similar to the answer above, but contains the code inline and does a full check for the last character.
function replace_title($title) {
$pattern = '/(\d+)(?!.*\d)+/';
return preg_replace_callback($pattern, function($m) { return ++$m[0]; }, $title);
}
echo replace_title('test 123'); // test 124
echo replace_title('test 12 3'); // test 12 4
echo replace_title('test 123 - 2'); // test 123 - 3
echo replace_title('test 123 - 3 - 5'); // test 123 - 3 - 6
echo replace_title('123test'); // 124test

How to transpose music chords with PHP?

I was wondering how would one create a function, in PHP, which is used for transposing some music chords.
I will try to explain how it works in music theory. I hope I don't forget something. If there are some mistakes, please help me to correct it.
1. The simple chords.
The simple chords are almost as simple as an alphabet and it goes like this:
C, C#, D, D#, E, F, F#, G, G#, A, A# B
From B it loops all over again to C. Therefore, If the original chord is E and we want to transpose +1, the resulting chord is F. If we transpose +4, the resulting chord is G#.
2. Expanded chords.
They work almost like the simple chords, but contain a few more characters, which can safely be ignored when transposing. For example:
Cmi, C#7, Dsus7, Emi, Fsus4, F#mi, G ...
So again, as with the simple chords, if we transpose Dsus7 + 3 = Fsus7
3. Non-root bass tone.
A problem arises when the bass plays a different tone than the chord root tone. This is marked by a slash after the chord and also needs to be transposed. Examples:
C/G, Dmi/A, F#sus7/A#
As with examples 1 and 2, everything is the same, but the part after the slash needs transpose too, therefore:
C/G + 5 = F/C
F#sus7/A# + 1 = Gsus7/B
So basically, imagine you have a PHP variable called chord and the transpose value transpose. What code would transpose the chord?
Examples:
var chord = 'F#sus7/C#';
var transpose = 3; // remember this value also may be negative, like "-4"
... code here ...
var result; // expected result = 'Asus7/E';
I have found an existed question on StackOverflow, at here. They talk about algorithm for chord-progressions.
How do I transpose music chords with PHP, by increasing or decreasing by semitones?
A quick solution:
<?php
// produces the expected result
echo transpose("F#sus7/C#",3);
function transpose($chord,$transpose)
{
// the chords
$chords = array("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B");
$result = "";
// get root tone
$root_arr = explode("/",$chord);
$root = strtoupper($root_arr[0]);
// the chord is the first character and a # if there is one
$root = $root[0].((strpos($root, "#") !== false)?"#":"");
// get any extra info
$root_extra_info = str_replace("#","",substr($root_arr[0],1)); // assuming that extra info does not have any #
// find the index on chords array
$root_index = array_search($root,$chords);
// transpose the values and modulo by 12 so we always point to existing indexes in our array
$root_transpose_index = floor(($root_index + $transpose) % 12);
if ($root_transpose_index < 0)
{
$root_transpose_index += 12;
}
$result.= $chords[$root_transpose_index].$root_extra_info;
if(count($root_arr)>1)
{
// get the non root tone
$non_root = $root_arr[1];
// the chord is the first character and a # if there is one
$non_root = strtoupper($non_root[0]).((strpos($non_root, "#") !== false)?"#":"");
// get any extra info
$non_root_extra_info = str_replace("#","",substr($root_arr[1],1)); // assuming that extra info does not have any #
// find the index on chords array
$non_root_index = array_search($non_root,$chords);
// transpose the values and modulo by 12 so we always point to existing indexes in our array
$non_root_transpose_index = floor(($non_root_index + $transpose) % 12);
if ($non_root_transpose_index < 0)
{
$non_root_transpose_index += 12;
}
$result.= "/".$chords[$non_root_transpose_index].$non_root_extra_info;
}
return $result;
}
https://3v4l.org/Cd9Pg
lots of room for improvement in code, i just tried to code it to be easy to understand.
Here my regex idea with preg_replace_callback (use of anonymous function requires PHP 5.3).
function transpose($str, $t=0)
{
// the chords
$chords = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];
// set transpose, return if none
$t = (int)$t % 12 + 12; if($t % 12 == 0) return $str;
// regex with callback
return preg_replace_callback('~[A-G]#?~', function($m) use (&$chords, &$t) {
return $chords[(array_search($m[0], $chords) + $t) % 12];
}, $str);
}
Demo at eval.in (for testing the regex pattern [A-G]#? see regex101)
echo transpose("Cmi, C#7, Dsus7, Emi, Fsus4, F#mi, G C/G, Dmi/A, F#sus7/A#", -3);
Ami, A#7, Bsus7, C#mi, Dsus4, D#mi, E A/E, Bmi/F#, D#sus7/G
Okay, so there are a few things you want to handle.
First, you want to be able to loop around in the array. That's easy: use the modulus operator, which in php is %.
function transpose($chord, $increment) {
$map = array('A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#');
// Get the index of the given chord
$index = array_search($chord, $map);
if($index === false)
return false;
// Get the transposed index and chord
$transpose_index = ($index + $increment) % count($map);
if($transpose_index < 0)
$transpose_index += count($map);
return $map[$transpose_index];
}
Second, you want to be able to strip out the actual chords that matter to you. You can do this using a regular expression (RegEx):
function transposeFull($chords, $increment) {
// This RegEx looks for one character (optionally followed by a sharp).
// .\#?
// This RegEx looks for an optional series of characters which are not /
// [^\/]*
// Put them together to get a RegEx that looks for an expanded chord
// (.\#?)([^\/]*)
// Then, do it again, but add a / first, and make it optional.
// (\/(.\#?)([^\/]*))?
$regex = '%(.\#?)([^\/]*)(\/(.\#?)([^\/]*))?%';
// Note that the () allow us to pull out the matches.
// $matches[0] is always the full thing.
// $matches[i] is the ith match
// (so $matches[3] is the whole optional second chord; which is not useful)
$matches = array();
preg_match($regex, $chords, $matches);
// Then, we get any parts that were matched and transpose them.
$chord1 = (count($matches) >= 2) ? transpose($matches[1], $increment) : false;
$expanded1 = (count($matches) >= 2) ? $matches[2] : '';
$chord2 = (count($matches) >= 5) ? transpose($matches[4], $increment) : false;
$expanded2 = (count($matches) >= 6) ? $matches[5] : '';
// Finally, put it back together.
$chords = '';
if($chord1 !== false)
$chords .= $chord1.$expanded1;
if($chord2 !== false)
$chords .= '/'.$chord2.$expanded2;
return $chords;
}

Find first occurence of substring before a certain position

In PHP, if I have a long string, IE 10'000 chars, how would you suggest I go about finding the first occurence of a certain string before and after a given position.
IE, if I have the string:
BaaaaBcccccHELLOcccccBaaaaB
I can use strpos to find the position of HELLO. How could I then go about finding the position of the first occurence of B before HELLO and the first occurence of B after HELLO?
You can use stripos() and strripos() to find the first occurrence of a sub-string inside a string. You can also supply a negative offset to strripos() function to search in reverse order (from right to left). strripos() with negative offset
$body = "BaaaaBcccccHELLOcccccBaaaaB";
$indexOfHello = stripos($body, 'Hello');
if ($indexOfHello !== FALSE)
{
// First Occurrence of B before Hello
$indexOfB= stripos(substr($body,0,$indexOfHello),'B',($indexOfHello * -1));
print("First Occurance of B before Hello is ".$indexOfB."\n") ;
// First Occurrence of B before Hello (in reverse order)
$indexOfB= strripos($body,'B',($indexOfHello * -1));
print("First Occurrence of B before Hello (in reverse order) is ".$indexOfB."\n") ;
// First Occurrence of B after Hello
$indexOfB= stripos($body,'B',$indexOfHello);
print("First Occurance of B after Hello is ".$indexOfB."\n") ;
}
If you think about optimization there a lot of pattern search algorithms
Here is sample of naive pattern search:
/**
* Naive algorithm for Pattern Searching
*/
function search(string $pat, string $txt, int $searchFrom = 0, ?int $searchTill = null)
{
$M = strlen($pat);
$N = strlen($txt);
if ($searchTill !== null && $searchTill < $N){
$N = $searchTill;
}
for ($i = $searchFrom; $i <= $N - $M; $i++)
{
// For current index i,
// check for pattern match
for ($j = 0; $j < $M; $j++)
if ($txt[$i + $j] != $pat[$j])
break;
// if pat[0...M-1] =
// txt[i, i+1, ...i+M-1]
if ($j == $M)
return $i;
}
}
// Driver Code
$txt = "BaaaaBcccccHELLOcccccBaaaaB";
if (null!==($helloPos = search("HELLO", $txt))){
print("First Occurance of B before Hello is ".search("B", $txt, 0, $helloPos)."<br>") ;
print("First Occurance of B after Hello is ".search("B", $txt, $helloPos, null)."<br>") ;
}
Given the position…
To find the first occurrence before, you can take the substr() before the match and use strrpos().
To find the first occurrence after, you can still use strpos() and set the offset parameter.

PHP replace a random word of a string

I want to replace one random word of which are several in a string.
So let's say the string is
$str = 'I like blue, blue is my favorite colour because blue is very nice and blue is pretty';
And let's say I want to replace the word blue with red but only 2 times at random positions.
So after a function is done the output could be like
I like red, blue is my favorite colour because red is very nice and blue is pretty
Another one could be
I like blue, red is my favorite colour because blue is very nice and red is pretty
So I want to replace the same word multiple times but every time on different positions.
I thought of using preg_match but that doesn't have an option that the position of the words peing replaced is random also.
Does anybody have a clue how to achieve this?
Much as I am loathed to use regex for something which is on the face of it very simple, in order to guarantee exactly n replaces I think it can help here, as it allows use to easily use array_rand(), which does exactly what you want - pick n random items from a list of indeterminate length (IMPROVED).
<?php
function replace_n_occurences ($str, $search, $replace, $n) {
// Get all occurences of $search and their offsets within the string
$count = preg_match_all('/\b'.preg_quote($search, '/').'\b/', $str, $matches, PREG_OFFSET_CAPTURE);
// Get string length information so we can account for replacement strings that are of a different length to the search string
$searchLen = strlen($search);
$diff = strlen($replace) - $searchLen;
$offset = 0;
// Loop $n random matches and replace them, if $n < 1 || $n > $count, replace all matches
$toReplace = ($n < 1 || $n > $count) ? array_keys($matches[0]) : (array) array_rand($matches[0], $n);
foreach ($toReplace as $match) {
$str = substr($str, 0, $matches[0][$match][1] + $offset).$replace.substr($str, $matches[0][$match][1] + $searchLen + $offset);
$offset += $diff;
}
return $str;
}
$str = 'I like blue, blue is my favorite colour because blue is very nice and blue is pretty';
$search = 'blue';
$replace = 'red';
$replaceCount = 2;
echo replace_n_occurences($str, $search, $replace, $replaceCount);
See it working
echo preg_replace_callback('/blue/', function($match) { return rand(0,100) > 50 ? $match[0] : 'red'; }, $str);
Well, you could use this algorithm:
calculate the random amount of times you want to replace the string
explode the string into an array
for that array replace the string occurence only if a random value between 1 and 100 is % 3 (for istance)
Decrease the number calculated at point 1.
Repeat until the number reaches 0.
<?php
$amount_to_replace = 2;
$word_to_replace = 'blue';
$new_word = 'red';
$str = 'I like blue, blue is my favorite colour because blue is very nice and blue is pretty';
$words = explode(' ', $str); //convert string to array of words
$blue_keys = array_keys($words, $word_to_replace); //get index of all $word_to_replace
if(count($blue_keys) <= $amount_to_replace) { //if there are less to replace, we don't need to randomly choose. just replace them all
$keys_to_replace = array_keys($blue_keys);
}
else {
$keys_to_replace = array();
while(count($keys_to_replace) < $amount_to_replace) { //while we have more to choose
$replacement_key = rand(0, count($blue_keys) -1);
if(in_array($replacement_key, $keys_to_replace)) continue; //we have already chosen to replace this word, don't add it again
else {
$keys_to_replace[] = $replacement_key;
}
}
}
foreach($keys_to_replace as $replacement_key) {
$words[$blue_keys[$replacement_key]] = $new_word;
}
$new_str = implode(' ', $words); //convert array of words back into string
echo $new_str."\n";
?>
N.B. I just realized this will not replace the first blue, since it is entered into the word array as "blue," and so doesn't match in the array_keys call.

Categories