Take three inputs, a string of text, T; a string of characters to replace, F; and a string of characters to replace them with, R. For each substring of T with the same (case insensitive) characters as F, replace them with the characters in R. However, keep the same case as the original text.

If there are more characters in R than F, the extra characters should be the same case as they are in R. If there are numbers or symbols in F, then the corresponding characters in R should keep the case they have in R. F will not necessarily appear in T.

You can assume all text will be in the printable ASCII range.


"Text input", "text", "test" -> "Test input"

"tHiS Is a PiEcE oF tExT", "is", "abcde" -> "tHaBcde Abcde a PiEcE oF tExT"

"The birch canoe slid on the smooth planks", "o", " OH MY " -> "The birch can OH MY e slid  OH MY n the sm OH MY  OH MY th planks"

"The score was 10 to 5", "10", "tEn" -> "The score was tEn to 5"

"I wrote my code in Brain$#@!", "$#@!", "Friend" -> "I wrote my code in BrainFriend"

"This challenge was created by Andrew Piliser", "Andrew Piliser", "Martin Ender" -> "This challenge was created by Martin Ender"

// Has a match, but does not match case 
"John does not know", "John Doe", "Jane Doe" -> "Jane does not know"

// No match
"Glue the sheet to the dark blue background", "Glue the sheet to the dark-blue background", "foo" -> "Glue the sheet to the dark blue background"

// Only take full matches
"aaa", "aa", "b" -> "ba"

// Apply matching once across the string as a whole, do not iterate on replaced text
"aaaa", "aa", "a" -> "aa"

"TeXT input", "text", "test" -> "TeST input"

Sandbox link

  • \$\begingroup\$ Requesting an example with weird casing: "TeXT input", "text", "test" \$\endgroup\$ Aug 23, 2017 at 21:05
  • \$\begingroup\$ @EngineerToast Added example \$\endgroup\$
    – Andrew
    Aug 23, 2017 at 21:41
  • \$\begingroup\$ Unsure why I found "The birch canoe slid on the smooth planks", "o", " OH MY " so humorous, but I loved that example. \$\endgroup\$ Aug 24, 2017 at 14:57

11 Answers 11


Retina, 116 bytes


Try it online! Explanation:


This searches T and whenever there's a case-insensitive match against the lookahead to F the match is surrounded in a bunch of newlines and the lookahead to R is also inserted.


Each letter of the copy of R is adjusted in case to match that of the match, following which it is moved out of the working area so that the next letter can be processed, until either the copy of R or the match runs out of letters.


If the copy of R runs out of letters, then the remainder of the match will be preceded by 4 newlines, so delete it. Otherwise, anything left will be left-over pieces of copies of R which need to be concatenated with the non-matching parts of the input to produce the result.


APL (Dyalog), 75 73 72 bytes

Prompts for T, R, and F in that order. R must b given in Dyalog transformation format and F must be given in PCRE format.


Try it online!

 prompt for T

 yield that (separates 1 and T)

⍞⎕R()⍠1 prompt for F and Replace matches with the result of the following function:

⍞∘{…} derive a monadic function by tying the prompted-for R as left argument to:

  ≢⍺ count the number of letters in R

  ⍺⍵.Match↑¨⍨ take that many letters from each of R and the match
    is the left argument, which we tied R as.
    is a namespace in which Match contains the currently found string.

   mix those two into a two-row matrix

  d← store as d

  ()  apply the following tacit function to that:

   819⌶ lowercase (mnemonic: 819 looks like Big)

   l← store that function as as l

  d≠ Boolean where d differs (i.e. gives 0/1 for each lowercase/uppercase letter)

  () apply the following tacit function to that:

   ≠⌿ vertical XOR

   ()∧ Boolean AND with the following array:

    l⎕A lowercased Alphabet

    ⎕A, prepend uppercase Alphabet

    d∊ Boolean for each letter in d whether a member of that (i.e. whether a letter)

    ⊢⌿ last row i.e. for character of the match whether it is a letter

   1∘⌷≠ XOR with the first row, i.e. whether each character of R is uppercase

  ()l¨⍨ use that to lowercase (if 0) or uppercase (if 1) each letter of:

   ⊣⌿ the first row, i.e. R

* Byte count for Dyalog Classic using ⎕OPT instead of .


Perl 5, 108 bytes

107 bytes code + 1 for -p.

chomp(($s,$r)=<>);s|\Q$s|$&=~s!.!$_=substr$r,"@-",1;$&=~/[a-z]/i?$&eq uc$&?uc:lc:$_!egr.substr$r,"@-"+1|gie

Try it online!


Withdrawn. Dom's answer beats it by a long shot.

# Perl 5, 136 + 1 (-p) = 137 bytes


Try it online!

made a huge cut after @Dom Hastings mentioned \Q

# Perl 5, 176 + 1 (-p) = 177 bytes

sub h($){chomp@_;pop=~s/[^a-z0-9 ]/\\$&/gir}$f=h<>;@R=($r=<>)=~/./g;for$i(/$f/gi){$c=$n='';$"=$R[$c++],$n.=/[A-Z]/?uc$":/[a-z]/?lc$":$"for$i=~/./g;$i=h$i;s/$i/$n.substr$r,$c/e}

Try it online!

  • \$\begingroup\$ Passes all test cases now ;) 108: Try it online! \$\endgroup\$ Aug 24, 2017 at 19:48
  • \$\begingroup\$ You should post it. It beats mine by quite a bit. \$\endgroup\$
    – Xcali
    Aug 24, 2017 at 19:59
  • \$\begingroup\$ Fair enough! Was fun making it. I do enjoy the challenge! \$\endgroup\$ Aug 24, 2017 at 20:02

PowerShell, 190 bytes


Try it online!


    input text T,
    Find text F with case insensitive and [regex]::escape() for symbols,
    {scriptblock} for computing the replacement

The replacment scriptblock does:

$m is the matched text with case information
loop over each character in R as $y
    $z is the same index character in $m ($null if R overruns)
    $z-match'[A-Z]' checks if alphabetic, so we must to case-match
      otherwise, non-alphabetic or null, no case-match, return $y unchanged.
    if case-matching, check if z case-sensitive matches '[A-Z]' and
      use dynamic method calling from a generated string, either 
      to force the match
-join the loop output into a replacement string

Test cases:

function f {

Import-Module Pester

$Cases = @(
    @{Text = "Text input"; Find = "text"; Replace = "test"; Result = "Test input" }
    @{Text = "tHiS Is a PiEcE oF tExT"; Find = "is"; Replace = "abcde"; Result = "tHaBcde Abcde a PiEcE oF tExT" }
    @{Text = "The birch canoe slid on the smooth planks"; Find = "o"; Replace = " OH MY "; Result = "The birch can OH MY e slid  OH MY n the sm OH MY  OH MY th planks" }
    @{Text = "The score was 10 to 5"; Find = "10"; Replace = "tEn"; Result = "The score was tEn to 5" }
    @{Text = "I wrote my code in Brain$#@!"; Find = "$#@!"; Replace = "Friend"; Result = "I wrote my code in BrainFriend" }
    @{Text = "This challenge was created by Andrew Piliser"; Find = "Andrew Piliser"; Replace = "Martin Ender"; Result = "This challenge was created by Martin Ender" }
    @{Text = "John does not know"; Find = "John Doe"; Replace = "Jane Doe" ; Result ="Jane does not know" }
    @{Text = "Glue the sheet to the dark blue background"; Find = "Glue the sheet to the dark-blue background"; Replace = "foo"; Result ="Glue the sheet to the dark blue background" }
    @{Text = "aaa" ; Find = "aa"; Replace = "b"; Result ="ba" }
    @{Text = "aaaa"; Find = "aa"; Replace = "a"; Result ="aa" }
    @{Text = "TeXT input"; Find = "text"; Replace = "test"; Result ="TeST input" }

Describe "Tests" {

    It "works on /<Text>/<Find>/<Replace>/ == '<Result>'" -TestCases $Cases {
        param($Text, $Find, $Replace, $Result)
        f $Text $Find $Replace | Should -BeExactly $Result


TXR Lisp, 285 bytes

(defun f(s f r)(let*((w(copy s))(x(regex-compile ^(compound,(upcase-str f))))(m(reverse(tok-where(upcase-str s)x))))(each((n m))(set[w n]r) (for((i(from n)))((< i (min(to n)(len w))))((inc i))(cond((chr-isupper[s i])(upd[w i]chr-toupper))((chr-islower[s i])(upd[w i]chr-tolower)))))w))

Conventionally formatted original:

(defun f (s f r)
  (let* ((w (copy s))
         (x (regex-compile ^(compound ,(upcase-str f))))
         (m (reverse (tok-where (upcase-str s) x))))
    (each ((n m))
      (set [w n] r)
      (for ((i (from n))) ((< i (min (to n) (len w)))) ((inc i))
        (cond ((chr-isupper [s i]) (upd [w i] chr-toupper))
              ((chr-islower [s i]) (upd [w i] chr-tolower)))))

JavaScript, 177 bytes



  ["Text input", "text", "test"],
  ["tHiS Is a PiEcE oF tExT", "is", "abcde"],
  ["The birch canoe slid on the smooth planks", "o", " OH MY "],
  ["The score was 10 to 5", "10", "tEn"],
  ["I wrote my code in Brain$#@!", "$#@!", "Friend"],
  ["This challenge was created by Andrew Piliser", "Andrew Piliser", "Martin Ender"],
  ["John does not know", "John Doe", "Jane Doe"],
  ["Glue the sheet to the dark blue background", "Glue the sheet to the dark-blue background", "foo"],
  ["aaa", "aa", "b"],
  ["aaaa", "aa", "a"],
  ["TeXT input", "text", "test"],

    //F=>eval("for(i in R)r+=(f=F[i])&&/[A-Z]/i.test(f)?R[i][`to${f>'`'&&f<'{'?'Low':'Upp'}erCase`]():R[i];r",r='')

Less golfed:

(T,F,R) => T.replace(
    F=>[...R].map((r,i) =>
        /[A-Z]/i.test(f = F[i] || '')
            ? r[`to${
                f > '`' && f < '{'
                    ? 'Low'
                    : 'Upp'
            : r

47 bytes came from this regex escape function since the program has to handle symbols. :(


Python 2, 193 200 bytes

T+=' '
while i:
 if w(s)==w(F):T=T[:i]+`[[y,[w(y),y.upper()][x<'a']][x.isalpha()]for x,y in zip(s,R)]`[2::5]+R[l:]+T[i+l:];i+=l-1
print T

Try it online!

  • \$\begingroup\$ This (193 bytes, from TIO link) will fail to find matches at the end of the string. \$\endgroup\$
    – tehtmi
    Aug 24, 2017 at 4:23

Python 3, 183 bytes

import re
f=lambda T,F,R:j((p,j((y,(y.lower(),y.upper())[x<'a'])[x.isalpha()]for(x,y)in zip(p,R))+R[len(F):])[i%2>0]for i,p in enumerate(re.split('('+re.escape(F)+')',T,0,2)))

Try it online!

re.split + keep all even elements and replace all the odd elements by the correct transformation of the replacement string:

>>> re.split("(is)","tHiS Is a PiEcE oF tExT",0,2) # 2=re.IGNORE_CASE
['tH', 'iS', ' ', 'Is', ' a PiEcE oF tExT']

C (gcc), 210 211 207 189 bytes

Had to add one byte to fix a bug with the capitalization for the "BrainFriend" testcase

Wow was this tedious... Now to golf away some bytes


Try it online!

  • \$\begingroup\$ I'm probably missing something obvious, but why do you need the *(p=f) when you set p=c=t just afterward? I tried it with just *f and it didn't work, so it's not getting immediately overwritten. \$\endgroup\$
    – Andrew
    Aug 24, 2017 at 18:13
  • \$\begingroup\$ f is by befault an int so we can't dereference it to get a char, but p is a char* \$\endgroup\$
    – cleblanc
    Aug 24, 2017 at 18:19
  • \$\begingroup\$ Ah, that makes sense. So it's a shorter way of writing *((char*)f)? Cool! \$\endgroup\$
    – Andrew
    Aug 24, 2017 at 18:20

C# (Mono C# compiler), 241 bytes

using System.Text.RegularExpressions;
class Program {
static void Main(string[] args) {
r("Text input","text","Test");
static void r(string v,string i,string u)

Try it online!

  • 1
    \$\begingroup\$ Welcome to PPCG! You can remove quite a bit of whitespace here, and actually you need to either take the inputs as arguments or input (coding them in is forbidden) or you could actually just include the function; you don't even need the Action<string,string,string> r = part \$\endgroup\$
    – hyper-neutrino
    Nov 7, 2017 at 14:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.