php - remove - ¿Usando str_replace para que solo actúe en la primera partida?
str_replace position php (23)
Quiero una versión de str_replace()
que solo reemplaza la primera aparición de $search
en el $subject
. ¿Existe una solución fácil para esto, o necesito una solución pirata?
Aquí hay una clase simple que creé para envolver nuestras funciones str_replace() ligeramente modificadas.
Nuestra función php :: str_rreplace () también le permite llevar a cabo un str_replace () inverso y limitado que puede ser muy útil cuando se intenta reemplazar solo las X instancias finales de una cadena.
Estos ejemplos usan 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) );
}
}
Complementando lo que dijo la gente, recuerde que toda la cadena es una matriz:
$string = "Lorem ipsum lá lá lá";
$string[0] = "B";
echo $string;
"Borem ipsum lá lá lá"
Creé esta pequeña función que reemplaza la cadena en cadena (distingue entre mayúsculas y minúsculas) con límite, sin la necesidad de Regexp. Funciona bien.
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;
}
Ejemplo de uso:
$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
De acuerdo con el resultado de mi prueba, me gustaría votar la regular_express proporcionada por karim79. (¡No tengo suficiente reputación para votar ahora!)
La solución de zombat usa demasiadas llamadas de función, incluso simplifico los códigos. Estoy usando PHP 5.4 para ejecutar ambas soluciones por 100,000 veces, y aquí está el resultado:
$str = ''Hello abc, have a nice day abc! abc!'';
$pos = strpos($str, ''abc'');
$str = substr_replace($str, ''123'', $pos, 3);
==> 1.85 seg.
$str = ''Hello abc, have a nice day abc! abc!'';
$str = preg_replace(''/abc/'', ''123'', $str, 1);
==> 1.35 seg
Como puedes ver. El rendimiento de preg_replace no es tan malo como muchos piensan. Así que sugeriría la solución elegante si su expreso regular no es complicado.
Desafortunadamente, no conozco ninguna función de PHP que pueda hacer esto.
Usted puede rodar su propio bastante fácilmente de esta manera:
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));
}
Echa un vistazo a Str.replaceFirst de Laravel ():
public static function replaceFirst($search, $replace, $subject)
{
if ($search == '''') {
return $subject;
}
$position = strpos($subject, $search);
if ($position !== false) {
return substr_replace($subject, $replace, $position, strlen($search));
}
return $subject;
}
Es fácil encontrar una solución para reemplazar solo la primera o la primera pareja de casos (dando el valor de conteo). No hay muchas soluciones para reemplazar el último o el último par de instancias.
Tal vez algo como str_replace ($ find, $ replace, $ subject, -3) debería reemplazar las últimas tres instancias.
De todos modos, sólo una sugerencia.
Esta es mi primera respuesta aquí, espero hacerlo correctamente. ¿Por qué no usar el cuarto argumento de la función str_replace para este problema?
mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] )
recuento: si se aprueba, se ajustará al número de reemplazos realizados.
edición: esta respuesta es incorrecta, porque el 4º parámetro de str_replace es una variable a la que se le asigna el número de reemplazos realizados. Esto es inconsistente con preg_replace , que tiene un $limit
4 de parámetro y un &$count
5 de parámetro.
Esta función está fuertemente inspirada en la respuesta de @renocor. Hace que la función multi byte sea segura.
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;
}
La forma más fácil sería usar expresiones regulares.
La otra forma es encontrar la posición de la cadena con strpos () y luego un substr_replace ()
Pero realmente me gustaría ir para el RegExp.
Me pregunté cuál era el más rápido, así que los probé a todos.
Abajo encontrarás:
- Una lista completa de todas las funciones que se han contribuido en esta página
- Pruebas de referencia para cada contribución (tiempo de ejecución promedio de más de 10,000 ejecuciones)
- Enlaces a cada respuesta (para el código completo)
Todas las funciones fueron probadas con la misma configuración:
$string = ''OOO.OOO.OOO.S'';
$search = ''OOO'';
$replace = ''B'';
Funciones que solo reemplazan la primera aparición de una cadena dentro de una cadena:
substr_replace($string, $replace, 0, strlen($search));
[CONTRIBUTED BY] => zombat [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000062883 [SLOWER BY] => FASTEST
replace_first($search, $replace, $string);
[CONTRIBUTED BY] => too much php [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000073902 [SLOWER BY] => 17.52%
preg_replace($search, $replace, $string, 1);
[CONTRIBUTED BY] => karim79 [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000077519 [SLOWER BY] => 23.27%
str_replace_once($search, $replace, $string);
[CONTRIBUTED BY] => happyhardik [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000082286 [SLOWER BY] => 30.86%
str_replace_limit($search, $replace, $string, $count, 1);
[CONTRIBUTED BY] => bfrohs - expanded renocor [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000083342 [SLOWER BY] => 32.54%
str_replace_limit($search, $replace, $string, 1);
[CONTRIBUTED BY] => renocor [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000093116 [SLOWER BY] => 48.08%
str_replace_limit($string, $search, $replace, 1, 0);
[CONTRIBUTED BY] => jayoaK [OOO.OOO.OOO.S] => B.OOO.OOO.S [AVERAGE TIME] => 0.0000093862 [SLOWER BY] => 49.26%
Funciones que solo reemplazan la última aparición de una cadena dentro de una cadena:
substr_replace($string, $replace, strrpos($string, $search), strlen($search));
[CONTRIBUTED BY] => oLinkSoftware - modified zombat [OOO.OOO.OOO.S] => OOO.OOO.B.S [AVERAGE TIME] => 0.0000068083 [SLOWER BY] => FASTEST
strrev(implode(strrev($replace), explode(strrev($search), strrev($string), 2)));
[CONTRIBUTED BY] => oLinkSoftware [OOO.OOO.OOO.S] => OOO.OOO.B.S [AVERAGE TIME] => 0.0000084460 [SLOWER BY] => 24.05%
No hay una versión de eso, pero la solución no es nada complicada.
$pos = strpos($haystack, $needle);
if ($pos !== false) {
$newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
}
Bastante fácil, y guarda la penalización de rendimiento de las expresiones regulares.
Bonificación: si desea reemplazar el último suceso, simplemente use strrpos
en lugar de strpos
.
Para ampliar la respuesta de @ renocor , escribí una función que es 100% compatible con versiones anteriores con str_replace()
. Es decir, puede reemplazar todas las apariciones de str_replace()
con str_replace_limit()
sin desordenar nada, incluso aquellos que usan matrices para $search
, $replace
y / o $subject
.
La función podría ser completamente autónoma, si desea reemplazar la llamada a la función con ($string===strval(intval(strval($string))))
, pero lo recomendaría ya que valid_integer()
es un Función bastante útil cuando se trata de enteros proporcionados como cadenas.
Nota: siempre que sea posible, str_replace_limit()
usará str_replace()
lugar, por lo que todas las llamadas a str_replace()
se pueden reemplazar con str_replace_limit()
sin preocuparse por un impacto en el rendimiento.
Uso
<?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 reemplazos - bbcbbc
$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.'' replacements -- ''.$new_string;
1 reemplazos - bbcabc
$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.'' replacements -- ''.$new_string;
2 reemplazos - bbcbbc
Función
<?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;
}
}
Para ampliar la respuesta de zombat (que creo que es la mejor respuesta), creé una versión recursiva de su función que toma un parámetro de $limit
para especificar cuántas ocurrencias desea reemplazar.
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;
}
}
}
Para una cuerda
$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
Para un solo personaje
$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
Puedes usar esto:
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;
}
Encontré este ejemplo de php.net
Uso:
$string = "Thiz iz an examplz";
var_dump(str_replace_once(''z'',''Z'', $string));
Salida:
ThiZ iz an examplz
Esto puede reducir el rendimiento un poco, pero la solución más fácil.
Reemplazando la primera ''o'' a ''ea'' por ejemplo:
$a=''I love you'';
echo str_replace_first(''o'',''ea'',$a);
//output: I leave you
La función:
function str_replace_first($this,$that,$where)
{
$b=strpos($where,$this);
return substr($where,0,$b).$that.substr($where,$b+1);
}
Se puede hacer con 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''
La magia está en el cuarto parámetro opcional [Límite]. De la documentación:
[Límite]: los reemplazos máximos posibles para cada patrón en cada cadena de asunto. Por defecto es -1 (sin límite).
Sin embargo, vea la respuesta de zombat para un método más eficiente (aproximadamente, 3-4 veces más rápido).
Si la cadena no contiene caracteres multibyte y si desea reemplazar solo un carácter, simplemente puede usar strpos
Aquí una función que maneja errores.
/**
* 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;
}
Edición: ambas respuestas se han actualizado y ahora son correctas. Dejaré la respuesta ya que los tiempos de función siguen siendo útiles.
Las respuestas de ''zombat'' y ''demasiado php'' desafortunadamente no son correctas. Esta es una revisión de la respuesta publicada por zombat (ya que no tengo suficiente reputación para publicar un comentario):
$pos = strpos($haystack,$needle);
if ($pos !== false) {
$newstring = substr_replace($haystack,$replace,$pos,strlen($needle));
}
Tenga en cuenta el strlen ($ needle), en lugar de strlen ($ replace). El ejemplo de Zombat solo funcionará correctamente si la aguja y el reemplazo tienen la misma longitud.
Esta es la misma funcionalidad en una función con la misma firma que el propio str_replace de 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;
}
Esta es la respuesta revisada de ''demasiado php'':
implode($replace, explode($search, $subject, 2));
Note el 2 al final en lugar de 1. O en formato de función:
function str_replace_first($search, $replace, $subject) {
return implode($replace, explode($search, $subject, 2));
}
Programé las dos funciones y la primera es dos veces más rápida cuando no se encuentra ninguna coincidencia. Son la misma velocidad cuando se encuentra un partido.
$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")
Hay un espacio adicional más, pero en mi caso no importaba como era para el script de backgound.
$string = ''this is my world, not my world'';
$find = ''world'';
$replace = ''farm'';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;
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));
}