Selectively replace multiple instances of a character within a string: Difference between revisions
(→Python: Added a further variant ( a map-accumulation )) |
m (→Python) |
||
Line 518:
Or, as a map-accumulation:
<lang python>
from functools import reduce
|
Revision as of 16:12, 28 July 2022
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".
- Metrics
- Counting
- Word frequency
- Letter frequency
- Jewels and stones
- I before E except after C
- Bioinformatics/base count
- Count occurrences of a substring
- Count how many vowels and consonants occur in a string
- Remove/replace
- XXXX redacted
- Conjugate a Latin verb
- Remove vowels from a string
- String interpolation (included)
- Strip block comments
- Strip comments from a string
- Strip a set of characters from a string
- Strip whitespace from a string -- top and tail
- Strip control codes and extended characters from a string
- Anagrams/Derangements/shuffling
- Word wheel
- ABC problem
- Sattolo cycle
- Knuth shuffle
- Ordered words
- Superpermutation minimisation
- Textonyms (using a phone text pad)
- Anagrams
- Anagrams/Deranged anagrams
- Permutations/Derangements
- Find/Search/Determine
- ABC words
- Odd words
- Word ladder
- Semordnilap
- Word search
- Wordiff (game)
- String matching
- Tea cup rim text
- Alternade words
- Changeable words
- State name puzzle
- String comparison
- Unique characters
- Unique characters in each string
- Extract file extension
- Levenshtein distance
- Palindrome detection
- Common list elements
- Longest common suffix
- Longest common prefix
- Compare a list of strings
- Longest common substring
- Find common directory path
- Words from neighbour ones
- Change e letters to i in words
- Non-continuous subsequences
- Longest common subsequence
- Longest palindromic substrings
- Longest increasing subsequence
- Words containing "the" substring
- Sum of the digits of n is substring of n
- Determine if a string is numeric
- Determine if a string is collapsible
- Determine if a string is squeezable
- Determine if a string has all unique characters
- Determine if a string has all the same characters
- Longest substrings without repeating characters
- Find words which contains all the vowels
- Find words which contains most consonants
- Find words which contains more than 3 vowels
- Find words which first and last three letters are equals
- Find words which odd letters are consonants and even letters are vowels or vice_versa
- Formatting
- Substring
- Rep-string
- Word wrap
- String case
- Align columns
- Literals/String
- Repeat a string
- Brace expansion
- Brace expansion using ranges
- Reverse a string
- Phrase reversals
- Comma quibbling
- Special characters
- String concatenation
- Substring/Top and tail
- Commatizing numbers
- Reverse words in a string
- Suffixation of decimal numbers
- Long literals, with continuations
- Numerical and alphabetical suffixes
- Abbreviations, easy
- Abbreviations, simple
- Abbreviations, automatic
- Song lyrics/poems/Mad Libs/phrases
- Mad Libs
- Magic 8-ball
- 99 Bottles of Beer
- The Name Game (a song)
- The Old lady swallowed a fly
- The Twelve Days of Christmas
- Tokenize
- Text between
- Tokenize a string
- Word break problem
- Tokenize a string with escaping
- Split a character string based on change of character
- Sequences
C++
<lang cpp>#include <map>
- include <iostream>
- 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
<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
<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 = let i = fromMaybe 0 (M.lookup c a) in ( M.insert c (succ i) a, otherChar i c (fromMaybe [] (M.lookup c ruleMap)) )
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
<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
- nthInstanceReplaced :: Dict Char [(None | Char)] ->
- String -> String
def nthInstanceReplaced(ruleMap):
def go(a, c): 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 )
return lambda s: .join( mapAccumL(go)({})(s)[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" ) )
- ----------------------- GENERIC ------------------------
- mapAccumL :: (acc -> x -> (acc, y)) ->
- 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, []) )
- 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]))
- 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
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