Related
I'm trying to make a method that, given a string, replaces every letter with its position in the alphabet.
If anything in the text isn't a letter, I want to ignore it and not return it.
"a" = 1, "b" = 2, etc.
Example
alphabet_position("The sunset sets at twelve o' clock.")
Should return "20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11" (as a string)
I tried this, but it didn't work:
def alphabet_position(text)
alph = ("a".."z").to_a
text = text.split(/./).map {|ch| if ch.in?(alph)
((alph.index[ch]).to_i+1).to_s
else
""
end
}.join(" ").strip
end
Thanks in advance!
First we can build the translation because Hash lookups are extremely fast:
# alternatively letter_to_number = ('a'..'z').each.with_index(1).to_h
letter_to_number = ('a'..'z').zip(1..26).to_h
#=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5, "f"=>6,
# "g"=>7, "h"=>8, "i"=>9, "j"=>10, "k"=>11, "l"=>12,
# "m"=>13, "n"=>14, "o"=>15, "p"=>16, "q"=>17, "r"=>18,
# "s"=>19, "t"=>20, "u"=>21, "v"=>22, "w"=>23, "x"=>24, "y"=>25, "z"=>26}
Then simply swap them out
# ruby >= 2.7
text.downcase.each_char.filter_map {|c| letter_to_number[c] }.join(' ')
# ruby < 2.7
text.downcase.each_char.map {|c| letter_to_number[c] }.compact.join(' ')
#=> "20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11"
def convert(str)
str.downcase.each_char.with_object('') do |c,s|
n = c.ord
if n.between?(97, 122)
s << ' ' unless s.empty?
s << (n-96).to_s
end
end
end
convert "Sunset was at twelve o'clock, somewhere, on some day"
#=> "19 21 14 19 5 20 23 1 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11 19 15 13 5 23 8 5 18 5 15 14 19 15 13 5 4 1 25"
Lady and gentleman, start your engines!
def alphabet_position(text)
text.downcase.split('').map do |letter|
index = ('a'..'z').find_index(letter)
index + 1 if index
end.compact.join(' ')
end
def smnky_1_7(str)
letter_to_number = ('a'..'z').zip(1..26).to_h
str.downcase.each_char.filter_map {|c| letter_to_number[c] }.join(' ')
end
def smnky_pre_1_7(str)
letter_to_number = ('a'..'z').zip(1..26).to_h
str.downcase.each_char.map {|c| letter_to_number[c] }.compact.join(' ')
end
LETTER_TO_NUMBER = ('a'..'z').zip(1..26).to_h
def smnky_pre_w_constant(str)
str.downcase.each_char.map {|c| LETTER_TO_NUMBER[c] }.compact.join(' ')
end
def convert_with_arr(str)
str.downcase.each_char.with_object([]) do |c,arr|
n = c.ord
arr << n - 96 if n.between?(97, 122)
end.join(' ')
end
str = "Sunset was at twelve o'clock, somewhere, on some day"
require 'benchmark'
def run(m, str)
500.times { method(m).call(str) }
end
Benchmark.bm(19) do |x|
x.report("Cary") { run(:convert, str) }
x.report("Tatiana") { run(:alphabet_position, str) }
x.report("smnky 1.7") { run(:smnky_1_7, str) }
x.report("smnky pre 1.7") { run(:smnky_1_7, str) }
x.report("smnky pre w/constant") { run(:smnky_pre_w_constant, str) }
x.report("Cary with arr") { run(:convert_with_arr, str) }
end
user system total real
Cary 0.018610 0.000300 0.018910 ( 0.019135)
Tatiana 0.067738 0.001138 0.068876 ( 0.070317)
smnky 1.7 0.028659 0.001035 0.029694 ( 0.030583)
smnky pre 1.7 0.032050 0.001662 0.033712 ( 0.035089)
smnky pre w/constant 0.013705 0.000323 0.014028 ( 0.014139)
Cary with arr 0.016989 0.000538 0.017527 ( 0.017925)
In order to debug a piece of code, run it line by line. Pretty soon you'll find that:
text = "The sunset sets at twelve o' clock."
text.split(/./) #=> []
It does not do what you think it does.
I think you meant one of those:
text.split('') #=> ["T", "h", "e", " ", ...
text.chars #=> ["T", "h", "e", " ", ...
There are several more bugs but I'll leave those for you to figure out.
def alphabet_position(text)
text.downcase.split('').map do |letter|
index = ('a'..'z').find_index(letter)
index + 1 if index
end.compact.join(' ')
end
Following the answer of #Siim Liiser, also you can use scan method to get letters.
text = "The sunset sets at twelve o' clock."
irb(main):002:0> text.scan(/[a-z]/)
=> ["h", "e", "s", "u", "n", "s", "e", "t", "s", "e", "t", "s", "a", "t", "t", "w", "e", "l", "v", "e", "o", "c", "l", "o", "c", "k"]
As you can see, the letter T is uppercase and are missing, to solve this youy can use downcase before to call the method scan:
irb(main):004:0> text.downcase.scan(/[a-z]/)
=> ["t", "h", "e", "s", "u", "n", "s", "e", "t", "s", "e", "t", "s", "a", "t", "t", "w", "e", "l", "v", "e", "o", "c", "l", "o", "c", "k"]
Not sure if that's the term I should use for it but what I'm trying to do is add len amount of characters to an array, then output that to a .txt. I have the generation done to my satisfaction but I'm not sure how to pack the strings into an array. Right now it just spits out all the strings into the console because of the puts statement, just to make sure it works.
#Password list generator by Nightc||ed, ©2015
norm = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
caps = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
puts "How many passwords would you like to generate?"
num = gets.to_i
system "cls"
puts "Password length (1-x):"
len = gets.to_i
system "cls"
puts """
Which characters would you like to use?
[1] a-z, 0-9
[2] A-Z, 0-9
[3] a-z, A-Z, 0-9
"""
arr = []
type = gets.chomp
if type == "1"
arr = [norm,nums]
elsif type == "2"
arr = [caps,nums]
elsif type == "3"
arr = [norm,caps,nums]
else
exit
end
num.times do |pass|
len.times do |char|
arr2 = arr.to_a.sample
char = arr2.to_a.sample
puts char
end
end
sleep
here your code simplified
#Password list generator by Nightc||ed, ©2015
norm = [*"a".."z"]
caps = [*"A".."Z"]
nums = [*0..9]
num, len, type = [
"How many passwords would you like to generate?",
"Password length (1-x):",
"Which characters would you like to use?
[1] a-z, 0-9
[2] A-Z, 0-9
[3] a-z, A-Z, 0-9"].map do |msg|
puts msg
gets.to_i
end
arr = case type
when 1 then
norm + nums
when 2 then
caps + nums
when 3 then
norm + caps + nums
else
exit
end
passwords = num.times.map { arr.sample(len).join }
puts passwords.inspect
sleep
I think you can simplify your life by replacing the if... and below with the following:
case type
when "1"
arr = [norm,nums].flatten
when "2"
arr = [caps,nums].flatten
when "3"
arr = [norm,caps,nums].flatten
else
exit
end
passwd_set = []
num.times { passwd_set << arr.sample(len).join }
p passwd_set
I find case statements easier to read, and more easily extended. Flattening the arrays makes it so sample can directly produce the desired number of characters/symbols, and those can be joined to produce a string which can be appended to your passwd_set array.
You can add to an array using the << method. For example:
arr = []
3.times do |el|
arr << el
end
arr.inspect #=> [0, 1, 2]
Another option would be the push method:
arr = []
(0..2).each { |el| arr.push(el)}
I have this question.
Using the Ruby language, have the function CaesarCipher(str,num) take the str parameter and perform a Caesar Cipher shift on it using the num parameter as the shifting number. A Caesar Cipher works by shifting each letter in the string N places down in the alphabet (in this case N will be num). Punctuation, spaces, and capitalization should remain intact. For example if the string is "Caesar Cipher" and num is 2 the output should be "Ecguct Ekrjgt".
Any my code looks like this. I think the onlt problem i have is to update each letter and then each word within the loops. please help. thank you
def Caesar_cipher(str, num)
if num > 25
num -= 26
end
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
str = str.split(" ")
str.each do |word|
word.each_char do |c|
if alphabet.include?(c)
n = alphabet.index(c) + num
if n > 25
n -= 26
end
c = alphabet[n]
end
end
end
return str
end
puts Caesar_cipher("zabcd", 1) // "zabcd"
str = str.split("")
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
alphabet2 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
while num > 25
num -= 26
end
str = str.map do |char|
if alphabet.include?(char)
n = alphabet.index(char) + num
while n > 25
n -= 26
end
char = alphabet[n]
elsif alphabet2.include?(char)
m = alphabet2.index(char) + num
while m > 25
m -= 26
end
char = alphabet2[m]
else
char
end
char
end
return str.join
end
def cipher_shift(s, n)
letters = [*'a'..'z']
s.chars.map {|x| letters.include?(x.downcase) ? (x.ord + n).chr : x}.join
end
So I figured out that I can count through the hash, the problem is that for 7 and 9 I have four values. I have tried several other things with no luck. Can someone help understand what else I could do to get the values I want out of the hash. I realize that I can match the numbers with the key, but I am confused how to get the values to permute.
letters = {"1" => ["1", "1", "1"],
"2" => ["a", "b", "c"],
"3" => ["d", "e", "f"],
"4" => ["g", "h", "i"],
"5" => ["j", "k", "l"],
"6" => ["m", "n", "o"],
"7" => ["p", "q", "r", "s"],
"8" => ["t", "u", "v"],
"9" => ["w", "x", "y", "z"]}
phone_number = gets.chomp.to_s
words = []
word = []
numbers = phone_number.chomp.chars
count0 = 0
while count0 < 3
count1 = 0
while count1 < 3
count2 = 0
while count2 < 3
count3 = 0
while count3 < 3
count4 = 0
while count4 < 3
count5 = 0
while count5 < 3
count6 = 0
while count6 < 3
word[0] = letters[numbers[0]][count0]
word[1] = letters[numbers[1]][count1]
word[2] = letters[numbers[2]][count2]
word[3] = letters[numbers[3]][count3]
word[4] = letters[numbers[4]][count4]
word[5] = letters[numbers[5]][count5]
word[6] = letters[numbers[6]][count6]
words << word.join
count6 += 1
end
count5 += 1
end
count4 += 1
end
count3 += 1
end
count2 += 1
end
count1 += 1
end
count0 += 1
end
puts words
Edit:
I want to a seven digit number and print out all possible letter combinations. I am a beginner so I want to understand with the things I know now. I want to try and do this with if statements please.
numbers = phone_number.chomp.chars
if letters.key?(numbers[0])
if letters.key?(numbers[1])
if letters.key?(numbers[2])
if letters.key?(numbers[3])
if letters.key?(numbers[4])
if letters.key?(numbers[5])
if letters.key?(numbers[6])
end
end
end
end
end
end
end
I understand how to grab a value from a matching key, but don't get how I can hold the first value while going through the rest, if that makes any sense.
product is the function you are looking for, the following works with any number of digits:
digits = '27'
keys = digits.chars.map{|digit|letters[digit]}
p keys.shift.product(*keys).map(&:join) #=> ["ap", "aq", "ar", "as", "bp", "bq", "br", "bs", "cp", "cq", "cr", "cs"]
This prints all possible words for a variable-sized phone number:
letters = {"1" => ["1"],
"2" => ["a", "b", "c"],
"3" => ["d", "e", "f"],
"4" => ["g", "h", "i"],
"5" => ["j", "k", "l"],
"6" => ["m", "n", "o"],
"7" => ["p", "q", "r", "s"],
"8" => ["t", "u", "v"],
"9" => ["w", "x", "y", "z"]}
digits = gets.chomp.split ''
# Total number of combinations
n = digits.inject(1) { |a,b| a * letters[b].size }
words = []
0.upto n-1 do |q|
word = []
digits.reverse.each do |digit|
q, r = q.divmod letters[digit].size
word.unshift letters[digit][r]
end
words << word.join
end
puts words
For example, if the input is 67, then there are 12 combinations:
mp mq mr ms np nq nr ns op oq or os
Edit: I don't see a way to make use of the 7 if statements as you have written, but perhaps this is closer to the kind of answer you are looking for:
words = []
letters[digits[0]].each do |c0|
letters[digits[1]].each do |c1|
letters[digits[2]].each do |c2|
letters[digits[3]].each do |c3|
letters[digits[4]].each do |c4|
letters[digits[5]].each do |c5|
letters[digits[6]].each do |c6|
words << [c0,c1,c2,c3,c4,c5,c6].join
end
end
end
end
end
end
end
puts words
A good exercise would be to re-write this in a way that can work for phone numbers of any length, not just 7. Again, this is only for instructional purposes. In practice, one would use Array's product method as in hirolau's answer.
LETTERS = {"1" => ["1", "1", "1"],
"2" => ["a", "b", "c"],
"3" => ["d", "e", "f"],
"4" => ["g", "h", "i"],
"5" => ["j", "k", "l"],
"6" => ["m", "n", "o"],
"7" => ["p", "q", "r", "s"],
"8" => ["t", "u", "v"],
"9" => ["w", "x", "y", "z"]}
def convert_to_phone_number(string)
string.each_char.with_object([]) { |x, arr| LETTERS.each { |k,v| (arr.push k; break) if v.include?(x) }}.join
end
convert_to_phone_number "foobar"
#=> "366227"
i think so it is issue of cache memory
u need to change like below
LETTERS = {"1" => ["1", "1", "1"],
"2" => ["a", "b", "c"],
"3" => ["d", "e", "f"],
"4" => ["g", "h", "i"],
"5" => ["j", "k", "l"],
"6" => ["m", "n", "o"],
"7" => ["p", "q", "r", "s"],
"8" => ["t", "u", "v"],
"9" => ["w", "x", "y", "z"]}
def convert_to_phone_number(string)
string.each_char.with_object([]) { |x, arr| LETTERS.each { |k,v| (arr.push k; break) if v.include?(x) }}.join
end
convert_to_phone_number "foobar"
I have a string containing many new line and spaces. I need to split it into fixed length sub strings.
E.g
a = "This is some\nText\nThis is some text"
And now i would like to split it into say strings of length 17.
so now it should result in
["This is some\nText", "\nThis is some tex", "t"]
Comment: My string may contain any character (white space/word etc)
"This is some\nText\nThis is some text".scan(/.{1,17}/m)
# => ["This is some\nText", "\nThis is some tex", "t"]
Yet another way:
(0..(a.length / 17)).map{|i| a[i * 17,17] }
#=> ["This is some\nText", "\nThis is some tex", "t"]
Update
And benchmarking:
require 'benchmark'
a = "This is some\nText\nThis is some text" * 1000
n = 100
Benchmark.bm do |x|
x.report("slice") { n.times do ; (0..(a.length / 17)).map{|i| a[i * 17,17] } ; end}
x.report("regex") { n.times do ; a.scan(/.{1,17}/m) ; end}
x.report("eachc") { n.times do ; a.each_char.each_slice(17).map(&:join) ; end }
end
result:
user system total real
slice 0.090000 0.000000 0.090000 ( 0.091065)
regex 0.230000 0.000000 0.230000 ( 0.233831)
eachc 1.420000 0.010000 1.430000 ( 1.442033)
A solution with enumerable : split the array in single char with each_char, then use each_slice for doing the partition, and join the results:
"This is some\nText\nThis is some text"
.each_char # => ["T", "h", "i", "s", " ", "i", "s", " ", "s", "o", "m", "e", "\n", T", "e", "x", "t", "\n", "T", "h", "i", "s", " ", "i", "s", " ", "s", "o", "m", "e", " ", t", "e", "x", "t"]
.each_slice(17) # => [["T", "h", "i", "s", " ", "i", "s", " ", "s", "o", "m", "e", \n", "T", "e", "x", "t"], ["\n", "T", "h", "i", "s", " ", "i", "s", " ", "s", "o", "m", e", ", "t", "e", "x"], ["t"]]
.map(&:join) # => ["This is some\nText", "\nThis is some tex", "t"]
Yet another solution: unpack.
You need to construct a string for it like a17a17a17a17a8 (the last chunk needs to be shorter if the string is not exactly x times 17 chars long.
a = "This is some\nText\nThis is some text\nThis is some more text"
a.unpack(('a17' * (a.length / 17)) + (a.size % 17 == 0 ? "" : "a#{a.length - (a.length / 17) * 17}"))
=> ["This is some\nText", "\nThis is some tex", "t\nThis is some mo", "re text"]
This appears to be by far the fastest one of the suggested, of course if the input string is huge, the unpack string will be huge as well. If that is the case, you will want a buffered reader for that thing, read it in chunks of x * 17 and do something like the above for each chunk.
require 'benchmark'
a = "This is some\nText\nThis is some text" * 1000
n = 100
Benchmark.bm do |x|
x.report("slice ") { n.times do ; (0..(a.length / 17)).map{|i| a[i * 17,17] } ; end}
x.report("regex ") { n.times do ; a.scan(/.{1,17}/m) ; end}
x.report("eachc ") { n.times do ; a.each_char.each_slice(17).map(&:join) ; end }
x.report("unpack") { n.times do ; a.unpack(('a17' * (a.length / 17)) + (a.size % 17 == 0 ? "" : "a#{a.length - (a.length / 17) * 17}")) ; end }
end
Results:
user system total real
slice 0.120000 0.000000 0.120000 ( 0.130709)
regex 0.190000 0.000000 0.190000 ( 0.186407)
eachc 1.430000 0.000000 1.430000 ( 1.427662)
unpack 0.030000 0.000000 0.030000 ( 0.032807)
I noted an issue with #yevgeniy's answer above (I would comment directly but I lack the reputation).
If the string divides without remainder (a.length % divisor = 0), you end up with an extra array element of "".
a = "123456789"
(0..(a.length / 3)).map{|i| a[i * 3,3] }
# => ["123", "456", "789", ""]
I have resolved this issue and generalized the solution to a function (the function uses keyword arguments with a required keyword, requires Ruby 2.1+):
def string_prettifier(a_string: , split_char_count: 3)
splits = (0...(a_string.length / split_char_count.to_f).ceil).map{|i| a_string[i * split_char_count, split_char_count] }
return splits
end
s = "123456789"
string_prettifier(a_string: , split_char_count: 3)
# => ["123", "456", "789"]
s = "12345678"
string_prettifier(a_string: , split_char_count: 3)
# => ["123", "456", "78"]
s = "1234567890"
string_prettifier(a_string: , split_char_count: 3)
# => ["123", "456", "789", "0"]