2

Problem:

Given two arrays of strings, for every string in list (query), determine how many anagrams of it are in the other list (dictionary). It should return an array of integers.

Example:

query = ["a", "nark", "bs", "hack", "stair"]
dictionary = ['hack', 'a', 'rank', 'khac', 'ackh', 'kran', 'rankhacker', 'a', 'ab', 'ba', 'stairs', 'raits']

The answer would be [2, 2, 0, 3, 1] since query[0] ('a') has 2 anagrams in dictionary: 'a' and 'a' and so on...

This was the code I came up with:

function sortArray(array) {
    let answer = [];
    for(let i = 0; i< array.length ; i++) {
         let data = array[i].split('').sort().join('');
         answer.push(data);
    }
    return answer;
}

function stringAnagram(dictionary, query) {
    // Write your code here
    let sortedDict = sortArray(dictionary);
    let sortedQuery = sortArray(query);
    let answer = [];
    console.log(sortedDict.length);
    console.log(sortedQuery.length);
    sortedQuery.map(data => {
        let i = 0;
        sortedDict.forEach(dictData => {
            if(data === dictData)
                i++;
        })
        answer.push(i);
    })

    return answer;
}

However it is returning timeout error for longer test cases. Need some help optimizing it. Any suggestions? I'm trying to achieve it in JavaScript.

customcommander
  • 17,580
  • 5
  • 58
  • 84
Vicky
  • 73
  • 1
  • 1
  • 7

2 Answers2

3

You may want to avoid using (expensive) Array.prototype.sort() to detect anagram and give your anagram detection algorithm as much shortcuts as possible.

So, if assume, anagrams should be the strings of the same length with the same count of the same characters, you may go something, like that:

const  query = ["a", "nark", "bs", "hack", "stair"], 
        dictionary = ['hack', 'a', 'rank', 'khac', 'ackh', 'kran', 'rankhacker', 'a', 'ab', 'ba', 'stairs', 'raits'],
        
        charCount = s => [...s].reduce((acc,c) => 
          (acc[c]=(acc[c]||0)+1, acc), {}),
          
        areAnagrams = (s1, s2) => {
          if(s1.length != s2.length) return false
          const s1CharCount = charCount(s1),
                s2CharCount = charCount(s2),
                result = Object
                  .keys(s1CharCount)
                  .every(char =>
                    s2CharCount[char] == s1CharCount[char])
          return result
        },
        
        outcome = query.map(word =>
          dictionary
            .filter(_word => areAnagrams(word, _word))
            .length
        )
            
console.log(outcome)
Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
0

Slightly more verbose way of doing it - but it works and makes sense to me - for each word in the original array, find the words in the target array that are the same length and then count those that are anagrams of the original word (an anagram is another word made up of the same letters in any order).

So the steps are -

Iterate over the firat array and for each word - filter the target array to get all words that are the same length as that word (potentialAnagrams)

Then iterate over that potentialAnagrams array and pass each word to a function that checks if all the letters and only the letters in the original word are present (in the given example - that would be [2, 2, 0, 3, 1]

Add up all anagrams of the word and pass the count to an array that is logged as the final result.

const  queryArr = ["a", "nark", "bs", "hack", "stair"];
const dictionaryArr = ['hack', 'a', 'rank', 'khac', 'ackh', 'kran', 'rankhacker', 'a', 'ab', 'ba', 'stairs', 'raits'];

let anagramsArr = [];

queryArr.forEach(function(query){
  let anagramsCount = 0
  const potentialAnagrams = dictionaryArr.filter(el => el.length === query.length);

  potentialAnagrams.forEach(function(potentialAnagram){
     if(isAnagram(query,potentialAnagram)){
      anagramsCount++
     }
   })
  anagramsArr.push(anagramsCount);
})

function isAnagram(word1, word2){
  let count = 0;
  const word1Arr = word1.split('');
  const word2Arr = word2.split('');
  
  if( word1Arr.length !== word2Arr.length) {
     return 'Invalid data - words 1 and 2 are of different lengths';
  }
  
  word1Arr.forEach(function(letter){
    if(word2.indexOf(letter) !== -1) {
     count++
    }
  })
    return count === word1Arr.length
}
console.log(isAnagram('ab', 'bab')); //gives 'Invalid data - words 1 and 2 are of different lengths';

console.log(anagramsArr); //gives [2, 2, 0, 3,1];
gavgrif
  • 15,194
  • 2
  • 25
  • 27
  • `console.log(isAnagram('ab', 'bab')) // true` – Yevhen Horbunkov Jan 18 '21 at 09:23
  • @YevgenGorbunkov - since the word length comparison happens in the filter function before words are passed to isAnagram() - the only words that get passed are in fact equal in length - so "ab" and "bab" would never get passed to the function - but that said - I take your point, so have added a length check and early return in the function to ensure that "ab" and "bab" are not considered valid. I am sure that thre are other examples of testing or checks that should be included if this was real code in a non SO environment. Thanks for the feedback :) – gavgrif Jan 18 '21 at 14:37
  • I guess, you got the point: it's insufficient to check whether all the same letters are present in both words, but the quantity of those letters should be the same. So, `'aab'` and `'bba'` would not be considered anagrams of each other. Note, now, the string length is the same. – Yevhen Horbunkov Jan 18 '21 at 15:35
  • Furthermore, returning the message for the strings of different size is a poor design choice, as now your function may return truthy value when `false` might be expected, so if used together with `.filter()`, for example, may give improper output. – Yevhen Horbunkov Jan 18 '21 at 15:42
  • And, by the way, if you're missing mocha to make sure your solution covers all the test scenarios properly, you might easily deploy one (consider [the example](https://stackoverflow.com/a/63189149/11299053)). – Yevhen Horbunkov Jan 18 '21 at 15:46