Created
March 13, 2015 06:02
-
-
Save iansinnott/c6897b00c3be2e1399be to your computer and use it in GitHub Desktop.
Find anagrams in an array – JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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