preg_replace to match all but first occurance of something? - php

I'm looking for a way to replace all but first occurrences of a group or some character.
For example a following random string:
+Z1A124B555ND124AB+A555
1,5,2,4,A,B and + are repeating through out the string.
124, 555 are groups of characters that are also reoccurring.
Now, let's say I want to remove every but first occurrence of 555, A and B.
What regex would be appropriate? I could think of an example replacing all:
preg_replace('/555|A|B/','',$string);
Something like ^ that, but I want to keep the first occurrence... Any ideas?

Are your strings always delimited by plus signs? Do 555, A, and B always occur in the first "group" (delimited by +)?
If so, you can split, replace and then join:
$input = '+Z1A124B555+A124AB+A555';
$array = explode('+', $input, 3); // max 3 elements
$array[2] = str_replace(array('555', 'A', 'B'), '', $array[2]);
$output = implode('+', $array);
ps. No need to use regexes, when we can use a simple str_replace
Use the preg_replace_callback function:
$replaced = array('555' => 0, 'A' => 0, 'B' => 0);
$input = '+Z1A124B555+A124AB+A555';
$output = preg_replace_callback('/555|[AB]/', function($matches) {
static $replaced = 0;
if($replaced++ == 0) return $matches[0];
return '';
}, $input);

This solution could be modified to do what you want: PHP: preg_replace (x) occurrence?
Here is a modified solution for you:
<?php
class Parser {
private $i;
public function parse($source) {
$this->i=array();
return preg_replace_callback('/555|A|B/', array($this, 'on_match'), $source);
}
private function on_match($m) {
$first=$m[0];
if(!isset($this->i[$first]))
{
echo "I'm HERE";
$this->i[$first]=1;
}
else
{
$this->i[$first]++;
}
// Return what you want the replacement to be.
if($this->i[$first]>1)
{
$result="";
}
else
{
$result=$m[0];
}
return $result;
}
}
$sample = '+Z1A124B555ND124AB+A555';
$parse = new Parser();
$result = $parse->parse($sample);
echo "Result is: [$result]\n";
?>

A more generic function that works with every pattern.
function replaceAllButFirst($pattern, $replacement, $subject) {
return preg_replace_callback($pattern,
function($matches) use ($replacement, $subject) {
static $s;
$s++;
return ($s <= 1) ? $matches[0] : $replacement;
},
$subject
);
}

Related

PHP string challenge

I have an string such as
$string = "This is my test string {ABC}. This is test {XYZ}. I am new for PHP {PHP}".
Now I need to replace occurrence of string within {}, in such a way that output will be:
This is my test string {ABC 1}. This is test {XYZ 2}. I am new for PHP {PHP 3}".
I am looking to resolve this with recursive function but not getting expected result.
$i = 1;
echo preg_replace_callback('/\{(.+?)\}/', function (array $match) use (&$i) {
return sprintf('{%s %d}', $match[1], $i++);
}, $string);
The "trick" is to simply keep an external counter running, here $i, which is used in the anonymous callback via use (&$i).
There is no recursion here. Simply counting.
$result = preg_replace_callback("/\{([^}]*+)\}/",function($m) {
static $count = 0;
$count++;
return "{".$m[1]." ".$count."}";
},$string);
If you really need recursive :^ )
$string = "This is my test string {ABC}. This is test {XYZ}. I am new for PHP {PHP}";
function my_replace($string, $count = 1)
{
if ($string)
{
return preg_replace_callback('/\{(.+?)\}(.*)$/', function (array $match) use ($count)
{
return sprintf('{%s %d} %s', $match[1], $count, my_replace($match[2], $count + 1));
}, $string, 1);
}
}
echo my_replace($string);

Replacing same needle with different strings

Is there any way to replace the same needle in string with differnet values out of a array?
Like that:
$string = ">>>?<<<>>>?<<<>>>?<<<"; // replacing the three occourances of "?"
// values of array
echo str_multiple_replace($string, array("Hello", "World", "!"));
Output:
">>>Hallo<<<>>>World<<<>>>!<<<"
How can the function str_multiple_replace look like to replace the three question marks with the content of the array.
EDIT: Let content NOT affect the replacing, so for example, if there is a "?" in the array, it shouldn't be replaced.
Use preg_replace_callback():
$string = ">>>?<<<>>>?<<<>>>?<<<";
$subs = array('Hello','World','!');
echo preg_replace_callback('#\?#',function ($matches) use (&$subs) {
return array_shift($subs);
},$string);
Or:
$string = ">>>?<<<>>>?<<<>>>?<<<";
$subs = array('Hello','World','!');
function str_multiple_replace($string, $needle, $subs) {
return preg_replace_callback('#'.preg_quote($needle,'#').'#',function ($matches) use (&$subs) {
return array_shift($subs);
},$string);
}
echo str_multiple_replace($string,'?',$subs);
You can actually utilize vprintf function to make this code extremely simple:
$string = ">>>?<<<%s>>>?<<<>>>?<<<";
$arr = array('Hello', 'World', '!');
vprintf(str_replace(array('%', '?'), array('%%', '%s'), $string), $subs);
UPDATE: Code using vsprintf function: (Thanks to #ComFreek)
function str_multiple_replace($str, $needle, $subs) {
return vsprintf(str_replace(array('%', $needle), array('%%', '%s'), $str), $subs);
}
$string = ">>>?<<<%s>>>?<<<>>>?<<<";
echo str_multiple_replace($string, '?', array('Hello', 'World', '!'));
OUTPUT:
>>>Hello<<<%s>>>World<<<>>>!<<<
This is not exactly the same format as your example, but the concept is the same:
PHP's printf() produces output according to a format:
$string=">>>%s<<<>>>%s<<<>>>%s<<<";
$length=printf($string,"Hello", "World", "!");
Outputs: >>>Hello<<<>>>World<<<>>>!<<<
http://php.net/manual/en/function.printf.php
A brute force solution would be something like....
function str_multiple_replace($haystack, $needle, $replacements)
{
$out = '';
while ($haystack && count($needle)) {
$out .= substr($haystack, 0,1);
$haystack = substr($haystack, 1);
if (substr($out, -1*strlen($needle)) === $needle) {
$out = substr($out, 0, -1*strlen($needle)) . array_shift($replacements);
}
}
$out .= $haystack;
return $out;
}

search for a substring which returns true if it is at the end

I would like to search for a substring in php so that it will be at the end of the given string.
Eg
on string 'abd def' if I search for def it would be at the end, so return true. But if I search for abd it will return false since it is not at the end.
Is it possible?
You could use preg_match for this:
$str = 'abd def';
$result = (preg_match("/def$/", $str) === 1);
var_dump($result);
An alternative way to do it which does not require splitting by a separator or regular expressions. This tests whether the last x characters equal the test string, where x equals the length of the test string:
$string = "abcdef";
$test = "def";
if(substr($string, -(strlen($test))) === $test)
{
/* logic here */
}
Assuming whole words:
$match = 'def';
$words = explode(' ', 'abd def');
if (array_pop($words) == $match) {
...
}
Or using a regex:
if (preg_match('/def$/', 'abd def')) {
...
}
This answer should be fully robust regardless of full words or anything else
$match = 'def';
$words = 'abd def';
$location = strrpos($words, $match); // Find the rightmost location of $match
$matchlength = strlen($match); // How long is $match
/* If the rightmost location + the length of what's being matched
* is equal to the length of what's being searched,
* then it's at the end of the string
*/
if ($location + $matchlength == strlen($words)) {
...
}
Please look strrchr() function. Try like this
$word = 'abcdef';
$niddle = 'def';
if (strrchr($word, $niddle) == $niddle) {
echo 'true';
} else {
echo 'false';
}

PHP regex: how to store $1, $2 in a variable

preg_replace('/([a-z]+)([0-9]+)/', '$2$1', $str);
I want to store $1 and $2 in a variable. How can I store it in a variable?
Finding matches is the job of preg_match().
preg_match('/([a-z]+)([0-9]+)/', $str, $matches);
matches:
If matches is provided, then it is filled with the results of search. $matches[0] will contain the text that matched the full
pattern, $matches[1] will have the text that matched the first
captured parenthesized subpattern, and so on.
$full_pattern = $matches[0]
$a = $matches[1] // $1
$b = $matches[2] // $2
$c = $matches[3] // $3
$n = $matches[n] // $n
Use preg_replace_callback:
$one; $two;
function callback($matches)
{
global $one,$two;
$one = $matches[0];
$two = $matches[1];
return $matches[1].$matches[0];
}
preg_replace_callback('/([a-z]+)([0-9]+)/', 'callback', $str);
NOTE
Global is... not a good idea. I actually left that in because it is a concise, clear example of how to accomplish what you're trying to do. I would that I had never used the word, but global is there now and I don't feel right removing it. Actually, its existence saddens me deeply. You are far better off with something like this:
class Replacer
{
private $matches, $pattern, $callback;
public function __construct($pattern, $callback)
{
$this->pattern = $pattern;
$this->callback = $callback;
}
public function getPregCallback()
{
return array( $this, '_callback' );
}
public function _callback( array $matches )
{
$template = $this->pattern;
foreach( $matches as $key => $val )
{
if( $this->callback )
{
$matches[ $key ] = $val = $this->callback( $val );
}
$template = str_replace( '$' . ( $key + 1 ), $val, $template );
}
$this->matches = $matches;
return $template;
}
public function getMatches(){ return $this->matches; }
}
USE
// does what the first example did, plus it calls strtolower on all
// of the elements.
$r = new Replacer( '$2$1', 'strtolower' );
preg_replace_callback('/([a-z]+)([0-9]+)/', $r->getPregCallback(), $str);
list( $a, $b ) = $r->getMatches();
Use preg_replace_callback:
preg_replace_callback('/([a-z]+)([0-9]+)/', function($m) {
// save data
return $m[1].$m[0];
}, $str);
(This uses PHP 5.3 anonymous function syntax, on older PHP just define a normal function and pass its name as the second parameter.)
use preg_replace with "e" ( eval flag)
or better use
preg_replace_callback
http://php.net/manual/en/function.preg-replace-callback.php
see code here :
http://codepad.org/22Qh3wRA
<?php
$str = "a9";
preg_replace_callback('/([a-z]+)([0-9]+)/', "callBackFunc", $str);
function callBackFunc($matches)
{
var_dump($matches);
// here you can assign $matches elements to variables.
}
?>

Perl to PHP With s///;

In perl, I can do: 1 while $var =~ s/a/b/;, and it will replace all a with b. In many cases, I would use it more like 1 while $var =~ s/^"(.*)"$/$1/; to remove all pairs of double quotes around a string.
Is there a way to do something similar to this in PHP, without having to do
while (preg_match('/^"(.*)"$/', $var)) {
$var = preg_replace('/^"(.*)"$/', '$1', $var, 1);
}
Because apparently,
while ($var = preg_replace('/^"(.*)"$/', '$1', $var, 1)) { 1; }
doesn't work.
EDIT: The specific situation I'm working in involves replacing values in a string with values from an associative array:
$text = "This is [site_name], home of the [people_type]".
$array = ('site_name' => 'StackOverflow.com', 'people_type' => 'crazy coders');
where I would be doing:
while (preg_match('/\[.*?\]/', $text)) {
$text = preg_replace('/\[(.*?)\]/', '$array[\'$1\']', $text, 1);
}
with the intended output being 'This is StackOverflow.com, home of the crazy coders'
preg_replace('#\[(.*?)\]#e', "\$array['$1']", $text);
In all of the cases, you can get rid of the loop by (e.g.) using the /g global replace option or rewriting the regexp:
$var =~ s/a/b/g;
$var =~ s/^("+)(.*)\1$/$2/;
The same patterns should work in PHP. You can also get rid of the $limit argument to preg_replace:
$text = preg_replace('/\[(.*?)\]/e', '$array[\'$1\']', $text);
Regular expressions can handle their own loops. Looping outside the RE is inefficient, since the RE has to process text it already processed in previous iterations.
Could something like this work?
$var = preg_replace('/^("+)(.*)\1$', '$2', $var, 1);
What does your input data look like?
Because you're checking for double quotes only at the head and tail of the string. If that's accurate, then you don't need to capture a backreference at all. Also, that would make sending 1 as the 4th parameter completely superfluous.
$var = '"foo"';
// This works
echo preg_replace( '/^"(.*)"$/', '$1', $var );
// So does this
echo preg_replace( '/^"|"$/', '', $var );
But if your input data looks different, that would change my answer.
EDIT
Here's my take on your actual data
class VariableExpander
{
protected $source;
public function __construct( array $source )
{
$this->setSource( $source );
}
public function setSource( array $source )
{
$this->source = $source;
}
public function parse( $input )
{
return preg_replace_callback( '/\[([a-z_]+)\]/i', array( $this, 'expand' ), $input );
}
protected function expand( $matches )
{
return isset( $this->source[$matches[1]] )
? $this->source[$matches[1]]
: '';
}
}
$text = "This is [site_name], home of the [people_type]";
$data = array(
'site_name' => 'StackOverflow.com'
, 'people_type' => 'crazy coders'
);
$ve = new VariableExpander( $data );
echo $ve->parse( $text );
The class is just for encapsulation - you could do this in a structured way if you wanted.
Use do-while:
do {
$var = preg_replace('/^"(.*)"$/', "$1", $var, 1, $count);
} while ($count == 1);
Requires at least php-5.1.0 due to its use of $count.
You could also write
do {
$last = $var;
$var = preg_replace('/^"(.*)"$/', "$1", $var);
} while ($last != $var);

Categories