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.

Discover more from Comedy Tragedy Epic

Subscribe now to keep reading and get access to the full archive.

Continue reading