My Ruby Anagram not working correctly - ruby

i was given an assignment to write an Anagram program
below is what i came up with
class Anagram
attr_accessor :anagram_value
def initialize(value)
#anagram_value = value
end
def matches(*collection)
matches = []
matches = collection.select do |word|
(word.length == #anagram_value.length) ? is_an_anagram?(word) : false
end
return matches
end
def is_an_anagram?(word)
return get_word_ord_sum(word) == get_word_ord_sum(#anagram_value)
end
def get_word_ord_sum(word)
sum = 0
word.split("").each { |c| sum += c.ord }
Areturn sum
end
end
while the Above works using the following cases, Surprisingly.
it "detects multiple Anagrams" do
subject = Anagram.new("allergy")
matches = subject.matches('gallery', 'ballerina', 'regally', 'clergy', 'largely', 'leading');
expect(matches).to eq ['gallery', 'regally', 'largely']
end
it actually fails the following
it "no matches" do
subject = Anagram.new("abc")
matches = subject.matches("bbb")
expect(matches).to eq []
end

The problem is that 97 + 98 + 99 == 98 + 98 + 98. Aka, the sum of the character numbers does not uniquely map to the histogram of a given string.
A way to fix it would be to map get_word_ord_sum to something else. For example, the "smallest" anagram will do. However, note it's O(nlgn):
word.chars.sort.join
EDIT: Expanding on the idea to use Array#group_by, replace get_word_ord_sum with:
word.downcase.chars.group_by(&:itself)
Now you will get a histogram-like hash, and since order of keys while comparing hashes doesn't matter, you will get your desired result in O(n).

This might help.
words = ['demo', 'none', 'tied', 'evil', 'dome', 'mode', 'live',
'fowl', 'veil', 'wolf', 'diet', 'vile', 'edit', 'tide',
'flow', 'neon']
groups = words.group_by { |word| word.split('').sort }
Return groups:
{["d", "e", "m", "o"]=>["demo", "dome", "mode"], ["e", "n", "n", "o"]=>["none", "neon"], ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"], ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"], ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]}
groups.each { |x, y| p y }
Returns each value:
["demo", "dome", "mode"]
["none", "neon"]
["tied", "diet", "edit", "tide"]
["evil", "live", "veil", "vile"]
["fowl", "wolf", "flow"]

Related

How do I access the current array when using map/select

I have the following (working) code I'm trying to convert into a more concise snippet using either #map or #select.
def duplicate_string(string)
s_list = []
string.downcase.chars.each do |char|
if string.index(char) != string.rindex(char) && s_list.include?(char) == false
s_list << char if char != ' '
end
end
s_list
end
puts duplicate_string("I am a duplicate string") == ["i", "a", "t"] #true
This is what I've come up with so far, but I don't know how to access the current array that's been stored by #map or #select and using self isn't working
def duplicate_string_with_map(string)
string.downcase.chars.select { |char| string.index(char) != string.rindex(char) && self.include?(char) == false && char != ' ' }
end
The following code would solve your purpose:
def duplicate_string_with_map(string)
(string.downcase.chars.select { |char| string.index(char) != string.rindex(char) && char != ' ' }).uniq
end
Here you need not check include condition as you are already ensuring string.index(char) != string.rindex(char).
However, for a better ruby approach, I would suggest you to re-open String class and write a method there.
It would look something like this:
class String
def duplicate_characters_array
(downcase.chars.select { |char| index(char) != rindex(char) && char != ' ' }).uniq
end
end
string = "I am a duplicate string"
string.duplicate_characters_array
You don't need to access the array and you don't need to use Array#map.
There are many ways to reach the goal. One of them is to split the string in chars then group the chars (get a hash), reject the groups of space character and the groups smaller than two elements and return the keys of the remaining groups:
"I am a duplicate string"
.downcase
.chars
.group_by{|i| i}
.reject{|k, v| k == ' ' || v.length < 2}
.keys
# ["a", "i", "t"]
Here we can make use of a helper method, Array#difference. The method is explained here. Note that that link contains a link to an SO answer where I cite examples of its use. Though I proposed that the method be added to the Ruby core there appears to be little interest in doing so.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Here we can use this helper as follows.
def duplicate_string(str)
a = str.gsub(/\s/,'').downcase.reverse.chars
a.difference(a.uniq).uniq.reverse
end
duplicate_string "I am a duplicate string"
#=> ["a", "i", "t"]
The steps are as follows.
str = "I am a duplicate string"
b = str.gsub(/\s/,'')
#=> "Iamaduplicatestring"
c = b.downcase
#=> "iamaduplicatestring"
d = c.reverse
#=> "gnirtsetacilpudamai"
a = d.chars
#=> ["g", "n", "i", "r", "t", "s", "e", "t", "a", "c", "i", "l", "p",
# "u", "d", "a", "m", "a", "i"]
e = a.uniq
#=> ["g", "n", "i", "r", "t", "s", "e", "a", "c", "l", "p", "u", "d", "m"]
f = a.difference(e)
#=> ["t", "i", "a", "a", "i"]
g = f.uniq
#=> ["t", "i", "a"]
g.reverse
#=> ["a", "i", "t"]
The key step is the calculation of f. For each element c of e, f contains n-1 instances of c, where n is the number of instances of c in a. The method therefore excludes characters other than spaces that appear in the string exactly once.

private method called noMethodError ruby

I've been trying to work the following problem out and ran into the error. The point of the problem is to use a given key sequence to encrypt a string. For example, when given "cat" and [1,2,3] the result should be "dcw"
Any suggestions? the error was the following
def vigenere_cipher(string, key_sequence)
keyIndex=0
string=string.each_char.map do |c|
c=c.shift!(c,keyIndex)
keyIndex+=1
if keyIndex=key_sequence.length
keyIndex=0
end
end
return string
end
def shift!(c,keyIndex)
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"]
inititalLetterIndex=alphabet.index(c)
finalLetterIndex=alphabet[inititalLetterIndex+keyIndex]
return alphabet[finalLetterIndex]
end
vigenere_cipher("cat", [1,2,3])
# private method `shift!' called for "c":String (NoMethodError)
You are trying to call shift! on string object that does not define on String Class, instead you defined on main object. You can call it like shift!(c,keyIndex) instead of c.shift!(c,keyIndex)
If you want to call you method shift! on a string, you will have to define it on String class.
class String
def shift!(keyIndex)
# you can access `c` using `self` here
...
end
end
Then you can call it as c.shift!(keyIndex) (Note the arguments are different).
Step 1
cipher.rb:4:in `block in vigenere_cipher': private method `shift!' called for "c":String (NoMethodError)
shift! isn't defined in String class, but at the top level.
So replace c=c.shift!(c,keyIndex) by c=shift!(c,keyIndex)
Step 2
cipher.rb:17:in `[]': no implicit conversion of String into Integer (TypeError)
Line 16 defines :
finalLetterIndex=alphabet[inititalLetterIndex+keyIndex]
alphabet contains letters as Strings, so finalLetterIndex isn't an index (Numeric), but a String.
On line 17, you try to use this String as an index.
Replace line 16 with :
finalLetterIndex=inititalLetterIndex+keyIndex
Step 3
Your script doesn't raise any exception anymore. It also doesn't display anything, so add a puts to the last line :
puts vigenere_cipher("cat", [1,2,3]).inspect
It returns :
[0, 0, 0]
Step 4
keyIndex seems to be stuck at 0. Why?
Look at line 6 :
if keyIndex=key_sequence.length
It doesn't test an equality, it assigns keyIndex to key_sequence.length.
Since any number is truthy in Ruby, it executes the code inside the if statement. Replace with
if keyIndex==key_sequence.length
Step 5
Your code returns [nil, nil, 0]. Why?
string is defined as the result of map. map returns an Array, in which each element is the result of the last executed command inside the block : in this case, the if statement.
if returns nil when the condition isn't satisfied, and returns the last executed command otherwise. In this case 0.
Add c at the last line of your map block.
Step 6
Your code now returns ["c", "b", "v"]. Why?
You only shift by shiftIndex, not by the amount defined in key_sequence Array. Replace
c=shift!(c,keyIndex)
with
c=shift!(c,key_sequence[keyIndex])
Step 7
Your code returns ["d", "c", "w"]. Almost there!
Ruby is a dynamic language. You're free to overwrite the String string with an Array, but it will confuse others and your future self.
Use array or letters instead of string, and return letters.join
Your script now returns "dcw".
It should look like :
def vigenere_cipher(string, key_sequence)
keyIndex=0
letters=string.each_char.map do |c|
c=shift!(c,key_sequence[keyIndex])
keyIndex+=1
if keyIndex==key_sequence.length
keyIndex=0
end
c
end
return letters.join
end
def shift!(c,keyIndex)
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"]
inititalLetterIndex=alphabet.index(c)
finalLetterIndex=inititalLetterIndex+keyIndex
return alphabet[finalLetterIndex]
end
Step 8
vigenere_cipher("Hello", [1,2,3])
raises
cipher.rb:17:in 'shift!': undefined method '+' for nil:NilClass (NoMethodError).
Well, 'H' isn't found in your alphabet. Use downcase :
array=string.downcase.each_char.map do |c|
Step 9
vigenere_cipher("Hello World", [1,2,3])
doesn't work either, because of the space. Delete anything that isn't a letter :
array=string.downcase.delete('^a-z').each_char.map do |c|
Step 10
vigenere_cipher("zzz", [1,2,3])
returns an empty String, because there's no letter after z.
Use modulo 26 :
return alphabet[finalLetterIndex%26]
Step 11
Remove typos, don't use camelCase for variables, remove unnecessary return and you get :
def vigenere_cipher(string, key_sequence)
key_index = 0
letters = string.downcase.delete('^a-z').each_char.map do |c|
c = shift(c, key_sequence[key_index])
key_index = (key_index + 1) % key_sequence.length
c
end
letters.join
end
def shift(c, key_index)
alphabet = ('a'..'z').to_a
initial_letter_index = alphabet.index(c)
final_letter_index = initial_letter_index + key_index
alphabet[final_letter_index % 26]
end
Step 12
Using each_char, zip and cycle, I'd rewrite the whole code this way :
class Integer
# 0 => 'a', 1 => 'b', ..., 25 => 'z', 26 => 'a'
def to_letter
('a'.ord + self % 26).chr
end
end
class String
# 'A' => '0', 'a' => 0, ..., 'z' => 25
def to_code
self.downcase.ord - 'a'.ord
end
end
def vigenere_cipher(string, key)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.cycle).map do |char, shift|
(char.to_code + shift).to_letter
end.join
end
Step 13
Wikipedia article uses a String as key :
def vigenere_cipher(string, key)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.each_char.cycle).map do |char, shift|
(char.to_code + shift.to_code).to_letter
end.join
end
vigenere_cipher('Attack at dawn!', 'LEMON').upcase # => "LXFOPVEFRNHR"
Step 14
You should also be able to decrypt the message :
def vigenere_cipher(string, key, decrypt = false)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.each_char.cycle).map do |char, shift|
(char.to_code + shift.to_code * (decrypt ? -1 : 1)).to_letter
end.join
end
vigenere_cipher("LXFOPVEFRNHR", 'LEMON', :decrypt) #=> "attackatdawn"
Well, that was longer than expected! :D

Ruby codes works

I have the ff array:
words = ['demo', 'none', 'tied', 'evil', 'dome', 'mode', 'live',
'fowl', 'veil', 'wolf', 'diet', 'vile', 'edit', 'tide',
'flow', 'neon']
And now I am trying to do an anagram like this:
["demo", "dome", "mode"]
["neon", "none"]
(etc)
So I got this code:
result = {}
words.each do |word|
key = word.split('').sort.join
if result.has_key?(key)
result[key].push(word)
else
result[key] = [word]
end
end
result.each do |k, v|
puts "------"
p v
end
I understand how the word got split and joined but this part here is not clear to me:
if result.has_key?(key)
result[key].push(word)
else
result[key] = [word]
end
On the code above it's pretty obvious that result is an empty hash and now we're asking if it has a key of the sorted/joined key via if result.has_key?(key) How does that work? Why ask an empty hash if it has a key of the selected key via word iteration?
result[key].push(word) also is not clear to me. So is this code putting the key inside the result as its key? or the word itself?
result[key] = [word] this one also. Is it adding the word inside the array with the key?
Sorry I am bit confused.
The results is only empty on the first iteration of the loop. The line
if result.has_key?(key)
is checking if the key created by sorting the letters in the current word exists, and in the case of the first iteration when it's empty, yes, it is obviously not there this time, but it still needs to check every other time too.
Now, when a particular key does not exist yet in results, that key is added to results and a new array containing the current word is added as the value for that key, in the line
result[key] = [word]
When a key already exists in results, that means there is already an array containing at least one word, so the current word is added into that array, in the line
result[key].push(word)
Stepping through what's happening:
words = ['demo', 'neon', 'dome', 'mode', 'none']
// first iteration of the loop
word = 'demo'
key = 'demo' // the letters in "demo" happen to already be sorted
Is 'demo' a key in results?
results is currently {}
No, 'demo' is not a key in {}
Add 'demo' as a key, and add an array with 'demo' inside
results is now { 'demo' => ['demo'] }
// second iteration
word = 'neon'
key = 'enno'
Is 'enno' a key in results?
results is currently { 'demo' => ['demo'] }
No, 'enno' is not a key in { 'demo' => ['demo'] }
Add 'enno' as a key, and add an array with 'neon' inside
results is now { 'demo' => ['demo'], 'enno' => ['neon'] }
// third iteration
word = 'dome'
key = 'demo'
Is 'demo' a key in results?
results is currently { 'demo' => ['demo'], 'enno' => ['neon'] }
Yes, 'demo' is a key in { 'demo' => ['demo'], 'enno' => ['neon'] }
Add 'dome' to the array at key = 'demo'
results is now { 'demo' => ['demo', 'dome'], 'enno' => ['neon'] }
// ... and so on
There are tools that help you figure this stuff out on your own. Here's an example using Seeing Is Believing with vim:
words = ['demo', 'mode']
result = {}
words.each do |word| # => ["demo", "mode"]
key = word # => "demo", "mode"
.split('') # => ["d", "e", "m", "o"], ["m", "o", "d", "e"]
.sort # => ["d", "e", "m", "o"], ["d", "e", "m", "o"]
.join # => "demo", "demo"
result # => {}, {"demo"=>["demo"]}
if result.has_key?(key)
result[key].push(word) # => ["demo", "mode"]
else
result[key] = [word] # => ["demo"]
end
result # => {"demo"=>["demo"]}, {"demo"=>["demo", "mode"]}
end
result.each do |k, v| # => {"demo"=>["demo", "mode"]}
puts "------"
p v
end
# >> ------
# >> ["demo", "mode"]
Other tools I'd use are Irb and Pry.
Considering that you have answers that provide good explanations of your problem, I would like to present some more Ruby-like approaches that could be used. All of these methods create a hash h whose values are arrays of words that are anagrams of each other, which can be extracted from the hash by executing h.values.
Use Enumerable#group_by and Array#sort
This is arguably the most direct approach.
words.group_by { |w| w.each_char.sort }.values
#=> [["demo", "dome", "mode"], ["none", "neon"], ["tied", "diet", "edit", "tide"],
# ["evil", "live", "veil", "vile"], ["fowl", "wolf", "flow"]]
group_by produces
words.group_by { |w| w.each_char.sort }
#=> {["d", "e", "m", "o"]=>["demo", "dome", "mode"],
# ["e", "n", "n", "o"]=>["none", "neon"],
# ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"],
# ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"],
# ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]}
after which it is a simple matter of extracting the values of this hash.
Build a hash by appending words to arrays that are the values of the hash
words.each_with_object({}) { |w,h| (h[w.each_char.sort] ||= []) << w }.values
#=> [["demo", "dome", "mode"], ["none", "neon"], ["tied", "diet", "edit", "tide"],
# ["evil", "live", "veil", "vile"], ["fowl", "wolf", "flow"]]
When "demo" is passed to the block the hash h is empty, so the block variables are assigned values
w = "demo"
h = {}
and the block calculation is performed:
h[["d", "e", "m", "o"]] ||= []) << w
as
w.each_char.sort
#=> ["d", "e", "m", "o"]
Ruby first expands this to
h[["d", "e", "m", "o"]] = (h[["d", "e", "m", "o"]] ||= []) << "demo"
At this point h has no keys, so h[["d", "e", "m", "o"]] evaluates to nil. The expression therefore becomes
h[["d", "e", "m", "o"]] = (nil ||= []) << "demo"
= [] << "demo"
= ["demo"]
Later, when "dome" is encountered,
w = "dome"
w.each_char.sort
#=> ["d", "e", "m", "o"]
and since h already has this key, the block calculation is as follows.
h[["d", "e", "m", "o"]] = (h[["d", "e", "m", "o"]] ||= []) << "dome"
= (["demo"] ||= []) << "dome"
= ["demo"] << "dome"
= ["demo", "dome"]
We obtain
words.each_with_object({}) { |w,h| (h[w.each_char.sort] ||= []) << w }
#=> {["d", "e", "m", "o"]=>["demo", "dome", "mode"],
# ["e", "n", "n", "o"]=>["none", "neon"],
# ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"],
# ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"],
# ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]}
after which the values are extracted.
A variant of this is the following.
words.each_with_object(Hash.new { |h,k| h[k] = []}) { |w,h|
h[w.each_char.sort] << w }.values
See the doc for Hash::new for an explanation, in particular the discussion of default values given by a block.
For each word, merge a hash having a single key into an initially-empty hash
words.each_with_object({}) { |w,h|
h.update(w.each_char.sort=>[w]) { |_,o,n| o+n } }.values
The argument w.each_char.sort=>[w] is shorthand for { w.each_char.sort=>[w] }.
This uses the form of Hash#update (aka merge!) that employs a "resolution" block (here { |_,o,n| o+n }) to determine the values of keys that are present in both hashes begin merged. See the doc for a description of that block's three keys (the first block variable, the common key, is not used in this calculation, which is why I used an underscore).

Ruby search for word in string

Given input = "helloworld"
The output should be output = ["hello", "world"]
Given I have a method called is_in_dict? which returns true if there's a word given
So far i tried:
ar = []
input.split("").each do |f|
ar << f if is_in_dict? f
// here need to check given char
end
How to achieve it in Ruby?
Instead of splitting the input into characters, you have to inspect all combinations, i.e. "h", "he", "hel", ... "helloworld", "e", "el" , "ell", ... "elloworld" and so on.
Something like this should work:
(0..input.size).to_a.combination(2).each do |a, b|
word = input[a...b]
ar << word if is_in_dict?(word)
end
#=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ar
#=> ["hello", "world"]
Or, using each_with_object, which returns the array:
(0..input.size).to_a.combination(2).each_with_object([]) do |(a, b), array|
word = input[a...b]
array << word if is_in_dict?(word)
end
#=> ["hello", "world"]
Another approach is to build a custom Enumerator:
class String
def each_combination
return to_enum(:each_combination) unless block_given?
(0..size).to_a.combination(2).each do |a, b|
yield self[a...b]
end
end
end
String#each_combination yields all combinations (instead of just the indices):
input.each_combination.to_a
#=> ["h", "he", "hel", "hell", "hello", "hellow", "hellowo", "hellowor", "helloworl", "helloworld", "e", "el", "ell", "ello", "ellow", "ellowo", "ellowor", "elloworl", "elloworld", "l", "ll", "llo", "llow", "llowo", "llowor", "lloworl", "lloworld", "l", "lo", "low", "lowo", "lowor", "loworl", "loworld", "o", "ow", "owo", "owor", "oworl", "oworld", "w", "wo", "wor", "worl", "world", "o", "or", "orl", "orld", "r", "rl", "rld", "l", "ld", "d"]
It can be used with select to easily filter specific words:
input.each_combination.select { |word| is_in_dict?(word) }
#=> ["hello", "world"]
This seems to be a task for recursion. In short you want to take letters one by one until you get a word which is in dictionary. This however will not guarantee that the result is correct, as the remaining letters may not form a words ('hell' + 'oworld'?). This is what I would do:
def split_words(string)
return [[]] if string == ''
chars = string.chars
word = ''
(1..string.length).map do
word += chars.shift
next unless is_in_dict?(word)
other_splits = split_words(chars.join)
next if other_splits.empty?
other_splits.map {|split| [word] + split }
end.compact.inject([], :+)
end
split_words('helloworld') #=> [['hello', 'world']] No hell!
It will also give you all possible splits, so pages with urls like penisland can be avoided
split_words('penisland') #=> [['pen', 'island'], [<the_other_solution>]]

How do I create a histogram by iterating over an array in Ruby

So I was told to rewrite this question and outline my goal. They asked me to iterate over the array and "Use .each to iterate over frequencies and print each word and its frequency to the console... put a single space between the word and its frequency for readability."
puts "Type something profound please"
text = gets.chomp
words = text.split
frequencies = Hash.new 0
frequencies = frequencies.sort_by {|x,y| y}
words.each {|word| frequencies[word] += 1}
frequencies = frequencies.sort_by{|x,y| y}.reverse
puts word +" " + frequencies.to_s
frequencies.each do |word, frequencies|
end
Why can't it convert the string into an integer? What am I doing incorrectly?
Try this code:
puts "Type something profound please"
words = gets.chomp.split #No need for the test variable
frequencies = Hash.new 0
words.each {|word| frequencies[word] += 1}
words.uniq.each {|word| puts "#{word} #{frequencies[word]}"}
#Iterate over the words, and print each one with it's frequency.
I'd do as below :
puts "Type something profound please"
text = gets.chomp.split
I called here Enumerable#each_with_object method.
hash = text.each_with_object(Hash.new(0)) do |word,freq_hsh|
freq_hsh[word] += 1
end
I called below Hash#each method.
hash.each do |word,freq|
puts "#{word} has a freuency count #{freq}"
end
Now run the code :
(arup~>Ruby)$ ruby so.rb
Type something profound please
foo bar foo biz bar baz
foo has a freuency count 2
bar has a freuency count 2
biz has a freuency count 1
baz has a freuency count 1
(arup~>Ruby)$
chunk is a good method for this. It returns an array of 2-element arrays. The first of each is the return value of the block, the second is the array of original elements for which the block returned that value:
words = File.open("/usr/share/dict/words", "r:iso-8859-1").readlines
p words.chunk{|w| w[0].downcase}.map{|c, words| [c, words.size]}
=> [["a", 17096], ["b", 11070], ["c", 19901], ["d", 10896], ["e", 8736], ["f", 6860], ["g", 6861], ["h", 9027], ["i", 8799], ["j", 1642], ["k", 2281], ["l", 6284], ["m", 12616], ["n", 6780], ["o", 7849], ["p", 24461], ["q", 1152], ["r", 9671], ["s", 25162], ["t", 12966], ["u", 16387], ["v", 3440], ["w", 3944], ["x", 385], ["y", 671], ["z", 949]]

Resources