Regex Game - Replace each word except specific ones by a variable number of characters

regex

Solution

For PCRE/Perl/Ruby/Java/.net

find:

(?:\G(?!^)|(?<!\S)(?!Joker(?!\S)))\S

replace:

$

demo

pattern details:

(?:
    \G (?!^) # contigous to a previous match (but not at the start of the string)
  |        # OR
    (?<!\S)  # not preceded by a non white-space
    (?!Joker(?!\S)) # not followed by the forbidden word
)
\S   # a non-whitespace character

If your words are only composed of word characters, you can simplify the pattern playing with word and non-word boundaries: `(?:\G\B|\b(?!Joker\b))\w`

Other way (PCRE/Perl): without the `\G` feature and with the backtracking control verb `(*SKIP)` (need less steps):

\s*(?:Joker(?:\s+|$))*(*SKIP)\K.

To be clear `(*SKIP)` is only useful when the string ends with the forbidden word or a whitespace. You can also replace it with `(*COMMIT)`.

demo

or:

\bJoker\b(*SKIP)(*F)|\S

and with pypi python regex module (that has a word boundary for the start and one for the end of a word):

\mJoker\M(*SKIP)(*F)|\S

A one that works with Javascript (if there's something to replace only):

find:

((?:\s+|\bJoker\b)*)\S((?:\s+Joker)*\s*$)?

replace: (backreference to group1, escaped $, backreference to group2)

$1$$$2 

demo

An other Javascript version that uses the y flag (that forces the matches to be contigous), but unfortunately this one isn't supported by Internet Explorer, Safari and mobile browsers except Firefox mobile:

var strs = ['Horse Banana Joker RoXx0r A_Long_Word Joker 1337 Joke Poker Joker', 'Joker Joker Joker'];

strs.forEach(function (s) {
    console.log(s.replace(/(?=((?:\s+|\bJoker\b)*))\1./gy, '$1$$'));
});

The `(?=(...))\1` emulates an atomic group (that forbids backtracking).

Problem

Hey there, you Regex Lovers ! I'm quite in Regex, these times and had a purely theorical problem. To put it simple, I will present it as a game. The game : Let's say you have a list of words separated by spaces. What I call a word is as they are defined by regular expressions : `[a-zA-Z_0-9]+` (There is no empty word here) Example of list : `Horse Banana Joker RoXx0r A_Long_Word Joker 1337` What I want you to do is replace each word except Joker by a number of $ equal to the number of character of the matched word. With our previous list we would obtain : `$$$$$ $$$$$$ Joker $$$$$$ $$$$$$$$$$$ Joker $$$$` In fewer words : I want a regex that matches each character that does not belong to the word "Joker" (In the string, I mean, not that compose the word Joker) While it is not easy, it's not impossible (I have my own regex for that). That's why I will set some rules. The rules : - It must be done with only 1 regex - I will not accept any regex that works only in specific languages - I will still accept most common features like Conditionals, Lookarounds, etc... even if some languages can't read them - No recursion allowed (but if you have a working recursive one, post it, just for the beauty of the regex ^^) - The regex must be optimized for performance - If your regex matches (get it ? ;) ) these rules but does not satisfy me, I will feel free to add some more rules Added rules : - None To help you out, here are some strings on which the regex must work : `Horse Banana Joker RoXx0r A_Long_Word Joker 1337 Joke Poker Joker Jokers` Must return after replacement : `$$$$$ $$$$$$ Joker $$$$$$ $$$$$$$$$$$ Joker $$$$ $$$$ $$$$$ Joker $$$$$$` `Joker Joker Joker` Must return after replacement : `Joker Joker Joker` Again, solving the problem is not the goal here, I want to see different solutions, and more importantly I want to see the best ones ! Solutions : A very elegant one by Casimir et Hippolyte : `(?:\G(?!^)|(?<!\S)(?!Joker(?:\s|$)))\S` (replace : `$`) See the post However the \G take the fun out of the problem and does not work in every language, so I can't accept it unless is is possible to create a custom delimiter that is equivalent to \G Almost accepted answer also by Casimir et Hippolyte : `((?:\s+|\bJoker\b)*)\S((?:\s+Joker)*\s*$)?` (replace : `$1$$2`) See the post Does not work when there are only Joker words in the string A similar solution by ClasG : `(\bJoker[^\w]+)\w|\w([^\w]+Joker\b)|\w` (replace : `$1$$2`) See the post Does not work when there are only Joker words in the string Another one by ClasG : `[^Joker\s]|(?<!\b)J|J(?!oker\b)|(?<!\bJ)o|o(?!ker\b)|(?<!\bJo)k|k(?!er\b)|(?<!\bJok)e|e(?!r\b)|(?<!\bJoke)r|r(?!\b)` (replace : `$`) See the post Not very efficient, though, but it's another way of seeing things ;) I came up with a similar regex after reading the comment of Rahul below : `(?(?<=\b|\bJ|\bJo|\bJok|\bJoke|\bJoker)(?!(?:Joke|oke|ke|e|)r\b)\w|\w)` (replace `$`) Regex101 It is also inefficient, but use the same lookaround list thing :) Here is my first solution : I use a trick that might be considered as cheating, but I don't because it would not alter the functions you use to replace characters. You just have to add a '$' at the end of the string before replacing charactes into it. So instead of something like : `string = replace(string, regex, '$1$2')` We would have : `string = replace(string+'$', regex, '$1$2')` So here is the regex : `(\bJoker\b)|.$|\w(?=.*(\$))` (replace : `$1$2`) Regex 101 This should work with all languages except those not supporting lookaheads (they are rather rare) Keep posting new regex if you find ones, I want to see more ways to do it ! :)

Original source