Here is the assigned problem:
Using the Ruby language, have the function LetterChanges(str) take the str parameter being passed and modify it using the following algorithm. Replace every letter in the string with the letter following it in the alphabet (ie. c becomes d, z becomes a). Then capitalize every vowel in this new string (a, e, i, o, u) and finally return this modified string.*
I figured I'd attack this in two parts, however I cannot seem to figure out what I am doing wrong with the first half of the problem!
Here is my code as it currently stands.
def LetterChanges(str)
str.downcase!
str = str.split(//)
alphabet_lower = ["a".."z"]
i = 0
letter = 0
while i < str.length - 1
if alphabet_lower[letter] == str[i]
str[i] = alphabet_lower[letter + 1]
i += 1
letter = 0 ## Added this for the instance when letter > 0 after an 'else'
else
letter += 1
end
end
str = str.join("")
return str
end
This code is having infinite loop. I did try a few other things such as
......
i = 0
alphabet.each do |letter|
if str[i] == letter
str[i] = letter.next ##I also tried letter + 1
i += 1
end
end
......
alphabet_lower = ["a".."z"]
This creates an Array of a single Range element, not what you expected. This is the reason alphabet_lower[letter] == str[i] is never true, causing infinite loop.
Change it to:
alphabet_lower = ("a".."z").to_a
Since there is also whitespace character in your string, it's better be:
alphabet_lower = ("a".."z").to_a + [' ']
Most probably, your if alphabet_lower[letter] == str[i] doesn't get a match, thus your letter increments to infinity, and you get an infinite loop. You can verify this by adding a puts on your else. It would help if you do as Yu Hao said and place your input and it's output. Also, kindly post the calling script for your LetterChanges.
As for the Socratic Way:
The question left, is why does your if alphabet_lower[letter] == str[i] doesn't get a match? And how can you get around with it? :)
#Yu has explained your problem. Here's another way that uses the form of String#gsub that uses a hash to substitute each character in the string.
All the work will be done by the following hash:
H = ('a'..'z').to_a.each_with_object(Hash.new { |h,k| k }) do |c,h|
h[c] = case c
when 'z' then 'A'
when 'd', 'h', 'n', 't' then c.next.upcase
else c.next
end
end
#=> {"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"}
The construction of the hash is straightforward, except possibly the line that creates it, which is an argument of Enumerable#each_with_object:
Hash.new { |h,k| k }
This creates an empty hash with a default value. Specifically, if h does not have a key k, h[k] returns the default value k. We can see how that works:
H['a'] #=> "b"
H['d'] #=> "E"
H['%'] #=> "%"
H['3'] #=> "3"
H['zombie'] #=> "zombie"
This allows us to write:
def convert(str)
str.gsub(/./, H)
end
which produces the desired results:
convert 'bcd tuv' #=> "cdE Uvw"
convert 'bcd3?tuv' #=> "cdE3?Uvw"
convert '123D ghi$' #=> "123D hIj$"
Note that if we created H without the default:
H = ('a'..'z').to_a.each_with_object({}) do |c,h|
h[c] = case c
...
end
end
the hash mapping would be the same, but we would obtain:
convert 'bcd tuv' #=> "cdEUvw"
convert 'bcd3?tuv' #=> "cdEUvw"
convert '123D ghi$' #=> "hIj"
which is not what we want.
Related
I completed an assignment that has the following instructions:
Pseudocode and write a method that takes a spy's real name (e.g., "Felicia Torres") and creates a fake name with it by doing the following:
Swapping the first and last name.
Changing all of the vowels (a, e, i, o, or u) to the next vowel in 'aeiou', and all of the consonants (everything else besides the vowels) to the next consonant in the alphabet.
My solution:
#vowels = %w(a e i o u)
#consonants = ("a".."z").to_a - #vowels
def next_vowel(letter)
i = 0
while i < #vowels.length
if #vowels[i] == "u"
return #vowels[0]
elsif #vowels[i] == letter
return #vowels[i+1]
end
i += 1
end
end
def next_consonant(letter)
i = 0
while i < (#consonants.length)
if #consonants[i] == "z"
return #consonants[0]
elsif #consonants[i] == letter
return #consonants[i + 1]
end
i += 1
end
end
def alias_manager(name)
name.downcase!
first_name = name.split(" ")[0]
last_name = name.split(" ")[1]
alias_first_name = last_name.chars.map do |i|
if #vowels.include?(i)
next_vowel(i)
elsif #consonants.include?(i)
next_consonant(i)
end
end
alias_last_name = first_name.chars.map do |i|
if #vowels.include?(i)
next_vowel(i)
elsif #consonants.include?(i)
next_consonant(i)
end
end
alias_first_name.join.capitalize! + " " + alias_last_name.join.capitalize!
end
I'm trying to think of a much more succinct way of writing this. The 'while' loops don't seem like the most efficient method. I was thinking of using 'rotate' but not sure how I could replace the letter in the string. Also, is there a way to refactor the last to iterations for first_name and last_name? I'm basically writing the same thing twice for different variables.
A better way to define next_vowel and next_consonant
#vowels = %w(a e i o u)
#consonants = ("a".."z").to_a - #vowels
def next_vowel(letter)
i = #vowels.index(letter)
# Return the next vowel, using modulo for the last case (next of `u` is `a`)
#vowels[(i + 1) % #vowels.length]
end
def next_consonant(letter)
i = #consonants.index(letter)
# Return the next vowel, using modulo for the last case (next of `z` is `b`)
#consonants[(i + 1) % #consonants.length]
end
Some test case:
2.3.3 :019 > next_vowel("a")
=> "e"
2.3.3 :020 > next_vowel("e")
=> "i"
2.3.3 :021 > next_vowel("u")
=> "a"
2.3.3 :022 > next_consonant("t")
=> "v"
2.3.3 :023 > next_consonant("z")
=> "b"
2.3.3 :024 > next_consonant("d")
=> "f"
FWIW:
VOWELS = %w(a e i o u) | %w(a e i o u).map(&:upcase)
CONSONANTS = ((?a..?z).to_a | (?a..?z).map(&:upcase)) - VOWELS
def next_elem letter
array = VOWELS.include?(letter) ? VOWELS : CONSONANTS
array.each_cons(2) { |me, they| break they if me == letter }
end
"Felicia Torres".split(' ').reverse.map do |l|
l.split('').map(&method(:next_elem))
end.map(&:join).join(' ')
#⇒ "Vussit Gimodoe"
It sounds like your question might be better suited for https://codereview.stackexchange.com/ ?
That said - I'd recommend you look into these two methods:
Array#rotate
String#tr
Which can simplify the code to something like this:
#vowels = %w( a e i o u )
#consonants = ('a'..'z').to_a - #vowels
def alias_manager(name)
rotate_letters(name).split.reverse.map(&:capitalize).join(' ')
end
def rotate_letters(name)
name.downcase.tr(#vowels.join, #vowels.rotate.join).tr(#consonants.join, #consonants.rotate.join)
end
This method has been designed with efficiency in mind. The idea is to first create a hash that does the mapping of letters when encrypting individual words (using the form of String#gsub that employs a hash for making substitutions). This should make encryption very fast, which would be particularly effective when there there are many strings to encrypt. As explained below, another benefit of using a hash is that it make it easy create a method that decrypts encrypted strings.
Code
def create_hash(arr)
puts "arr=#{arr}"
(arr + [arr.first]).each_cons(2).with_object({}) { |(k,v),h| h[k]=v }
end
v = %w(a e i o u)
c = ("a".."z").to_a - v
subs_hash = create_hash(v).
merge(create_hash(v.map(&:upcase))).
merge(create_hash(c)).
merge(create_hash(c.map(&:upcase)))
#=> {"a"=>"e", "e"=>"i", "i"=>"o", "o"=>"u", "u"=>"a",
# "A"=>"E", "E"=>"I", "I"=>"O", "O"=>"U", "U"=>"A",
# "b"=>"c", "c"=>"d", ..., "y"=>"z", "z"=>"b",
# "B"=>"C", "C"=>"D", ..., "Y"=>"Z", "Z"=>"B"}
def code(str, subs_hash)
str.split.reverse.map { |word| word.gsub(/./, subs_hash) }.join(' ')
end
Examples
code("Felicia Torres", h)
#=> "Vussit Gimodoe"
code("eenie meanie", h)
#=> "niepoi iipoi"
Decrypt encrypted string
One advantage of using a hash is that it makes writing a decode method very easy.
inverted_subs_hash = subs_hash.invert
#=> {"e"=>"a", "i"=>"e", "o"=>"i", "u"=>"o", "a"=>"u",
# "E"=>"A", "I"=>"E", "O"=>"I", "U"=>"O", "A"=>"U",
# "c"=>"b", "d"=>"c",..., "z"=>"y", "b"=>"z",
# "C"=>"B", "D"=>"C",..., "Z"=>"Y", "B"=>"Z"}
def decode(str, inverted_subs_hash)
code(str, inverted_subs_hash)
end
decode "Vussit Gimodoe", inverted_subs_hash
#=> "Felicia Torres"
Upper and lower case
If the string is first downcased, remove
merge(create_hash(v.map(&:upcase))).
and
merge(create_hash(c.map(&:upcase)))
Doing so results in decode(code(str)) != str unless we assume the first letter of each word is capitalized and all other characters are lower case, in which case we could make decode return the original string by applying (as a final step) String#capitalize to each decoded word.
I am struggling with my code to write a simple code-breaking game.
There is a hidden code:
code = ["a","b","b","c"]
My program asks for user input, then stores it in a variable.
I want to compare user input against the secret code variable and give the user feedback: 1 for a good letter in good place, 0 for good letter in wrong place, "-" for wrong letter.
I came up with something like this:
feedback = []
input.each_with_index do |v,i|
if v == code.fetch(i)
feedback << "1"
else
feedback << "-"
end
end
It works OK when it compares elements at the same index. I have no idea how I can find elements that are in the code array, but not in the same index and give feedback to the user.
For example:
code = ["a","b","b","c"]
input = ["b","b","a","z"]
feedback = ["0","1","0","-"]
This code works with the 3 examples you mentioned.
2 passes are used because the 1s must be returned before the 0s :
def give_feedback(input, code)
feedback = Array.new(input.size) { '-' }
code2 = code.dup
input.each_with_index do |letter, index|
if letter == code[index]
feedback[index] = '1'
code2[index] = nil
end
end
input.each_with_index do |letter, index|
next if feedback[index] == '1'
found = code2.index(letter)
if found
feedback[index] = '0'
code2[found] = nil
end
end
feedback
end
p give_feedback(%w(b b a z), %w(a b b c))
# ["0", "1", "0", "-"]
p give_feedback(%w(a a a a), %w(a b b c))
# ["1", "-", "-", "-"]
p give_feedback(%w(c c b a), %w(a b b c))
# ["0", "-", "1", "0"]
One more solution
code = ["a","b","b","c"]
input = ["b","b","a","z"]
feedback = input.map.with_index do |num, ind|
if code.include? num
code[ind] == num ? '1' : '0'
else
'-'
end
end
=> ['0', '1', '0', '-']
if you want define feedback before, just edit 1st variant to:
code = ["a","b","b","c"]
input = ["b","b","a","z"]
feedback = []
input.each_with_index do |num, ind|
if code.include? num
feedback << (code[ind] == num ? '1' : '0')
else
feedback << '-'
end
end
result would be the same
You can use zip and map to make it a little more functional. include? will check to see if the input is in code
code = %w(a b b c)
input = %w(b b a z)
result = code.zip(input).map do |c, i|
if c == i
'1'
elsif code.include?(i)
'0'
else
'-'
end
end
puts result.to_s
You can use the include? method to see if that character is in the list at a different index. Something like this:
input.each_with_index do |v,i|
if v == code.fetch(i)
feedback << "1"
elsif code.include?(v)
# right character, wrong spot
feedback << "0"
else
feedback << "-"
end
end
Just out of curiosity:
[code, input].map { |a| (0...a.size).zip(a).to_h }
.reduce do |e, acc|
c = acc.values.dup
acc.merge(e) do |_, v1, v2|
case (c.delete_at(c.index(v2)) rescue nil)
when v1 then "1"
when nil then "-"
else "0"
end
end
end.values
I have an array containing capital and small letters. I am trying to concatenate capital letters with the following small letters in a new array. For example, I have the following array
first_array = ["A","b","C","d","e"]
and I want to obtain the following array
["Ab","Cde"] #new array
I am trying to iterate through the first array with a code that looks like this:
new_array = []
first_array.each_with_index do |a,index|
if (a!~/^[a-z].*$/)
new_array = new_array.push "#{a}"
else
new_array[-1] = first_array[index-1] + "#{a}" #the idea is to concatenate the small letter with the previous capital letter and replace the last item in the new array
end
but it does not work. I am not sure I am tackling this issue efficiently which is why I can't resolve it. Could somebody suggest some options?
If you join as a string you can then scan to get all the matches:
first_array.join.scan(/[A-Z][a-z]*/)
=> ["Ab", "Cde"]
While I prefer #Paul's answer, you could do the following.
first_array.slice_before { |s| s.upcase == s }.map(&:join)
#=> ["Ab", "Cde"]
So, you want to split your original array when next char is uppercase, and then make strings of those subarrays? There's a method in standard lib that can help you here:
first_array = ["A","b","C","d","e"]
result = first_array.slice_when do |a, b|
a_lower = a.downcase == a
b_upper = b.upcase == b
a_lower && b_upper
end.map(&:join)
result # => ["Ab", "Cde"]
I like Sergio's answer a lot, here's what I brewed up while trying it out:
def append_if_present(lowercase, letters)
lowercase << letters.join if letters.size > 0
end
first_array = ["A","b","C","d","e"]
capitals = []
lowercase = []
letters = []
first_array.each_with_index do |l, i|
if l =~ /[A-Z]/
capitals << l
append_if_present(lowercase, letters)
letters = []
else
letters << l
end
end
append_if_present(lowercase, letters)
p capitals.zip(lowercase).map(&:join)
Here's a method that uses Enumerable#slice_when :
first_array.slice_when{ |a, b| b.upcase == b && a.downcase == a }.map(&:join)
I'm trying to count the number of vowels in a string by splitting the string into an array of letters and then map vowel letters to 1 and summing up the array.
def count_vowels(string)
vowels = ['a','e', 'i', 'o', 'u']
return string.split("").map{ |n| vowels.include? n ? 1 : 0}.inject(0,:+)
end
The include? part doesn't correctly return 1 or 0. Any suggestion why this won't fly?
I hacked it to this version which works, but looks kind of foolish:
def count_vowels(string)
vowels = ['a','e', 'i', 'o', 'u']
return string.split("").map{ |n| vowels.include? n}.inject(0) do |mem,x|
x ? mem + 1 : mem
end
end
The reason:
string.split("").map{ |n| vowels.include? n ? 1 : 0}.inject(0,:+)
does not work is because n ? 1 : 0 is evaluated and passed as an argument to include? instead of n. You need to add some parentheses in include?:
string.split("").map{ |n| vowels.include?(n) ? 1 : 0}.inject(0,:+)
You can simply do
def count_vowels(string)
vowels = ['a','e', 'i', 'o', 'u']
string.split(//).select { |x| vowels.include? x }.length
end
You don't need map.
def count_vowels(string)
vowels = %w[a e i o u]
string.chars.select{|n| vowels.include? n}.size
end
In this case you need parantheses for include? method parameter. So
return string.split("").map{ |n| vowels.include?(n) }.inject(0) do |mem,x|
Anyway, your code might be better
VOWELS = %w(a e i o u) # string's array
you don't need return in your method, it's the last statement
string.split("") => string.chars
Note that your method could be so:
def count_vowels(string)
string.count "aeiou"
end
I am trying to write a method that takes an array and returns trueif there is an element that occurs three times in a row or false if it doesn't. I can't think of the syntax. Would you use count? See the example below.
def got_three?(array)
end
got_three?([1,2,2,3,4,4,4,5,6]) would return true as 4 shows up three times in a row.
Toying with the new Ruby 2.3.0 method chunk_while:
def got_three?(array)
array.chunk_while(&:==).any?{|g| g.size >= 3}
end
With Enumerable#chunk:
def got_three?(xs)
xs.chunk(&:itself).any? { |y, ys| ys.size >= 3 }
end
Not so smart but a naive one (using a instead of array since it is long):
a.each_index.any?{|i| a[i] == a[i + 1] and a[i + 1] == a[i + 2]}
I assume you don't have any nil in the array.
An alternative which may be more performant (as per #sawa's comments)...
def got_three?(array)
(0..(array.count-2)).any?{|i|array[i] == array[1+1] && array[i] == array[i+2]}
end
Look, ma, no indices!
def ducks_in_a_row?(arr, n)
cnt = 0
last = arr.first
arr.each do |d|
if d==last
cnt += 1
return true if cnt==n
else
last = d
cnt = 1
end
end
false
end
ducks_in_a_row?([1,2,3,4,5,6,6,7,7,7], 3)
#=> true
def got_three?(array)
array.each_cons(3).map{|g|g.uniq.length == 1}.any?
end
or as #wandmaker suggests...
def got_three?(array)
array.each_cons(3).any?{|g|g.uniq.length == 1}
end
Here is my take on this problem - I tried to make it generic, hopefully efficient so that the loop terminates as soon as n-consecutive elements are found.
def got_consecutive?(array, n = 3)
case array.size
when 0...n
return false
when n
return array.uniq.size == n
else
array[n..-1].each_with_object(array[0...n]) do |i, t|
(t.uniq.size == 1 ? (break t) : (t << i).shift)
end.uniq.size == 1
end
end
p got_consecutive?([])
#=> false
p got_consecutive?([1,2])
#=> false
p got_consecutive?([1,2,2,3,2,3,3,3], 3)
#=> true
p got_consecutive?([1,2,2,3,2,3,3,3], 4)
#=> false
p got_consecutive?([1,2,2,3,2,3,3,3,3,3,4,4,4,4], 5)
#=> true
The code takes care of border cases first such as when array did not have n elements in which case answer is obviously false, and another one being when the array had only n elements - in which case just a uniqueness check would suffice.
For cases where array size is greater than n, the code uses Enumerable#each_with_object with the initial object being an array of n elements from the array - this array is used also as temporary work area to track n consecutive elements and perform a check whether all those elements are same or not.