How to prevent iterating over duplicate array elements? - arrays

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!"

Related

Checking that string contains only unique array elements, repeating letters

I am creating a method to test if a string contains only array elements in a game of scrabble.
Given this array:
hand = ["b", "l", "c", "o", "h", "e", "a"] if word is "beach"
Originally I used this block:
def uses_available_letters?(word, letters_in_hand)
input = word.split("")
input.index{ |x| !letters_in_hand.include?(x) }.nil?
end
Given this array:
hand = ["b", "l", "c", "o", "h", "e", "a", "c"]
if word = "beach", method returns true. However, if word = "beeeach", it will still return true even though the array only contains 1 "e".
SO, I tried deleting the array element after it was compared:
def uses_available_letters?(word, letters_in_hand)
input = word.split("")
input.each do
if letters_in_hand.include?(input[i])
letters_in_hand.delete(input[i])
else
return false
end
end
end
BUT, given "beacch", false is returned even though there are 2 c's in the array. So it seems every like letter is being deleted.
Send help!
The trouble with Ruby array operations is most treat them as a set of unique values, as in:
%w[ a a b b c c ] - %w[ a b c ]
# => []
Where that removes every a, b and c, not just the first. The same goes for delete unless you use a very specific index, but that gets messy in a hurry since deleting shuffles the remaining indexes, etc.
I'd consider storing the hand as a letter/count pair, as in:
def hand_count(str)
str.chars.group_by(&:itself).map { |l,a| [ l, a.length ] }.to_h
end
Where that gives you a hash instead of an array:
hand_count('example')
# => {"e"=>2, "x"=>1, "a"=>1, "m"=>1, "p"=>1, "l"=>1}
So now you can write a "sub" method:
def hand_sub(str, sub)
hand = hand_count(str)
hand_count(sub).each do |l, c|
# Unless the letter is present in the hand...
unless (hand.key?(l))
# ...this isn't possible.
return false
end
# Subtract letter count
hand[l] -= c
# If this resulted in a negative number of remaining letters...
if (hand[l] < 0)
# ...this isn't possible.
return false
end
end
# Convert back into a string...
hand.map do |l, c|
# ...by repeating each letter count times.
l * c
end.join
end
Where that works quite simply:
hand_sub('example', 'exam')
# => "epl"
hand_sub('example', 'expel')
# => "am"
hand_sub('example', 'plexi')
# => false
hand_sub('example', 'ell')
# => false
def uses_available_letters?(word, hand)
result = true
word.split('').each do |letter|
if hand.include?(letter)
hand.delete_at(hand.index(letter))
else
result = false
end
end
result
end
2.6.5 :065 > uses_available_letters?('beeeach', %w[b l e h c a e e])
=> true
2.6.5 :066 > uses_available_letters?('beach', %w[b l e h c a])
=> true
2.6.5 :067 > uses_available_letters?('beeeach', %w[b l e h c a])
=> false
2.6.5 :068 > uses_available_letters?('beeeach', %w[b l e d h e c e r a])
=> true
Here are two ways that could be done.
Compare counting hashes
def uses_available_letters?(word, letters_in_hand)
word_tally = word.each_char.tally
letters_in_hand_tally = letters_in_hand.tally
word_tally.all? { |k,v| letters_in_hand_tally[k].to_i >= v }
end
letters_in_hand = ["b", "l", "c", "e", "o", "h", "e", "a"]
uses_available_letters?("belch", letters_in_hand)
#=> true
uses_available_letters?("belche", letters_in_hand)
#=> true
uses_available_letters?("beleche", letters_in_hand)
#=> false
When word = "beleche",
word_tally
#=> {"b"=>1, "e"=>3, "l"=>1, "c"=>1, "h"=>1}
letters_in_hand_tally
#=> {"b"=>1, "l"=>1, "c"=>1, "e"=>2, "o"=>1, "h"=>1, "a"=>1}
See Enumerable#tally, which was introduced in Ruby v2.7. To support earlier versions of Ruby one could use the form of Hash::new that takes an argument (here zero), called the default value, and no block.
def uses_available_letters?(word, letters_in_hand)
word_tally = tally_ho(word.chars)
letters_in_hand_tally = tally_ho(letters_in_hand)
word_tally.all? { |k,v| letters_in_hand_tally[k].to_i >= v }
end
def tally_ho(arr)
arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
end
If letters_in_hand_tally does not have a key k, letters_in_hand_tally[k] #=> nil, resulting in the comparison nil >= v, which would raise an exception. It is for that reason I wrote letters_in_hand_tally[k].to_i, as nil.to_i #=> 0 and all values of word_tally will be positive integers.
One could alternatively write the block as follows:
{ |k,v| letters_in_hand_tally.key?(k) && letters_in_hand_tally[k] >= v }
Treat duplicate letters in letters_in_hand as distinct letters
This second method assumes letters_in_hand is given by a string, rather than an array. For example:
letters_in_hand = "blceohea"
Of course we could always form that string from the array as a first step, but I think it's more pleasing for letters_in_hand to be a string, just as word is. The following method makes use of the fact that String#index accepts an optional second argument that equals the index into the string where the search is to begin:
def uses_available_letters?(word, letters_in_hand)
start_index = Hash.new(0)
word.each_char.all? do |c|
i = letters_in_hand.index(c, start_index[c])
return false if i.nil?
start_index[c] = i + 1
end
true
end
uses_available_letters?("belch", letters_in_hand)
#=> true
uses_available_letters?("belche", letters_in_hand)
#=> true
uses_available_letters?("belecher", letters_in_hand)
#=> false

Skipping to next vowel and consonant in a string

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.

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)

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