Coded Communication / Coded Correspondence

Hello! Just in case any fellow learners are looking for a discussion/solution thread for:

6. Learn Python 3 - Strings, Off-Platform Project: Coded Correspondence

Steps 1 - 4 should be understandable with the solution key.

As for Step 5, the Vignere Cipher section:
I’ve tried this project twice separate times, and seen 2 different solutions in the Jupyter Notebook posted by Codecademy. The current solution they have posted is much cleaner than the previous one, but it took me some time to understand it fully.

Here’s my notes, I hope it helps you if you should ever feel stumped:

def vignere_decoder(coded_message, keyword):
    # Part 1 - Creating keyword phrase:
    keyword_charindx = 0
    keyword_phrase = ""
    # Part 1a & 1b can be interswitched (doesn't matter if you work on the characters/letters in or out of the punctuation list)
    # Part 1a - Including all punctuations in keyword phrase
    for i in range(0, len(coded_message)):
        if coded_message[i] in punctuation: 
            keyword_phrase += coded_message[i] #add it as it is
    # Part 1b - Skipping punctuations while mapping keyword phrase to coded message
        else:
            keyword_phrase += keyword[keyword_charindx]
            keyword_charindx = (keyword_charindx + 1) % len(keyword)
    # print(keyword_phrase) # just to check how it looks

    # Part 2 - Decoding the message
    decoded_msg = ''
    # Part 2a - Including all punctuations in decoded message
    for i in range(0, len(coded_message)):
        if coded_message[i] in punctuation:
            decoded_msg += coded_message[i]  #add it as it is
    # Part 2b - Deducing the char index of the decoded char
        else:
            decoded_charindx = alphabet.find(coded_message[i]) - alphabet.find(keyword_phrase[i])
            decoded_msg += alphabet[decoded_charindx % len(alphabet)]
            
    return decoded_msg

message = "dfc aruw fsti gr vjtwhr wznj? vmph otis! cbx swv jipreneo uhllj kpi rahjib eg fjdkwkedhmp!"
keyword = "friends"

print(vignere_decoder(message, keyword))
# prints "you were able to decode this? nice work! you are becoming quite the expert at crytography!"

Hope this helps if anyone is looking! Step 6 can be achievable in similar steps reversed.

I also found an alternate video that might be interesting to watch on the Vignere Cipher on Youtube:
Vigenere Cipher Encryption and Decryption in Python. It uses ASCII characters to build the module, which means that we do not need to create a separate alphabet and punctuation list.

hmm part 1 and 2 are almost identical, are they really different things?

What if the key instead had its separate index? And maybe better yet, making kind of like a dispenser, each time it is poked at, the next key comes out, and only poke at it when a key is needed?

>>> from itertools import cycle
>>> dispenser = cycle('key')
>>> next(dispenser)
'k'
>>> next(dispenser)
'e'
>>> next(dispenser)
'y'
>>> next(dispenser)
'k'
>>> next(dispenser)
'e'
>>> next(dispenser)
'y'
>>> next(dispenser)
'k'
>>> next(dispenser)
'e'
>>> next(dispenser)
'y'
>>> next(dispenser)
'k'

I clearly knew where to look for something like that, but it can also be implemented somewhat easily:

def cycle(key):
    i = 0

    def poke():
        nonlocal i
        current = key[i]
        i = (i + 1) % len(key)
        return current
    return poke

>>> dispenser = cycle('key')
>>> dispenser()
'k'
>>> dispenser()
'e'
>>> dispenser()
'y'
>>> dispenser()
'k'
>>> dispenser()
'e'
>>> dispenser()
'y'
>>> dispenser()
'k'
>>> dispenser()
'e'
5 Likes

hey there!

part 1 and 2 is belongs to the same function. i broke them down in parts in attempt to help those who are following the solution to the project, but may be confused on how the solution works.

when i previously attempted this project, there wasn’t a thread on it but thanks for checking it out and providing another angle to this topic. i looked through the code you posted, looks cool and handy to execute. i was wondering there were any life applications employing this code as well?

cheers!

I love the idea of extending the key. But looking at the… shape of the code? Makes me want to re-order things a bit…or a lot.

I’m sure plenty of apps use cycle, I wouldn’t call it special, just abstracting out a common pattern.
Functional programming languages tend to do this more than imperative ones.

If there are too many details on screen it can be difficult to figure out what’s going on, a “can’t see the forest for all the trees” kind of deal. When we think about a problem in our heads it’s usually at a more abstract level and then the code quickly gets littered with little details. Things like a rotating key is a concept all by itself, that can be implemented separately from the rest, it would remove lots of code from the decode function.

There might be other useful ways to implement the rotating key, but if it results in two almost identical copies of code then I won’t like it xD

What I ended up with after poking at it for a little while:

from itertools import cycle


def letterToInt(ch):
    return ord(ch) - ord('a')


def intToLetter(n):
    return chr(n + ord('a'))


def vignere_decoder(coded_message, keyword):
    # convert key to integers and cycle it
    pez = cycle(map(letterToInt, keyword))

    # either pass through punctuation, or grab a key and decode
    def decode(ch):
        if ch in '''?!.,:;'" ''':
            return ch
        return intToLetter((letterToInt(ch) - next(pez)) % 26)

    # apply decode to every char and concat the chars to a string
    return ''.join(map(decode, coded_message))


message = (
    "dfc aruw fsti gr vjtwhr wznj? vmph otis! cbx swv jipreneo uhllj kpi "
    "rahjib eg fjdkwkedhmp!"
)
keyword = "friends"

print(vignere_decoder(message, keyword))
5 Likes
from itertools import cycle


def letterToInt(ch):
    return ord(ch) - ord('a')


def intToLetter(n):
    return chr(n + ord('a'))


def decipher(ch, key):
    return intToLetter((letterToInt(ch) - letterToInt(key)) % 26)


def vignere_decoder(coded_message, key):
    pez = cycle(key)
    return ''.join(
        ch if ch in '''?!.,:;'" '''
        else decipher(ch, next(pez))
        for ch in coded_message
    )


message = (
    "dfc aruw fsti gr vjtwhr wznj? vmph otis! cbx swv jipreneo uhllj kpi "
    "rahjib eg fjdkwkedhmp!"
)
keyword = "friends"

print(vignere_decoder(message, keyword))

I’m just going in circles here :^)

try to extend -> two almost identical loops -> cycle -> oh this is what I did the first time

1 Like

Idea: if extending with 0 (identity for subtraction), then after that, ALL letters can be deciphered with no if-statement.
Actually. no. Because (%26) will turn space to 'n'.

I think I’ll make the claim that because it ends up as two identical loops, they should be merged, which means a separate index for the key, which means wanting to “box it up into a dispenser” -> cycle

I still think the idea of massaging the key before deciphering is great, I can’t do it in a way I like though.

1 Like

O.O

    # apply decode to every char and concat the chars to a string
    return ''.join(map(decode, coded_message))

this part is legit. thanks for this really, despite it being very beyond my league at the moment. XD

both your suggestions work real nice sans the clutter. also, i’m positive i have to google a lot more about the itertools module, cycle(), map() in your 2 posts.

just to check the “int” in “intToLetter” refers to integer or…?

for my own reference:

itertools.cycle(iterable)
Make an iterator returning elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely. Roughly equivalent to:

def cycle(iterable):
    # cycle('ABCD') --> A B C D A B C D A B C D ...
    saved = []
    for element in iterable:
        yield element
        saved.append(element)
    while saved:
        for element in saved:
              yield element

Here’s a tutorial for Itertools if anyone who passes this thread needs it:
Python Tutorial: Itertools Module - Iterator Functions for Efficient Looping

2 Likes

A number yeah, so, not the type, but … a whole number. That’s what we convert it to do the math (subtract the key, mod 26). Though, yeah, obviously we use the int type to represent that. It’s just to get that logic out of the rest, it’s unintersting, super cluttery, it is its own thing.

For a while I even had an implementation that was just a list of functions, and then another function that called them in order. That was getting out of hand. But in some languages this would be reasonable.

map is “apply f to each value and return the results”
so, yeah, I shy away from “basic” things like indices because that’s… frankly it’s too hard for me. And I would argue that map and all the other things are simpler!

4 Likes

you’re right in your own way to say it’s simpler, with proficiency

for beginners, we probably only know and have “indices” in our arsenal, which possibility might not be the easiest way to get to the solution, i can attest to that

i’m glad you contributed your input to this thread really, someone else had seem to have a problem with jupyter notebook for this proj and there wasn’t a response. now we are 8 replies deep, with 3 solutions of varying challenge, sounds like a good thing accomplished, kudos! :smile:

2 Likes

Riight but the way this works is, people can come here and make posts to entertain me/others (what’s jupyter got to do with it anyways…)

Vigenere ciphers are fun, there’s a really simple pattern to it, but that pattern is tricky to implement simply, but it really feels like it should be possible — it has this “just map decode over it” shape, but in a context of “the key is ever-changing” which is simple too, but how can they be composed?

Read two streams in sync: have to insert dummy values into the key so that they match up.
Map in context of a key: apply decode to all characters. have to first preload decode with the context of a key.
Two separate character streams: always read from the message, possibly read from the key.

All equivalent, but slightly different perspectives. Maybe there are more ways to view it.

4 Likes

I have a problem writing a function to brute force. I know the solution just prints out all offset messages and then you check manually, but I wanted something more elegant.

I wanted a function that tries to match an input 'code' to an input 'guess', both as strings. It then should return the decoded string + the offset.
I tried following code:

alph = 'abcdefghijklmnopqrstuvwxyz'
def decode_new_1(message, guess):
    decoded = ""
    offset = 0
    while decoded != guess:
        offset = offset + 1
        for i in message:
            decoded += alph[(alph.find(i) + offset) % 26]
        return decoded
    return decoded, offset

print(decode_new_1('a', 'h'))

I checked if I return the cariable wrong (wrong intendation), tried different things, none worked. Someone have a pointer how I would get this to work or where my error lies?

Your while loop isn’t a loop at all because it exits the function during each iteration so there’s never a second iteration.

You want to find what the offset is? There’ll be 26 possibilities, 0 through 25 (26 and 0 is the same offset)
So you could iterate through each one, see if you get the expected result, and if so, it’s done.

Some useful things to define would be the solution candidates (numbers 0 through 25), and a decode function, and then you could filter the candidates based on which produce the correct result.

2 Likes

Thank you for the pointer. Used while loops not that often, so I still struggle with the synthax a lot.

This fixed it:

def decode_new_1(message, guess):
    offset = 0
    while decoded != guess:
        decoded = ""
        offset = offset + 1
        
        for i in message:
            decoded += alph[(alph.find(i) + offset) % 26]
    return decoded, offset

Let me throw more strange code at you.

A couple of things are useful. Decoding (or encoding, either is fine) … and candidates. Yeah I guess that’s it.
And then take the candidates, filter them by whether they match the other word after shifting by that amount.

> findOffset "a" "h"
Just 7
> findOffset "bb" "hh"
Just 6
> findOffset "aa" "ha"
Nothing       <- there isn't always an answer. (unsolvable input)
{-# LANGUAGE NoImplicitPrelude #-}
module Main where

import           Data.List
import           Control.Arrow
import           Protolude

alph :: [Char]
alph = "abcdefghijklmnopqrstuvwxyz"

shiftLetter :: Int -> Char -> Maybe Char
shiftLetter n ch = do
  startLoc <- findIndex (== ch) alph
  pure $ alph !! ((startLoc + n) `mod` length alph)

shiftWord :: [Char] -> Int -> Maybe [Char]
shiftWord s n = mapM (shiftLetter n) s

candidates :: [Int]
candidates = [0 .. 25]

findOffset :: [Char] -> [Char] -> Maybe Int
findOffset encoded decoded
  = candidates
  & filter (shiftWord encoded >>> (== Just decoded))
  & listToMaybe
1 Like

after banging my head against the viginère-decoder for 2 hours I came up with a solution, but somehow it felt like stumpling through iterations until suddenly it worked. For example adding break was a hail mary, because the code was doubling every letter, like hheelloo, and I had no idea why.

This is my code:

alph = 'abcdefghijklmnopqrstuvwxyz'
punctuation = '!?,.\' '
def decode_viginere(message, keyword):
    decode  = ''
    keystring = ''
    running = 0
    #generate a keystring Example: frien dsfri ends.:
    for i in message:
        if not i in punctuation:
            keystring += keyword[running % len(keyword)]
            running += 1
        else:
            keystring += i
    zipped = zip(message, keystring)        
    for block in zipped:
        for s in block:
            if not s in punctuation:
                offset = alph.find(block[0]) - alph.find(block[1])
                decode += alph[offset % 26]
                break
            else:
                decode += s
                break
    print(decode)

would there have been a more elegant soulution using not too advanced python?

Thanks in advance for any suggestions!

You could generate a key for each letter

[7, 8, 9, 10, 11, ... ]

pair them up with the text

decode each pair

put it back together

…am i missing something

punctuation. uhm. well you could treat the keys like a pez dispenser, and only ask for a key when you need one

keys = iter('abcdefg')
print(next(keys))  # a
print(next(keys))  # b
1 Like

So, if i unterstand your suggestion correctly:
generate a “keylist” that has every value for every position that I need to shift my ciphertext in one loop.

Make a second loop afterwards that shifts the ciphertext and returns the decoded string?

should be easier to comprehend and not as hard to follow what the loop does :slight_smile: I will try this and see how this works :smiley:

Yeah but you’re already doing that aren’t you.

And when you’re done with that, your indentation is back all the way to the left side. So that’s fine.

The problem would be that you have two reading locations, one for the input, one for the key, and they don’t move in sync.

So you would move the input reader every iteration.
But you would only move the key reader when the input value is a letter.

Oh. You’re putting dummy values in the key when it’s punctuation.

I really don’t know what the problem would be xD

replace this:

zipped = zip(message, keystring)

with:

''.join(map(decodePair, message, key))

and define:

def decodePair(inputCh, keyCh):
    ...

Right, more ore less. Just would make the code easier to digest visually.

Ok, so it seems I missunerstood your suggestion :wink: I will think through it again after my head has stopped smoking. That solution was quite the battle for my fifth day of learning python :smiley: