Your cart is currently empty!
Anagrams as a Matter of Secret Identity
On my quest to master algorithms in JavaScript, I make a point of cross referencing different tutorials and solutions, that I might find a happy middle ground and greater understanding.
However, if you want to learn straight from the masters, I recommend you check out Stephen Grider’s Algorithms and Data Structures course on Udemy and also the Gold Standard of Algo practice: AlgoExpert.io. Also recommended on Udemy are Colt Steele, Andrei Neagoie, Jose Portilla, and lastly Dmitri Nesteruk for Design Patterns.
Onward to the code… when it comes to anagrams, you will likely face a situation where you will be asked to compare two strings to return a boolean value (are they a match?), likewise, you may be asked to take an array of strings that are anagrams and match them.
While anagrams are straightforward enough, I found it difficult to determine a real world (or semi-real-world) application that could make them “sticky” enough to be memorable. After much consideration, it seemed that “facial recognition” and Machine Learning for Facial Recognition might be described as a more “epic” approach the anagram problem.
I don’t expect this to be fully accurate, but I do suspect the concept of “taking a series of traits in one instance” and comparing them to “a series of traits in another instance”, to see whether or not they are the same (once any useless info or noise is cleaned away) is sorta the base essence of facial recognition. In a more “use case” way… a face is the same whether it has a beard, mustache, sunglasses or otherwise.
The first part of the secretIdentityChecker
is the example above. The second part is about grouping anagrams (or in this case identities). A use case might be: suspected evildoers lurk in an airport and the security system needs to scan faces that may or may not be wearing disguises to determine if the appearance of any two people is actually just one.
One final thought before you explore the code…
You’ll notice that no arguments are passed to secretIdentityChecker
(). In the spirit of the analogy, I imagined such a thing would likely be a single machine, and that it should be able to handle more than one input. While attempting to modify for the example below, I had originally attempted to use typeof
arguments
to check for strings or arrays, but discovered that JavaScript’s typeof
doesn’t immediately distinguish between an object or array. With that in mind, I opted for the sloppier “boolean switch” of arguments.length
(.length
being one — if not the only — array methods supported on arguments.)
function secretIdentityChecker() {
if (arguments.length === 2) {
let args = [...arguments]
console.log(args)
const faceParser = (facialFeatures) => { // this is the same as The Snitch ObjectMapper previously blogged about
facialFeaturesBrokenDown = {};
for (let feature of facialFeatures.replace(/[^\w]/g, "").toLowerCase()) {
facialFeaturesBrokenDown[feature] = facialFeaturesBrokenDown[feature] + 1 || 1
}
return facialFeaturesBrokenDown
}
const faceDataScrubbedA = faceParser(args[0])
const faceDataScrubbedB = faceParser(args[1])
console.log(faceDataScrubbedA)
console.log(faceDataScrubbedB)
if (Object.keys(faceDataScrubbedA).length !== Object.keys(faceDataScrubbedB).length) {
return false;
}
for (let val in faceDataScrubbedA) {
if (faceDataScrubbedA[val] !== faceDataScrubbedB[val]) {
console.log(faceDataScrubbedB[val])
return false
}
}
return true;
} else if (arguments.length === 1) {
console.log("this is for an array")
const faces = arguments[0]
console.log("these are the arguments/faces: ", arguments)
console.log("")
const descrambledFaces = {};
for (const facialFeatures of faces) {
console.log("")
console.log("these are facialFeatures: ", facialFeatures)
let filteredFace = facialFeatures.replace(/[^\w]/g, "").toLowerCase()
console.log("this is a filteredFace: ", filteredFace)
let sortedFace = filteredFace.split('').sort().join('');
console.log("this is a sorted face: ", sortedFace)
if (sortedFace in descrambledFaces) {
console.log("")
console.log("inside the if statement")
descrambledFaces[sortedFace].push(facialFeatures);
console.log("this... descrambledFaces[sortedFace]: ", descrambledFaces[sortedFace], " receives a .push of: ", facialFeatures)
} else {
console.log("")
console.log("inside the else statement")
console.log("this... descrambledFaces[sortedFace]: ", descrambledFaces[sortedFace], " will be set to this: ", [facialFeatures])
descrambledFaces[sortedFace] = [facialFeatures];
}
}
return console.log(descrambledFaces), Object.values(descrambledFaces)
} else {
console.log("this won't work")
}
}
If the above code were run with the following argument:secretIdentityChecker("jK!!! mna", "kmn.A ?J")
You would get:
[ 'jK!!! mna', 'kmn.A ?J' ]
{ j: 1, k: 1, m: 1, n: 1, a: 1 }
{ k: 1, m: 1, n: 1, a: 1, j: 1 }
true
If it were instead run with:secretIdentityChecker(["huO eS ! ", "mnopqm", "eS ho u", "c!a r", "MMnOPq", "r ac"])
this is for an array
these are the arguments/faces: [Arguments] {
'0': [ 'huO eS ! ', 'mnopqm', 'eS ho u', 'c!a r', 'MMnOPq', 'r ac' ]
}
these are facialFeatures: huO eS !
this is a filteredFace: huoes
this is a sorted face: ehosu
inside the else statement
this... descrambledFaces[sortedFace]: undefined will be set to this: [ 'huO eS ! ' ]
these are facialFeatures: mnopqm
this is a filteredFace: mnopqm
this is a sorted face: mmnopq
inside the else statement
this... descrambledFaces[sortedFace]: undefined will be set to this: [ 'mnopqm' ]
these are facialFeatures: eS ho u
this is a filteredFace: eshou
this is a sorted face: ehosu
inside the if statement
this... descrambledFaces[sortedFace]: [ 'huO eS ! ', 'eS ho u' ] receives a .push of: eS ho u
these are facialFeatures: c!a r
this is a filteredFace: car
this is a sorted face: acr
inside the else statement
this... descrambledFaces[sortedFace]: undefined will be set to this: [ 'c!a r' ]
these are facialFeatures: MMnOPq
this is a filteredFace: mmnopq
this is a sorted face: mmnopq
inside the if statement
this... descrambledFaces[sortedFace]: [ 'mnopqm', 'MMnOPq' ] receives a .push of: MMnOPq
these are facialFeatures: r ac
this is a filteredFace: rac
this is a sorted face: acr
inside the if statement
this... descrambledFaces[sortedFace]: [ 'c!a r', 'r ac' ] receives a .push of: r ac
{
ehosu: [ 'huO eS ! ', 'eS ho u' ],
mmnopq: [ 'mnopqm', 'MMnOPq' ],
acr: [ 'c!a r', 'r ac' ]
}
[
[ 'huO eS ! ', 'eS ho u' ],
[ 'mnopqm', 'MMnOPq' ],
[ 'c!a r', 'r ac' ]
]
To gain the most learning, I recommend pasting the above code into a repl.it (or your favorite code editor) and following along with the console.logs so you can understand what is happening as it happens.
It seems, as I go through these algorithms, that annotating code with descriptive console.logs is/has been as beneficial (if not moreso) than working through the actual solutions.