Skip to content

Instantly share code, notes, and snippets.

@iansinnott
Created March 13, 2015 06:02
Show Gist options
  • Save iansinnott/c6897b00c3be2e1399be to your computer and use it in GitHub Desktop.
Save iansinnott/c6897b00c3be2e1399be to your computer and use it in GitHub Desktop.
Find anagrams in an array – JavaScript
/**
* Alphabet weight: The index + 1 of each letter in the alphabet. We build this
* dictionary to allow speedy lookup of the index of any letter in the alpabet.
* We use i + 1 in order to avoid the case where 'a' would have a weight of
* zero.
*/
var ALPHABET_WEIGHT = {};
[].forEach.call('abcdefghijklmnopqrstuvwxyz', function(letter, i) {
ALPHABET_WEIGHT[letter] = i + 1;
});
/**
* Given an array of non-empty strings, return an array of all strings that are
* anagrams of at least one other string in the array. The values of the array
* don't have to be actual english words, but they should be lowercase.
* @param {array} words An array of non-empty text strings
* @return {array}
*/
function findAnagrams(words) {
var ANAGRAM_CACHE = {},
result = [];
words.forEach(function(word) {
var hash = hashWord(word);
if (!ANAGRAM_CACHE[hash])
ANAGRAM_CACHE[hash] = [word];
else
ANAGRAM_CACHE[hash].push(word);
});
// More than one word at a given key in the cache indicates an anagram. Filter
// for those values and flatten them into a single array.
return flatten(values(filterObject(ANAGRAM_CACHE, function(val, key) {
return val.length > 1;
})));
}
module.exports = findAnagrams;
// See the output in the console if this file is called directly via
// `node anagrams.js`
if (require.main === module) {
console.log(findAnagrams(['pastel', 'staple', 'banana', 'kiwi', 'late', 'tale']));
console.log(findAnagrams(['abc', 'cba', 'acb', 'zzz', 'ab', 'ba', 'c']));
console.log(findAnagrams(['abc']));
}
/**
* HELPERS
*
* The hashWord function is a necessary addition for the solution to this
* problem. However, I also wrote my own version of a few common
* underscore/lodash functions. Normally I would use one of the aforementioned
* libs but for the sake of writing this solution from scratch I wrote my own
* versions of _.filter, _.values and _.flatten. Less robust and totally
* untested :D
*/
/**
* Given a word (non-empty string) return a numeric representation of the
* anagramatical (is that a word?) properties of that word. Namely number of
* occurances of each letter in the word.
*
* Each character in the alphabet is 'weighted' by its index + 1 position in the
* alphabet. For example, 'a' has a weight of 1, b: 2, c: 3, etc.
*
* The sum of the 'alphabetical weight' of the word is calculated by summing the
* weights of each of its letters in turn. Then finally the sum is multiplied by
* the length of the word to avoid cases such as 'ab' and 'c' which would carry
* the same weight (3) if we didn't account for the length (2 and 1
* respectively).
* @param {string}
* @return {number}
*/
function hashWord(word) {
var hash = 0;
for (var i = 0; i < word.length; i++)
hash += ALPHABET_WEIGHT[word[i]];
return hash * word.length;
}
/**
* A simple array flattening function. Does not modify original array and only
* flattens one level deep.
* @param {array} arr
* @return {array}
*/
function flatten(arr) {
var result = [];
arr.forEach(function(item) {
if (!Array.isArray(item))
result.push(item);
else
result = result.concat(item);
});
return result;
}
/**
* A helper function to filter out only certain elements of an object
* Note: Not meant to be used with arrays.
* @param {object} obj
* @return {object}
*/
function filterObject(obj, pred) {
var result = {};
for (var key in obj)
if (obj.hasOwnProperty(key))
if (pred(obj[key], key)) // Call the predicate
result[key] = obj[key];
return result;
}
/**
* Helper function to extract all values from an object as an array. Works like
* Object.keys, except for the values. Why isn't there a native Object.values
* func?
* @param {object} obj
* @return {array} all values of obj
*/
function values(obj) {
var result = [];
for (var key in obj)
if (obj.hasOwnProperty(key))
result.push(obj[key]);
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment