Selectively replace multiple instances of a character within a string

From Rosetta Code
Revision as of 17:00, 28 July 2022 by Hout (talk | contribs) (→‎Python)
Task
Selectively replace multiple instances of a character within a string
You are encouraged to solve this task according to the task description, using any language you may know.
Task

This is admittedly a trivial task but I thought it would be interesting to see how succinctly (or otherwise) different languages can handle it.

Given the string: "abracadabra", replace programatically:

  • the first 'a' with 'A'
  • the second 'a' with 'B'
  • the fourth 'a' with 'C'
  • the fifth 'a' with 'D'
  • the first 'b' with 'E'
  • the second 'r' with 'F'


Note that there is no replacement for the third 'a', second 'b' or first 'r'.

The answer should, of course, be : "AErBcadCbFD".

Other tasks related to string operations:
Metrics
Counting
Remove/replace
Anagrams/Derangements/shuffling
Find/Search/Determine
Formatting
Song lyrics/poems/Mad Libs/phrases
Tokenize
Sequences


C++

<lang cpp>#include <map>

  1. include <iostream>
  2. include <string>

int main() {

 std::map<char, std::string> rep = 
   {{'a', "DCaBA"}, // replacement string is reversed
    {'b', "E"},
    {'r', "Fr"}};
 std::string magic = "abracadabra";
 for(auto it = magic.begin(); it != magic.end(); ++it)
 {
   if(auto f = rep.find(*it); f != rep.end() && !f->second.empty())
   {
     *it = f->second.back();
     f->second.pop_back();
   }
 }
 std::cout << magic << "\n";

}</lang>

Output:
AErBcadCbFD

Factor

Works with: Factor version 0.99 2022-04-03

<lang factor>USING: assocs formatting grouping kernel random sequences ;

CONSTANT: instrs {

   CHAR: a 1 CHAR: A
   CHAR: a 2 CHAR: B
   CHAR: a 4 CHAR: C
   CHAR: a 5 CHAR: D
   CHAR: b 1 CHAR: E
   CHAR: r 2 CHAR: F

}

counts ( seq -- assoc )
   H{ } clone swap [ 2dup swap inc-at dupd of ] zip-with nip ;
replace-nths ( seq instrs -- seq' )
   [ counts ] dip 3 group [ f suffix 2 group ] map substitute keys ;
test ( str -- )
   dup instrs replace-nths "" like "%s -> %s\n" printf ;


"abracadabra" test "abracadabra" randomize test</lang>

Output:
abracadabra -> AErBcadCbFD
caaarrbabad -> cABarFECbDd

FreeBASIC

<lang freebasic>Function replaceChar(Byref S As String) As String

   Dim As String A = "ABaCD", B = "Eb", R = "rF"
   Dim As Byte pA = 1, pB = 1, pR = 1
   For i As Byte = 0 To Len(S)
       Select Case Mid(S,i,1)
       Case "a"
           Mid(S,i,1) = Mid(A,pA,1)
           pA += 1
       Case "b"
           Mid(S,i,1) = Mid(B,pB,1)
           pB += 1
       Case "r"
           Mid(S,i,1) = Mid(R,pR,1)
           pR += 1
       End Select
   Next i
   Return S

End Function

Dim As String S S = "abracadabra" Print S; " -> "; replaceChar(S) S = "caarabadrab" Print S; " -> "; replaceChar(S) Sleep</lang>

Output:
abracadabra -> AErBcadCbFD
caaarrbabad -> cABarFECbDd

Go

Translation of: Wren

<lang go>package main

import (

   "fmt"
   "strings"

)

func main() {

   s := "abracadabra"
   ss := []byte(s)
   var ixs []int
   for ix, c := range s {
       if c == 'a' {
           ixs = append(ixs, ix)
       }
   }
   repl := "ABaCD"
   for i := 0; i < 5; i++ {
       ss[ixs[i]] = repl[i]
   }
   s = string(ss)
   s = strings.Replace(s, "b", "E", 1)
   s = strings.Replace(s, "r", "F", 2)
   s = strings.Replace(s, "F", "r", 1)
   fmt.Println(s)

}</lang>

Output:
AErBcadCbFD

Haskell

As a map-accumulation: <lang haskell>import Data.List (mapAccumL) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe)


POSITIONAL CHARACTER-REPLACEMENT RULES --------

nthCharsReplaced :: M.Map Char [Maybe Char] -> String -> String nthCharsReplaced ruleMap = snd . mapAccumL go M.empty

 where
   go a c =
     if M.member c ruleMap
       then
         let i = fromMaybe 0 (M.lookup c a)
          in ( M.insert c (succ i) a,
               otherChar i c (fromMaybe [] (M.lookup c ruleMap))
             )
       else (a, c)

otherChar :: Int -> Char -> [Maybe Char] -> Char otherChar i c deltas

 | i < length deltas = fromMaybe c (deltas !! i)
 | otherwise = c

TEST -------------------------

main :: IO () main = putStrLn $ nthCharsReplaced rules "abracadabra"

rules :: M.Map Char [Maybe Char] rules =

 M.fromList
   [ ('a', (Just <$> "AB") <> [Nothing] <> (Just <$> "CD")),
     ('b', [Just 'E']),
     ('r', [Nothing, Just 'F'])
   ]</lang>
Output:
AErBcadCbFD

J

<lang J> upd=: {{ x (n{I.y=m)} y }}

  'ABCD' 'a' upd 0 1 3 4 'E' 'b' upd 0 'F' 'r' upd 1 'abracadabra'

AErBcadCbFD</lang>

upd here takes four arguments -- two on the left (replacement characters, original character) and two on the right(index values for which instances to replace, and the original string).

However, here's a more compact approach (the first item in the left argument is the target, and the rest of the left argument explicitly provides values for every instance of that item in the right argument):

<lang J> chg=: {{ (}.x) (I.y={.x)} y}}

  'aABaCD' chg 'bEb' chg 'rrF' chg 'abracadabra'

AErBcadCbFD</lang>

JavaScript

<lang javascript>function findNth(s, c, n) {

 if (n === 1) return s.indexOf(c);
 return s.indexOf(c, findNth(s, c, n - 1) + 1);

}

function selectiveReplace(s, ops) {

 const chars = Array.from(s);
 for ([n, old, rep] of ops) {
   chars[findNth(s, old, n)] = rep;
 }
 return chars.join("");

}

console.log(

 selectiveReplace("abracadabra", [
   [1, "a", "A"], // the first 'a' with 'A'
   [2, "a", "B"], // the second 'a' with 'B'
   [4, "a", "C"], // the fourth 'a' with 'C'
   [5, "a", "D"], // the fifth 'a' with 'D'
   [1, "b", "E"], // the first 'b' with 'E'
   [2, "r", "F"], // the second 'r' with 'F'
 ])

);</lang>

Output:
AErBcadCbFD


Or, expressed as a map-accumulation: <lang javascript>(() => {

   "use strict";
   const main = () => {
       const
           s = "abracadabra",
           subs = {
               a: "AB CD",
               b: "E",
               r: " F"
           };
       return mapAccumL(a => c => {
           const
               i = a[c] || 0,
               ds = subs[c];
           return [
               Object.assign(a, {[c]: 1 + i}),
               ds && ds[i] ? (
                   ds[i].trim() || c
               ) : c
           ];
       })({})([...s])[1].join("");
   };


   // --------------------- GENERIC ---------------------
   // mapAccumL :: (acc -> x -> (acc, y)) -> acc ->
   // [x] -> (acc, [y])
   const mapAccumL = f =>
   // A tuple of an accumulation and a list
   // obtained by a combined map and fold,
   // with accumulation from left to right.
       acc => xs => [...xs].reduce(
           ([a, bs], x) => second(
               v => [...bs, v]
           )(
               f(a)(x)
           ),
           [acc, []]
       );


   // second :: (a -> b) -> ((c, a) -> (c, b))
   const second = f =>
   // A function over a simple value lifted
   // to a function over a tuple.
   // f (a, b) -> (a, f(b))
       ([x, y]) => [x, f(y)];
   return main();

})();</lang>

Output:
AErBcadCbFD

Julia

<lang ruby> rep = Dict('a' => Dict(1 => 'A', 2 => 'B', 4 => 'C', 5 => 'D'), 'b' => Dict(1 => 'E'), 'r' => Dict(2 => 'F'))

function trstring(oldstring, repdict)

   seen, newchars = Dict{Char, Int}(), Char[]
   for c in oldstring
       i = get!(seen, c, 1)
       push!(newchars, haskey(repdict, c) && haskey(repdict[c], i) ? repdict[c][i] : c)
       seen[c] += 1
   end
   return String(newchars)

end

println("abracadabra -> ", trstring("abracadabra", rep))

</lang>

Output:

Same as Perl.

Lambdatalk

1) first answer

We first translate the replacements program into a sequence of rules

the first  'a' with 'A'   -> aA1
...and so on

Then we add to the existing set of array functions a new one finding the indexes of some value in a given array. <lang Scheme> {def A.findindexes

{def A.findindexes.rec
 {lambda {:v :a :b :i}
  {if {A.empty? :a}
   then :b
   else {A.findindexes.rec :v {A.rest :a} 
                           {if {W.equal? {A.first :a} :v}
                            then {A.addlast! :i :b}
                            else :b} 
                           {+ :i 1}} }}}
{lambda {:v :a}
 {A.findindexes.rec :v :a {A.new} 0} }}

-> A.findindexes

{A.findindexes a {A.split abracadabra}} -> [0,3,5,7,10] ... and so on </lang>

Using findindexes we can translate the aA1 aB2 aC4 aD5 bE1 rF2 sequence into a new one where numbers are replaced by indexes in the given string, here abracadabra.

<lang Scheme> {def replacements.rules

 {lambda {:w :r}
  {A.new 
   {W.get 0 :r}
   {W.get 1 :r}
   {A.get {- {W.get 2 :r} 1}  // arrays begin at 0
          {A.findindexes {W.get 0 :r} {A.split :w}}}}}}

-> replacements.rules

{A.join {replacements.rules abracadabra aA1}} -> aA0 ... and so on </lang>

Finally the replacements function will apply this sequence of rules to the word.

<lang Scheme> {def replacements

{def replacements.rec
 {lambda {:word :rules}
  {if {A.empty? :rules}
   then {A.join :word}
   else {replacements.rec {A.set! {A.get 2 {A.first :rules}}
                                  {A.get 1 {A.first :rules}}
                                  :word} 
                          {A.rest :rules}} }}}
{lambda {:word :rules}
 {replacements.rec
  {A.split :word} 
  {A.map {replacements.rules :word} {A.new :rules}} }}}

-> replacements

{replacements abracadabra aA1 aB2 aC4 aD5 bE1 rF2} -> AErBcadCbFD

 (AErBcadCbFD)

{replacements caaarrbabad aA1 aB2 aC4 aD5 bE1 rF2} -> cABarFECbDd

 (cABarFECbDd)

</lang>

2) second answer using regexps

Here is a quick & dirty answer using the S.replace_once primitive.

<lang Scheme> {def multrepl_rex

{lambda {:word :rules}
 {if {A.empty? :rules}
  then :word
  else {multrepl_rex 
        {S.replace_once {W.first {A.first :rules}}
                    by {W.last {A.first :rules}}
                    in :word }
        {A.rest :rules}} }}}

-> multrepl_rex

{multrepl_rex

abracadabra
 {A.new
  aA aB a3 aC aD 3a   // save third "a" as "3" and restore it
  bE                  // first "b"
  r1 rF 1r            // save first "r" as "1" and restore it

}} -> AErBcadCbFD

 (AErBcadCbFD)

</lang>

Perl

<lang perl>use strict; use warnings; use feature 'say';

sub transmogrify {

   my($str, %sub) = @_;
   for my $l (keys %sub) {
       $str =~ s/$l/$_/ for split , $sub{$l};
       $str =~ s/_/$l/g;
   }
   $str

}

my $word = 'abracadabra'; say "$word -> " . transmogrify $word, 'a' => 'AB_CD', 'r' => '_F', 'b' => 'E';</lang>

Output:
abracadabra -> AErBcadCbFD


Phix

Couldn't really decide which I prefer so posted both.

with javascript_semantics
function replace_nth(string s, r)
    string res = s
    for i=1 to length(r) by 3 do
        res[find_all(r[i],s)[r[i+1]-'0']] = r[i+2]
    end for
    return res
end function
?replace_nth("abracadabra","a1Aa2Ba4Ca5Db1Er2F")

-- Alternative version
function replace_nths(string s, sequence r)
    for icr in r do
        {sequence idx, integer ch, string reps} = icr
        s = reinstate(s,extract(find_all(ch,s),idx),reps)
    end for
    return s
end function

constant r = {{{1,2,4,5},'a',"ABCD"},
              {{1},'b',"E"},
              {{2},'r',"F"}}
?replace_nths("abracadabra",r)
Output:
"AErBcadCbFD"
"AErBcadCbFD"

Python

Translation of: Julia

<lang python>from collections import defaultdict

rep = {'a' : {1 : 'A', 2 : 'B', 4 : 'C', 5 : 'D'}, 'b' : {1 : 'E'}, 'r' : {2 : 'F'}}

def trstring(oldstring, repdict):

   seen, newchars = defaultdict(lambda:1, {}), []
   for c in oldstring:
       i = seen[c]
       newchars.append(repdict[c][i] if c in repdict and i in repdict[c] else c)
       seen[c] += 1
   return .join(newchars)

print('abracadabra ->', trstring('abracadabra', rep)) </lang>

Alternative

<lang python>import functools

from typing import Iterable from typing import Tuple


@functools.cache def find_nth(s: str, sub: str, n: int) -> int:

   assert n >= 1
   if n == 1:
       return s.find(sub)
   return s.find(sub, find_nth(s, sub, n - 1) + 1)


def selective_replace(s: str, ops: Iterable[Tuple[int, str, str]]) -> str:

   chars = list(s)
   for n, old, new in ops:
       chars[find_nth(s, old, n)] = new
   return "".join(chars)


print(

   selective_replace(
       "abracadabra",
       [
           (1, "a", "A"),  # the first 'a' with 'A'
           (2, "a", "B"),  # the second 'a' with 'B'
           (4, "a", "C"),  # the fourth 'a' with 'C'
           (5, "a", "D"),  # the fifth 'a' with 'D'
           (1, "b", "E"),  # the first 'b' with 'E'
           (2, "r", "F"),  # the second 'r' with 'F'
       ],
   )

)</lang>

Output:
AErBcadCbFD


Or, as a map-accumulation: <lang python>Instance-specific character replacement rules

from functools import reduce


  1. nthInstanceReplaced :: Dict Char [(None | Char)] ->
  2. String -> String

def nthInstanceReplaced(ruleMap):

   def go(a, c):
       if c in ruleMap:
           i = a.get(c, 0)
           deltas = ruleMap.get(c)
           return (
               dict(a, **{c: 1 + i}),
               deltas[i] or c if i < len(deltas) else c
           )
       else:
           return a, c
   return lambda s: .join(
       mapAccumL(go)({})(s)[1]
   )


  1. ------------------------- TEST -------------------------

def main():

   Rule-set applied to a given string.
   print(
       nthInstanceReplaced({
           'a': ['A', 'B', None, 'C', 'D'],
           'b': ['E'],
           'r': [None, 'F']
       })(
           "abracadabra"
       )
   )


  1. ----------------------- GENERIC ------------------------
  1. mapAccumL :: (acc -> x -> (acc, y)) ->
  2. acc -> [x] -> (acc, [y])

def mapAccumL(f):

   A tuple of an accumulation and a map
      with accumulation from left to right.
   
   def go(a, x):
       return second(lambda v: a[1] + [v])(
           f(a[0], x)
       )
   return lambda acc: lambda xs: reduce(
       go, xs, (acc, [])
   )


  1. second :: (a -> b) -> ((c, a) -> (c, b))

def second(f):

   A simple function lifted to a function over a tuple,
      with f applied only to the second of two values.
   
   return lambda xy: (xy[0], f(xy[1]))


  1. MAIN ---

if __name__ == '__main__':

   main()</lang>
Output:
AErBcadCbFD

Raku

Set up to not particularly rely on absolute structure of the word. Demonstrate with both the original 'abracadabra' and with a random shuffled instance.

<lang perl6>sub mangle ($str is copy) {

   $str.match(:ex, 'a')».from.map: { $str.substr-rw($_, 1) = 'ABaCD'.comb[$++] };
   $str.=subst('b', 'E');
   $str.substr-rw($_, 1) = 'F' given $str.match(:ex, 'r')».from[1];
   $str

}

say $_, ' -> ', .&mangle given 'abracadabra';

say $_, ' -> ', .&mangle given 'abracadabra'.comb.pick(*).join;</lang>

Output:
abracadabra -> AErBcadCbFD
caarabadrab -> cABraECdFDb

Vlang

A similar approach to the C++ entry. <lang ruby>fn selectively_replace_chars(s string, char_map map[string]string) string {

   mut bytes := s.bytes()
   mut counts := {
       'a': 0
       'b': 0
       'r': 0
   }
   for i := s.len - 1; i >= 0; i-- {
       c := s[i].ascii_str()
       if c in ['a', 'b', 'r'] {
           bytes[i] = char_map[c][counts[c]]
           counts[c]++
       }
   }
   return bytes.bytestr()

}

fn main() {

   char_map := {
       'a': 'DCaBA'
       'b': 'bE'
       'r': 'Fr'
   }
   for old in ['abracadabra', 'caaarrbabad'] {
       new := selectively_replace_chars(old, char_map)
       println('$old -> $new')
   }

}</lang>

Output:
abracadabra -> AErBcadCbFD
caaarrbabad -> cABarFECbDd

Wren

Library: Wren-seq
Library: Wren-str
Library: Wren-regex

Not particularly succinct but, thanks to a recently added library method, better than it would have been :) <lang ecmascript>import "./seq" for Lst import "./str" for Str

var s = "abracadabra" var sl = s.toList var ixs = Lst.indicesOf(sl, "a")[2] var repl = "ABaCD" for (i in 0..4) sl[ixs[i]] = repl[i] s = sl.join() s = Str.replace(s, "b", "E", 1) s = Str.replace(s, "r", "F", 2, 1) System.print(s)</lang>

Output:
AErBcadCbFD

Alternatively, using regular expressions (embedded script) producing output as before. <lang ecmascript>import "./regex" for Regex

var s = "abracadabra" var split = Regex.compile("a").split(s) var repl = "ABaCD" var res = "" for (i in 0...split.count-1) res = res + split[i] + repl[i] s = res + split[-1] s = Regex.compile("b").replace(s, "E") s = Regex.compile("r").replaceAll(s, "F", 2, 1) System.print(s)</lang>

XPL0

<lang XPL0>string 0; proc Mangle(S); char S, A, B, R; [A:= "ABaCD"; B:= "Eb"; R:= "rF"; while S(0) do

   [case S(0) of
     ^a: [S(0):= A(0);  A:= A+1];
     ^b: [S(0):= B(0);  B:= B+1];
     ^r: [S(0):= R(0);  R:= R+1]
   other [];
   S:= S+1;
   ];

];

char S; [S:= "abracadabra"; Text(0, S); Text(0, " -> "); Mangle(S); Text(0, S); CrLf(0); S:= "caarabadrab"; Text(0, S); Text(0, " -> "); Mangle(S); Text(0, S); CrLf(0); ]</lang>

Output:
abracadabra -> AErBcadCbFD
caarabadrab -> cABraECdFDb