0

I'm trying to replace a string simultaneously with strtr but I came across a problem. If the letter being replaced simultaneously is next to another letter that is being replaced simultaneously it doesn't replace the letter after the first letter. What can I do to fix this? Thanks.

$text = " no bacon cats nobody ";
$text = strtr($text, array(" no " => " bacon ", " bacon " => " no ", " cats " => " dogs "));
echo $text;

Expected Results

bacon no dogs nobody

Actual Results

bacon bacon dogs nobody

P.S: I have to make sure those words aren't part of another word. That's why the white spaces are there. For example, the word "no" is part of "nobody", and if you replaced all "no" in the string, it would also replace words like "nobody";

jessica
  • 1,667
  • 1
  • 17
  • 35
  • inconsistent amount of spaces in your string `" a b c "` – CodeGodie Sep 04 '15 at 02:37
  • same problem as before: too many spaces. Why do you have those extra spaces in your array? thats whats causing your issue – CodeGodie Sep 04 '15 at 02:44
  • @CodeGodie to make sure those words aren't part of another word. For example, the word "no" is part of "nobody", and if you replaced all "no" in the string, it would also replace words like "nobody"; – jessica Sep 04 '15 at 02:45
  • gotcha. you should edit your question and place that there, that would help us in giving you a better answer – CodeGodie Sep 04 '15 at 02:48
  • @CodeGodie Okay. I updated it. Now it looks kind of complicated to look at. – jessica Sep 04 '15 at 02:54

2 Answers2

5

So what happens here?

$text = " a b c ";
$text = strtr($text, array(" a " => " b ", " b " => " a ", " c " => " d "));

First you have to know that:

  1. The longest keys will be replaced first
  2. After it got replaced it doesn't search for this key anymore

So here all keys have the same length, means it will replace the first key => value element, then the second and so on.

So first " a " => " b " will be replaced, which will change your start string:

 a b c 
↑↑↑ Match

to this:

 b b c
↑↑↑ Replacement

Remember how we said, after replacing the values it doesn't search for it anymore? Exactly! Now it searches for " b " => " a ", but only in this part of the string:

 a b c 

So it won't find it, since there is no space in front of the b to search for. That's why you don't get your expected output. And c will be found again, since it has a space in front of it.

So the problem here in your code is, that it doesn't include replaced values in the search of the next key. Even if [space]b[space] is in your string, it doesn't find it, since the first space is from the replaced value of the key [space]a[space], which won't be included in the next search.

EDIT:

As from your updated question it seems that you have done this to try to prevent words be part of other words. To solve this simply make a lookup array and use preg_replace_callback() with word boundarys to match only full words and not only a part of it, e.g.

<?php

    $text = " apple bacon cats ";
    $replacement = ["apple" => "bacon", "bacon" => "apple", "cats" => "dogs"];
    $search = array_map(function($v){
        return preg_quote($v, "/");
    }, array_keys($replacement));

    echo $text = preg_replace_callback("/\b(" . implode("|", $search) . ")\b/", function($m)use($replacement){
        return $replacement[$m[1]];
    }, $text);

?>
Rizier123
  • 58,877
  • 16
  • 101
  • 156
  • How do I fix it? Also please read my new updates, and why I included the white spaces around the words. – jessica Sep 04 '15 at 02:48
  • 1
    Looks like those 40K reputation weren't for nothing! You're smart! Thanks! – jessica Sep 04 '15 at 03:00
  • This answer assumes that the strings to be replaced consist of only word characters. It's a fair assumption here, but I think it would be nice to explicitly state the assumption, since the behavior would become erratic when the strings to be replaced start/end with non-word character, or contain regex metacharacters. – nhahtdh Sep 04 '15 at 07:36
  • @nhahtdh The answer was written when the question was in the first revision: http://stackoverflow.com/revisions/32389082/1 – Rizier123 Sep 04 '15 at 09:31
  • @Rizier123: I'm talking about the code in the edit section. It's a non-issue so far, but hopefully, no one assume that the solution can be used to replace strings such as `A*STAR` – nhahtdh Sep 04 '15 at 09:57
  • 1
    @nhahtdh Since it wasn't necessary in OP's code I didn't escaped them. Just updated my answer, just in case – Rizier123 Sep 04 '15 at 10:13
  • @Rizier123: Your previous code is fine. I actually just want a warning note/clear assumption to tell people the cases where this solution applies. – nhahtdh Sep 04 '15 at 10:22
  • @nhahtdh You mean the previous version wouldn't replace words like "I\'m" with the escape for the apostrophe? – jessica Sep 04 '15 at 17:21
  • @Rizier123 Hey. Just wondering. Does the newer version replace words like "I\'m" with the escape for the apostrophe? – jessica Sep 04 '15 at 17:22
  • 1
    @jessica Both will, but if regex metacharacters are involved, e.g. dot `.` then only the new version will work correctly. – nhahtdh Sep 04 '15 at 17:33
0

A way without strtr:

$text = 'no bacon cats nobody';
$rep = ['no'=>'bacon', 'bacon'=>'no', 'cats'=>'dogs'];

$parts = explode(' ', $text);
foreach ($parts as &$part) {
    if (isset($rep[$part]))
        $part = $rep[$part];
}

$result = implode(' ', $parts);

if you want something more flexible, replace the explode line with:

$parts = preg_split('~\b~', $parts);

and use an empty string with implode.

Casimir et Hippolyte
  • 88,009
  • 5
  • 94
  • 125