Используя замену str, чтобы она действовала только при первом совпадении?


Мне нужна версия str_replace(), которая заменяет только первое вхождение $search в $subject. Есть ли простое решение для этого, или мне нужно хакерское решение?

 259
Author: JakeGould, 2009-08-10

22 answers

Можно сделать с помощью preg_replace:

function str_replace_first($from, $to, $content)
{
    $from = '/'.preg_quote($from, '/').'/';

    return preg_replace($from, $to, $content, 1);
}

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

Волшебство заключается в необязательном четвертом параметре [Предел]. Из документации:

[Предел] - Максимально возможные замены для каждого шаблона в каждом строка темы. По умолчанию -1 (без ограничений).


Хотя, смотрите Ответ зомбата для более эффективного метода (примерно в 3-4 раза быстрее).

 287
Author: karim79, 2018-02-22 17:16:53

Такой версии нет, но решение совсем не хакерское.

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

Довольно просто и экономит потери производительности регулярных выражений.


Бонус: Если вы хотите заменить последнее вхождение , просто используйте strrpos вместо strpos.

 468
Author: zombat, 2016-12-04 05:13:14

Изменить: оба ответа были обновлены и теперь являются правильными. Я оставлю ответ, так как тайминги функций все еще полезны.

Ответы "зомбат" и "слишком много php", к сожалению, неверны. Это пересмотр ответа, опубликованного зомбатом (так как у меня недостаточно репутации, чтобы оставить комментарий):

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

Обратите внимание на strlen($игла) вместо strlen($заменить). Пример Зомбата будет работать правильно только в том случае, если игла и замена имеют одинаковую длину.

Здесь та же функциональность в функции с той же сигнатурой, что и у собственного str_replace PHP:

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

Это пересмотренный ответ "слишком много php":

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

Обратите внимание на 2 в конце вместо 1. Или в формате функции:

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

Я рассчитал время выполнения двух функций, и первая из них выполняется в два раза быстрее, если совпадение не найдено. Они имеют одинаковую скорость, когда найдено совпадение.

 88
Author: Bas, 2016-02-26 20:20:36

Мне было интересно, какой из них самый быстрый, поэтому я проверил их все.

Ниже вы найдете:

  • Полный список всех функций, которые были добавлены на эту страницу
  • Контрольное тестирование для каждого дополнения (среднее время выполнения более 10 000 запусков)
  • Ссылки на каждый ответ (для полного кода)

Все функции были протестированы с одинаковыми настройками:

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

Функции, которые только заменяют первое вхождение строки в строку:


Функции, которые заменяют только последнее вхождение строки в строку:

 65
Author: oLinkWebDevelopment, 2017-05-23 11:55:02

К сожалению, я не знаю ни одной функции 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));
}
 52
Author: too much php, 2016-08-18 12:03:33

Я создал эту маленькую функцию, которая заменяет строку на строку (с учетом регистра) с ограничением, без необходимости регулярного выражения. Это прекрасно работает.

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
 6
Author: Parziphal, 2016-03-23 15:14:38

Проще всего было бы использовать регулярное выражение.

Другой способ - найти позицию строки с помощью strpos(), а затем substr_replace()

Но я бы действительно выбрал регулярное выражение.

 3
Author: Rufinus, 2009-08-10 00:43:59
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;
 3
Author: zack, 2012-06-21 19:08:34
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));
}
 3
Author: luchaninov, 2014-09-06 20:05:29

Дополняя то, что говорили люди, помните, что вся строка представляет собой массив:

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

$string[0] = "B";

echo $string;

"Борем ипсум ла ла ла"

 3
Author: Leandro Castro, 2016-07-01 12:36:10

Замена первого 'o' на 'ea' например:

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

//output: I leave you

Функция:

function str_replace_first($this,$that,$where)
{
        $b=strpos($where,$this);
        return substr($where,0,$b).$that.substr($where,$b+1);
}
 3
Author: PYK, 2016-08-08 11:13:34

Чтобы расширить ответ @renocor, я написал функцию, которая на 100% обратно совместима с str_replace(). То есть вы можете заменить все вхождения str_replace() на str_replace_limit(), ничего не испортив, даже те, которые используют массивы для $search, $replace, и/или $subject.

Функция может быть полностью автономной, если вы хотите заменить вызов функции на ($string===strval(intval(strval($string)))), но я бы не рекомендовал этого делать, так как valid_integer() является довольно полезной функцией, когда работа с целыми числами, представленными в виде строк.

Примечание: Всякий раз, когда это возможно, 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;

    }

}
 2
Author: 0b10011, 2017-05-23 12:02:57

Согласно результатам моего теста, я хотел бы проголосовать за вариант regular_express, предоставленный karim79. (У меня недостаточно репутации, чтобы проголосовать за это сейчас!)

Решение от zombat использует слишком много вызовов функций, я даже упрощаю коды. Я использую PHP 5.4 для запуска обоих решений 100 000 раз, и вот результат:

$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 не так плоха, как думают многие. Поэтому я бы предложил классное решение, если ваш обычный экспресс не сложен.

 2
Author: Hunter Wu, 2013-11-22 21:07:41

Чтобы расширить ответ зомбата (который я считаю лучшим ответом), я создал рекурсивную версию его функции, которая принимает параметр $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;
        }
    }
}
 2
Author: jtate, 2014-01-11 19:13:24

Для строки

$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
 2
Author: oLinkWebDevelopment, 2014-03-08 18:17:18

Вы можете использовать это:

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

Это может немного снизить производительность, но это самое простое решение.

 1
Author: happyhardik, 2014-04-16 10:39:16

Эта функция в значительной степени вдохновлена ответом @renocor. Это делает функцию многобайтовой безопасной.

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;
}
 1
Author: blablabla, 2016-04-11 12:55:23

Вот простой класс, который я создал, чтобы обернуть наши слегка измененные функции str_replace().

Наша функция 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) );
    }
}
 0
Author: Aran, 2014-05-07 15:49:55
$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")

Есть еще одно дополнительное пространство, но это не имело значения, так как в моем случае это было для сценария backgound.

 0
Author: loqan, 2017-11-27 18:59:52

Если ваша строка не содержит многобайтовых символов и если вы хотите заменить только один символ, вы можете просто использовать 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;
}
 0
Author: RousseauAlexandre, 2018-05-30 09:04:52

Это мой первый ответ здесь, я надеюсь сделать это правильно. Почему бы не использовать четвертый аргумент функции str_replace для этой проблемы?

mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )

Количество: Если пройдено, будет установлено количество выполненных замен.


редактировать: Этот ответ неверен, потому что 4-й параметр str_replace является переменной, которой присваивается количество выполненных замен. Это несовместимо с preg_replace, который имеет 4-й параметр $limit и 5-й параметр &$count.

 -3
Author: Don_Diego, 2016-02-15 14:43:55

Легко найти решение для замены только первого или первых нескольких экземпляров (указав значение счетчика). Существует не так много решений для замены последнего или последней пары экземпляров.

Возможно, что-то вроде str_replace ($find, $replace, $subject, -3) должно заменить последние три экземпляра.

В любом случае, просто предложение.

 -5
Author: Hassan, 2018-04-21 06:54:56