Histogram matching (sorta)

Concept summary and lesson

The next thing we're going to do is create a simple function that uses our frequencies functions to make a "decent guess" at the key for decrypting a simple substitution cipher.

The decryption key we're going to make is another Map instance, but this time it's going to map letters to other letters. When we're done, we should be able to use cipher_key_map.get(ciphertext_letter) and it should return to us a likely plaintext letter. It won't be exactly right! This is a messy science, and we're going to add more features to our tools as we proceed with this big project, so when you get garbled text out of it, that's okay!

We're going to get the letter frequencies of regular English text (called the model here), and of our encrypted text (called the ciphertext here). Then we're going to sort them, so that the most frequently-used letters in each piece of text come first. Finally, we're going to pair them up, so that we have a Map with the keys being letters from our encrypted message, and the values being the most likely letter they should be transformed into.

Later, we'll make this interactive so that you can use it to figure out the right key string, but for now it's just going to give us its best first guess.

Here's how it needs to work:

Sorting arrays in javascript

Javascript array sorting is notorious for being janky. In order to make sure we're getting the result we want, we need to make a special helper function that will convert our Map into an Array of [string,number] pairs. Then, we'll sort the array with a comparison function that only looks at the numbers.

First, get the pairs out of the map like this:

let letter_freq_pairs = Array.from(mymap.entries())

Next, we have to sort them. To do that, we can use the Array.sort method (Array.sort documentation):

Array.from(letter_freq_pairs)
    .sort((a,b)=>b[1]-a[1])

We pass the sort function our own special function to do the comparison our way (instead of turning them into strings and comparing the numbers as if they were words...). sort expects to give you two arguments (a and b), and it wants you to return:

By subtracting with b-a, we're getting the list sorted from greatest to least. Also, notice that we're only comparing the count part of the key/value pair we got from the map! That means we're sorting the pairs, but we're only looking at the frequency to make our ordering.

Zip functions

It turns out that combining two arrays into an array of pairs (or something else similar) is a pretty common thing to want to do. We have a special name for a function that does that: zip.

Take a look at this code and see if you can figure out how it works:

function zip(a:number[], b:number[]):number[][] {
    return a.map((key,index) => [key, b[index]])
}

How could you use this function in our code project for today?

Reducing one type to another

We got a list of pairs, but that's not really what we wanted. It would be nice if we could get a Map...

Here's another function you might find useful, try to figure out what it does:

function make_map(pairs:string[][]):Map<string,string> {
    return pairs.reduce((mymap,pair) =>
            mymap.set(pair[0],pair[1]), 
        new Map())
}
    

Media resources

Exercises