0

I would like to replace complete word contained in an array with its equivalent

$arrWords = array(
   "cat" => "dog",
   "dog" => "mouse",
   "phone" => "book"
);

$txttest = "My catalogue has a cat and a phone";

cat should became dog and not mouse, and has to be case insensitive

this text

My catalogue has a Cat and a phone

should become

My catalogue has a dog and a book

with this

$result = str_replace(array_keys($arrWords), array_values($arrWords), $txttest);

I get

My mousealogue has a mouse and a book

and is not what I expect

EDIT: my question has been marked as duplicate of this PHP string replace match whole word

in that question it was necessary to replace only one word with another, instead i need to replace multiple words, searching for the first occurrence of the array(key) and replace with array(value)

al404IT
  • 1,380
  • 3
  • 21
  • 54
  • If you had read past the first answer on first the duplicate: you can also use an array. And yes, there are better duplicates, if you were to google it. – mario Nov 23 '17 at 17:19
  • I tried with last response but I get as results: My catalogue has a mouse and a book. cat become mouse. – al404IT Nov 23 '17 at 17:27
  • New answer on the duplicate, albeit you might need `/i` flag and/or `strtolower` in the callback. (Couldn't be bothered to look for another previous question...) – mario Nov 23 '17 at 17:49
  • @al404IT the answer that you have received will fail you. It is not green tick worthy and will teach future readers an inaccurate/unstable method. I can think of two stable methods for your task but cannot post them until this is reopened. – mickmackusa Nov 23 '17 at 20:27
  • Those duplicates don't consider the possibility of erroneously replacing replaced substrings. – mickmackusa Nov 23 '17 at 20:35
  • @mickmackusa I've exchanged the dupe links for better ones. Albeit I think the case-preservation is taking it too far (given *this* questions summary/title.) – mario Nov 24 '17 at 03:26
  • That's a good one @mario I hope this one is deleted, there's nothing worth saving. – mickmackusa Nov 24 '17 at 11:07
  • I reopened the question – al404IT Nov 24 '17 at 13:25
  • @al404IT please donot use my previous solution as it wont work with special characters in the string and is a poor solution , i updated my answer and this is far better approach than the previous one, hope it helps and so select the answer as the correct one if it works for you. – Muhammad Omer Aslam Nov 24 '17 at 23:46

1 Answers1

2

this should work for you

$arrWords = array(
    "cat" => "dog",
    "dog" => "mouse",
    "phone" => "book"
 );

 $txttest = "My catalogue has a cat and a phone";
 $stringArr =   explode(' ',$txttest);

foreach($stringArr as $k=>$v){
    if(array_key_exists($v,$arrWords)){
        $stringArr[$k]=$arrWords[$v];
    }
}
echo implode(' ',$stringArr);

EDIT

As per the given situation to replace the string

My catalog has a cat and a phone

using the following array to match the keys and replace with the respective values

$arrWords = array(
        "cat" => "dog",
        "dog" => "mouse",
        "phone" => "book"
     );

As there are specific values that are matching the keys there is a strong possibility that our replacement string could be substituting each other if we use preg_replace within a for loop for above array,

possible suspects ("cat" => "dog","dog" => "mouse")

A better alternative, in this case, is preg_replace_callback with word boundary \b for such requirements as there could be special characters too inside the string other than words like \t i.e TAB or any other.

so ideally the original solution provided above should not (in fact never) be used and isn't really a solution as it would fail when there are special characters.

What actually should be done is to first create a regular expression which

should use word boundary with a capturing group inside containing all the words to be matched as alternatives(using OR operator).

So if I convert my above statement into a regular expression keeping in mind the given search&replace array $arrWords it would be like.

/\b(cat|dog|phone)\b/gi

the g modifier, in the end, is the global pattern flag which assures all matches (don't return after the first match) and i for the case-insensitive match. We can omit g modifier when using in preg_replace_callback() function as the 4th parameter of this function will take care of it.

So if I add my regex to any online regex utility and provide a string like

My catalog has a cat cat cat cat a\tdog and a phone

(notice that using online utility you should remove the \t with actual TAB when writing the string to the regex editors online to be detected as a special character or \t)

it will highlight all the matches found like below

My catalog has a cat cat cat cat a dog and a phone

(the spaces after a are because of the TAB) Now they need to be replaced and after replacing the above statement should be converted to

My catalog has a dog dog dog dog a mouse and a book

so now comes the preg_replace_callback() and the callback function that will do the trick of replacing and also taking care of not swapping/substituting replacement keys.

$r = preg_replace_callback(
    "/\b(".implode("|",array_keys($arrWords)).")\b/i",
    function($matchingPharse) use ($arrWords) {
        return strtolower($arrWords[strtolower($matchingPharse[0])]);
    },
    $txttest
);

What we are doing here is that in the first parameter we are making the same regex above by using array_keys() to extract the words to be matched in the string from the given array ,as preg_replace_callback() finds the matching words in the string, it passes them to the callable anonymous function in the second parameter and returns the replacement words for those matches in the replacement array $arrWords only one thing new is use keyword, as we need to access the $arrWords array and return the respective values from the matched keys and the callback function has only one parameter matches so the easiest way to pass more than one parameters to the callback function is with the 'use' keyword. I got the idea about it from here under section User Contributed Notes.

So the above will output

My catalog has a dog dog dog dog a mouse and a book

and if we need to check that it preserves the case-insensitivity we need to add the i feed

INPUT

The cat's mouse(named Mouse), cat (named Cat), and dog (named Giraffe) are in this catalog.

OUTPUT

The dog's mouse(named Mouse), dog (named dog), and mouse (named Giraffe) are in this catalog.

Another case provided by senior member was

$arrWords=["cat"=>"dog","dog"=>"mouse","mouse"=>"Mickey"];

INPUT

The cat's mouse(named Mouse), cat (named Cat), and dog (named Giraffe) are in this catalog.

OUTPUT

The dog's mickey(named mickey), dog (named dog), and mouse (named Giraffe) are in this catalog.

I had a discussion with one of the senior members of the community and he guided me about the severity of misinformation in the original answer that could guide others in the wrong direction which neither SO, me or any other member of this community intend to do, so I tried to improve the answer and would request the OP owner to follow this approach if he intended to use my previous answer. And I apologize for providing a poor solution at the start.

Muhammad Omer Aslam
  • 22,976
  • 9
  • 42
  • 68
  • 1
    Yikes, `explode`. This is gonna fall flat as soon as the text does not just contain spaces, `"My catalogue has a cat, and a\tphone"` – mario Nov 23 '17 at 17:17
  • yeah in this case it would, apparently the requirements didnt listed any specific special character, and was also marked duplicate already when i posted the answer, if you say i would update it. – Muhammad Omer Aslam Nov 23 '17 at 17:19
  • @mario i just updated it hope this is what you were expecting – Muhammad Omer Aslam Nov 23 '17 at 17:29
  • 2
    @mickmackusa hmm, you are right about it I am willing to delete this answer, as I do not and never intended to misguide anyone, just trying to help out and learn too, but hey read your last comment again you are stepping off the `**BE NICE POLICY**` you can correct others without being rude, and you can keep your answer to yourself if you like. – Muhammad Omer Aslam Nov 23 '17 at 20:33
  • I've deleted my comments. Perhaps I am a bit disgruntled about some of the injustices on SO, sorry about that. – mickmackusa Nov 23 '17 at 20:46
  • @mickmackusa its ok brother, i am sorry too if i was rude anywhere,i am still looking to improve the answer and looking into using word boundaries in regex with `preg_replace` , there could be one thing i can do is to make this answer wiki so that it could be improved, what you say , be the first one to update it ? – Muhammad Omer Aslam Nov 23 '17 at 20:50
  • @mickmackusa i just updated the answer again, please can you review and correct if i am wrong, would appreciate. thanks – Muhammad Omer Aslam Nov 23 '17 at 22:24
  • 1
    A solid solution will not replace replaced text, will include wordboundaries, and preserve uppercase/lowercase strings after replacing. Test with these variables: `$txttest="The cat's mouse(named Mouse), cat (named Cat), and dog (named Giraffe) are in this catalogue."; $arrWords=["cat"=>"dog","dog"=>"mouse","mouse"=>"Mickey"];` Be very careful. The truth is, there is probably a duplicate already on SO for this task -- SO has been around for quite a while now. I don't have enough computer-time at the moment to do more. – mickmackusa Nov 23 '17 at 22:31
  • @mickmackusa , i updated the answer again, hope i won't disappoint you this time, I appreciate the all help and guidance by your side.cheers – Muhammad Omer Aslam Nov 24 '17 at 23:44
  • 1
    Yes, this is nearly a duplicate/adequate method that [Wiktor](https://stackoverflow.com/a/32770462/2943403) provided 2 years ago (recently added to the closure link list by @mario ). Your method still does not provide the case-insensitive matching that the OP's question requires. While I still believe this page should be deleted, at least you are taking the opportunity to improve your skills -- so it is not a waste of time for you. The OP should have researched before posting the question. – mickmackusa Nov 25 '17 at 04:14
  • 1
    ahh I just interpreted it opposite I made it case-sensitive,saw that post you mentioned, i need to add `strtolower` to achieve it along with the `i`, thanks for your input @mickmackusa – Muhammad Omer Aslam Nov 25 '17 at 04:20