// https://www.interviewcake.com/question/javascript/word-cloud?course=fc1§ion=hashing-and-hash-tables
//Mine. Ignores case and converts words to lowercase
class WordCloudData {
constructor(str) {
this.wordsToCounts = new Map()
this.populateWordsToCounts(str)
}
// valid characters are apostrophes and latin alphabets
isValidChar(char) {
if (typeof char !== 'string') return false
const charCode = char.toLowerCase().charCodeAt(0)
if (charCode === 39 || this.isLetter(char)) return true
else return false
}
isLetter(char) {
if (typeof char !== 'string') return false
const charCode = char.toLowerCase().charCodeAt(0)
if (charCode >= 97 && charCode <= 122) return true
else return false
}
// Count the frequency of each word
populateWordsToCounts(str) {
const strLen = str.length
let currWordArr = []
for (let i = 0; i <= strLen; i++) {
const currChar = str[i],
isCharValid = this.isValidChar(currChar)
/* If currChar is a not valid(like standalone or trailing punctuation)
and we've already handled the word before it, skip it */
if (!isCharValid && currWordArr.length === 0) continue
/*
If currChar is valid (apostrophes and letters) or if it is a hyphen surrounded by letters,
consider it a letter and push into currWordArr
Otherwise we've reached end of word
1. Create lowercase string from currWordArr and check if it exists in the map
2. Get and set the word count accordingly
3. Reset currWordArr to be empty
*/
if (isCharValid || (currChar === '-' && this.isLetter(str[i + 1]))) {
currWordArr.push(currChar)
} else {
const currWord = currWordArr.join('').toLowerCase()
const wordCount = this.wordsToCounts.has(currWord)
? this.wordsToCounts.get(currWord) + 1
: 1
this.wordsToCounts.set(currWord, wordCount)
currWordArr = []
}
}
}
}
/*
n = length of string
Time Complexity - O(n)
Space complexity - O(n) + k (length of current word. comes from currWordArr). Can be refactored to use a pointer
*/
/* ---------------------------------------------------------------------------- */
// Tests
const testCases = [
[
'I like cake. i like cake',
new Map([
['i', 2],
['like', 2],
['cake', 2],
]),
],
[
'Mmm...mmm...decisions...decisions',
new Map([
['mmm', 2],
['decisions', 2],
]),
],
['cake-is-da-best', new Map([['cake-is-da-best', 1]])],
[
'Chocolate cake for dinner?! And pound cake for dessert?! Yay!!',
new Map([
['chocolate', 1],
['cake', 2],
['for', 2],
['dessert', 1],
['and', 1],
['pound', 1],
['dinner', 1],
['yay', 1],
]),
],
[
`We came, we saw, we conquered...then we ate Bill's (Mille-Feuille) cake."
"The bill came to five dollars.`,
new Map([
['we', 4],
['came', 2],
['saw', 1],
['conquered', 1],
['then', 1],
['ate', 1],
["bill's", 1],
['mille-feuille', 1],
['cake', 1],
['the', 1],
['bill', 1],
['to', 1],
['five', 1],
['dollars', 1],
]),
],
[
'Hey- wait',
new Map([
['hey', 1],
['wait', 1],
]),
],
]
function isMapsEqual(map1, map2) {
if (map1.size !== map2.size) {
return false
}
for (let [key, val] of map1) {
const testVal = map2.get(key)
if (testVal !== val || (testVal === undefined && !map2.has(key))) {
return false
}
}
return true
}
for (const test of testCases) {
const worldCloud = new WordCloudData(test[0])
console.log(worldCloud.wordsToCounts)
console.log(isMapsEqual(worldCloud.wordsToCounts, test[1]))
}