1

I want to replace letters in a string using the following mapping:

letters = ("a".."z").to_a.zip(("b".."z").to_a.push("a"))
[["a", "b"], ["b", "c"], ["c", "d"], ["d", "e"], ["e", "f"], ["f", "g"], ["g", "h"], ["h", "i"], ["i", "j"], ["j", "k"], ["k", "l"], ["l", "m"], ["m", "n"], ["n", "o"], ["o", "p"], ["p", "q"], ["q", "r"], ["r", "s"], ["s", "t"], ["t", "u"], ["u", "v"], ["v", "w"], ["w", "x"], ["x", "y"], ["y", "z"], ["z", "a"]]

From here I just want to replace all letters in a string so what I was trying is

str = "Somebody help I am on drugs and can't program! Just kidding."

letters.each {|letter| str.gsub!(letter[0],letter[1])}

puts str

returns

Saaaaaaa aaaa I aa aa aaaaa aaa aaa'a aaaaaaa! Jaaa aaaaaaa.

For some reason things that I want replaced are being replaced with an a and ignoring the mapping I created in the beginning. Anyone know whats going on? I got this idea from Ruby multiple string replacement but for some reason this doesn't apply to my example.

Community
  • 1
  • 1
theamateurdataanalyst
  • 2,794
  • 4
  • 38
  • 72
  • Could you add the expected outcome as well? Changing `push("a")` into `push("b")` results in `Sbbbbbbb bbbb I bb bb bbbbb bbb bbb'b bbbbbbb! Jbbb bbbbbbb.` – 030 Jul 13 '14 at 20:53

4 Answers4

7
replace_with = (('b'..'z').to_a + ['a']).join('')
str = "Somebody help I am on drugs and can't program! Just kidding."

puts str.tr('a-z', replace_with)
# "Spnfcpez ifmq I bn po esvht boe dbo'u qsphsbn! Jvtu ljeejoh."

To replace capital letters, str.downcase.tr('a-z', replace_with).

HTH

Edit: Manual replacement:

letters = Hash[('a'..'z').to_a.zip(('a'..'z').to_a.rotate)] # Thanks to @Jonathan
str = "Somebody help I am on drugs and can't program! Just kidding."
puts str.chars.inject([]) { |result, char| result << (letters[char] || char) }.join
# "Spnfcpez ifmq I bn po esvht boe dbo'u qsphsbn! Jvtu ljeejoh."
Harsh Gupta
  • 4,348
  • 2
  • 25
  • 30
  • 3
    Nice solution, using `tr` when applicable. By the way, your `replace_with` can also be written as `[*'b'..'z','a'].join`, though no real difference there. – Daniël Knippers Jul 13 '14 at 21:40
  • @DaniëlKnippers, Never thought of `[*'b'..'z','a']`. It is smaller and concise, although difficult for people who don't quite grasp the concept of `*` in Ruby. Thank you for sharing. – Harsh Gupta Jul 13 '14 at 21:44
  • 1
    Harsh, if a reader doesn't understand splat, writing it the way @Daniël suggests will require them to figure it out, so they will learn something useful [in the bargain](http://www.wordwebonline.com/en/INTHEBARGAIN). – Cary Swoveland Jul 13 '14 at 21:53
  • Hi guys, thanks for the help. I knew about string#tr, but sometimes I feel like I should be writing things more manually to gain a better grasp of programming and Ruby. – theamateurdataanalyst Jul 13 '14 at 21:59
  • 2
    Because there are so many ways to skin a cat, you can also use `replace_with = ('a'..'z').to_a.rotate.join` if you don't like the splat. – jonathan Jul 13 '14 at 22:06
  • Nice solution, I did not know about `#tr` function. – Boris Stitnicky Jul 13 '14 at 22:08
  • @user2801122, If you really want to write this by yourself, I edited my answer to add a solution you might check. – Harsh Gupta Jul 13 '14 at 22:32
  • 1
    Or just use `tr`'s range notation: `str.tr('a-z', 'b-za')` – Stefan Jul 14 '14 at 09:29
3

Here's another way, using the form of String#gsub that takes a block:

str = "Somebody help I am on drugs and can't program! Just kidding."

str.gsub(/[a-z]/) { |c| (?a.ord + (c.ord + 1 - ?a.ord) % 26).chr }
  #=> "Spnfcpez ifmq I bn po esvht boe dbo'u qsphsbn! Jvtu ljeejoh."
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
3

You could use String#tr's range notation:

str = "Somebody help I am on drugs and can't program! Just kidding."

str.tr('a-z', 'b-za')
#=> "Spnfcpez ifmq I bn po esvht boe dbo'u qsphsbn! Jvtu ljeejoh."

Or String#gsub's hash replacement:

mapping = ("a".."z").zip(("a".."z").to_a.rotate).to_h
#=> {"a"=>"b", "b"=>"c", "c"=>"d", "d"=>"e", "e"=>"f", "f"=>"g", "g"=>"h", "h"=>"i", "i"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"o", "o"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"u", "u"=>"v", "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"a"}

str.gsub(Regexp.union(mapping.keys), mapping)
#=> "Spnfcpez ifmq I bn po esvht boe dbo'u qsphsbn! Jvtu ljeejoh."
Stefan
  • 109,145
  • 14
  • 143
  • 218
2

So I ran this

letters = ("a".."z").to_a.zip(("b".."z").to_a.push("a"))[["a", "b"], ["b", "c"], ["c", "d"], ["d", "e"], ["e", "f"], ["f", "g"], ["g", "h"], ["h", "i"], ["i", "j"], ["j", "k"], ["k", "l"], ["l", "m"], ["m", "n"], ["n", "o"], ["o", "p"], ["p", "q"], ["q", "r"], ["r", "s"], ["s", "t"], ["t", "u"], ["u", "v"], ["v", "w"], ["w", "x"], ["x", "y"], ["y", "z"], ["z", "a"]]
str = "Somebody help I am on drugs and can't program! Just kidding."

letters.each do |letter|
   puts "#{letter[0]} #{letter[1]}"
   str.gsub!(letter[0], letter[1])
   puts str
end

and the output was

a b
Somebody help I bm on drugs bnd cbn't progrbm! Just kidding.
b c
Somecody help I cm on drugs cnd ccn't progrcm! Just kidding.
c d
Somedody help I dm on drugs dnd ddn't progrdm! Just kidding.
d e
Someeoey help I em on erugs ene een't progrem! Just kieeing.
e f
Somffofy hflp I fm on frugs fnf ffn't progrfm! Just kiffing.
f g
Somggogy hglp I gm on grugs gng ggn't progrgm! Just kigging.
g h
Somhhohy hhlp I hm on hruhs hnh hhn't prohrhm! Just kihhinh.
h i
Somiioiy iilp I im on iruis ini iin't proirim! Just kiiiini.
i j
Somjjojy jjlp I jm on jrujs jnj jjn't projrjm! Just kjjjjnj.
j k
Somkkoky kklp I km on kruks knk kkn't prokrkm! Just kkkkknk.
k l
Somlloly lllp I lm on lruls lnl lln't prolrlm! Just lllllnl.
l m
Sommmomy mmmp I mm on mrums mnm mmn't promrmm! Just mmmmmnm.
m n
Sonnnony nnnp I nn on nruns nnn nnn't pronrnn! Just nnnnnnn.
n o
Sooooooy ooop I oo oo oruos ooo ooo't prooroo! Just ooooooo.
o p
Sppppppy pppp I pp pp prups ppp ppp't prpprpp! Just ppppppp.
p q
Sqqqqqqy qqqq I qq qq qruqs qqq qqq't qrqqrqq! Just qqqqqqq.
q r
Srrrrrry rrrr I rr rr rrurs rrr rrr't rrrrrrr! Just rrrrrrr.
r s
Sssssssy ssss I ss ss ssuss sss sss't sssssss! Just sssssss.
s t
Stttttty tttt I tt tt ttutt ttt ttt't ttttttt! Jutt ttttttt.
t u
Suuuuuuy uuuu I uu uu uuuuu uuu uuu'u uuuuuuu! Juuu uuuuuuu.
u v
Svvvvvvy vvvv I vv vv vvvvv vvv vvv'v vvvvvvv! Jvvv vvvvvvv.
v w
Swwwwwwy wwww I ww ww wwwww www www'w wwwwwww! Jwww wwwwwww.
w x
Sxxxxxxy xxxx I xx xx xxxxx xxx xxx'x xxxxxxx! Jxxx xxxxxxx.
x y
Syyyyyyy yyyy I yy yy yyyyy yyy yyy'y yyyyyyy! Jyyy yyyyyyy.
y z
Szzzzzzz zzzz I zz zz zzzzz zzz zzz'z zzzzzzz! Jzzz zzzzzzz.
z a
Saaaaaaa aaaa I aa aa aaaaa aaa aaa'a aaaaaaa! Jaaa aaaaaaa.

So as you see, your code slowly replaces each letter so by the end every letter becomes a z and is replaced by a

A solution to this is just iterating in reverse like this

letters.reverse_each do |letter|
   str.gsub!(letter[0], letter[1])
end

Output is

Spnfcpez ifmq I bn po esvht boe dbo'u qsphsbn! Jvtu ljeejoh.
Steven
  • 271
  • 1
  • 8
  • This doesnt work because if you try str = "zzaa" it changes zz to bb which is not what I have in letters. I am guessing I am going to have to take a different route to solve this one? – theamateurdataanalyst Jul 13 '14 at 21:25
  • @user2801122 Hmm, well that means iterating won't work since it'll replace letter depending on the order of letters. A better solution could also be using the [String#tr](http://www.ruby-doc.org/core-2.1.2/String.html#tr-method) method edit: Nvm, I guess Harsh Gupta beat me to it – Steven Jul 13 '14 at 21:41