Vous utilisez str_replace pour qu'il n'agisse que sur la première correspondance?

Réponses:

346

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 optionnel [Limite]. De la documentation:

[Limite] - Le nombre maximal de remplacements possibles pour chaque modèle dans chaque chaîne de sujet. Par défaut à -1 (pas de limite).


Cependant, consultez la réponse de zombat pour une méthode plus efficace (environ 3 à 4 fois plus rapide).

karim79
la source
39
L'inconvénient de cette méthode est la pénalité de performance des expressions régulières.
zombat
27
Un autre inconvénient est que vous devez utiliser preg_quote () sur "l'aiguille" et échapper les méta-caractères $ et \ dans le remplacement.
Josh Davis
32
Cela échoue en tant que solution générique en raison de problèmes d'échappement méchants.
Jeremy Kauffman
2
Trop souvent, les expressions régulières sont ignorées en raison de la «performance», si la performance était la principale préoccupation, nous n'écririons pas PHP! Quelque chose d'autre que «/» pourrait être utilisé pour encapsuler le motif, peut-être «~», ce qui aiderait à éviter le problème de fuite dans une certaine mesure. Cela dépend de ce que sont les données et d'où elles proviennent.
ThomasRedstone
1
Mis à part les performances, ceux qui se plaignent d'échapper à des problèmes ont-ils quelque chose de spécifique à l'esprit, en plus des bogues potentiels preg_quote? Par exemple, @ThomasRedstone craint que le délimiteur /ne soit dangereux s'il apparaît $from, mais heureusement ce n'est pas le cas: il est correctement échappé à cause du preg_quotesecond paramètre de (on peut facilement le tester). Je serais intéressé d'entendre parler de problèmes spécifiques (qui seraient de graves bogues de sécurité PCRE dans mon livre).
MvanGeest
611

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

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

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


Bonus: si vous souhaitez remplacer la dernière occurrence, utilisez-la simplement strrposà la place de strpos.

zombat
la source
17
Peut être beaucoup plus rapide et utilise moins de mémoire que les expressions régulières. Aucune idée de la raison pour laquelle quelqu'un voterait contre ...
Josh Davis
12
J'aime cette approche, mais le code a une erreur, le dernier paramètre de l'appel substr_replace devrait être strlen ($ needle) au lieu de strlen ($ replace) .. s'il vous plaît méfiez-vous de cela !!
Nelson
C'est "hacky" en ce sens qu'il faut beaucoup plus de temps pour comprendre ce qui se passe. De plus, s'il s'agissait d'un code clair, il n'aurait pas été mentionné que le code contient une erreur. S'il est possible de faire une erreur dans un si petit extrait, c'est déjà beaucoup trop hacky.
Camilo Martin
9
Je ne suis pas d'accord avec @CamiloMartin en ce qui concerne le nombre de lignes par rapport à la possibilité d'erreurs. Bien qu'il substr_replaces'agisse d'une fonction quelque peu difficile à utiliser en raison de tous les paramètres, le vrai problème est que la manipulation de chaînes par des nombres est parfois délicate - vous devez faire attention à transmettre la bonne variable / décalage aux fonctions. J'irais même jusqu'à dire que le code ci-dessus est l'approche la plus simple et la plus logique pour moi.
Alex
1
Une approche brillante. Fonctionne parfaitement lors du remplacement de valeurs de variables qui ont réservé des caractères regex (donc preg_replace est bear). C'est simple et élégant.
Praesagus
96

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

Les réponses de «zombat» et «trop de php» ne sont malheureusement pas correctes. Ceci est une révision de la réponse publié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 l'aiguille et le remplacement sont de la même longueur.

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

Ceci est la réponse révisée de «trop de 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 chronométré les deux fonctions et la première est deux fois plus rapide lorsqu'aucune correspondance n'est trouvée. Ils ont la même vitesse lorsqu'un match est trouvé.

Bas
la source
Pourquoi ne pas généraliser ceci comme: str_replace_flexible ($ mixte, $ r mixte, décalage $ int, limite $ int) où la fonction remplace les occurrences $ limit à partir de la correspondance $ décalage (nième).
Adam Friedman
Dommage que cela ne s'applique qu'aux remplacements sensibles à la casse.
andreszs
4
@Andrew stripos()à la rescousse :-)
Gras Double
76

Je me suis demandé laquelle était la plus rapide, alors je les ai toutes testées.

Vous trouverez ci-dessous:

  • Une liste complète de toutes les fonctions qui ont été apportées à cette page
  • Tests 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 la première occurrence d'une chaîne dans une chaîne:


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

oLinkWebDevelopment
la source
Merci pour cela, j'utilise généralement preg_replace car c'est le plus flexible si un ajustement futur est nécessaire dans la plupart des cas, 27% plus lent ne sera pas significatif
zzapper
@oLinkWebDevelopment Je serais intéressé de voir votre script de référence. Je pense que cela pourrait s'avérer utile.
Dave Morton
La raison pour laquelle substr_replace()remporte le résultat est simple; parce que c'est une fonction interne. Deux fonctions internes et définies par l'utilisateur faisant la même chose diffèrent dans les performances, car la fonction interne s'exécute dans les couches inférieures. Alors pourquoi pas preg_match()? Les expressions régulières sont presque plus lentes que chaque fonction de manipulation de chaîne interne, en raison de leur nation de recherche dans une chaîne plusieurs fois.
MAChitgarha
1
J'espère que la référence sur votre "gagnant" ( substr_replace($string, $replace, 0, strlen($search));) n'a pas simplement écrit cette statique 0. Une partie de la convolution des solutions non regex est qu'elles doivent "trouver" le point de départ avant de savoir où remplacer.
mickmackusa
55

Malheureusement, je ne connais aucune fonction PHP qui puisse le faire.
Vous pouvez rouler le vôtre 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));
}
trop de php
la source
Je pense que c'est la version la plus golfique de tous - en utilisant joinau lieu de implode.
Titus
return implode($replace, explode($find, $subject, $limit+1));pour les numéros de remplacement personnalisés
beppe9000
7

J'ai créé cette petite fonction qui remplace chaîne sur chaîne (sensible à la casse) avec limite, sans avoir besoin de Regexp. Ça fonctionne 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
Parziphal
la source
Bien que je préfère faire ===falseplutôt que d' is_bool(être plus explicite - je donne ce coup de pouce juste parce qu'il a évité la folie RegExp ! ... et en même temps ça marche et une solution propre ...
jave.web
Préférer une preg_solution facilement personnalisable n'est pas de la folie mais une préférence personnelle. return preg_replace('/'.preg_quote($search, '/').'/', $replace, $content, 1);est assez simple à lire pour les personnes qui ne craignent pas les regex. Besoin d'une recherche insensible à la casse? Ajoutez iaprès le délimiteur de motif de fin. Besoin d'un support unicode / multi-octets? Ajoutez uaprès le délimiteur de motif de fin. Besoin de prise en charge des limites de mots? Ajoutez des \bdeux côtés de votre chaîne de recherche. Si vous ne voulez pas d'expression régulière, n'utilisez pas d'expression régulière. Des chevaux pour les cours, mais certainement pas de la folie.
mickmackusa
3

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

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

Mais j'irais vraiment pour le RegExp.

Rufinus
la source
Cette "indication" est plutôt vague / de faible valeur par rapport aux autres articles de cette page.
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));
}
luchaninov
la source
Les réponses de code uniquement sont de faible valeur sur StackOverflow car elles font un mauvais travail d'éduquer / d'autonomiser des milliers de futurs chercheurs.
mickmackusa
3

=> LE CODE A ÉTÉ RÉVISÉ, alors considérez certains commentaires trop anciens

Et merci à tous de m'aider à améliorer cela

Tout BUG, ​​veuillez me communiquer; Je vais arranger ça juste après

Alors, allons-y pour:

Remplacement du premier «o» par «ea» par exemple:

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

//output: I leave you

La fonction:

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
la source
Échoue si $ this a répété des caractères comme aaa vs aaaaaaaaa
Cristo
Je pense que ça devrait l'être substr($where,$b+strlen($this)), non substr($where,$b+1). Et je suppose que substr_replacec'est plus rapide.
Titus
Le code a été révisé, maintenant il fonctionne même pour les cordes longues
PYK
Cette solution ne fonctionne pas comme codée. Preuve: 3v4l.org/cMeZj Et lorsque vous corrigez le problème de dénomination des variables, cela ne fonctionne pas lorsque la valeur de recherche n'est pas trouvée - cela endommage la chaîne d'entrée. Preuve: 3v4l.org/XHtfc
mickmackusa
Est-il juste que quelqu'un demande à FIXER le code? @mickmackusa Pouvez-vous vérifier à nouveau s'il vous plaît?
PYK
2
$string = 'this is my world, not my world';
$find = 'world';
$replace = 'farm';
$result = preg_replace("/$find/",$replace,$string,1);
echo $result;
zack
la source
C'est la même chose que la première réponse. En outre, vous devriez faire preg_quotedes $findavant de l' utiliser comme une expression.
Emil Vikström
c'est ce que j'ai utilisé, donc je l'ai voté. La première réponse a provoqué un conflit avec Drupal, elle doit avoir écrasé une fonction d'aide drupal. J'ai donc pris le code qui était à l'intérieur de la fonction et l'ai utilisé en ligne avec le reste du code ...
Dan Mantyla
Cette réponse uniquement codée fournit des conseils redondants sur la page (sans oublier qu'elle fait défaut preg_quote(). Cette réponse en double tardive peut être supprimée de la page en toute sécurité car ses conseils sont fournis par la réponse acceptée la plus ancienne et la plus élevée.
mickmackusa
2

Pour développer la réponse de @ renocor , j'ai écrit une fonction qui est 100% rétrocompatible avec str_replace(). Autrement dit, vous pouvez remplacer toutes les occurrences de str_replace()avec str_replace_limit()sans rien gâcher, même ceux qui utilisent des tableaux pour la $search, $replaceet / 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éconseille car valid_integer()c'est une fonction plutôt utile lorsque vous traitez avec des entiers fournis sous forme de chaînes.

Remarque: Dans la mesure du possible, str_replace_limit()utilisera à la str_replace()place, de sorte que tous les appels à str_replace()puissent être remplacés str_replace_limit()sans se soucier de la performance.

Usage

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

    }

}
0b10011
la source
4
un peu gonflé si vous me demandez. Ce que je déteste le plus à cette solution, c'est la gestion des erreurs. Il casse le script si vous passez des valeurs incorrectes. Vous pensez qu'il a l'air professionnel, mais ce n'est pas le cas, au lieu d'une erreur, produisez plutôt un avis ou un avertissement. Il vaut mieux ignorer les conneries, renvoyer false à la place ou null et ne jamais utiliser de trace dans une fonction comme celle-ci. La meilleure solution est que le programmeur puisse décider quoi faire lorsque la sortie est incorrecte / inattendue.
Codebeat
@Erwinus Ceci est utilisé E_USER_WARNINGpartout, ce qui est un avertissement et non une erreur . La trace est extrêmement utile pour savoir quel code transmet les données non valides à la fonction en premier lieu (ce qui est absolument nécessaire pour traquer les bogues en production). Quant au retour $subjectau lieu de false/ nullou le lancement d'une erreur, c'était simplement un choix personnel pour mon cas d'utilisation. Pour faire correspondre str_replace()la fonctionnalité de, l'utilisation d'erreurs fatales capturables serait le meilleur pari (tout comme str_replace()lors de la fermeture des deux premiers arguments).
0b10011
Ah, je n'ai pas remarqué les E_USER_WARNING que vous utilisez, désolé pour cela. Le problème avec le retour du sujet est que vous ne pouvez jamais voir qu'il y avait quelque chose de mal, en dehors de la fonction. Cela dit, la fonction peut être la moitié de la taille si vous la faites plus intelligemment (c'est possible). Deuxièmement, les commentaires sont bons quand ils expliquent quelque chose de complexe mais pas très utile pour des choses simples comme augmenter une valeur. Dans l'ensemble, je pense que c'est inutile énorme. En outre, l'utilisation d'avertissements dans un environnement de production peut poser un problème de sécurité lorsque vous utilisez ce code sur un serveur qui ne supprime pas les messages d'exécution par défaut (journaux).
Codebeat
@Erwinus J'ai été prolixe en ce qui concerne les commentaires, car certaines personnes ne comprennent pas la langue aussi bien que d'autres, et les commentaires peuvent toujours être supprimés par ceux qui la comprennent. Si vous connaissez un meilleur moyen d'obtenir le même résultat final pour tous les cas marginaux, modifiez certainement la réponse. Et si votre environnement de production ne supprime pas les messages d'erreur, vous avez un problème plus important que cette fonction;)
0b10011
TL; DR Cet extrait est tellement gonflé que je ne peux pas imaginer le choisir plutôt qu'une fonction d'expression régulière (je déteste le défilement). Si vous souhaitez compter les remplacements effectués, il existe un paramètre pour cela dans preg_replace(). De plus, preg_replace()/ regex offre une gestion des limites des mots (si cela est souhaitable) - quelque chose que les fonctions non regex ne fourniront pas avec élégance.
mickmackusa
2

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

La solution de zombat utilise trop d'appels de fonctions, je simplifie même les codes. J'utilise PHP 5.4 pour exécuter les deux solutions 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 aussi mauvaise que beaucoup de gens le pensent. Je suggère donc la solution chic si votre express régulier n'est pas compliqué.

Hunter Wu
la source
Votre premier extrait est une comparaison injuste car il n'utilise pas une implémentation correcte. Vous ne vérifiez pas $pospour false, donc quand l'aiguille n'existe pas dans la botte de foin, il endommagera la sortie.
mickmackusa
Merci @mickmackusa, vous avez raison. Mais ce n'est pas le but. J'ai dit que ce code est simplifié juste pour comparer l'efficacité des implémentations.
Hunter Wu
C'est exactement ce que je veux dire. Vous ne devez jamais faire de comparaisons de référence qui n'effectuent pas exactement le même processus. Comparer des pommes à des demi-oranges n'est pas utile. La mise en œuvre complète de l'approche non regex complète rendra la différence de vitesse plus profonde.
mickmackusa
Eh bien, merci encore. Mais ce que je veux, c'est trouver la meilleure mise en œuvre, pas faire plus de différence en profondeur.
Hunter Wu
2

Pour développer la réponse de zombat (que je pense être la meilleure réponse), j'ai créé une version récursive de sa fonction qui prend un $limitparamètre pour spécifier le nombre d'occurrences que vous souhaitez 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;
        }
    }
}
jtate
la source
Remarque, il n'y a pas de contrôle de la qualité sur $start_pos, donc si elle est au - delà de la longueur de chaîne, cette fonction génère: Warning: strpos(): Offset not contained in string.... Cette fonction ne parvient pas à effectuer un remplacement lorsqu'elle $start_posest au-delà de la longueur. Preuve d'échec: 3v4l.org/qGuVIR ... Votre fonction peut combiner les return $haystackconditions et éviter de déclarer des variables à usage unique comme ceci: 3v4l.org/Kdmqp Cependant, comme je l'ai dit dans les commentaires ailleurs sur cette page, je préfère utiliser un appel très net, direct et non récursif preg_replace().
mickmackusa
oui pour que vous puissiez ajouter cette elsedéclaration de ligne$start_pos > strlen($haystack) ? $start_pos = strlen($haystack) : '';
Manojkiran.A
2

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 personnage

$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
la source
Le premier extrait substr_replace () échoue lorsque la chaîne de recherche n'est pas au décalage 0 de la chaîne d'entrée. Preuve d'échec: 3v4l.org/oIbRv Et les deux substr_replace()techniques endommagent la chaîne d'entrée lorsque la valeur de recherche n'est pas présente. Preuve d'échec: 3v4l.org/HmEml (Et cette dernière technique avec tous les revappels est sérieusement alambiquée / dure pour les yeux.)
mickmackusa
2

En complément de 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á"

Leandro Castro
la source
3
Sauf s'il contient des caractères multi-octets ... et que votre technique échoue. Quel dommage que vous ayez proposé un exemple de chaîne d'entrée contenant á. Démonstration d'échec
mickmackusa
Vous pouvez vérifier si votre stringchaîne est multi-octets en utilisantmb_strlen($subject) != strlen($subject)
RousseauAlexandre
Ce message ne tente pas de répondre à la question posée.
mickmackusa
2
$str = "/property/details&id=202&test=123#tab-6p";
$position = strpos($str,"&");
echo substr_replace($str,"?",$position,1);

En utilisant substr_replace, nous pouvons remplacer l'occurrence du premier caractère uniquement dans la chaîne. comme & est répété plusieurs fois mais seulement en première position nous devons remplacer & avec?

pooja goyal
la source
1

Cette fonction est fortement inspirée de la réponse de @renocor. Il rend la fonction multi-octets sûre.

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;
}
blablabla
la source
0

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 sur php.net

Usage:

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

Production:

ThiZ iz an examplz

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

happyhardik
la source
Si c'est la sortie, quel est le point? Ne devrait-il pas seulement remplacer le premier "z" minuscule par un "Z" majuscule? Au lieu de les remplacer tous? Je pensais que c'était ce dont nous parlions ici ...
Swivel
Mon mauvais, il ne remplacera que la première occurrence. Édité.
happyhardik
Ce même conseil avait déjà été proposé par Bas près de 3 ans plus tôt (et sans trop appeler strpos()). Voté parce qu'il n'ajoute aucune nouvelle valeur à la page.
mickmackusa
0

Si votre chaîne ne contient aucun caractère multi-octets 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;
}
RousseauAlexandre
la source
0

Pour la solution de boucle

<?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;
}
Noir
la source
-1

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

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

Ces exemples utilisent tous 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) );
    }
}
Aran
la source
Votre message n'ajoute pas de valeur à cette page déjà saturée. Votre solution d'expression régulière échoue sur de nombreux cas de franges, car vous avez utilisé l'outil incorrect pour échapper les caractères dans la chaîne d'aiguille. Preuve d'échec: 3v4l.org/dTdYK La réponse largement votée et acceptée de 2009 montre déjà la bonne exécution de cette technique. Votre deuxième méthode ne répond pas à la question posée et a déjà été fournie par 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")

Il y a un espace supplémentaire, mais cela n'avait pas d'importance comme c'était le cas pour le script d'arrière-plan dans mon cas.

loqan
la source
Cette technique a été fournie par toomuchphp en 2009 ! J'ai voté contre parce que votre message n'ajoute aucune nouvelle valeur aux chercheurs. Veuillez vous assurer, avant de publier une réponse, que votre solution est unique sur la page et ajoute de la valeur à la page.
mickmackusa
-3

c'est ma première réponse ici, 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 réussi, 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 obtient le nombre de remplacements effectués. Ceci n'est pas cohérent avec preg_replace , qui a un 4ème paramètre $limitet un 5ème paramètre &$count.

Don_Diego
la source
Le quatrième argument sera défini par str_replace () au nombre de remplacements effectués. C'est pourquoi vous obtenez une erreur lorsque vous lui passez un entier et non une variable.
arminrosu
-6

Il est facile de trouver une solution pour remplacer uniquement le premier ou le premier couple d'instances (en donnant la valeur de comptage). Il n'y a pas beaucoup de solutions pour remplacer le dernier ou le dernier couple d'instances.

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.

Hassan
la source
4
Pourquoi répondre à une question avec une suggestion alors qu'une réponse a été acceptée deux ans auparavant?!
mbinette
De plus, -3 ne fonctionnera pas comme paramètre, car le 4ème paramètre est une sortie et non un paramètre d'entrée. Il serait préférable de tester ce que vous proposez, au lieu de publier du code qui se bloque.
Ghostwriter78
Ouais, c'est faux, cependant, pourquoi est-ce que j'obtiens un crash d'écran vide, quand je l'essaye? J'ai fait le error_reporting habituel (E_ALL); ini_set ("display_errors", 1); Erreur d'écran encore vierge.
Doug Cassidy