I wanted to write an anagram solver that would return a list of the anagrams of the same length of a given word/combination of characters, such that:
same_length_anagrams("deify") => "edify"
or
same_length_anagrams("ogb") => "bog", "gob"
or
same_length_anagrams("quone") => nil
It seemed clear that a good way to tell if two words are anagrams is by checking for a match when both words' characters are sorted alphabetically, i.e.
("deify").chars.to_a.sort.join == ("edify").chars.to_a.sort.join
The difficult part, for me, was figuring out how to speedily iterate through an enormous word list to find matches.
In my first attempt, I created a hash from a list of (English) words in which values
are words and keys
are characters in those words, sorted alphabetically. Anagrams are found by returning the values associated with an input word's sorted characters.
Then, just to see if a different approach would be faster and/or require fewer lines of code, I wrote another method which simply uses a loop to check each entry of the word list for a match.
So far, one method does not appear to be substantially faster than the other.
class AnagramSolver
def find_same_length_anagrams(word, word_list)
# Create 'dictionary' by reading word list into array
word_list = File.open(word_list).read.split(/\b/)
# Map array to hash
word_hash = Hash[word_list.map { |x| [x.upcase, x.chars.to_a.sort.join.upcase] }]
# Group hash elements by values
word_hash = word_hash.group_by { |key, value| word_hash[key] }
# Remove non-word values
word_hash.each_pair do |key, value|
word_hash[key] = value.transpose.delete_at(0)
end
# Search for anagrams
if word_hash[word.chars.to_a.sort.join.upcase] == nil
puts "Sorry! No #{word.length}-letter anagrams found."
else
puts word_hash[word.chars.to_a.sort.join.upcase]
end
end
end
end
solver = AnagramSolver.new
puts "Anagram this:"
solver.same_length_anagrams(gets.chomp, 'wordlist.txt')
I like the idea of using a hash, but my implementation seems cumbersome and slow. Seems like it could use improvement in the following areas:
- Aesthetics/readability: Nicer-looking, more compact code for creating the hash
- Functionality: Defining the creation of the hash as a separate activity which occurs when the program loads, rather than each time I call
#same_length_anagrams
.
class AnagramSolver
def same_length_anagrams(word, word_list)
# Create 'dictionary' by reading word list into array
dictionary = File.open(word_list).read.split(/\b/)
result = []
# Sort the characters of the input word alphabetically
word_characters = word.chars.to_a.sort.join.downcase
# Use a loop to check each dictionary entry for a match
dictionary.each do |x|
if x.chars.to_a.sort.join.downcase == word_characters
result.push(x)
end
end
# Return anagrams
if result == []
puts "Sorry! No #{word.length}-letter anagrams found."
else
puts "Anagrams of '#{word}':"
puts result
end
end
end
solver = AnagramSolver.new
puts "Anagram this:"
solver.same_length_anagrams(gets.chomp, 'wordlist.txt')