Мне нужна версия, str_replace(), которая заменяет только первое вхождение $search в $subject. Есть ли простое решение для этого, или мне нужно использовать какое-то специальное решение?
Ответ 1
Решение самое обыкновенное:
$pos = strpos($haystack, $needle);
if ($pos !== false) {
$newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
}
Довольно просто и избавляет от потери производительности при использовании регулярных выражений. Если же, вы хотите заменить последнее вхождение, просто используйте strrpos вместо strpos.
Ответ 2
Это можно сделать с помощью 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');
// выводит '123def abcdef abcdef'
Суть заключается в необязательном четвертом параметре [Limit].
Из документации:
Максимально возможные замены для каждого шаблона в каждой строке темы. По умолчанию -1 (без ограничений).
Ответ 3
К сожалению, предыдущие ответы не совсем верны. Это пересмотр предыдущих ответов:
$pos = strpos($haystack,$needle);
if ($pos !== false) {
$newstring = substr_replace($haystack,$replace,$pos,strlen($needle));
}
Обратите внимание на strlen($Need), вместо использования strlen($replace). Пример будет работать правильно, только если строка поиска и замены имеют одинаковую длину. Вот та же функциональность в функции с той же сигнатурой, что и встроенная в 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;
}
Или так:
implode($replace, explode($search, $subject, 2));
Обратите внимание на «2» в конце вместо «1». И в формате функции:
function str_replace_first($search, $replace, $subject) {
return implode($replace, explode($search, $subject, 2));
}
Я рассчитал время для двух функций, и первая работает в два раза быстрее, когда совпадений не найдено. Они имеют одинаковую скорость при обнаружении совпадения.
Ответ 4
Мне было интересно узнать, какая из функций самая быстрая, поэтому я протестировал их все. Ниже вы найдете:
Полный список всех функций, которые были добавлены на эту страницу.
Эталонное тестирование для каждой функции (среднее время выполнения за 10 000 запусков)
Все функции были протестированы с одинаковыми настройками:
$string = 'OOO.OOO.OOO.S';
$search = 'OOO';
$replace = 'B';
Функции, которые заменяют только первое вхождение строки, получая новую строку:
substr_replace($string, $replace, 0, strlen($search));
[ПРИМЕР ТЕСТА] => zombat
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000062883
[МЕДЛЕННЕЕ НА] => FASTEST
replace_first($search, $replace, $string);
[ПРИМЕР ТЕСТА] => too much php
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000073902
[МЕДЛЕННЕЕ НА] => 17.52%
preg_replace($search, $replace, $string, 1);
[ПРИМЕР ТЕСТА] => karim79
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000077519
[МЕДЛЕННЕЕ НА] => 23.27%
str_replace_once($search, $replace, $string);
[ПРИМЕР ТЕСТА] => happyhardik
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000082286
[МЕДЛЕННЕЕ НА] => 30.86%
str_replace_limit($search, $replace, $string, $count, 1);
[ПРИМЕР ТЕСТА] => bfrohs - expanded renocor
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000083342
[МЕДЛЕННЕЕ НА] => 32.54%
str_replace_limit($search, $replace, $string, 1);
[ПРИМЕР ТЕСТА] => renocor
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000093116
[МЕДЛЕННЕЕ НА] => 48.08%
str_replace_limit($string, $search, $replace, 1, 0);
[ПРИМЕР ТЕСТА] => jayoaK
[OOO.OOO.OOO.S] => B.OOO.OOO.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000093862
[МЕДЛЕННЕЕ НА] => 49.26%
Функции, которые заменяют только последнее вхождение строки, получая новую строку:
substr_replace($string, $replace, strrpos($string, $search), strlen($search));
[ПРИМЕР ТЕСТА] => oLinkSoftware - modified zombat
[OOO.OOO.OOO.S] => OOO.OOO.B.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000068083
[МЕДЛЕННЕЕ НА] => FASTEST
strrev(implode(strrev($replace), explode(strrev($search), strrev($string), 2)));
[ПРИМЕР ТЕСТА] => oLinkSoftware
[OOO.OOO.OOO.S] => OOO.OOO.B.S
[СРЕДНЕЕ ВРЕМЯ] => 0.0000084460
[МЕДЛЕННЕЕ НА] => 24.05%
Ответ 5
К сожалению, я не знаю ни одной функции PHP, которая могла бы это сделать. Вы можете создать собственную довольно легко, например, так:
function replace_first($find, $replace, $subject) {
// Разделяет $subject на массив из 2 элементов по $find,
// и затем объединяет массив с помощью $replace
return implode($replace, explode($find, $subject, 2));
}
Ответ 6
Я создал эту маленькую функцию, которая заменяет строку на другую строку (с учетом регистра) с ограничением, без необходимости использования 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;
Ответ 7
Например, такой код:
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));
}
Ответ 8
Я написал функцию, которая на 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; // без ограничения
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;
результат: 2 замены – bbcbbc
$limit = 1; // ограничено до «1»
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;
результат: 1 замена – bbcabc
$limit = 10; // ограничено до «10»
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;
результат: 2 замены -- bbcbbc
Непосредственно функция
<?php
/**
* Проверяет, является ли $string действительным целым числом. Целые числа, представленные в виде строк (например, '2' против 2)
* также поддерживаются.
* @param mixed $string
* @return bool Возвращает булево значение TRUE, если строка является допустимым целым числом, или FALSE, если это не так.
*/
function valid_integer($string){
// 1. Приведение к строке (в случае, если представлено целое число)
// 1. Преобразование строки в целое число и обратно в строку
// 2. Проверить, идентичны ли они (примечание: "идентичны", а не просто "равны")
// Примечание: значения TRUE, FALSE и NULL $string возвращают FALSE
$string = strval($string);
return ($string===strval(intval($string)));
}
/**
* Заменяет $limit повторений строки поиска строкой замены.
* @param mixed $search Искомое значение, иначе называемое целевым значением.
* массив может быть использован для обозначения нескольких таких значений.
* @param mixed $replace Значение замены, которое заменяет найденные значения поиска.
* массив может быть использован для указания нескольких замен.
* @param mixed $subject Строка или массив, по которым выполняется поиск и замена. Если subject является массивом, то поиск и замена
* выполняется с каждым элементом subject, и возвращаемое значение также является массивом.
* @param string $count Если передано, то будет установлено количество замен,
* @param int $limit Максимально возможное количество замен для каждого шаблона в каждой теме. По умолчанию -1 (без ограничения).
* @return string Эта функция возвращает строку с замененными значениями.
*/
function str_replace_limit($search, $replace, $subject, &$count, $limit = -1) {
// Установите некоторые значения по умолчанию
$count = 0;
// Указано неверное значение $limit. Вызов предупреждения.
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;
}
// Указано неверное значение $limit. Вызов предупреждения.
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;
}
// Замена не требуется. Вызовите уведомление, так как это, скорее всего, не предполагалось
// использовать. А если и предполагалось (например, для части цикла, динамически устанавливающая $limit), то это можно
// обойти, просто проверив, что $limit===0, и если это так, то пропустите вызов функции.
// вызов функции (и установить $count в 0, если необходимо).
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;
}
// Используйте str_replace(), когда это возможно (по соображениям производительности)
if($limit===-1){
return str_replace($search, $replace, $subject, $count);
}
if(is_array($subject)){
// Перебираем значения $subject и вызываем эту функцию для каждого из них.
foreach($subject as $key => $this_subject){
// Пропустите значения, которые являются массивами (для соответствия функции str_replace()).
if(!is_array($this_subject)){
// Вызовите эту функцию снова
$this_function = __FUNCTION__;
$subject[$key] = $this_function($search, $replace, $this_subject, $this_count, $limit);
// Корректировка $count
$count += $this_count;
// корректировка $limit, если не «-1»
if($limit!=-1){
$limit -= $this_count;
}
// Достигнут $limit, возврат $subject
if($limit===0){
return $subject;
}
}
}
return $subject;
} elseif(is_array($search)){
// Рассматривайте $replace как массив, только если $search также является массивом (для соответствия str_replace())
// Очистите ключи $search (для соответствия str_replace()).
$search = array_values($search);
// Очистите ключи $replace, если необходимо(чтобы соответствовать str_replace()).
if(is_array($replace)){
$replace = array_values($replace);
}
// Перебираем массив $search.
foreach($search as $key => $this_search){
// Не поддерживаются многомерные массивы (чтобы соответствовать str_replace()).
$this_search = strval($this_search);
// Если $replace - массив, используйте значение $replace[$key] в качестве замены. Если
// $replace[$key] не существует, просто пустая строка (для соответствия 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);
}
// Вызовите эту функцию снова
$this_function = __FUNCTION__;
$subject = $this_function($this_search, $this_replace, $subject, $this_count, $limit);
// корректировка $count
$count += $this_count;
// корректировка $limit, если не «-1»
if($limit!=-1){
$limit -= $this_count;
}
// достигнут $limit, возврат $subject
if($limit===0){
return $subject;
}
}
return $subject;
} else {
$search = strval($search);
$replace = strval($replace);
// получение позиции первого вхождения $search
$pos = strpos($subject, $search);
// возврат $subject, если $search не найден
if($pos===false){
return $subject;
}
// Получение длины $search, чтобы в дальнейшем произвести правильную замену
$search_len = strlen($search);
// Цикл до тех пор, пока $search больше не будет найден, или пока не будет достигнут $limit
for($i=0;(($i<$limit)||($limit===-1));$i++){
// замена
$subject = substr_replace($subject, $replace, $pos, $search_len);
// инкремент $count
$count++;
// получение следующей позиции $search
$pos = strpos($subject, $search);
// Выйти из цикла, если $needle
if($pos===false) {
break;
}
}
// возврат нового $subject
return $subject;
}
}
Web