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

325

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

Ник Хейнер
источник

Ответы:

346

Может быть сделано с помощью 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'

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

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


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

karim79
источник
39
Недостатком этого метода является снижение производительности регулярных выражений.
зомбат
27
Другим недостатком является то, что вы должны использовать preg_quote () на «игле» и избегать метасимволов $ и \ в замене.
Джош Дэвис
32
Это не является общим решением из-за неприятных проблем с выходом из строя.
Джереми Кауфман
2
Слишком часто регулярные выражения отбрасываются из-за «производительности», если бы производительность была главной задачей, мы бы не писали PHP! Для обертывания паттерна можно использовать что-то иное, чем «/», возможно, «~», что поможет в некоторой степени избежать побега. Это зависит от того, что это за данные и откуда они поступили.
ThomasRedstone
1
Помимо отрицательных сторон производительности - имеют ли в виду те, кто жалуется на проблемы с выходом из строя, помимо потенциальных ошибок preg_quote? Например, @ThomasRedstone беспокоится о том, что разделитель /может быть опасным, если он появляется $from, но, к счастью, это не так: он корректно экранируется из-за preg_quoteвторого параметра (это легко проверить). Мне было бы интересно услышать о конкретных проблемах (которые будут серьезными ошибками безопасности PCRE в моей книге).
MvanGeest
611

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

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

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


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

zombat
источник
17
Может быть намного быстрее и использовать меньше памяти, чем регулярные выражения. Понятия не имею, почему кто-то проголосовал бы за это ...
Джош Дэвис
12
Мне нравится такой подход, но в коде есть ошибка, последний параметр вызова substr_replace должен быть strlen ($ needle) вместо strlen ($ replace) .. пожалуйста, будьте осторожны!
Нельсон
Это «хакерство» в том смысле, что требуется гораздо больше времени, чтобы понять, что происходит. Кроме того, если бы это был чистый код, не упоминалось бы, что в коде есть ошибка. Если в таком маленьком фрагменте можно ошибиться, это уже слишком глупо.
Камило Мартин
9
Я не согласен с @CamiloMartin в отношении количества строк и вероятности ошибок. Хотя substr_replaceэто довольно громоздкая функция для использования из-за всех параметров, реальная проблема заключается в том, что иногда манипулирование строками с помощью чисел просто сложно - нужно быть осторожным, чтобы передать правильную переменную / смещение в функции. На самом деле я бы сказал, что приведенный выше код является наиболее простым и логичным подходом.
Алекс
1
Блестящий подход. Прекрасно работает при замене значений переменных, в которых зарезервированы символы регулярных выражений (поэтому preg_replace является медведем). Это просто и элегантно.
Praesagus
96

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

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

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

Обратите внимание на strlen ($ needle) вместо strlen ($ replace). Пример Zombat будет работать правильно только если иглы и замены имеют одинаковую длину.

Вот та же функциональность в функции с той же сигнатурой, что и в собственном 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));
}

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

Bas
источник
Почему бы не обобщить это, например: str_replace_f Flexible (mixed $ s, mixed $ r, int $ offset, int $ limit), где функция заменяет вхождения $ limit, начиная с соответствия $ offset (nth).
Адам Фридман
Жаль, что это применимо только для замены с учетом регистра.
andreszs
4
@ Эндрю stripos()на помощь :-)
Gras Double
76

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

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

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

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

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

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


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

oLinkWebDevelopment
источник
Спасибо за это, я обычно использую preg_replace, так как он наиболее гибок, если будущая настройка требуется в большинстве случаев.
Замедление на
@oLinkWebDevelopment Мне было бы интересно увидеть ваш тестовый скрипт. Я думаю, что это может оказаться полезным.
Дейв Мортон
Причина, по которой substr_replace()выигрывает результат, проста; потому что это внутренняя функция. Две внутренние и пользовательские функции, выполняющие одно и то же, отличаются по производительности, поскольку внутренняя функция работает на более низких уровнях. Так почему бы и нет preg_match()? Регулярные выражения почти медленнее, чем любая внутренняя функция манипуляции со строками, из-за того, что они ищут несколько раз в строке.
MAChitgarha
1
Я надеюсь, что тест на вашем "winner" ( substr_replace($string, $replace, 0, strlen($search));) не просто записал эту статическую 0. Частью свертки решений без регулярных выражений является то, что им нужно «найти» отправную точку, прежде чем знать, где заменить.
mickmackusa
55

К сожалению, я не знаю ни одной функции 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
источник
Я думаю, что это самая лучшая версия из всех - использование joinвместо implode.
Тит
return implode($replace, explode($find, $subject, $limit+1));для пользовательских замен номера
beppe9000
7

Я создал эту маленькую функцию, которая заменяет строку на строку (с учетом регистра) с помощью 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
Parziphal
источник
Хотя я предпочел бы сделать ===falseвместо того , is_bool(чтобы быть более четко - я даю этот палец вверх только потому , что он избежал RegExp безумие ! ... и в то же время это рабочее и чистое решение ...
jave.web
Предпочитать легко настраиваемое preg_решение - это не безумие, а личное предпочтение. return preg_replace('/'.preg_quote($search, '/').'/', $replace, $content, 1);довольно просто читать для людей, которые не боятся регулярных выражений. Нужен поиск без учета регистра? Добавьте iпосле конца шаблон разделителя. Нужна поддержка Unicode / Multibyte? Добавьте uпосле конца шаблон разделителя. Нужна поддержка границ слов? Добавьте \bпо обе стороны вашей строки поиска. Если вы не хотите регулярное выражение, не используйте регулярное выражение. Лошади на курсах, но уж точно не безумие.
mickmackusa
3

Самый простой способ - использовать регулярные выражения.

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

Но я бы действительно пошел на RegExp.

Руфин
источник
Этот «намек» довольно расплывчатый / низкозначный по сравнению с другими постами на этой странице.
mickmackusa
3
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));
}
Лучаниновыми
источник
Ответы только на код не имеют большого значения в StackOverflow, потому что они плохо справляются с обучением / расширением возможностей тысяч будущих исследователей.
mickmackusa
3

=> КОД БЫЛ ПЕРЕСМОТРЕН, поэтому считаю некоторые комментарии слишком старыми

И спасибо всем за помощь в улучшении этого

Любая ошибка, пожалуйста, сообщите мне; Я исправлю это сразу после

Итак, давайте перейдем к:

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

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

//output: I leave you

Функция:

function str_replace_first($a,$b,$s)
         {
         $w=strpos($s,$a);
         if($w===false)return $s;
         return substr($s,0,$w).$b.substr($s,$w+strlen($a));
         }
PYK
источник
Сбой, если $ это повторяет символы, такие как aaa vs aaaaaaaaa
Cristo
Я думаю, что должно быть substr($where,$b+strlen($this)), а не substr($where,$b+1). И я думаю, что substr_replaceэто быстрее.
Тит
Код был пересмотрен, теперь он работает даже для длинных строк
PYK
Это решение не работает как закодировано. Доказательство: 3v4l.org/cMeZj. И когда вы исправляете проблему именования переменных, она не работает, когда не найдено значение поиска - она ​​повреждает входную строку. Доказательство: 3v4l.org/XHtfc
mickmackusa
Справедливо ли, что кто-то просит исправить код? @mickmackusa Можете ли вы проверить это еще раз, пожалуйста?
PYK
2
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;
Zack
источник
Это так же, как первый ответ. Кроме того, вы должны сделать preg_quoteиз , $findпрежде чем использовать его в качестве выражения.
Эмиль Викстрем
это то, что я использовал, поэтому я проголосовал за это. Первый ответ вызвал конфликт с Drupal, он должен был перезаписать вспомогательную функцию drupal. Поэтому я просто взял код, который был внутри функции, и использовал его в соответствии с остальным кодом ...
Дэн
Этот ответ, содержащий только код, предоставляет избыточные советы на странице (не говоря уже о том, что его не хватает preg_quote(). Этот поздний дубликат ответа можно безопасно удалить со страницы, поскольку его рекомендации предоставлены более ранним и более высоко оцененным принятым ответом.
mickmackusa
2

Чтобы расширить ответ @ 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;

    }

}
0b10011
источник
4
вроде раздутый, если вы спросите меня. Также больше всего я ненавижу в этом решении - обработку ошибок. Это нарушает сценарий, если вы передаете неправильные значения. Вы думаете, что это выглядит профессионально, но это не так, вместо ошибки вместо этого выдается уведомление или предупреждение. Лучше пропустить ерунду, вернуть false или null и никогда не использовать обратную трассировку в такой функции. Лучшее решение состоит в том, что программист может решить, что делать, если вывод неправильный / неожиданный.
Codebeat
@Erwinus этом используется E_USER_WARNINGво всем , что является предупреждением , не ошибка . Обратная трассировка чрезвычайно полезна, чтобы выяснить, какой код передает некорректные данные функции в первую очередь (что абсолютно необходимо для отслеживания ошибок в работе). Что касается возврата $subjectвместо false/ nullили выдачи ошибки, это был просто личный выбор для моего варианта использования. Чтобы соответствовать str_replace()функциональным возможностям, лучше всего использовать подхватываемые фатальные ошибки (как str_replace()при закрытии первых двух аргументов).
0b10011
Ах, вы не заметили, что E_USER_WARNING вы используете, извините за это. Проблема с возвратом предмета состоит в том, что вы никогда не увидите, что что-то не так, вне функции. Тем не менее, функция может быть вдвое меньше, если вы делаете это умнее (это возможно). Во-вторых, комментарии хороши, когда они объясняют что-то сложное, но не очень полезно для простых вещей, таких как увеличение значения. В целом я думаю, что это не нужно, огромный. Кроме того, использование предупреждений в производственной среде может быть проблемой безопасности при использовании этого кода на сервере, который не подавляет сообщения во время выполнения по умолчанию (журналы).
Codebeat
@ Erwinus Я был многословен, когда дело дошло до комментариев, потому что некоторые люди не понимают язык так же, как другие, и комментарии всегда могут быть удалены теми, кто его понимает. Если вам известен лучший способ получить один и тот же конечный результат для всех крайних случаев, отредактируйте ответ. И если ваша производственная среда не подавляет сообщения об ошибках, у вас есть большая проблема, чем эта функция;)
0b10011
TL; DR Этот фрагмент настолько раздут, что я не могу себе представить, выбирая его вместо функции регулярного выражения (я ненавижу прокрутку). Если вы хотите сосчитать сделанные замены, для этого есть параметр preg_replace(). Кроме того, preg_replace()/ regex предлагает обработку границ слов (если желательно) - то, что не-регулярные функции не будут предоставлять элегантно.
mickmackusa
2

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

Хантер Ву
источник
Ваш первый фрагмент - несправедливое сравнение, потому что он не использует правильную реализацию. Вы не проверять $posна false, так что, когда игла не существует в стоге сена, это повредит выход.
mickmackusa
Спасибо @mickmackusa, ты прав. Но дело не в этом. Я сказал, что этот код упрощен только для сравнения эффективности реализаций.
Хантер Ву
Это именно моя точка зрения. Вы никогда не должны делать сравнительные сравнения, которые не выполняют точно такой же процесс. Сравнение яблок с полу-апельсинами бесполезно. Полное внедрение полного подхода без регулярных выражений сделает разницу в скорости более глубокой.
mickmackusa
Ну, спасибо еще раз. Но я хочу найти лучшую реализацию, а не делать глубокие различия.
Охотник Ву
2

Чтобы расширить ответ зомбата (который я считаю лучшим), я создал рекурсивную версию его функции, которая принимает $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;
        }
    }
}
jtate
источник
Обратите внимание, что нет никакой проверки качества на $start_pos, поэтому , если он находится за пределами длины строки, то эта функция будет генерировать: Warning: strpos(): Offset not contained in string.... Эта функция не может произвести замену, если она $start_posнаходится за пределами длины. Доказательство отказа: 3v4l.org/qGuVIR ... Ваша функция может объединять return $haystackусловия и избегать объявления одноразовых переменных, например: 3v4l.org/Kdmqp. Однако, как я уже говорил в комментариях в других местах на этой странице, я бы предпочел используйте очень чистый, прямой, нерекурсивный preg_replace()вызов.
mickmackusa
да так , что вы можете добавить эту строку elseПостулаты$start_pos > strlen($haystack) ? $start_pos = strlen($haystack) : '';
Manojkiran.A
2

Для строки

$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
oLinkWebDevelopment
источник
Первый фрагмент substr_replace () завершается ошибкой, когда строка поиска не имеет смещения 0 входной строки. Подтверждение ошибки: 3v4l.org/oIbRv И оба substr_replace()метода повреждают входную строку, когда поисковое значение отсутствует. Доказательство неудачи: 3v4l.org/HmEml (И эта последняя техника со всеми revвызовами серьезно запутана /
жестока
2

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

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

$string[0] = "B";

echo $string;

"Borem ipsum lá lá lá"

Леандро Кастро
источник
3
Если он не содержит многобайтовые символы ... и тогда ваша техника не сработает. Как жаль, что вы предложили образец входной строки, содержащий á. Демонстрация неудачи
mickmackusa
Вы можете проверить, является ли ваша stringмногобайтовая строка, используяmb_strlen($subject) != strlen($subject)
RousseauAlexandre
Этот пост не пытается ответить на заданный вопрос.
mickmackusa
2
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);

Используя substr_replace, мы можем заменить вхождение первого символа только в строке. как & повторяется несколько раз, но только на первой позиции, которую мы должны заменить & на?

Пуджа Гоял
источник
1

Эта функция в значительной степени вдохновлена ​​ответом @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;
}
блаблабла
источник
0

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

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

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

happyhardik
источник
Если это выход, то какой смысл? Разве это не должно заменить только первые строчные буквы "z" заглавными буквами "Z"? Вместо того, чтобы заменить их все? Я думал, что это то, о чем мы здесь говорили ...
Swivel
Мой плохой, он заменит только первое вхождение. Ред.
happyhardik
Этот же совет уже был предложен Басом почти 3 года назад (и без чрезмерных звонков strpos()). Понижено, потому что это не добавляет никакого нового значения к странице.
mickmackusa
0

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

Для петлевого решения

<?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;
}
черный
источник
-1

Вот простой класс, который я создал, чтобы обернуть наши слегка модифицированные функции 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) );
    }
}
Аран
источник
Ваш пост не добавляет ценности этой уже перенасыщенной странице. Ваше решение регулярных выражений дает сбой во многих дополнительных случаях, потому что вы использовали неправильный инструмент для экранирования символов в строке иглы. Доказательство неудачи: 3v4l.org/dTdYK Сильно одобренный и принятый в 2009 году ответ уже показывает правильное выполнение этой техники. Ваш второй метод не отвечает на заданный вопрос и уже был предоставлен oLinkWebDevelopment.
mickmackusa
-1
$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 в моем случае.

loqan
источник
Этот метод был предоставлен Toomuchphp еще в 2009 году ! Я снизил рейтинг, потому что ваш пост не добавляет исследователям никакой новой ценности. Прежде чем публиковать ответ, убедитесь, что ваше решение является уникальным для данной страницы и повышает ее ценность.
mickmackusa
-3

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

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

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


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

Don_Diego
источник
Четвертые аргументы будут установлены функцией str_replace () для числа произведенных замен. Вот почему вы получаете ошибку, когда передаете целое число, а не переменную.
arminrosu
-6

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

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

Во всяком случае, просто предложение.

Hassan
источник
4
Зачем отвечать на вопрос предложением, если ответ был принят два года назад ?!
mbinette
Также -3 не будет работать как параметр, потому что 4-й параметр является выходным, а не входным параметром. Было бы лучше, если вы протестируете то, что вы предлагаете, вместо того, чтобы публиковать код, который вылетает.
Ghostwriter78
Да, это неправильно, однако, почему я получаю сбой пустого экрана, когда я пытаюсь это сделать? Я сделал обычный error_reporting (E_ALL); ini_set ("display_errors", 1); Все еще ошибка пустого экрана.
Даг Кэссиди