# What my dog really hears

My dog is called Rex. Every time I scold him, he seems not very impressed and the only times I see him react is when I pronounce his name. If I say

Rex, I told you not to do this! You're making me angry Rex!

all he hears is

Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!

The challenge : given an input string, your program must output the same string where all alphabetic characters have been turned to stars, except the characters in the appearances of the word Rex, who must be left untouched. The non-alphabetic characters are also left untouched.

Details : The challenge is case-insensitive, thus rex must be left untouched also. The word Rex can be part of another word, so for example anorexic must be rendered as ***rex**.

Update : as the initial text of this challenge did not clarify how underscores or numbers or accentuated characters should be treated, I do not make any special requirement on those characters. Thus, a solution is valid as long as characters in a-zA-Z (and also the one mentioned in the examples, ,!".) are handled correctly.

Test cases :

Input : Rex lives in Rexland.

Output : Rex ***** ** Rex****.

Input : To call Rex, just say "Rex".

Output : ** **** Rex, **** *** "Rex".

Input : My cat is getting anorexic.

Output : ** *** ** ******* ***rex**.

• Test case: Hello! Isn't this ... a nice challenge? Can you dog hear *****! ***'* *** ... * **** *********?? If so, you might consider communicating in morse from now on... – Stewie Griffin May 9 '17 at 9:16
• Do we only need to support a-zA-Z, or also 0-9 and/or äëïöüÿãõñáéíóúýàèìòùç etc. etc.? And could you add some test cases regarding these? – Kevin Cruijssen May 9 '17 at 9:20
• @KevinCruijssen Since $0-9$ or accented characters are not mentioned in the initial challenge, do not consider them as part of the challenge. – Ewan Delanoy May 9 '17 at 9:23
• Since "dogs don't get the difference between lowercase and uppercase", may the output case be different to the input? (For example: input="Rex lives in Rexland.", output="rex ***** ** rex****."; or alternatively input="My cat is getting anorexic.", output="** *** ** ******* ***Rex**.") – Jonathan Allan May 9 '17 at 16:02
• Why did this challenge use asterisks instead of "blah"s??? – hBy2Py May 11 '17 at 19:44

## Retina, 24 21 bytes

i(rex)|(\w)
$1$#2$**  Try it online! ### Explanation Skipping rexs is easiest by matching them as well, because matches can't overlap. So if we give priority to rex over other letters, those will be covered in a single match and not touched by the individual-letter matches. But how do we do different things depending on the alternative that was used for the match? Unfortunately, Retina doesn't (yet) have any conditional substitution syntax like the Boost regex flavour. But we can fake it by including both substitutions in a single replacement and making sure that only one of them is non-empty: • $1 is the first capturing group, i.e. the (rex). If we did match rex this simply writes it back (so it does nothing), but if we didn't match rex then $1 is an empty string and vanishes. • $#2$** should be read as ($#2)$*(*). $#2 is the number of times group 2 was used, i.e. the (\w). If we matched rex this is 0, but if we matched any other individual letter, this is 1. $* repeats the next character as many times as its left-hand operand. So this part inserts a single * for individual-letter matches and nothing at all for rex. • Doesn't retina have \a for [a-z] z.B.? – Leaky Nun May 9 '17 at 12:44 • @LeakyNun no. I'd have to tokenise the regex (or even implement my own flavour) to add any features to the regex flavour itself. – Martin Ender May 9 '17 at 12:45 ## **REXX 151148 141 bytes ** (Kinda seemed appropriate) i=arg(1) m=i o=translate(m,'',xrange('A','z'),'*') p=0 do forever p=pos('REX',m,p+1) if p=0 then leave o=overlay(substr(i,p,3),o,p) end say o  Try it here Notes for non-REXXers: 1. translate is a character replacement function (name comes from an assembler instruction on IBM MF). It searches string1 for the characters in string3. Every time it finds one it replaces it with the same positioned one in string2. If string2 is too short it is padded with the pad character. See here for translate function 1. overlay simply overlays the string1 on top of string2 at the specified position. See here for overlay function • ... but the dog thought he programmed it in REX*. – GuitarPicker May 9 '17 at 13:24 • How many of these upvotes are purely for the choice of language?! :D – Shaggy May 9 '17 at 15:05 • @Shaggy At least all of them – TheLethalCoder May 9 '17 at 15:39 # JavaScript (ES6), 4241 38 bytes s=>s.replace(/rex|\w/gi,m=>m[1]?m:"*")  ## Try It o.innerText=(f= s=>s.replace(/rex|\w/gi,m=>m[1]?m:"*") )(i.value="Rex, I told you not to do this! You're making me angry Rex!") oninput=_=>o.innerText=f(i.value) <input id=i><pre id=o> ## Explanation s=> :Anonymous function that takes the string as an argument via parameter s. s.replace(x,y) :Replace x in s with y. /rex|\w/gi :Case-insenstive regular expression that globally matches all occurrences of "rex" or any single letter, number or underscore. (numbers & underscores will never be included in the input.) m=> :Anonymous function that takes each match as an argument via parameter m. m[1]? :If string m has a second character, ... (Equivalent to m.charAt(1)) m :Return m, ... :"*" :Otherwise return "*".  • – Adám May 9 '17 at 10:40 • nice solution!. – Steve Bennett May 9 '17 at 10:49 # APL (Dyalog Unicode), 22 bytesSBCS 'rex' '\w'⎕R'\0' '*'⍠1  Try it online! Simple PCRE Replace. ⍠1 sets case insensitivity. Simply replaces rex with itself and all other word characters with asterisks. • \w includes the underscore character, assuming this is RegEx - I don't know APL. – Shaggy May 9 '17 at 22:10 • @Shaggy Yeah, Dyalog APL uses PCRE, but it is not certain from OP that underscores will occur. E.g. digits will not. – Adám May 9 '17 at 22:13 • Do we not have to assume they can occur, unless told otherwise? – Shaggy May 9 '17 at 22:15 • @Shaggy Normally, yes, but this seems to indicate that what isn't mentioned will not occur. OP only mentions comma, period, space, and exclamation point. – Adám May 9 '17 at 22:17 • Hmm ... I think I'll wait for confirmation that they won't occur, seeing as I've left that comment about \w on a few answers now! – Shaggy May 10 '17 at 8:41 # Perl 5, 24 bytes 23 bytes of code + -p flag. I used Martin Ender's regex from his Retina answer (which happens to be shorter in Perl, thanks to \pl), and only had to adapt the right side of the s///. s%(rex)|\pl%$1//"*"%gie


Try it online!

# Retina, 32 31 bytes

iS(rex)
%iTLl*^(?!rex).*
¶



Try it online! Explanation: Splits the string into occurrences of the word rex and everything else, but keeps the matches. Then, on lines that don't begin rex (i.e. the "everything else"), change letters to *s. Finally, join everything back together.

• This is at least the second time that I'm thinking transliteration mode could use an option to transliterate the non-matches... – Martin Ender May 9 '17 at 9:49
• @MartinEnder too late :-D – John Dvorak May 9 '17 at 13:50

## C, 99979286747372 65 bytes

f(char*s){*s&&f(s+=strnicmp("Rex",s,3)?!isalpha(*s)||(*s=42):3);}


Pelles IDE environment provides (compile with /Go) the function strnicmp. This function is identical to strncasecmp. See it work here (with the replacement function).

The output is stored in the first parameter which is an in/out parameter.

Thanks to Johan du Toit for letting me know that recursion is slightly shorter.

• There exists a C environment that provided strncmpi so you can get it in 69. I've got it on CD. – Joshua May 10 '17 at 20:52
• @Joshua Thanks. What is CD? – 2501 May 10 '17 at 20:56
• Borland C++ 4.5 – Joshua May 10 '17 at 21:14

# Ruby, 3635 32 bytes

->s{s.gsub(/(rex)|\w/i){$1||?*}}  As a test: f=->s{s.gsub(/(rex)|\w/i){$1||?*}}

tests = [
["Rex, I told you not to do this! You're making me angry Rex!", "Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!"],
["Rex lives in Rexland.", "Rex ***** ** Rex****."],
["To call Rex, just say \"Rex\".", %q(** **** Rex, **** *** "Rex".)],
["My cat is getting anorexic.", "** *** ** ******* ***rex**."]
]

tests.each do |input, output|
if f.call(input) == output
puts "Fine for #{input.inspect}"
else
puts "Problem with :\n#{input.inspect}"
puts f.call(input)
puts output
end
puts
end


It outputs:

Fine for "Rex, I told you not to do this! You're making me angry Rex!"

Fine for "Rex lives in Rexland."

Fine for "To call Rex, just say \"Rex\"."

Fine for "My cat is getting anorexic."


# PHP, 78 Bytes

<?=preg_replace("#[a-df-qs-wyz]|r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x#i","*",$argn);  Try it online! # PHP, 84 Bytes <?=preg_replace_callback("#(rex)|\pL#i",function($t){return$t[1]?$t[1]:"*";},$argn);  -1 Byte \w instead \pl in this case underscore and numbers are replaced too \pL is shorter as [a-z] or [[:alpha:]] Try it online! • You can use \w instead of \pL. – Adám May 11 '17 at 14:07 • @Adám Thank you it makes my longer approach only 1 Byte shorter and not really clearer what should make in case of underscore or number – Jörg Hülsermann May 11 '17 at 14:20 • The input will never have any underscores or numbers.. – Adám May 11 '17 at 14:23 • @Adám I have make an extra point for it and have edit the longer approach why I have found an other improvement – Jörg Hülsermann May 11 '17 at 14:50 # C (GCC on POSIX), 16711893 87 bytes i,j;f(char*s){for(i=0;j=s[i];i++)strncasecmp("Rex",s+i,3)?s[i]=isalpha(j)?42:j:(i+=2);}  Try it online! • f(char*s){for(;*s;s++)strncasecmp("Rex",s,3)?putchar(isalpha(*s)?42:*s):write(1,s-2,3,s+=2);}. What kind of sorcery is f(s)char*s;{}? I've never seen this syntax before. – Christoph May 9 '17 at 10:35 • Oh, it used to have another parameter but i forgot to remove that. – betseg May 9 '17 at 11:13 • Doesn't work correctly, because only the first call to the function works. See here: tio.run/nexus/… Solutions in function form, such as this one, must be able to be called again and produce correct results. – 2501 May 9 '17 at 18:26 • @2501 thanks, fixed. – betseg May 9 '17 at 18:49 # Python 2 or 3, 7573 70 bytes import re;f=lambda s:re.sub('(?i)(rex)|\w',lambda x:x.group(1)or'*',s)  Basically the same as my Ruby's answer. -2 bytes thanks to @Wondercricket. As a test: tests = [ ["Rex, I told you not to do this! You're making me angry Rex!", "Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!"], ["Rex lives in Rexland.", "Rex ***** ** Rex****."], ["To call Rex, just say \"Rex\".", "** **** Rex, **** *** \"Rex\"."], ["My cat is getting anorexic.", "** *** ** ******* ***rex**."] ] for test_in, test_out in tests: print(test_in) print(f(test_in)) print(f(test_in) == test_out)  • You could save 2 bytes by removing the spacing between x.group(1) or '*' – Wondercricket May 9 '17 at 15:25 • @Wondercricket: Thank you very much, it's my first golf answer in Python. – Eric Duminil May 9 '17 at 16:16 # Java 8, 187192168164159 138 bytes s->{for(int i=0;i<s.length();System.out.print(s.regionMatches(0<1,i,"rex",0,3)?s.substring(i,i+=3):s.replaceAll("\\w","*").charAt(i++)));}  -28 bytes thanks to @OlivierGrégoire. Explanation: Try it online. s->{ // Method with String parameter and no return-type for(int i=0;i<s.length(); // Loop over the characters of the input-String System.out.print // Print: s.regionMatches(1>0,i,"rex",0,3)? // If we've found "rex" (case-insensitive): s.substring(i,i+=3) // Print this REX-word (case-sensitive) : // Else: s.replaceAll("\\w","*").charAt(i++)); // Print the current character, // or '*' if it's an alpha-numeric character  • @Shaggy Should be fixed now. This was posted before it was specified in the comments that 0-9 and accented characters shouldn't be included, only a-zA-Z should. – Kevin Cruijssen May 9 '17 at 14:35 • Could you replace "[a-zA-z]" with /[a-z]/i? – Shaggy May 9 '17 at 14:38 • @Shaggy Yes/no. Java uses a slightly different regex syntax than python or C# for example. So yes, it is possible to use a case-insensitive regex, but it will be one byte longer: "[a-zA-Z]" -> "(?i)[a-z]". – Kevin Cruijssen May 9 '17 at 14:43 • I think you should use s.regionMatches(0<1,i,"rex",0,3) instead of s.toLowerCase().substring(i,i+(i>l-3?1:3)).equals("rex"). – Olivier Grégoire May 9 '17 at 14:44 • @KevinCruijssen Take the current code that you have (at 168 bytes), remove the variable l and save 4 bytes. – Olivier Grégoire May 9 '17 at 19:39 ## Python 2, 87 Bytes import re print re.sub(r'(?i)[a-df-qs-wyz]|r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x','*',input())  I guess that can be shortened? :) • You can get rid of the spaces after the commas between arguments for 2 bytes off. – Mego May 9 '17 at 11:34 # sed, 37 33 bytes 36 bytes sourcecode + 1 byte for -r flag. s:(rex)|\w:\1*:Ig s:(rex)\*:\1:Ig  Try it online! • @Shaggy Thanks for the heads up! – Maxim Mikhaylov May 11 '17 at 14:01 • You use use . instead of \* – Kritixi Lithos Aug 28 '19 at 5:39 # Gema, 25 characters /[rR][eE][xX]/=$0
<L1>=\*


Sample run:

bash-4.3$gema '/[rR][eE][xX]/=$0;<L1>=\*' <<< "Rex, I told you not to do this! You're making me angry Rex!
Rex lives in Rexland.
To call Rex, just say \"Rex\".
My cat is getting anorexic."
Rex, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!
Rex ***** ** Rex****.
** **** Rex, **** *** "Rex".
** *** ** ******* ***rex**.


Painful fact that could be \CRex=$0;<L1>=\*, but unfortunately $0 contains the template, not the match. ☹

# Retina, 5450 49 bytes

Golfed 5 bytes thanks to @MartinEnder

# Retina, 49 bytes

i([\w-[rex]]
*
(?<!r)e|e(?!x)|r(?!ex)|(?<!re)x
*


Try it online!

• @Emigna Just realised that my solution does not work for dex, it gives *e* while yours gives **x. – Kritixi Lithos May 9 '17 at 9:21
• If you group both stages by putting ( after the first i you don't need to configure the second stage. – Martin Ender May 9 '17 at 11:44
• And your first regex can be written as [a-z-[rex]]. – Martin Ender May 9 '17 at 11:46
• @MartinEnder Thanks, this is the first time I'm seeing character classes where you can exclude certain characters – Kritixi Lithos May 10 '17 at 15:49
• they exist in several flavours but I believe .NET's syntax for them is unique. – Martin Ender May 10 '17 at 17:09

## PowerShell, 60 bytes

{$args|%{$_-replace'(rex)|\p{L}','$1*'-replace'(x)\*','$1'}}


Try it online

• My fault. I've replace \w to \p{L}. – Andrei Odegov May 9 '17 at 13:22
• Interesting idea. Note that using $args as an array has consequences when things are quoted, as in one example. And if you're only using the first argument anyway, you don't need the foreach. – Joey May 10 '17 at 17:02 • @AndreiOdegov You can go back to \w. Side note: Are the braces in \p{L} really needed? – Adám May 11 '17 at 14:06 • very nice Regex, "$args"-replace'(rex)|\p{L}','$1*'-replace'(x)\*','$1' is much shorter overall, wrapping $args in quotes turns it all into a single string and saves you a lot. – colsw May 11 '17 at 15:39 • @Adám The curly braces in .NET is required. – Andrei Odegov May 11 '17 at 17:35 # QuadR, 11 10 + 1 = 11 bytes +1 byte for the i flag. rex \w & *  Try it online! Explanation: Case-insensitively replace rex and word-chars with itself and asterisks, respectively. # MATL, 24 bytes 42y3Y2mFGk'rex'Xf!3:q+((  The input is a string enclosed in single quotes. Try it online! ### Explanation Consider input 'Rex lives in Rexland.' 42 % Push 42 (ASCII for '*') % STACK: 42 y % Implicit input. Duplicate from below % STACK: 'Rex lives in Rexland.', 42, 'Rex lives in Rexland.' 3Y2 % Push string 'ABC...YZabc...yz' % STACK: 'Rex lives in Rexland.', 42, 'Rex lives in Rexland.', 'ABC...YZabc...yz' m % Ismember % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0] F % Push false % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0 Gk % Push input lower-cased % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, 'rex lives in rexland' 'rex' % Push this string % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, 'rex lives in rexland', 'rex' Xf! % Strfind and transpose: gives indices of matchings as a column vector % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, [1; 14] 3:q % Push [0 1 2] % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, [1; 14], [0 1 2] + % Addition, element-wise with broadcast % STACK: 'Rex lives in Rexland.', 42, [1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0], 0, [1 2 3; 14 15 16] ( % Assignment indexing: sets indicated entries to 0 % STACK: 'Rex lives in Rexland.', 42, [0 0 0 0 1 1 1 1 1 0 1 1 0 0 0 0 1 1 1 1 0] ( % Assignment indexing: sets indicated entries to 42 (that is, '*'). Implicit display % 'Rex ***** ** Rex****.'  # Python 2, 93 92 bytes f=lambda s:s and("rex"==s[:3].lower()and s[:3]+f(s[3:])or("*"+s)[s[0].isalpha()<1]+f(s[1:]))  Try it online! # Perl, 31 bytes s/(rex)|[a-z]/$1||"*"/ieg;print


Invoke perl with -n option. For example:

echo 'To call rex, just say "Rex".'| perl -ne 's/(rex)|[a-z]/$1||"*"/ieg;print' ** **** rex, **** *** "Rex".  • [a-z] can now be replaced with \w as the input will never contain numbers or underscores. – Shaggy May 11 '17 at 13:59 • You can use -p instead of -n and remove ;print – wastl Mar 14 '18 at 16:12 # Bash, 128 bytes r=REXrex;a=tr -c$r'",. !
' l<<<$1;for i in {r,R}{e,E}{x,X};{ a=echo${a[@]//$i/$(tr $r f-k<<<$i)};}
tr $r l<<<$a|tr f-l $r*  Try it online! I'm sticking to tr on my previous answer, non-functional bash array string replace and no preg replace! Less golfed:  a=echo$1 |tr -c 'REXrex.,\"! ' 'z';        -> a replaces with z chars in input not matching REXrex or punctuation
for i in {r,R}{e,E}{x,X}; {                   -> iterates over rex .. rEx .. REX
j=$(tr 'REXrex' 'ABCabc' <<<$i)}            -> holds a for r, A for R, ando so on
a=echo ${a[@]//$i/$j; -> replace each combination of rex .. rEx .. REX with abc ... aBc.. ABC } tr 'REXrex' 'z' <<<$a |tr 'ABCabcz' 'REXrex*' -> replaces each remainig r,e,x,R,E,X with z and finally each ABC with REX and z with *


Had to use z instead of * because of expansion

• looks its not the right tool :P – marcosm May 9 '17 at 15:07
• You can save a couple of characters by not quoting tr's parameters when they contain nothing expandable. – manatwork May 9 '17 at 15:46
• In regards to your other bash answer: Feel free to fix, and then flag for mod attention requesting undeletion. – Rɪᴋᴇʀ May 9 '17 at 17:53
• Looking again, there are quite many rexes for a r=REXrex variable to worth. – manatwork May 10 '17 at 11:20
• If you keep $r out of quoted string, you not need to change the surrounding ' to ", so no need to escape the literal ". Of course, then you write a literal newline by breaking the line in that place instead of \n: $r'",. !␤'. – manatwork May 10 '17 at 12:14

# Java 7, 969897 96 bytes

+2 bytes for missing e's preceded by r or followed by x but not both

-1 byte for changing [a-z&&[^rex]] to (?![rex])\\w

String a(String s){return s.replaceAll("(?i)r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x|(?![rex])\\w","*");}


Try it online!

A regex version for replacing using Java

Replaces everything in this regex with a * (note in Java \w has to be escaped as \\w)

(?i)r(?!ex)|(?<!r)e|e(?!x)|(?<!re)x|(?![rex])\w

(?i)                                                   // Case Insensitive
r(?!ex)                                            // Any r not followed by ex
|(?<!r)e                                    // Or any e not preceded by r
|e(?!x)                             // Or any e not followed by x
|(?<!re)x                    // Or any x not preceded by re
|(?![rex])\w        // Or any other word character


## C#, 93 90 bytes

s=>System.Text.RegularExpressions.Regex.Replace(s,"(?i)rex|\w",m=>m.Length>1?m.Value:"*");


Believe this is the first time I've used a regex in a C# answer here because of the long namespace System.Text.RegularExpressions.

Didn't realise it when I wrote my answer but this seems to be the C# version of @Shaggy's JavaScript answer.

• Thanks for the mention, even though you came up with your answer independently of mine. – Shaggy May 9 '17 at 17:26
• @Shaggy Ah thanks didn't know it had been updated – TheLethalCoder May 11 '17 at 20:37

# CJam, 39 bytes

q{_3<_el"rex"=3{elc_'{,97>&'*@?1}?\o>}h


Try it online!

How it works

q           e# Read the input.
{           e# Do:
_3<        e#  Copy the string and get its first three characters.
_el"rex"=  e#  Check case-insensitively if they equal "rex".
3         e#   If they do, push 3.
{         e#   If they don't:
elc_     e#    Take the first character of the three, and make it lowercase.
'{,97>&  e#    Take its set intersection with the lowercase alphabet. Returns a non-empty
e#      string (truthy) if it's a letter or an empty string (falsy) if not.
'*@?     e#    Push a * if the char is a letter, or itself if it's not.
1        e#    Push 1.
}?        e#  (end if)
\o         e#  Print the second-from-the-top stack item.
>          e#  Slice the string after the first 3 or 1 characters, depending on previous outcome.
}h          e# Repeat the above until the string is empty.


# VimScript, 34 bytes

s/\v(rex|r@<=ex|(re)@<=x)@!\w/*/gi


And here's an interesting substitution that almost works:

s/\vr(ex)@!|<e|<x|[rex]@!\w/*/gi


Imagine running this repeatedly on the string Rex, dex, I told you not to do this! You're making me angry Rex! After the first line, the string is Rex, *ex, * **** *** *** ** ** ****! ***'*e ****** *e ***** Rex! The second pass will result in Rex, **x, * **** *** *** ** ** ****! ***'** ****** ** ***** Rex!, and the third pass will finish it. Any subsequent passes will not change the string. However, it could take more than 3 substitutions to get there, for example on string xxxxxxxx. So, if there were an easy way to run the above substitution until it stopped changing things, or as many times as the length of the input, that would be another solution. I bet it could be done in V, but it would still probably be longer than 34 bytes.

# Jelly, 23 bytes

No response in over 24 hours to my question regarding case, so I'll post this tentative 23 byter.

“rex”
Œlœṣ¢µØaW;”*yµ€j¢


See the test cases at Try it online!

### How?

“rex” - Link 1, get "rex": no arguments
“rex” - literal "rex"

Œlœṣ¢µØaW;”*yµ€j¢ - Main link: string s (or list of characters)
Œl                - convert s to lowercase
¢             - call the last link (1) as a nilad (get "rex")
œṣ              - split left (s) at sublists equal to right ("rex")
µ            - call the result t
µ€   - for each word, w, in t:
Øa          -   literal: lowercase alphabet
W         -   wrap it in a list
”*      -   literal: '*'
;        -   concatenate the wrapped alphabet with the asterisk
y     -   translate: replace all lowercase letters with asterisks.
¢ - call the last link (1) as a nilad (get "rex")
j  - join left (translated, split text) with copies of right ("rex")

• I'm not sure this is valid. It doesn't match the test cases. – Okx May 10 '17 at 19:24
• See my note right at the top, and my question to OP. – Jonathan Allan May 10 '17 at 19:25
• ( although unfortunately the comment I linked to where the OP said "dogs don't get the difference between lowercase and uppercase" has now gone) – Jonathan Allan May 10 '17 at 19:27

# CJam, 26 bytes (uppercase output) / 36 bytes (case-preserving)

qeu"REX":R/{__el-'*@?}f%R*


Try it online!

If letter case needs to be preserved (since that's still a little bit unclear), that can be accomplished with 10 extra bytes:

q_32f&:i\eu"REX":R/{__el-'*@?}f%R*.|


Try it online!

By the way, while writing this answer, I found what I would consider to be a design bug in CJam: the bitwise operators & and | are not defined between two char values, so I can't use .| to take the bitwise OR of two strings. The solution, which ended up costing me two extra bytes, is to first convert one of the strings with :i into an array of integers, which then can be ORed with the other string. (Actually it cost me three bytes, because if & worked between two chars, I could've also used Sf& instead of 32f& to save the letter case information.)

On the positive side, I did discover that {...}f% indeed works as expected for iterating over the characters in an array of strings. Nice.

Anyway, here's a (lightly) commented version of the 36-byte code:

q                                       "read input";
_32f&:i\                               "save the case bit of each input char";
eu"REX":R/                     "uppercase input and split it on 'REX'";
{                    "start code block:"
__el-'*@?           "c = (c != lowercase(c) ? '*' : c)";
}f%        "apply block to chars in each substring";
R*      "join the substrings with 'REX' again";
.|    "bitwise OR the case bits back in";


The case-saving trick works because the case of ASCII letters is solely determined by the fifth bit of the ASCII code: this bit is 0 for uppercase and 1 for lowercase letters. Thus, taking the bitwise AND of the character code with 32 = 25 extracts the case bit, and bitwise ORing this bit with the uppercased letters restores their original case.

Of course, non-alphabetic characters may have arbitrary values for the fifth bit (although, due to the way ASCII characters are organized, most punctuation characters have the fifth bit set to 1) but this doesn't matter, since those characters are left untouched by uppercasing and the letter censoring loop anyway, and ORing a character with its own fifth bit does't change it. Also, conveniently, the * character already has the fifth bit set, so it is also left unchanged by the final .|.

# Pip, 21 19 bytes

qR-(rex)|\w{b|'*}


Takes input from stdin, outputs to stdout. Try it online!

### Explanation

q                    Read a line of stdin
R                   and replace
(rex)|\w          a regex matching rex or any single alphanumeric character,
-                    case-insensitive
{    }  with this callback function:
b|'*     If the 1st capture group (rex) matched, return it, else asterisk
The result of the replacement is auto-printed


# V, 27, 24 bytes

I'm leaving both answers up, because I think they're equally interesting.

# 27 bytes

òÓãr¨ex©À!ü¼eü¼xü[rex]À!÷/*


Try it online!

Thanks to Brian McCutchon for his idea to do this in V

Hexdump:

00000000: f2d3 e372 a865 78a9 c021 fcbc 65fc bc78  ...r.ex..!..e..x
00000010: fc5b 7265 785d c021 f72f 2a              .[rex].!./*


Explanation:

ò                           " Until the output stops changing...
Óãr¨ex©À!ü¼eü¼xü[rex]À!÷/* " Run the compressed vim regex:

:s/\vr(ex)@!|<e|<x|[rex]@!\w/*/gi


# 24 bytes

Óãrex/ò&ò
HòÓ÷/*/e
jjòÍî


Try it online!

Ó           " Substitute
ã          "   (case-insensitive)
rex       "   'rex'
/      " with
&    "   'rex'
ò ò   "   Surrounded in new lines
ò           " Recursively...
Ó          "   Substitute...
÷         "     A word character
/        "   with
*       "     An asterisk
/e     "     (don't break if there are 0 matches)
jj          "   Move down 2 lines
ò         " end the recursive loop
Íî       " Remove all newlines