Keep characters and whitespace in ruby method - ruby

Building out a Rot method to solve encryption. I have something that is working but takes out whitespaces and any characters that are included. Was going to use bytes instead of chars then turn it back into a string once I have the byte code but I can't seem to get it working. How would you go about keeping those in place from this code:
code
def rot(x, string, encrypt=true)
alphabet = Array("A".."Z") + Array("a".."z")
results = []
if encrypt == true
key = Hash[alphabet.zip(alphabet.rotate(x))]
string.chars.each do |i|
if ('a'..'z').include? i
results << key.fetch(i).downcase
elsif ('A'..'Z').include? i
results << key.fetch(i).upcase
end
end
return results.join
else
key_false = Hash[alphabet.zip(alphabet.rotate(26 - x))]
string.chars.each do |i|
if ('a'..'z').include? i
results << key_false.fetch(i).downcase
elsif ('A'..'Z').include? i
results << key_false.fetch(i).upcase
end
end
return results.join
end
end
puts rot(10, "Hello, World")
=> RovvyGybvn
puts rot(10, "Rovvy, Gybvn", false)
=> HelloWorld
Thanks for your help in advance!

Just add to both if blocks an else condition like this:
if ('a'..'z').include? i
# ...
elsif ('A'..'Z').include? i
# ...
else
results << i
end
Which will add all non A-z characters untouched to the output.

I've noticed some issues with your code:
Broken replacement hash
This is the biggest problem - your replacement hash is broken. I'm using a smaller alphabet for demonstration purposes, but this applies to 26 characters as well:
uppercase = Array("A".."C")
lowercase = Array("a".."c")
alphabet = uppercase + lowercase
#=> ["A", "B", "C", "a", "b", "c"]
You build the replacement hash via:
x = 1
key = Hash[alphabet.zip(alphabet.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"a", "a"=>"b", "b"=>"c", "c"=>"A"}
"C"=>"a" and "c"=>"A" are referring to the wrong character case. This happens because you rotate the entire alphabet at once:
alphabet #=> ["A", "B", "C", "a", "b", "c"]
alphabet.rotate(x) #=> ["B", "C", "a", "b", "c", "A"]
Instead. you have to rotate the uppercase and lowercase letter separately:
uppercase #=> ["A", "B", "C"]
uppercase.rotate(x) #=> ["B", "C", "A"]
lowercase #=> ["a", "b", "c"]
lowercase.rotate(x) #=> ["B", "C", "A"]
and concatenate the rotated parts afterwards. Either:
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}
or:
key = Hash[(uppercase + lowercase).zip(uppercase.rotate(x) + lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}
Replacing the characters
Back to a full alphabet:
uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = 10
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
Having a working replacement hash makes replacing the characters almost trivial:
string = "Hello, World!"
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
#=> "Rovvy, Gybvn!"
I've changed result from an array to a string. It also has a << method and you don't have to join it afterwards.
Hash#fetch works almost like Hash#[], but you can pass a default value that is returned if the key is not found in the hash:
key.fetch("H", "H") #=> "R" (replacement value)
key.fetch("!", "!") #=> "!" (default value)
Handling encryption / decryption
You're duplicating a lot of code to handle the decryption part. But there's a much easier way - just reverse the direction:
rot(10, "Hello") #=> "Rovvy"
rot(10, "Rovvy", false) #=> "Hello"
rot(-10, "Rovvy") #=> "Hello"
So within your code, you can write:
x = -x unless encrypt
Putting it all together
def rot(x, string, encrypt = true)
uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = -x unless encrypt
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
end
rot(10, "Hello, World!") #=> "Rovvy, Gybvn!"
rot(10, "Rovvy, Gybvn!", false) #=> "Hello, World!"

Related

How can I capitalize a letter from a word one at a time, then add each instance of the word with a caps letter into a array?

My code:
def wave(str)
ary = []
increase_num = 0
str = str.chars
until increase_num > str.size
ary << str[increase_num].upcase && increase_num += 1
end
end
What it's supposed to do:
wave("hello") => ["Hello", "hEllo", "heLlo", "helLo", "hellO"]
I would really appreciate some help, as you probably know by looking at it I'm relatively new.
str = "hello"
str.size.times.map { |i| str[0,i] << str[i].upcase << str[i+1..] }
#=> ["Hello", "hEllo", "heLlo", "helLo", "hellO"]
I would go about it as follows:
def wave(str)
str = str.downcase # so we can ensure a wave even if the original string had capitalization
str.each_char.with_index.map do |c,idx|
str[0...idx].concat(c.upcase,str[idx.+(1)..-1])
end
end
wave("hello")
#=> ["Hello", "hEllo", "heLlo", "helLo", "hellO"]
str.each_char.with_index.map do |c,idx| - This converts the String into an Enumerator and yields each character and its index to the map block.
str[0...idx] - In the block we slice the string into characters 0 through index (exclusive)
.concat(c.upcase,str[idx.+(1)..-1]) - Then we concatenate that with the current character upcased and the remaining portion of the String (index + 1 through the end of the String)
First 2 passes will look like:
# idx = 0
# c = "h"
# str[0...idx].concat(c.upcase,str[idx.+(1)..-1])
"".concat("H","ello")
# idx = 1
# c = "e"
# str[0...idx].concat(c.upcase,str[idx.+(1)..-1])
"h".concat("E","llo")

How to fix incorrect character counting code

I have a question about mysterious 'e' characters appearing in my counts hash.
My initial approach was clunky and inelegant:
def letter_count(str)
counts = {}
words = str.split(" ")
words.each do |word|
letters = word.split("")
letters.each do |letter|
if counts.include?(letter)
counts[letter] += 1
else
counts[letter] = 1
end
end
end
counts
end
This approach worked, but I wanted to make it a little more readable, so I abbreviated it to:
def letter_count(str)
counts = Hash.new(0)
str.split("").each{|letter| counts[letter] += 1 unless letter == ""}
counts
end
This is where I encountered the issue, and fixed it by using:
str.split("").each{|letter| counts[letter] += 1 unless letter == " "} # added a space.
I don't understand why empty spaces were being represented by the letter 'e' or being counted at all.
I don't understand why empty spaces were being represented by the letter 'e' or being counted at all.
I can't duplicate the problem:
def letter_count(str)
counts = Hash.new(0)
str.split("").each{|letter| counts[letter] += 1 unless letter == ""}
counts
end
letter_count('a cat') # => {"a"=>2, " "=>1, "c"=>1, "t"=>1}
"empty spaces"? There's no such thing. A space is not empty; It's considered blank but not empty:
' '.empty? # => false
Loading the ActiveSupport extension:
require 'active_support/core_ext/object/blank'
' '.blank? # => true
spaces are valid characters, which is why they're being counted. You have to disallow them if you don't want them counted.
For reference, here's how I'd do it:
def letter_count(str)
str.chars.each_with_object(Hash.new(0)) { |l, h| h[l] += 1 }
end
letter_count('a cat') # => {"a"=>2, " "=>1, "c"=>1, "t"=>1}
A messier way would be:
def letter_count(str)
str.chars.group_by { |c| c }.map { |char, chars| [char, chars.count] }.to_h
end
Breaking that down:
def letter_count(str)
str.chars # => ["a", " ", "c", "a", "t"]
.group_by { |c| c } # => {"a"=>["a", "a"], " "=>[" "], "c"=>["c"], "t"=>["t"]}
.map { |char, chars| [char, chars.count] } # => [["a", 2], [" ", 1], ["c", 1], ["t", 1]]
.to_h # => {"a"=>2, " "=>1, "c"=>1, "t"=>1}
end
Ruby already has String#each_char which you could use.
def char_count(string)
counts = Hash.new(0)
string.each_char { |char|
counts[char] += 1
}
return counts
end
puts char_count("Basset hounds got long ears").inspect
# {"B"=>1, "a"=>2, "s"=>4, "e"=>2, "t"=>2, " "=>4, "h"=>1,
# "o"=>3, "u"=>1, "n"=>2, "d"=>1, "g"=>2, "l"=>1, "r"=>1}
As for why you're getting the wrong characters, are you sure you're passing in the string you think you are?

How to count a string elements' occurrence in another string in ruby?

How can I check how many times a phrase occurs in a string?
For example, let's say the phrase is donut
str1 = "I love donuts!"
#=> returns 1 because "donuts" is found once.
str2 = "Squirrels do love nuts"
#=> also returns 1 because of 'do' and 'nuts' make up donut
str3 = "donuts do stun me"
#=> returns 2 because 'donuts' and 'do stun' has all elements to make 'donuts'
I checked this SO that suggests using include, but it only works if donuts is spelled in order.
I came up with this, but it doesn't stop spelling after all elements of "donuts"is spelled. i.e. "I love donuts" #=> ["o", "d", "o", "n", "u", "t", "s"]
def word(arr)
acceptable_word = "donuts".chars
arr.chars.select { |name| acceptable_word.include? name.downcase }
end
How can I check how many occurrences of donuts are there in a given string? No edge cases. Input will always be String, no nil. If it contains elements of donut only it should not count as 1 occurrence; it needs to contain donuts, doesn't have to be in order.
Code
def count_em(str, target)
target.chars.uniq.map { |c| str.count(c)/target.count(c) }.min
end
Examples
count_em "I love donuts!", "donuts" #=> 1
count_em "Squirrels do love nuts", "donuts" #=> 1
count_em "donuts do stun me", "donuts" #=> 2
count_em "donuts and nuts sound too delicious", "donuts" #=> 3
count_em "cats have nine lives", "donuts" #=> 0
count_em "feeding force scout", "coffee" #=> 1
count_em "feeding or scout", "coffee" #=> 0
str = ("free mocha".chars*4).shuffle.join
# => "hhrefemcfeaheomeccrmcre eef oa ofrmoaha "
count_em str, "free mocha"
#=> 4
Explanation
For
str = "feeding force scout"
target = "coffee"
a = target.chars
#=> ["c", "o", "f", "f", "e", "e"]
b = a.uniq
#=> ["c", "o", "f", "e"]
c = b.map { |c| str.count(c)/target.count(c) }
#=> [2, 2, 1, 1]
c.min
#=> 1
In calculating c, consider the first element of b passed to the block and assigned to the block variable c.
c = "c"
Then the block calculation is
d = str.count(c)
#=> 2
e = target.count(c)
#=> 1
d/e
#=> 2
This indicates that str contains enough "c"'s to match "coffee" twice.
The remaining calculations to obtain c are similar.
Addendum
If the characters of str matching characters target must be in the same order as those of target, the following regex could be used.
target = "coffee"
r = /#{ target.chars.join(".*?") }/i
#=> /c.*?o.*?f.*?f.*?e.*?e/i
matches = "xcorr fzefe yecaof tfe erg eeffoc".scan(r)
#=> ["corr fzefe ye", "caof tfe e"]
matches.size
#=> 2
"feeding force scout".scan(r).size
#=> 0
The questions marks in the regex are needed to make the searches non-greedy.
The solution is more or less simple (map(&:dup) is used there to avoid inputs mutating):
pattern = 'donuts'
[str1, str2, str3].map(&:dup).map do |s|
loop.with_index do |_, i|
break i unless pattern.chars.all? { |c| s.sub!(c, '') }
end
end
#⇒ [1, 1, 2]
Here's an approach with two variants, one where the letters must appear in order, and one where order is irrelevant. In both cases the frequency of each letter is respected, so "coffee" must match vs. two 'f' and two 'e' letters, "free mocha" is insufficient to match, lacking a second 'f'.
def sorted_string(string)
string.split('').sort.join
end
def phrase_regexp_sequence(phrase)
Regexp.new(
phrase.downcase.split('').join('.*')
)
end
def phrase_regexp_unordered(phrase)
Regexp.new(
phrase.downcase.gsub(/\W/, '').split('').sort.chunk_while(&:==).map do |bit|
"#{bit[0]}{#{bit.length}}"
end.join('.*')
)
end
def contains_unordered(phrase, string)
!!phrase_regexp_unordered(phrase).match(sorted_string(string.downcase))
end
def contains_sequence(phrase, string)
!!phrase_regexp_sequence(phrase).match(string.downcase)
end
strings = [
"I love donuts!",
"Squirrels do love nuts",
"donuts do stun me",
"no stunned matches",
]
phrase = 'donut'
strings.each do |string|
puts '%-30s %s %s' % [
string,
contains_unordered(phrase, string),
contains_sequence(phrase, string)
]
end
# => I love donuts! true true
# => Squirrels do love nuts true true
# => donuts do stun me true true
# => no stunned matches true false
Simple solution:
criteria = "donuts"
str1 = "I love donuts!"
str2 = "Squirrels do love nuts"
str3 = "donuts do stun me"
def strings_construction(criteria, string)
unique_criteria_array = criteria.split("").uniq
my_hash = {}
# Let's count how many times each character of the string matches a character in the string
unique_criteria_array.each do |char|
my_hash[char] ? my_hash[char] = my_hash[char] + 1 : my_hash[char] = string.count(char)
end
my_hash.values.min
end
puts strings_construction(criteria, str1) #=> 1
puts strings_construction(criteria, str2) #=> 1
puts strings_construction(criteria, str3) #=> 2

Find all the possible permutations using Ruby and recursion

I've been trying to solve a simple quiz question to find all the possible permutation of a string using Ruby and recursion.
I have the following Ruby code:
def permutation(string)
return [string] if string.size < 2
chr = string.chars.first
perms = permutation(string[1..-1])
result = []
for perm in perms
for i in (0..perm.size)
result << (perm[0..i] + chr + perm[i..-1])
end
end
return result
end
Whenever I try to test the code with puts permutation("abc") I get the following output:
cacbc
cbabc
cbcac
cbca
cacb
cbab
cba
Theoretically speaking it's supposed to be a very simple and straightforward problem, but I'm sure I'm doing something wrong. Most probably it's something with the ranges of the loops. And I know that Ruby Array class has instance method permutation to do that but I'm trying to solve it for practising.
Please note that the complexity is O(N!) for the current implementation. Is there anyway to enhance the performance further?
To see what the difficulty may be, let's try it with an even simpler example:
string = "ab"
Your desired result is ["ab", "ba"]. Let's see what you get:
string.size #=> 2
so we don't return when
return [string] if string.size < 2
#=> return ["ab"] if "ab".size < 2
is executed.
Next we calculate:
chr = string.chars.first #=> "a"
Notice that a more direct way of making this calculation is as follows:
chr = string[0] #=> "a"
or, better, using String#chr,
chr = string.chr #=> "a"
The latter illustrates why chr is not the best choice for the variable name.
Next
perms = permutation(string[1..-1])
#=> = permutation("b")
I will now indent the return values to emphasize that we are calling permutation a second time. permuation's argument is:
string #=> "b"
Now when we execute:
return [string] if string.size < 2
#=> return ["b"] if "b".size < 2
we return ["b"], so (back to original call to permutation):
perms = ["b"]
to go with chr => "a", calculated earlier. Next:
result = []
for perm in perms
for i in (0..perm.size)
result << (perm[0..i] + chr + perm[i..-1])
end
end
As perms contains only the single element "b", the two for loops simplify to:
for i in (0.."b".size)
result << ("b"[0..i] + "a" + "b"[i..-1])
end
which is:
for i in (0..1)
result << ("b"[0..i] + "a" + "b"[i..-1])
end
Notice that "b"[0..0], "b"[0..1] and "b"[0..-1] all equal "b"[0], which is just "b", and "b"[1..-1] #=> ''. Therefore, when i => 0, we execute:
result << ("b"[0..0] + "a" + "b"[0..-1])
#=> result << ("b" + "a" + "b")
#=> result << "bab"
and when i => 1:
result << ("b"[0..1] + "a" + "b"[1..-1])
#=> result << ("b" + "a" + "")
#=> result << "ba"
so:
result => ["bab" + "ba"]
which clearly is not what you want.
What you need to do is is change the double for loops to:
for perm in perms
result << chr + perm
for i in (1..perm.size-1)
result << (perm[0..i-1] + chr + perm[i..-1])
end
result << perm + chr
end
which could be written more compactly by employing the method String#insert:
for perm in perms
for i in (0..perm.size)
result << perm.dup.insert(i,chr)
end
end
which you would normally see written like this:
perms.each_with_object([]) do |perm, result|
(0..perm.size).each { |i| result << perm.dup.insert(i,chr) }
end
Notice that we have to .dup the string before sending insert, as insert modifies the string.
Doing it like this, you don't need result = []. Neither do you need return result, as parms.each_with_object returns result and if there is no return statement, the method returns the last quantity calculated. Also, you don't need the temporary variable perms (or ch, if desired).
Putting this altogether, we have:
def permutation(string)
return [string] if string.size < 2
ch = string[0]
permutation(string[1..-1]).each_with_object([]) do |perm, result|
(0..perm.size).each { |i| result << perm.dup.insert(i,ch) }
end
end
Let's try it:
permutation("ab")
#=> ["ab", "ba"]
permutation("abc")
#=> ["abc", "bac", "bca", "acb", "cab", "cba"]
permutation("abcd")
#=> ["abcd", "bacd", "bcad", "bcda", "acbd", "cabd",
# "cbad", "cbda", "acdb", "cadb", "cdab", "cdba",
# "abdc", "badc", "bdac", "bdca", "adbc", "dabc",
# "dbac", "dbca", "adcb", "dacb", "dcab", "dcba"]
Eki, which one are you in the picture?
You can use Array#permutation:
def permutation(string)
string.permutation(string.size).to_a
end
permutation('abc'.chars)
# => [["a", "b", "c"], ["a", "c", "b"], ["b", "a", "c"], ["b", "c", "a"],
# ["c", "a", "b"], ["c", "b", "a"]]
UPDATE Without usign Array#permutation:
def permutation(string)
return [''] if string.empty?
chrs = string.chars
(0...string.size).flat_map { |i|
chr, rest = string[i], string[0...i] + string[i+1..-1]
permutation(rest).map { |sub|
chr + sub
}
}
end
permutation('abc')
# => ["abc", "acb", "bac", "bca", "cab", "cba"]

Why is my all? function not working? What's wrong with my syntax?

I originally wrote a method to take a word and find out if its vowels were in alphabetical order. I did it by using the code below:
def ordered_vowel_word?(word)
vowels = ["a", "e", "i", "o", "u"]
letters_arr = word.split("")
vowels_arr = letters_arr.select { |l| vowels.include?(l) }
(0...(vowels_arr.length - 1)).all? do |i|
vowels_arr[i] <= vowels_arr[i + 1]
end
end
However, I decided to try to change it by using an all? method. I tried to do so with the following code:
def ordered_vowel_word?(word)
vowels = ["a","e", "i", "o", "u"]
splitted_word = word.split("")
vowels_in_word = []
vowels_in_word = splitted_word.select {|word| vowels.include?(word)}
vowels_in_word.all? {|x| vowels_in_word[x]<= vowels_in_word[x+1]}
end
ordered_vowel_word?("word")
Anyone have any ideas why it isnt working? I would have expected this to work.
Also, if anyone has a better solution please feel free to post. Thanks!
Examples are:
it "does not return a word that is not in order" do
ordered_vowel_words("complicated").should == ""
end
it "handle double vowels" do
ordered_vowel_words("afoot").should == "afoot"
end
it "handles a word with a single vowel" do
ordered_vowel_words("ham").should == "ham"
end
it "handles a word with a single letter" do
ordered_vowel_words("o").should == "o"
end
it "ignores the letter y" do
ordered_vowel_words("tamely").should == "tamely"
end
Here is how I would do it:
#!/usr/bin/ruby
def ordered?(word)
vowels = %w(a e i o u)
check = word.each_char.select { |x| vowels.include?(x) }
# Another option thanks to #Michael Papile
# check = word.scan(/[aeiou]/)
puts check.sort == check
end
ordered?("afoot")
ordered?("outaorder")
Output is:
true
false
In your original example, you use the array values (String) as array indices which should be Integers when the all? method fires.
def ordered_vowel_word?(word)
vowels = ["a","e", "i", "o", "u"]
splitted_word = word.split("")
vowels_in_word = []
vowels_in_word = splitted_word.select {|word| vowels.include?(word)}
p vowels_in_word #=> ["o"]
vowels_in_word.all? {|x| vowels_in_word[x]<= vowels_in_word[x+1]}
end
p ordered_vowel_word?("word")
#=> `[]': no implicit conversion of String into Integer (TypeError)
vowels_in_word contains only 'o', and inside the vowels_in_word.all? {|x| vowels_in_word[x]<= vowels_in_word[x+1]} the expression vowels_in_word[x] means vowels_in_word["o"], which in-turn throws error as index can never be string.

Resources