使用str_replace以便它只对第一个匹配起作用?

我想要一个只替换$subject$search第一次出现的str_replace()版本。有一个简单的解决方案,还是我需要一个hack的解决方案?

309997 次浏览

可以用preg_replace完成:

function str_replace_first($search, $replace, $subject)
{
$search = '/'.preg_quote($search, '/').'/';
return preg_replace($search, $replace, $subject, 1);
}


echo str_replace_first('abc', '123', 'abcdef abcdef abcdef');
// outputs '123def abcdef abcdef'

神奇之处在于可选的第四个参数[Limit]。从文档中可以看到:

[Limit] -最大可能 每个中的每个模式的替换 主题字符串。默认为-1 (no 限制)。< / p >

不过,请参阅zombat的回答以获得更有效的方法(大约快3-4倍)。

最简单的方法是使用正则表达式。

另一种方法是用strpos()找到字符串的位置,然后用substr_replace()

但我真的会选择RegExp。

它没有版本,但解决方案一点也不俗气。

$pos = strpos($haystack, $needle);
if ($pos !== false) {
$newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
}

非常简单,并且节省了正则表达式的性能损失。

附加条件:如果你想替换最后的的出现,只需使用strrpos来代替strpos

不幸的是,我不知道任何PHP函数可以做到这一点 你可以像这样轻松地滚动你自己的:

function replace_first($find, $replace, $subject) {
// stolen from the comments at PHP.net/str_replace
// Splits $subject into an array of 2 items by $find,
// and then joins the array with $replace
return implode($replace, explode($find, $subject, 2));
}

编辑:两个答案都已更新,现在是正确的。我将把答案留下,因为函数时间仍然是有用的。

“僵尸”和“太多php”的答案很不幸是不正确的。这是对僵尸发布的答案的修正(因为我没有足够的声誉来发表评论):

$pos = strpos($haystack,$needle);
if ($pos !== false) {
$newstring = substr_replace($haystack,$replace,$pos,strlen($needle));
}

注意strlen($needle),而不是strlen($replace)。Zombat的例子只有在针头和替代物长度相同的情况下才能正确工作。

下面是与PHP自己的str_replace具有相同签名的函数中的相同函数:

function str_replace_first($search, $replace, $subject) {
$pos = strpos($subject, $search);
if ($pos !== false) {
return substr_replace($subject, $replace, $pos, strlen($search));
}
return $subject;
}

这是修改后的“too much php”的答案:

implode($replace, explode($search, $subject, 2));

注意最后的2而不是1。或函数格式:

function str_replace_first($search, $replace, $subject) {
return implode($replace, explode($search, $subject, 2));
}

我对这两个函数进行了计时,当没有找到匹配时,第一个函数的速度是原来的两倍。当找到匹配时,它们的速度是一样的。

我创建了这个函数,用limit替换字符串上的字符串(区分大小写),而不需要Regexp。它工作得很好。

function str_replace_limit($search, $replace, $string, $limit = 1) {
$pos = strpos($string, $search);


if ($pos === false) {
return $string;
}


$searchLen = strlen($search);


for ($i = 0; $i < $limit; $i++) {
$string = substr_replace($string, $replace, $pos, $searchLen);


$pos = strpos($string, $search);


if ($pos === false) {
break;
}
}


return $string;
}

使用示例:

$search  = 'foo';
$replace = 'bar';
$string  = 'foo wizard makes foo brew for evil foo and jack';
$limit   = 2;


$replaced = str_replace_limit($search, $replace, $string, $limit);


echo $replaced;
// bar wizard makes bar brew for evil foo and jack
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;

为了扩展@renocor的回答,我写了一个与str_replace() 100%向后兼容的函数。也就是说,你可以用str_replace_limit()替换所有中出现的str_replace(),而不会搞砸任何东西,即使是那些使用$search$replace和/或$subject数组的数组。

如果你想用($string===strval(intval(strval($string))))替换函数调用,函数可以是完全自包含的,但我不建议这样做,因为valid_integer()在处理作为字符串提供的整数时是一个相当有用的函数。

< em >注意:只要可能,str_replace_limit()将使用str_replace()代替,因此所有对str_replace()的调用都可以替换为str_replace_limit(),而不用担心对性能的影响

使用

<?php
$search = 'a';
$replace = 'b';
$subject = 'abcabc';
$limit = -1; // No limit
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2个替换——BBCBBC

$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

1个替换——bbcabc

$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2个替换——BBCBBC

函数

<?php


/**
* Checks if $string is a valid integer. Integers provided as strings (e.g. '2' vs 2)
* are also supported.
* @param mixed $string
* @return bool Returns boolean TRUE if string is a valid integer, or FALSE if it is not
*/
function valid_integer($string){
// 1. Cast as string (in case integer is provided)
// 1. Convert the string to an integer and back to a string
// 2. Check if identical (note: 'identical', NOT just 'equal')
// Note: TRUE, FALSE, and NULL $string values all return FALSE
$string = strval($string);
return ($string===strval(intval($string)));
}


/**
* Replace $limit occurences of the search string with the replacement string
* @param mixed $search The value being searched for, otherwise known as the needle. An
* array may be used to designate multiple needles.
* @param mixed $replace The replacement value that replaces found search values. An
* array may be used to designate multiple replacements.
* @param mixed $subject The string or array being searched and replaced on, otherwise
* known as the haystack. If subject is an array, then the search and replace is
* performed with every entry of subject, and the return value is an array as well.
* @param string $count If passed, this will be set to the number of replacements
* performed.
* @param int $limit The maximum possible replacements for each pattern in each subject
* string. Defaults to -1 (no limit).
* @return string This function returns a string with the replaced values.
*/
function str_replace_limit(
$search,
$replace,
$subject,
&$count,
$limit = -1
){


// Set some defaults
$count = 0;


// Invalid $limit provided. Throw a warning.
if(!valid_integer($limit)){
$backtrace = debug_backtrace();
trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
'`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting an '.
'integer', E_USER_WARNING);
return $subject;
}


// Invalid $limit provided. Throw a warning.
if($limit<-1){
$backtrace = debug_backtrace();
trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
'`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
'a positive integer', E_USER_WARNING);
return $subject;
}


// No replacements necessary. Throw a notice as this was most likely not the intended
// use. And, if it was (e.g. part of a loop, setting $limit dynamically), it can be
// worked around by simply checking to see if $limit===0, and if it does, skip the
// function call (and set $count to 0, if applicable).
if($limit===0){
$backtrace = debug_backtrace();
trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
'`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
'a positive integer', E_USER_NOTICE);
return $subject;
}


// Use str_replace() whenever possible (for performance reasons)
if($limit===-1){
return str_replace($search, $replace, $subject, $count);
}


if(is_array($subject)){


// Loop through $subject values and call this function for each one.
foreach($subject as $key => $this_subject){


// Skip values that are arrays (to match str_replace()).
if(!is_array($this_subject)){


// Call this function again for
$this_function = __FUNCTION__;
$subject[$key] = $this_function(
$search,
$replace,
$this_subject,
$this_count,
$limit
);


// Adjust $count
$count += $this_count;


// Adjust $limit, if not -1
if($limit!=-1){
$limit -= $this_count;
}


// Reached $limit, return $subject
if($limit===0){
return $subject;
}


}


}


return $subject;


} elseif(is_array($search)){
// Only treat $replace as an array if $search is also an array (to match str_replace())


// Clear keys of $search (to match str_replace()).
$search = array_values($search);


// Clear keys of $replace, if applicable (to match str_replace()).
if(is_array($replace)){
$replace = array_values($replace);
}


// Loop through $search array.
foreach($search as $key => $this_search){


// Don't support multi-dimensional arrays (to match str_replace()).
$this_search = strval($this_search);


// If $replace is an array, use the value of $replace[$key] as the replacement. If
// $replace[$key] doesn't exist, just an empty string (to match str_replace()).
if(is_array($replace)){
if(array_key_exists($key, $replace)){
$this_replace = strval($replace[$key]);
} else {
$this_replace = '';
}
} else {
$this_replace = strval($replace);
}


// Call this function again for
$this_function = __FUNCTION__;
$subject = $this_function(
$this_search,
$this_replace,
$subject,
$this_count,
$limit
);


// Adjust $count
$count += $this_count;


// Adjust $limit, if not -1
if($limit!=-1){
$limit -= $this_count;
}


// Reached $limit, return $subject
if($limit===0){
return $subject;
}


}


return $subject;


} else {
$search = strval($search);
$replace = strval($replace);


// Get position of first $search
$pos = strpos($subject, $search);


// Return $subject if $search cannot be found
if($pos===false){
return $subject;
}


// Get length of $search, to make proper replacement later on
$search_len = strlen($search);


// Loop until $search can no longer be found, or $limit is reached
for($i=0;(($i<$limit)||($limit===-1));$i++){


// Replace
$subject = substr_replace($subject, $replace, $pos, $search_len);


// Increase $count
$count++;


// Get location of next $search
$pos = strpos($subject, $search);


// Break out of loop if $needle
if($pos===false){
break;
}


}


// Return new $subject
return $subject;


}


}

你可以用这个:

function str_replace_once($str_pattern, $str_replacement, $string){


if (strpos($string, $str_pattern) !== false){
$occurrence = strpos($string, $str_pattern);
return substr_replace($string, $str_replacement, strpos($string, $str_pattern), strlen($str_pattern));
}


return $string;
}

从php.net找到这个例子

用法:

$string = "Thiz iz an examplz";
var_dump(str_replace_once('z','Z', $string));

输出:

ThiZ iz an examplz

这可能会降低一点性能,但这是最简单的解决方案。

根据我的测试结果,我想投票给karim79提供的regular_express。(我现在没有足够的声誉去投票!)

僵尸的解决方案使用了太多的函数调用,我甚至简化了代码。我使用PHP 5.4运行这两个解决方案10万次,结果如下:

$str = 'Hello abc, have a nice day abc! abc!';
$pos = strpos($str, 'abc');
$str = substr_replace($str, '123', $pos, 3);

1.85秒

$str = 'Hello abc, have a nice day abc! abc!';
$str = preg_replace('/abc/', '123', $str, 1);

1.35秒

如你所见。preg_replace的性能并不像很多人想象的那么差。所以如果你的常规快递不是很复杂的话,我建议你用一个漂亮的解决方案。

为了扩展zombat的答案(我相信这是最好的答案),我创建了他的函数的递归版本,该函数接受$limit参数来指定你想要替换多少次。

function str_replace_limit($haystack, $needle, $replace, $limit, $start_pos = 0) {
if ($limit <= 0) {
return $haystack;
} else {
$pos = strpos($haystack,$needle,$start_pos);
if ($pos !== false) {
$newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
return str_replace_limit($newstring, $needle, $replace, $limit-1, $pos+strlen($replace));
} else {
return $haystack;
}
}
}

对于字符串

$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';


//replace ONLY FIRST occurance of "OOO" with "B"
$string = substr_replace($string,$replace,0,strlen($search));
//$string => B.OOO.OOO.S


//replace ONLY LAST occurance of "OOOO" with "B"
$string = substr_replace($string,$replace,strrpos($string,$search),strlen($search))
//$string => OOO.OOO.B.S


//replace ONLY LAST occurance of "OOOO" with "B"
$string = strrev(implode(strrev($replace),explode(strrev($search),strrev($string),2)))
//$string => OOO.OOO.B.S

对于单个字符

$string[strpos($string,$search)] = $replace;




//EXAMPLE


$string = 'O.O.O.O.S';
$search = 'O';
$replace = 'B';


//replace ONLY FIRST occurance of "O" with "B"
$string[strpos($string,$search)] = $replace;
//$string => B.O.O.O.S


//replace ONLY LAST occurance of "O" with "B"
$string[strrpos($string,$search)] = $replace;
// $string => B.O.O.B.S

我想知道哪一个是最快的,所以我都测试了。

下面你会发现:

  • 这个页面上的所有功能的综合列表
  • 对每个贡献进行基准测试(平均执行时间超过10,000次运行)
  • 链接到每个答案(完整代码)

所有功能都在相同的设置下进行测试:

$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';

仅替换字符串中字符串出现第一个的函数:


Functions that only replace the last occurrence of a string within a string:

下面是我创建的一个简单类,用于包装稍作修改的(大小写不敏感)函数。

我们的php::str_rreplace()函数还允许执行反向的、有限的str_replace(),这在试图只替换字符串的最后X个实例时非常方便。

这些例子都使用了preg_replace ()

<?php
class php {


/**
* str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
*
* @param string   $find
* @param string   $replace
* @param string   $subject
* @param int      $replacement_limit | -1 to replace all references
*
* @return string
*/
public static function str_replace($find, $replace, $subject, $replacement_limit = -1) {
$find_pattern = str_replace('/', '\/', $find);
return preg_replace('/' . $find_pattern . '/', $replace, $subject, $replacement_limit);
}


/**
* str_replace() from the end of a string that can also be limited e.g. replace only the last instance of '</div>' with ''
*
* @param string   $find
* @param string   $replace
* @param string   $subject
* @param int      $replacement_limit | -1 to replace all references
*
* @return string
*/
public static function str_rreplace($find, $replace, $subject, $replacement_limit = -1) {
return strrev( self::str_replace(strrev($find), strrev($replace), strrev($subject), $replacement_limit) );
}
}
function str_replace_once($search, $replace, $subject) {
$pos = strpos($subject, $search);
if ($pos === false) {
return $subject;
}


return substr($subject, 0, $pos) . $replace . substr($subject, $pos + strlen($search));
}

=比;代码被修改了,所以考虑一些太旧的注释

谢谢大家帮助我改善

有任何BUG,请与我沟通;我马上就去做

那么,让我们开始:

将第一个“o”替换为“ea”,例如:

$s='I love you';
$s=str_replace_first('o','ea',$s);
echo $s;


//output: I leave you

功能:

function str_replace_first($this,$that,$s)
{
$w=strpos($s,$this);
if($w===false)return $s;
return substr($s,0,$w).$that.substr($s,$w+strlen($this));
}
这个函数很大程度上受到@ renoor答案的启发。 它使函数多字节安全
function str_replace_limit($search, $replace, $string, $limit)
{
$i = 0;
$searchLength = mb_strlen($search);


while(($pos = mb_strpos($string, $search)) !== false && $i < $limit)
{
$string = mb_substr_replace($string, $replace, $pos, $searchLength);
$i += 1;
}


return $string;
}


function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
{
$string = (array)$string;
$encoding = is_null($encoding) ? mb_internal_encoding() : $encoding;
$length = is_null($length) ? mb_strlen($string) - $start : $length;


$string = array_map(function($str) use ($replacement, $start, $length, $encoding){


$begin = mb_substr($str, 0, $start, $encoding);
$end = mb_substr($str, ($start + $length), mb_strlen($str), $encoding);


return $begin . $replacement . $end;


}, $string);


return ( count($string) === 1 ) ? $string[0] : $string;
}

补充一下人们所说的,记住整个字符串是一个数组:

$string = "Lorem ipsum lá lá lá";


$string[0] = "B";


echo $string;

“Borem ipsum lá lá lá”

$str = "Hello there folks!"
$str_ex = explode("there, $str, 2);   //explodes $string just twice
//outputs: array ("Hello ", " folks")
$str_final = implode("", $str_ex);    // glues above array together
// outputs: str("Hello  folks")

还有一个额外的空间,但这并不重要,因为它是背景脚本在我的情况下。

如果你的字符串不包含任何多字节字符,如果你只想替换一个字符,你可以简单地使用strpos

这里是处理错误的函数

/**
* Replace the first occurence of given string
*
* @param  string $search  a char to search in `$subject`
* @param  string $replace a char to replace in `$subject`
* @param  string $subject
* @return string
*
* @throws InvalidArgumentException if `$search` or `$replace` are invalid or if `$subject` is a multibytes string
*/
function str_replace_first(string $search , string $replace , string $subject) : string {
// check params
if(strlen($replace) != 1 || strlen($search) != 1) {
throw new InvalidArgumentException('$search & $replace must be char');
}elseif(mb_strlen($subject) != strlen($subject)){
throw new InvalidArgumentException('$subject is an multibytes string');
}
// search
$pos = strpos($subject, $search);
if($pos === false) {
// not found
return $subject;
}


// replace
$subject[$replace] = $subject;


return $subject;
}

For循环解决方案

<?php
echo replaceFirstMatchedChar("&", "?", "/property/details&id=202&test=123#tab-6");


function replaceFirstMatchedChar($searchChar, $replaceChar, $str)
{
for ($i = 0; $i < strlen($str); $i++) {


if ($str[$i] == $searchChar) {
$str[$i] = $replaceChar;
break;
}
}
return $str;
}
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);
使用substr_replace,我们可以替换字符串中第一个字符的出现。 为,重复多次,但只是在第一个位置,我们必须替换&< / p > ?

我会用孕妇代替。它有一个LIMIT参数,你可以将它设置为1

preg_replace (regex, subst, string, limit) // default is -1