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.
Related
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
I was in need of a method to count the number of words (not characters) within PHP, and start a <SPAN> tag within HTML to wrap around the remaining words after the specified number.
I looked into functions such as wordwrap and str_word_count, but those didn't seem to help. I went ahead and modified the code found here: http://php.timesoft.cc/manual/en/function.str-word-count.php#55818
Everything seems to work great, however I wanted to post here as this code is from 2005 and maybe there is a more modern / efficient way of handling what I'm trying to achieve?
<?php
$string = "One two three four five six seven eight nine ten.";
// the first number words to extract
$n = 3;
// extract the words
$words = explode(" ", $string);
// chop the words array down to the first n elements
$first = array_slice($words, 0, $n);
// chop the words array down to the retmaining elements
$last = array_slice($words, $n);
// glue the 3 elements back into a spaced sentence
$firstString = implode(" ", $first);
// glue the remaining elements back into a spaced sentence
$lastString = implode(" ", $last);
// display it
echo $firstString;
echo '<span style="font-weight:bold;"> '.$lastString.'</span>';
?>
You could use preg_split() with a regex instead. This is the modified version of this answer with an improved regex that uses a positive lookbehind:
function get_snippet($str, $wordCount) {
$arr = preg_split(
'/(?<=\w)\b/',
$str,
$wordCount*2+1,
PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
$first = implode('', array_slice($arr, 0, $wordCount));
$last = implode('', array_slice($arr, $wordCount));
return $first.'<span style="font-weight:bold;">'.$last.'</span>';
}
Usage:
$string = "One two three four five six seven eight nine ten.";
echo get_snippet($string, 3);
Output:
One two three four five six seven eight nine ten.
Demo
Lets more even simple . Try this
<?php
$string = "One two three four five six seven eight nine ten.";
// the first number words to extract
$n = 2;
// extract the words
$words = explode(" ", $string);
for($i=0; $i<=($n-1); $i++) {
$firstString[] = $words[$i]; // This will return one, two
}
for($i =$n; $i<count($words); $i++) {
$firstString[] = $words[$i]; // This will return three four five six seven eight nine ten
}
print_r($firstString);
print_r($firstString);
?>
Demo here
I borrowed the code from here:
https://stackoverflow.com/a/18589825/1578471
/**
* Find the position of the Xth occurrence of a substring in a string
* #param $haystack
* #param $needle
* #param $number integer > 0
* #return int
*/
function strposX($haystack, $needle, $number){
if($number == '1'){
return strpos($haystack, $needle);
}elseif($number > '1'){
return strpos($haystack, $needle, strposX($haystack, $needle, $number - 1) + strlen($needle));
}else{
return error_log('Error: Value for parameter $number is out of range');
}
}
$string = "One two three four five six seven eight nine ten.";
$afterThreeWords = strposX($string, " ", 3);
echo substr($string, 0, $afterThreeWords); // first three words
This looks good to me, here's another way that you might check against this for efficiency?
I have no idea which is quicker. My guess is yours is quicker for longer strings
$string = "This is some reasonably lengthed string";
$n = 3;
$pos = 0
for( $i = 0; $i< $n; $i++ ){
$pos = strpos($string, ' ', $pos + 1);
if( !$pos ){
break;
}
}
if( $pos ){
$firstString = substr($string, 0, $pos);
$lastString = substr($string, $pos + 1);
}else{
$firstString = $string;
$lastString = null;
}
Pattern search within a string.
for eg.
$string = "111111110000";
FindOut($string);
Function should return 0
function FindOut($str){
$items = str_split($str, 3);
print_r($items);
}
If I understand you correctly, your problem comes down to finding out whether a substring of 3 characters occurs in a string twice without overlapping. This will get you the first occurence's position if it does:
function findPattern($string, $minlen=3) {
$max = strlen($string)-$minlen;
for($i=0;$i<=$max;$i++) {
$pattern = substr($string,$i,$minlen);
if(substr_count($string,$pattern)>1)
return $i;
}
return false;
}
Or am I missing something here?
What you have here can conceptually be solved with a sliding window. For your example, you have a sliding window of size 3.
For each character in the string, you take the substring of the current character and the next two characters as the current pattern. You then slide the window up one position, and check if the remainder of the string has what the current pattern contains. If it does, you return the current index. If not, you repeat.
Example:
1010101101
|-|
So, pattern = 101. Now, we advance the sliding window by one character:
1010101101
|-|
And see if the rest of the string has 101, checking every combination of 3 characters.
Conceptually, this should be all you need to solve this problem.
Edit: I really don't like when people just ask for code, but since this seemed to be an interesting problem, here is my implementation of the above algorithm, which allows for the window size to vary (instead of being fixed at 3, the function is only briefly tested and omits obvious error checking):
function findPattern( $str, $window_size = 3) {
// Start the index at 0 (beginning of the string)
$i = 0;
// while( (the current pattern in the window) is not empty / false)
while( ($current_pattern = substr( $str, $i, $window_size)) != false) {
$possible_matches = array();
// Get the combination of all possible matches from the remainder of the string
for( $j = 0; $j < $window_size; $j++) {
$possible_matches = array_merge( $possible_matches, str_split( substr( $str, $i + 1 + $j), $window_size));
}
// If the current pattern is in the possible matches, we found a duplicate, return the index of the first occurrence
if( in_array( $current_pattern, $possible_matches)) {
return $i;
}
// Otherwise, increment $i and grab a new window
$i++;
}
// No duplicates were found, return -1
return -1;
}
It should be noted that this certainly isn't the most efficient algorithm or implementation, but it should help clarify the problem and give a straightforward example on how to solve it.
Looks like you more want to use a sub-string function to walk along and check every three characters and not just break it into 3
function fp($s, $len = 3){
$max = strlen($s) - $len; //borrowed from lafor as it was a terrible oversight by me
$parts = array();
for($i=0; $i < $max; $i++){
$three = substr($s, $i, $len);
if(array_key_exists("$three",$parts)){
return $parts["$three"];
//if we've already seen it before then this is the first duplicate, we can return it
}
else{
$parts["$three"] = i; //save the index of the starting position.
}
}
return false; //if we get this far then we didn't find any duplicate strings
}
Based on the str_split documentation, calling str_split on "1010101101" will result in:
Array(
[0] => 101
[1] => 010
[2] => 110
[3] => 1
}
None of these will match each other.
You need to look at each 3-long slice of the string (starting at index 0, then index 1, and so on).
I suggest looking at substr, which you can use like this:
substr($input_string, $index, $length)
And it will get you the section of $input_string starting at $index of length $length.
quick and dirty implementation of such pattern search:
function findPattern($string){
$matches = 0;
$substrStart = 0;
while($matches < 2 && $substrStart+ 3 < strlen($string) && $pattern = substr($string, $substrStart++, 3)){
$matches = substr_count($string,$pattern);
}
if($matches < 2){
return null;
}
return $substrStart-1;
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;
I already have a function that counts the number of items in a string ($paragraph) and tells me how many characters the result is, ie tsp and tbsp present is 7, I can use this to work out the percentage of that string is.
I need to reinforce this with preg_match because 10tsp should count as 5.
$characters = strlen($paragraph);
$items = array("tsp", "tbsp", "tbs");
$count = 0;
foreach($items as $item) {
//Count the number of times the formatting is in the paragraph
$countitems = substr_count($paragraph, $item);
$countlength= (strlen($item)*$countitems);
$count = $count+$countlength;
}
$overallpercent = ((100/$characters)*$count);
I know it would be something like preg_match('#[d]+[item]#', $paragraph) right?
EDIT sorry for the curve ball but there might be a space inbetween the number and the $item, can one preg_match catch both instances?
It's not quite clear to me what you are trying to do with the regex, but if you are just trying to match for a particular number-measurement combination, this might help:
$count = preg_match_all('/\d+\s*(tbsp|tsp|tbs)/', $paragraph);
This will return the number of times a number-measurement combination occurs in $paragraph.
EDIT switched to use preg_match_all to count all occurrences.
Example for counting the number of matched characters:
$paragraph = "5tbsp and 10 tsp";
$charcnt = 0;
$matches = array();
if (preg_match_all('/\d+\s*(tbsp|tsp|tbs)/', $paragraph, $matches) > 0) {
foreach ($matches[0] as $match) { $charcnt += strlen($match); }
}
printf("total number of characters: %d\n", $charcnt);
Output from executing the above:
total number of characters: 11