问题
I am supposed to write a program in JavaScript to find all the anagrams within a series of words provided. e.g.: "monk, konm, nkom, bbc, cbb, dell, ledl, llde" The output should be categorised into rows: 1. monk konm, nkom; 2. bbc cbb; 3. dell ledl, llde;
I already sorted them into alphabetical order i.e.: "kmno kmno bbc bbc dell dell" and put them into an array.
However I am stuck in comparing and finding the matching anagram within the array.
Any help will be greatly appreciated.
回答1:
Javascript objects are excellent for this purpose, since they are essentially key/value stores:
// Words to match
var words = ["dell", "ledl", "abc", "cba"];
// The output object
var anagrams = {};
for (var i in words) {
var word = words[i];
// sort the word like you've already described
var sorted = sortWord(word);
// If the key already exists, we just push
// the new word on the the array
if (anagrams[sorted] != null) {
anagrams[sorted].push(word);
}
// Otherwise we create an array with the word
// and insert it into the object
else {
anagrams[sorted] = [ word ];
}
}
// Output result
for (var sorted in anagrams) {
var words = anagrams[sorted];
var sep = ",";
var out = "";
for (var n in words) {
out += sep + words[n];
sep = "";
}
document.writeln(sorted + ": " + out + "<br />");
}
回答2:
Here is my take:
var input = "monk, konm, bbc, cbb, dell, ledl";
var words = input.split(", ");
for ( var i = 0; i < words.length; i++) {
var word = words[i];
var alphabetical = word.split("").sort().join("");
for (var j = 0; j < words.length; j++) {
if (i === j) {
continue;
}
var other = words[j];
if(alphabetical === other.split("").sort().join("")){
console.log(word + " - " + other + " (" + i + ", " + j + ")");
}
}
}
where the output would be (the word, the match and the index of both):
monk - konm (0, 1)
konm - monk (1, 0)
bbc - cbb (2, 3)
cbb - bbc (3, 2)
dell - ledl (4, 5)
ledl - dell (5, 4)
To get the characters in the in alphabetical order, I used split("") ot get an array, called sort() and used join("") to get a string from the array.
回答3:
I worked through a similar question to this today and wanted to share the results of my work. I was focused on just detecting the anagram so processing the list of words was not part of my exercise but this algorithm should provide a highly performant way to detect an anagram between two words.
function anagram(s1, s2){
if (s1.length !== s2.length) {
// not the same length, can't be anagram
return false;
}
if (s1 === s2) {
// same string must be anagram
return true;
}
var c = '',
i = 0,
limit = s1.length,
match = 0,
idx;
while(i < s1.length){
// chomp the next character
c = s1.substr(i++, 1);
// find it in the second string
idx = s2.indexOf(c);
if (idx > -1) {
// found it, add to the match
match++;
// assign the second string to remove the character we just matched
s2 = s2.substr(0, idx) + s2.substr(idx + 1);
} else {
// not found, not the same
return false;
}
}
return match === s1.length;
}
I think technically is can be solved like this:
function anagram(s1, s2){
return s1.split("").sort().join("") === s2.split("").sort().join("");
}
The reason I chose the earlier approach is that it is more performant for larger strings since you don't need to sort either string, convert to an array or loop through the entire string if any possible failure case is detected.
回答4:
Probably not the most efficient way, but a clear way around using es6
function sortStrChars(str) {
if (!str) {
return;
}
str = str.split('');
str = str.sort();
str = str.join('');
return str;
}
const words = ["dell", "ledl", "abc", "cba", 'boo'];
function getGroupedAnagrams(words){
const anagrams = {}; // {abc:[abc,cba], dell:[dell, ledl]}
words.forEach((word)=>{
const sortedWord = sortStrChars(word);
if (anagrams[sortedWord]) {
return anagrams[sortedWord].push(word);
}
anagrams[sortedWord] = [word];
});
return anagrams;
}
const groupedAnagrams = getGroupedAnagrams(words);
for(const sortedWord in groupedAnagrams){
console.log(groupedAnagrams[sortedWord].toString());
}
回答5:
I know this is an ancient post...but I just recently got nailed during an interview on this one. So, here is my 'new & improved' answer:
var AnagramStringMiningExample = function () {
/* Author: Dennis Baughn
* This has also been posted at:
* http://stackoverflow.com/questions/909449/anagrams-finder-in-javascript/5642437#5642437
* Free, private members of the closure and anonymous, innner function
* We will be building a hashtable for anagrams found, with the key
* being the alphabetical char sort (see sortCharArray())
* that the anagrams all have in common.
*/
var dHash = {};
var sortCharArray = function(word) {
return word.split("").sort().join("");
};
/* End free, private members for the closure and anonymous, innner function */
/* This goes through the dictionary entries.
* finds the anagrams (if any) for each word,
* and then populates them in the hashtable.
* Everything strictly local gets de-allocated
* so as not to pollute the closure with 'junk DNA'.
*/
(function() {
/* 'dictionary' referring to English dictionary entries. For a real
* English language dictionary, we could be looking at 20,000+ words, so
* an array instead of a string would be needed.
*/
var dictionaryEntries = "buddy,pan,nap,toot,toto,anestri,asterin,eranist,nastier,ratines,resiant,restain,retains,retinas,retsina,sainter,stainer,starnie,stearin";
/* This could probably be refactored better.
* It creates the actual hashtable entries. */
var populateDictionaryHash = function(keyword, newWord) {
var anagrams = dHash[keyword];
if (anagrams && anagrams.indexOf(newWord) < 0)
dHash[keyword] = (anagrams+','+newWord);
else dHash[keyword] = newWord;
};
var words = dictionaryEntries.split(",");
/* Old School answer, brute force
for (var i = words.length - 1; i >= 0; i--) {
var firstWord = words[i];
var sortedFirst = sortCharArray(firstWord);
for (var k = words.length - 1; k >= 0; k--) {
var secondWord = words[k];
if (i === k) continue;
var sortedSecond = sortCharArray(secondWord);
if (sortedFirst === sortedSecond)
populateDictionaryHash(sortedFirst, secondWord);
}
}/*
/*Better Method for JS, using JS Array.reduce(callback) with scope binding on callback function */
words.reduce(function (prev, cur, index, array) {
var sortedFirst = this.sortCharArray(prev);
var sortedSecond = this.sortCharArray(cur);
if (sortedFirst === sortedSecond) {
var anagrams = this.dHash[sortedFirst];
if (anagrams && anagrams.indexOf(cur) < 0)
this.dHash[sortedFirst] = (anagrams + ',' + cur);
else
this.dHash[sortedFirst] = prev + ','+ cur;
}
return cur;
}.bind(this));
}());
/* return in a nice, tightly-scoped closure the actual function
* to search for any anagrams for searchword provided in args and render results.
*/
return function(searchWord) {
var keyToSearch = sortCharArray(searchWord);
document.writeln('<p>');
if (dHash.hasOwnProperty(keyToSearch)) {
var anagrams = dHash[keyToSearch];
document.writeln(searchWord + ' is part of a collection of '+anagrams.split(',').length+' anagrams: ' + anagrams+'.');
} else document.writeln(searchWord + ' does not have anagrams.');
document.writeln('<\/p>');
};
};
Here is how it executes:
var checkForAnagrams = new AnagramStringMiningExample();
checkForAnagrams('toot');
checkForAnagrams('pan');
checkForAnagrams('retinas');
checkForAnagrams('buddy');
Here is the output of the above:
toot is part of a collection of 2 anagrams: toto,toot.
pan is part of a collection of 2 anagrams: nap,pan.
retinas is part of a collection of 14 anagrams: stearin,anestri,asterin,eranist,nastier,ratines,resiant,restain,retains,retinas,retsina,sainter,stainer,starnie.
buddy does not have anagrams.
回答6:
Simple Solution
function anagrams(stringA, stringB) {
return cleanString(stringA) === cleanString(stringB);
}
function cleanString(str) {
return str.replace(/[^\w]/g).toLowerCase().split('').sort().join()
}
anagrams('monk','konm')
If it is anagrams function will return true otherwise false
回答7:
My solution to this old post:
// Words to match
var words = ["dell", "ledl", "abc", "cba"],
map = {};
//Normalize all the words
var normalizedWords = words.map( function( word ){
return word.split('').sort().join('');
});
//Create a map: normalizedWord -> real word(s)
normalizedWords.forEach( function ( normalizedWord, index){
map[normalizedWord] = map[normalizedWord] || [];
map[normalizedWord].push( words[index] );
});
//All entries in the map with an array with size > 1 are anagrams
Object.keys( map ).forEach( function( normalizedWord , index ){
var combinations = map[normalizedWord];
if( combinations.length > 1 ){
console.log( index + ". " + combinations.join(' ') );
}
});
Basically I normalize every word by sorting its characters so stackoverflow would be acefkloorstvw, build a map between normalized words and the original words, determine which normalized word has more than 1 word attached to it -> That's an anagram.
回答8:
I had this question in an interview. Given an array of words ['cat', 'dog', 'tac', 'god', 'act'], return an array with all the anagrams grouped together. Makes sure the anagrams are unique.
var arr = ['cat', 'dog', 'tac', 'god', 'act'];
var allAnagrams = function(arr) {
var anagrams = {};
arr.forEach(function(str) {
var recurse = function(ana, str) {
if (str === '')
anagrams[ana] = 1;
for (var i = 0; i < str.length; i++)
recurse(ana + str[i], str.slice(0, i) + str.slice(i + 1));
};
recurse('', str);
});
return Object.keys(anagrams);
}
console.log(allAnagrams(arr));
//["cat", "cta", "act", "atc", "tca", "tac", "dog", "dgo", "odg", "ogd", "gdo", "god"]
回答9:
Maybe this?
function anagram (array) {
var organized = {};
for (var i = 0; i < array.length; i++) {
var word = array[i].split('').sort().join('');
if (!organized.hasOwnProperty(word)) {
organized[word] = [];
}
organized[word].push(array[i]);
}
return organized;
}
anagram(['kmno', 'okmn', 'omkn', 'dell', 'ledl', 'ok', 'ko']) // Example
It'd return something like
{
dell: ['dell', 'ledl'],
kmno: ['kmno', okmn', 'omkn'],
ko: ['ok', ko']
}
It's a simple version of what you wanted and certainly it could be improved avoiding duplicates for example.
回答10:
function isAnagram(str1, str2) {
var str1 = str1.toLowerCase();
var str2 = str2.toLowerCase();
if (str1 === str2)
return true;
var dict = {};
for(var i = 0; i < str1.length; i++) {
if (dict[str1[i]])
dict[str1[i]] = dict[str1[i]] + 1;
else
dict[str1[i]] = 1;
}
for(var j = 0; j < str2.length; j++) {
if (dict[str2[j]])
dict[str2[j]] = dict[str2[j]] - 1;
else
dict[str2[j]] = 1;
}
for (var key in dict) {
if (dict[key] !== 0)
return false;
}
return true;
}
console.log(isAnagram("hello", "olleh"));
回答11:
I have an easy example
function isAnagram(strFirst, strSecond) {
if(strFirst.length != strSecond.length)
return false;
var tempString1 = strFirst.toLowerCase();
var tempString2 = strSecond.toLowerCase();
var matched = true ;
var cnt = 0;
while(tempString1.length){
if(tempString2.length < 1)
break;
if(tempString2.indexOf(tempString1[cnt]) > -1 )
tempString2 = tempString2.replace(tempString1[cnt],'');
else
return false;
cnt++;
}
return matched ;
}
Calling function will be isAnagram("Army",Mary);
Function will return true
or false
回答12:
Another solution for isAnagram using reduce
const checkAnagram = (orig, test) => {
return orig.length === test.length
&& orig.split('').reduce(
(acc, item) => {
let index = acc.indexOf(item);
if (index >= 0) {
acc.splice(index, 1);
return acc;
}
throw new Error('Not an anagram');
},
test.split('')
).length === 0;
};
const isAnagram = (tester, orig, test) => {
try {
return tester(orig, test);
} catch (e) {
return false;
}
}
console.log(isAnagram(checkAnagram, '867443', '473846'));
console.log(isAnagram(checkAnagram, '867443', '473846'));
console.log(isAnagram(checkAnagram, '867443', '475846'));
回答13:
var check=true;
var str="cleartrip";
var str1="tripclear";
if(str.length!=str1.length){
console.log("Not an anagram");
check=false;
}
console.log(str.split("").sort());
console.log("----------"+str.split("").sort().join(''));
if(check){
if((str.split("").sort().join(''))===((str1.split("").sort().join('')))){
console.log("Anagram")
}
else{
console.log("not a anagram");
}
}
回答14:
Here is my solution which addresses a test case where the input strings which are not anagrams, can be removed from the output. Hence the output contains only the anagram strings. Hope this is helpful.
/**
* Anagram Finder
* @params {array} wordArray
* @return {object}
*/
function filterAnagram(wordArray) {
let outHash = {};
for ([index, word] of wordArray.entries()) {
let w = word.split("").sort().join("");
outHash[w] = !outHash[w] ? [word] : outHash[w].concat(word);
}
let filteredObject = Object.keys(outHash).reduce(function(r, e) {
if (Object.values(outHash).filter(v => v.length > 1).includes(outHash[e])) r[e] = outHash[e]
return r;
}, {});
return filteredObject;
}
console.log(filterAnagram(['monk', 'yzx','konm', 'aaa', 'ledl', 'bbc', 'cbb', 'dell', 'onkm']));
回答15:
i have recently faced this in the coding interview, here is my solution.
function group_anagrams(arr) {
let sortedArr = arr.map(item => item.split('').sort().join(''));
let setArr = new Set(sortedArr);
let reducedObj = {};
for (let setItem of setArr) {
let indexArr = sortedArr.reduce((acc, cur, index) => {
if (setItem === cur) {
acc.push(index);
}
return acc;
}, []);
reducedObj[setItem] = indexArr;
}
let finalArr = [];
for (let reduceItem in reducedObj) {
finalArr.push(reducedObj[reduceItem].map(item => arr[item]));
}
return finalArr;
}
group_anagrams(['car','cra','rca', 'cheese','ab','ba']);
output will be like
[
["car", "cra", "rca"],
["cheese"],
["ab", "ba"]
]
来源:https://stackoverflow.com/questions/909449/anagrams-finder-in-javascript