Skipping to next vowel and consonant in a string - arrays

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.

Related

How to collect user input and give feedback

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

Ruby merging items in array conditionally

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)

How to prevent iterating over duplicate array elements?

Is it possible to avoid messing with duplicate array elements when you're messing with the first one?
Consider the following:
def rot13(str)
alphabet = ("a".."z").to_a
letters = str.split("").each{|x| x.downcase! }
letters.map! do |let|
alphabet[(alphabet.index(let) + 13) % alphabet.length]
end
#werd = letters.join("")
letters.map.with_index do |char,index|
str.each_char.with_index do |c,idx|
if str[idx].upcase! == nil
letters.at(idx).upcase!
end
end
end
#werd
letters
end
rot13("ANdrea")
This is just a Ceaser Cypher fixed at 13 letters over. Straightforward until we hit the duplicate "a"s, which turns into duplicate "n"s after the code runs. As it is here, the upcase! loop upcases in letters everything that was at those indexes in the original string, and I only need those indexes to be capitalized. How do I isolate that?
Another way you could do this
def input(str)
alphabet = ("a".."z").to_a
str.chars.map do |let|
next let unless alphabet.include?(let.downcase)
ceaser_letter = alphabet[(alphabet.index(let.downcase) + 13) % alphabet.length]
let == let.upcase ? ceaser_letter.upcase : ceaser_letter.downcase
end
end
input('ANdrea')
=> ["N", "A", "q", "e", "r", "n"]
Your question was a little unclear. This is my solution from what I got from it.
def rot13(str)
alphabet = ("a".."z").to_a
cap_alphabet = ("A".."Z").to_a
letters = str.split("")
letters.map! do |letter|
if alphabet.include?(letter)
# Change these for different scambling
alphabet[(alphabet.index(letter) + 13) % alphabet.length]
else
cap_alphabet[(cap_alphabet.index(letter) + 13) % alphabet.length]
end
end
end
p rot13("Andrea")
This will return ["N", "A", "q", "e", "r", "n"]
For this particular problem, something much simpler will do the trick:
def rot13(str)
str.chars.map do |c|
next c unless c =~ /[A-z]/
((c.ord % 32 + 13) % 26 + c.ord / 32 * 32).chr
end.join
end
rot13("It's PJSCopeland!")
#=> "Vg'f CWFPbcrynaq!"

Dynamically deleting elements from an array while enumerating through it

I am going through my system dictionary and looking for words that are, according to a strict definition, neither subsets nor supersets of any other word.
The implementation below does not work, but if it did, it would be pretty efficient, I think. How do I iterate through the array and also remove items from that same array during iteration?
def collect_dead_words
result = #file #the words in my system dictionary, as an array
wg = WordGame.new # the class that "knows" the find_subset_words &
# find_superset_words methods
result.each do |value|
wg.word = value
supersets = wg.find_superset_words.values.flatten
subsets = wg.find_subset_words.values.flatten
result.delete(value) unless (matches.empty? && subsets.empty?)
result.reject! { |cand| supersets.include? cand }
result.reject! { |cand| subsets.include? cand }
end
result
end
Note: find_superset_words and find_subset_words both return hashes, hence the values.flatten bit
It is inadvisable to modify a collection while iterating over it. Instead, either iterate over a copy of the collection, or create a separate array of things to remove later.
One way to accomplish this is with Array#delete_if. Here's my run at it so you get the idea:
supersets_and_subsets = []
result.delete_if do |el|
wg.word = el
superset_and_subset = wg.find_superset_words.values.flatten + wg.find_subset_words.values.flatten
supersets_and_subsets << superset_and_subset
!superset_and_subset.empty?
end
result -= supersets_and_subsets.flatten.uniq
Here's what I came up with based on your feedback (plus a further optimization by starting with the shortest words):
def collect_dead_words
result = []
collection = #file
num = #file.max_by(&:length).length
1.upto(num) do |index|
subset_by_length = collection.select {|word| word.length == index }
while !subset_by_length.empty? do
wg = WordGame.new(subset_by_length[0])
supermatches = wg.find_superset_words.values.flatten
submatches = wg.find_subset_words.values.flatten
collection.reject! { |cand| supermatches.include? cand }
collection.reject! { |cand| submatches.include? cand }
result << wg.word if (supermatches.empty? && submatches.empty?)
subset.delete(subset_by_length[0])
collection.delete(subset_by_length[0])
end
end
result
end
Further optimizations are welcome!
The problem
As I understand, string s1 is a subset of string s2 if s1 == s2 after zero or more characters are removed from s2; that is, if there exists a mapping m of the indices of s1 such that1:
for each index i of s1, s1[i] = s2[m(i)]; and
if i < j then m(i) < m(j).
Further s2 is a superset of s1 if and only if s1 is a subset of s2.
Note that for s1 to be a subset of s2, s1.size <= s2.size must be true.
For example:
"cat" is a subset of "craft" because the latter becomes "cat" if the "r" and "f" are removed.
"cat" is not a subset of "cutie" because "cutie" has no "a".
"cat" is not a superset of "at" because "cat".include?("at") #=> true`.
"cat" is not a subset of "enact" because m(0) = 3 and m(1) = 2, but m(0) < m(1) is false;
Algorithm
Subset (and hence superset) is a transitive relation, which permit significant algorithmic efficiencies. By this I mean that if s1 is a subset of s2 and s2 is a subset of s3, then s1 is a subset of s3.
I will proceed as follows:
Create empty sets neither_sub_nor_sup and longest_sups and an empty array subs_and_sups.
Sort the words in the dictionary by length, longest first.
Add w to neither_sub_nor_sup, where w is longest word in the dictionary.
For each subsequent word w in the dictionary (longest to shortest), perform the following operations:
for each element u of neither_sub_nor_sup determine if w is a subset of u. If it is, move u from neither_sub_nor_sup to longest_sups and append u to subs_and_sups.
if one or more elements were moved from from neither_sub_nor_sup to longest_sups, append w to subs_and_sups; else add w to neither_sub_nor_sup.
Return subs_and_sups.
Code
require 'set'
def identify_subs_and_sups(dict)
neither_sub_nor_sup, longest_sups = Set.new, Set.new
dict.sort_by(&:size).reverse.each_with_object([]) do |w,subs_and_sups|
switchers = neither_sub_nor_sup.each_with_object([]) { |u,arr|
arr << u if w.subset(u) }
if switchers.any?
subs_and_sups << w
switchers.each do |u|
neither_sub_nor_sup.delete(u)
longest_sups << u
subs_and_sups << u
end
else
neither_sub_nor_sup << w
end
end
end
class String
def subset(w)
w =~ Regexp.new(self.gsub(/./) { |m| "#{m}\\w*" })
end
end
Example
dict = %w| cat catch craft cutie enact trivial rivert river |
#=> ["cat", "catch", "craft", "cutie", "enact", "trivial", "rivert", "river"]
identify_subs_and_sups(dict)
#=> ["river", "rivert", "cat", "catch", "craft"]
Variant
Rather than processing the words in the dictionary from longest to shortest, we could instead order them shortest to longest:
def identify_subs_and_sups1(dict)
neither_sub_nor_sup, shortest_sups = Set.new, Set.new
dict.sort_by(&:size).each_with_object([]) do |w,subs_and_sups|
switchers = neither_sub_nor_sup.each_with_object([]) { |u,arr|
arr << u if u.subset(w) }
if switchers.any?
subs_and_sups << w
switchers.each do |u|
neither_sub_nor_sup.delete(u)
shortest_sups << u
subs_and_sups << u
end
else
neither_sub_nor_sup << w
end
end
end
identify_subs_and_sups1(dict)
#=> ["craft", "cat", "rivert", "river"]
Benchmarks
(to be continued...)
1 The OP stated (in a later comment) that s1 is not a substring of s2 if s2.include?(s1) #=> true. I am going to pretend I never saw that, as it throws a spanner into the works. Unfortunately, subset is no longer a transitive relation with that additional requirement. I haven't investigate the implications of that, but I suspect it means a rather brutish algorithm would be required, possibly requiring pairwise comparisons of all the words in the dictionary.

Coderbytes Letter Changes (Ruby)

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.

Resources