Home

Kenny Porterfield
Frequency Counter Algorithm Strategy

Frequency Counter Algorithm Strategy

Frequency Counter

This pattern uses objects or sets to collect values/frequencies of values. This can often avoid the need for nested loops or O(N2) operations with arrays/strings.
Example Problem: Write a function called same, which accepts 2 arrays. The function should return true if every value in the array has it's corresponding value squared in the second array. The frequency of values must be the same.

Example Inputs/Ouputs

same([1,2,3], [4,1,9]) // true
same([1,2,3], [1,9]) // false
same([1,2,1], [4,1,4]) // false (must be same frequency)

Algorithm Walkthrough

"Naive" Algorithm - O(N2) time complexity with nested loops

function same(arr1, arr2) {
	// If the lengths of the arrays aren't equal, the conditons can't be met so we return false
	if(arr1.length !== arr2.length) {
		return false;
	}
	// Loop over each index in the first array, checking to see what the index of its value squared is in the second array
	// If the squared value isn't present in the second array, indexOf will return -1, and we will return false
	for(let i = 0; i < arr1.length; i++) {
		let correctIndex = arr2.indexOf(arr1[i] ** 2);
		if(correctIndex === -1) {
			return false;
		}
		// If the squared value is in the second array, we will splice it out so that that same value can't be repeated
		// to ensure the frequency of values is the same
		arr2.splice(correctIndex,1);
	}
	// If we make it through the loop without returning false, then the conditions are met and we return true
	return true;
}

Refactored Algorithm - O(N) time complexity, using 2 separate loops is vastly better than 2 nested loops. O(2N) is way faster than O(N2)

function sameRefactored(arr1, arr2) {
	if(arr1.length !== arr2.length) {
		return false;
	}
	// We use an object to count the frequency of individual values in the arrays
	let frequencyCounter1 = {};
	let frequencyCounter2 = {};
	// These for ... of loops count the frequency of individual values in the arrays
	for (let val of arr1) {
		// We check to see if our array value is a key in the object
		// If it is we add 1 to it, if it isn't we initialize it to 1
		frequencyCounter1[val] = (frequencyCounter1[val] || 0) + 1;
	}
	for (let val of arr2) {
		frequencyCounter2[val] = (frequencyCounter2[val] || 0) + 1;
	}
	// console.log our objects just to see them later on
	console.log(frequencyCounter1);
	console.log(frequencyCounter2);
	// For each key in the first object
	for(let key in frequencyCounter1) {
		// Is the key squared in the second object? If not, we return false
		if(!(key ** 2 in frequencyCounter2)) {
			return false;
		}
		// Do the values in the object correspond? This is how we check if the frequency is the same.
		// If not, return false
		if(frequencyCounter2[key ** 2] !== frequencyCounter1[key]) {
			return false;
		}
	}
	// If we never return false then our conditions are met, so return true
	return true;
}

Anagrams Coding Challenge

Given two strings, write a function to determine if the second string is an anagram of the first. An anagram is a word, phrase, or name formed by rearranging the letters of another, such as cinema, formed from iceman.

function validAnagram(str1, str2) {
	// If the lengths of the strings aren't the same, it won't be an anagram
	if(str1.length !== str2.length) {
		return false;
	}
	// We use an object to count the number of individual characters in the strings
	let characterCounter1 = {};
	let characterCounter2 = {};
	// Use for ... of loop on each string to count the number of each character in the string
	for (let char of str1) {
		characterCounter1[char] = (characterCounter1[char] || 0) + 1;
	}
	for (let char of str2) {
		characterCounter2[char] = (characterCounter2[char] || 0) + 1;
	}
	// console.log our objects just to see them later
	console.log(characterCounter1);
	console.log(characterCounter2);
	// For each key in the first object
	for(let key in characterCounter1) {
		// Does the key in the second object? If not, we return false
		if(!(key in characterCounter2)) {
			return false;
		}
		// Are the counts of each character the same in both objects?
		// If not, return false
		if(characterCounter2[key] !== characterCounter1[key]) {
			return false;
		}
	}
	// If it has met all of these conditions then we have an anagram, so return true
	return true;
}