Given a set of words, we need to find the anagram words and display each category alone using the best algorithm.
input:
man car kile arc none like
<
JavaScript version. using hashing.
Time Complexity: 0(nm) , where n is number of words, m is length of word
var words = 'cat act mac tac ten cam net'.split(' '),
hashMap = {};
words.forEach(function(w){
w = w.split('').sort().join('');
hashMap[w] = (hashMap[w]|0) + 1;
});
function print(obj,key){
console.log(key, obj[key]);
}
Object.keys(hashMap).forEach(print.bind(null,hashMap))
I used a Godel-inspired scheme:
Assign the primes P_1 to P_26 to the letters (in any order, but to obtain smallish hash values best to give common letters small primes).
Built a histogram of the letters in the word.
Then the hash value is the product of each letter's associated prime raised to the power of its frequency. This gives a unique value to every anagram.
Python code:
primes = [2, 41, 37, 47, 3, 67, 71, 23, 5, 101, 61, 17, 19, 13, 31, 43, 97, 29, 11, 7, 73, 83, 79, 89, 59, 53]
def get_frequency_map(word):
map = {}
for letter in word:
map[letter] = map.get(letter, 0) + 1
return map
def hash(word):
map = get_frequency_map(word)
product = 1
for letter in map.iterkeys():
product = product * primes[ord(letter)-97] ** map.get(letter, 0)
return product
This cleverly transforms the tricky problem of finding subanagrams into the (also known to be tricky) problem of factoring large numbers...
I have implemented this before with a simple array of letter counts, e.g.:
unsigned char letter_frequency[26];
Then store that in a database table together with each word. Words that have the same letter frequency 'signature' are anagrams, and a simple SQL query then returns all anagrams of a word directly.
With some experimentation with a very large dictionary, I found no word that exceeded a frequency count of 9 for any letter, so the 'signature' can be represented as a string of numbers 0..9 (The size could be easily halved by packing into bytes as hex, and further reduced by binary encoding the number, but I didn't bother with any of this so far).
Here is a ruby function to compute the signature of a given word and store it into a Hash, while discarding duplicates. From the Hash I later build a SQL table:
def processword(word, downcase)
word.chomp!
word.squeeze!(" ")
word.chomp!(" ")
if (downcase)
word.downcase!
end
if ($dict[word]==nil)
stdword=word.downcase
signature=$letters.collect {|letter| stdword.count(letter)}
signature.each do |cnt|
if (cnt>9)
puts "Signature overflow:#{word}|#{signature}|#{cnt}"
end
end
$dict[word]=[$wordid,signature]
$wordid=$wordid+1
end
end
Just want to add simple python solution in addition to the other useful answers:
def check_permutation_group(word_list):
result = {}
for word in word_list:
hash_arr_for_word = [0] * 128 # assuming standard ascii
for char in word:
char_int = ord(char)
hash_arr_for_word[char_int] += 1
hash_for_word = ''.join(str(item) for item in hash_arr_for_word)
if not result.get(hash_for_word, None):
result[str(hash_for_word)] = [word]
else:
result[str(hash_for_word)] += [word]
return list(result.values())
You will need large integers (or a bit vector actually) but the following might work
the first occurrence of each letter get's assigned the bit number for that letter, the second occurence gets the bit number for that letter + 26.
For example
a #1 = 1 b #1 = 2 c #1 = 4 a #2 = 2^26 b #2 = 2 ^ 27
You can then sum these together, to get a unique value for the word based on it's letters.
Your storage requirements for the word values will be:
n * 26 bits
where n is the maximum number of occurrences of any repeated letter.
In C, I just implemented the following hash which basically does a 26-bit bitmask on whether the word in the dictionary has a particular letter in it. So, all anagrams have the same hash. The hash doesn't take into account repeated letters, so there will be some additional overloading, but it still manages to be faster than my perl implementation.
#define BUCKETS 49999
struct bucket {
char *word;
struct bucket *next;
};
static struct bucket hash_table[BUCKETS];
static unsigned int hash_word(char *word)
{
char *p = word;
unsigned int hash = 0;
while (*p) {
if (*p < 97 || *p > 122) {
return 0;
}
hash |= 2 << (*p - 97);
*p++;
}
return hash % BUCKETS;
}
Overloaded buckets created and added as linked list, etc. Then just write a function that makes sure that the words that match the hash value are the same length and that the letters in each are 1 to 1 and return that as a match.