En utilisant str replace pour qu'il n'agisse que sur le premier match?

Je veux une version de str_replace(), ce qui ne remplace la première occurrence de $search dans le $subject. Y a-t-il une solution facile à cela, ou Ai-je besoin d'une solution hacky?

259
demandé sur JakeGould 2009-08-10 04:39:54

22 réponses

Peut être fait avec 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 magie est dans le quatrième paramètre facultatif [Limit]. De la documentation:

[Limite] - Le maximum possible remplacements pour chaque motif dans chaque l'objet string. Par défaut à -1 (Non limite).


cependant, voir la réponse de zombat pour une méthode plus efficace (à peu près, 3-4x plus rapide).

287
répondu karim79 2018-02-22 17:16:53

Il n'y a pas de version, mais la solution n'est pas hacky du tout.

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

Assez facile, et enregistre la pénalité de performance des expressions régulières.


Bonus: Si vous souhaitez remplacer , dernier occurrence, il suffit d'utiliser strrpos au lieu de strpos.

468
répondu zombat 2016-12-04 05:13:14

Edit: les deux réponses ont été mises à jour et sont maintenant correctes. Je vais laisser la réponse puisque les timings de fonction sont toujours utiles.

Les réponses de 'zombat' et 'Too much php' ne sont malheureusement pas correctes. Ceci est une révision de la réponse Postée par zombat (car je n'ai pas assez de réputation pour poster un commentaire):

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

Notez le strlen ($needle), au lieu de strlen ($replace). L'exemple de Zombat ne fonctionnera correctement que si needle et replace ont la même longueur.

Voici la même fonctionnalité dans une fonction avec la même signature que le propre 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;
}

C'est la réponse révisée de 'Too much php':

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

Notez le 2 à la fin au lieu de 1. Ou au format de fonction:

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

J'ai programmé les deux fonctions et la première est deux fois plus vite si aucune correspondance n'est trouvée. Ils sont la même vitesse lorsqu'une correspondance est trouvée.

88
répondu Bas 2016-02-26 20:20:36

Je me demandais lequel était le plus rapide, alors je les ai tous testés.

Vous trouverez ci-dessous:

  • Une liste complète de toutes les fonctions qui ont été apportées sur cette page
  • test de référence pour chaque contrubution (temps d'exécution moyen sur 10 000 exécutions)
  • liens vers chaque réponse (pour le code complet)

Toutes les fonctions ont été testées avec les mêmes paramètres:

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

Fonctions qui ne remplacent que première occurrence d'une chaîne dans une chaîne:


Fonctions qui remplacent uniquement le dernier occurrence d'une chaîne dans une chaîne:

65
répondu oLinkWebDevelopment 2017-05-23 11:55:02

Malheureusement, je ne connais aucune fonction PHP qui puisse le faire.
Vous pouvez rouler votre propre assez facilement comme ceci:

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
répondu too much php 2016-08-18 12:03:33

J'ai créé cette fonction little qui remplace string on string (sensible à la casse) par limit, sans avoir besoin de Regexp. Il fonctionne très 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;
}

Exemple d'utilisation:

$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
répondu Parziphal 2016-03-23 15:14:38

, Le plus simple serait d'utiliser une expression régulière.

L'autre moyen est de trouver la position de la chaîne avec strpos () puis un substr_replace ()

Mais j'irais vraiment pour L'expression rationnelle.

3
répondu 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
répondu 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
répondu luchaninov 2014-09-06 20:05:29

Complétant ce que les gens ont dit, Rappelez-vous que la chaîne entière est un tableau:

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

$string[0] = "B";

echo $string;

"Borem ipsum lá lá lá"

3
répondu Leandro Castro 2016-07-01 12:36:10

Remplacement de la première " 'o' à 'ea' par exemple:

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

//output: I leave you

La fonction:

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

Pour développer la réponse de @renocor , j'ai écrit une fonction 100% rétrocompatible avec str_replace(). Autrement dit, Vous pouvez remplacer toutes les occurrences de str_replace() par str_replace_limit() sans rien gâcher, même ceux qui utilisent des tableaux pour le $search, $replace, et / ou $subject.

La fonction pourrait être complètement autonome, si vous vouliez remplacer l'appel de fonction par ($string===strval(intval(strval($string)))), mais je le déconseillerais puisque valid_integer() est une fonction plutôt utile lors du traitement avec des entiers fournis sous forme de chaînes.

Note: dans la mesure du possible, str_replace_limit() utilisera str_replace() à la place, de sorte que tous les appels à str_replace() peuvent être remplacés par str_replace_limit() sans se soucier d'un coup à la performance.

Utilisation

<?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 remplacements -- bbcbbc

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

1 remplacements -- bbcabc

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

2 remplacements -- bbcbbc

Fonction

<?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
répondu 0b10011 2017-05-23 12:02:57

Selon mon résultat de test, j'aimerais voter le regular_express fourni par karim79. (Je n'ai pas assez de réputation pour voter maintenant!)

La solution de zombat utilise trop d'appels de fonction, je simplifie même les codes. J'utilise PHP 5.4 pour exécuter les deux solutions pour 100 000 fois, et voici le résultat:

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

==> 1.85 sec

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

==> 1.35 sec

Comme vous pouvez le voir. La performance de preg_replace n'est pas si mauvaise que beaucoup de gens le pensent. Donc, je vous suggère de l' solution chic si votre express régulier n'est pas compliqué.

2
répondu Hunter Wu 2013-11-22 21:07:41

Pour développer la réponse de zombat (que je crois être la meilleure réponse), j'ai créé une version récursive de sa fonction qui prend un paramètre $limit pour spécifier le nombre d'occurrences que vous voulez remplacer.

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
répondu jtate 2014-01-11 19:13:24

Pour une chaîne

$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

Pour un seul caractère

$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
répondu oLinkWebDevelopment 2014-03-08 18:17:18

, Vous pouvez utiliser ceci:

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; 
    } 

Trouvé cet exemple de php.net

Utilisation:

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

Sortie:

ThiZ iz an examplz

Cela peut réduire les performances un peu, mais la solution la plus simple.

1
répondu happyhardik 2014-04-16 10:39:16

Cette fonction est fortement inspirée par la réponse de @ renocor. Il rend la fonction multi octet sûr.

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
répondu blablabla 2016-04-11 12:55:23

Voici une classe simple que j'ai créée pour envelopper nos fonctions str_replace() légèrement modifiées.

Notre fonction php::str_rreplace () vous permet également d'effectuer une inverse, limitée str_replace () qui peut être très pratique lorsque vous essayez de remplacer uniquement la ou les instances x finales d'une chaîne.

Ces exemples utilisent tous les deux 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
répondu 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")

Il y a un espace supplémentaire de plus mais cela n'a pas d'importance comme pour le script backgound dans mon cas.

0
répondu loqan 2017-11-27 18:59:52

Si vous chaîne ne contient aucun des caractères multioctets et si vous souhaitez remplacer un seul caractère, vous pouvez simplement utiliser strpos

Voici une fonction qui gère les erreurs

/**
 * 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
répondu RousseauAlexandre 2018-05-30 09:04:52

C'est ma première réponse, j'espère le faire correctement. Pourquoi ne pas utiliser le quatrième argument de la fonction str_replace pour ce problème?

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

Count: s'il est passé, il sera défini sur le nombre de remplacements effectués.


edit: cette réponse est fausse, car le 4ème paramètre de str_replace est une variable qui reçoit le nombre de remplacements effectués. Ceci est incompatible avec preg_replace , qui a un 4ème paramètre $limit et un 5ème paramètre &$count.

-3
répondu Don_Diego 2016-02-15 14:43:55

Il est facile de trouver une solution pour remplacer uniquement la première ou la première paire d'instances (en donnant la valeur count). Il n'y a pas beaucoup de solutions pour remplacer le dernier ou le dernier couple d'instance.

Peut-être que quelque chose comme str_replace ($find, $replace, $subject, -3) devrait remplacer les trois dernières instances.

Quoi Qu'il en soit, juste une suggestion.

-5
répondu Hassan 2018-04-21 06:54:56