logo

Python Challenge - Level 1

Problem

The image lists 3 pairs:

  • K->M
  • O->Q
  • E->G

everybody thinks twice before solving this.

g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj.

Hints

The text must be encoded.

Hint from url: map. And notice that there's exactly one character between the given pairs: K->L->M, O->P->Q, E->F->G, and look at the very first letter in the text g, if this is English, a good guess is that g is actually i. What is the distance from g to i? Yes, G->H->I. So let's shift each character to the right, by 2, and y back to a, z back to b.

The string is not so long, so let's just copy and past it to REPL:

>>> raw = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle grgl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."

Solution 1

The pair of functions that would help up make the change:

  • ord(): character to integer
  • chr(): integer to character

e.g.

>>> chr(65)
'A'
>>> ord('A')
65

To shift a character:

>>> ord('k')
107
>>> ord('k') + 2
109
>>> chr(ord('k') + 2)
'm'

but it is not working for 'z':

>>> chr(ord('z') + 2)
'|'

To make it circular, calculate it's distance from 'a'

>>> (ord('z') + 2) - ord('a')
27

if it is larger than 26, go back to the beginning

>>> ((ord('z') + 2) - ord('a')) % 26
1

then add that difference to 'a'

>>> chr(((ord('z') + 2) - ord('a')) % 26 + ord('a'))
'b'

Let's translate the whole string:

>>> result = ""
>>> for c in raw:
...     if c >= 'a' and c <= 'z':
...         result += chr(((ord(c) + 2) - ord('a')) % 26 + ord('a'))
...     else:
...         result += c
...
>>> result
"i hope you didnt translate it by hand. thats what computers are for. doing itin by hand is inefficient and that's why this text is so long. using string.maketrans() is recommended. now apply on the url."

That is how we make a loop in Java or C, but python has a better way: list comprehension

>>> ''.join([chr(((ord(s) + 2) - ord('a')) % 26 + ord('a')) if s >= 'a' and s <= 'z' else s for s in raw])
"i hope you didnt translate it by hand. thats what computers are for. doing itin by hand is inefficient and that's why this text is so long. using string.maketrans() is recommended. now apply on the url."

But since it suggest us use .maketrans(), let's try it next.

Put Everything Together

raw = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle grgl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."

print(''.join([chr(((ord(s) + 2) - ord('a')) % 26 + ord('a')) if s >= 'a' and s <= 'z' else s for s in raw]))

Solution 2: .maketrans()

In Python 3, .maketrans is not in string as indicated; instead call str.maketrans() or bytes.maketrans()

>>> table = str.maketrans("abcdefghijklmnopqrstuvwxyz", "cdefghijklmnopqrstuvwxyzab")
>>> raw.translate(table)
"i hope you didnt translate it by hand. thats what computers are for. doing itin by hand is inefficient and that's why this text is so long. using string.maketrans() is recommended. now apply on the url."

Put Everything Together

raw = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle grgl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."

table = str.maketrans(
    "abcdefghijklmnopqrstuvwxyz", "cdefghijklmnopqrstuvwxyzab"
)

result = raw.translate(table)

print(result)

Solution 3

Let's have our own version of maketrans. The inputs are two lists, then each character is mapped from one list to another.

Define the two lists

>>> a = "abcdefghijklmnopqrstuvwxyz,. '()"
>>> b = "cdefghijklmnopqrstuvwxyzab,. '()"

zip them

>>> list(zip(a, b))
[('a', 'c'), ('b', 'd'), ('c', 'e'), ('d', 'f'), ('e', 'g'), ('f', 'h'), ('g', 'i'), ('h', 'j'), ('i', 'k'), ('j', 'l'), ('k', 'm'), ('l', 'n'), ('m', 'o'), ('n', 'p'), ('o', 'q'), ('p', 'r'), ('q', 's'), ('r', 't'), ('s', 'u'), ('t', 'v'), ('u', 'w'), ('v', 'x'), ('w', 'y'), ('x', 'z'), ('y', 'a'), ('z', 'b'), (',', ','), ('.', '.'), (' ', ' '), ("'", "'"), ('(', '('), (')', ')')]

actually we can create a dict

>>> dict(zip(a, b))
{'t': 'v', 'g': 'i', 'b': 'd', 'i': 'k', ',': ',', 'v': 'x', 'u': 'w', 'd': 'f', 'e': 'g', 'h': 'j', 'm': 'o', "'": "'", '(': '(', '.': '.', 'q': 's', 'l': 'n', 'a': 'c', 'x': 'z', ' ': ' ', 'f': 'h', 'o': 'q', 'w': 'y', 'n': 'p', 'c': 'e', 'p': 'r', 's': 'u', 'z': 'b', 'j': 'l', 'y': 'a', 'r': 't', 'k': 'm', ')': ')'}

then mapping is as easy as

>>> dict(zip(a, b))['z']
'b'

translate the whole string:

>>> "".join([dict(zip(a,b))[x] for x in raw])
"i hope you didnt translate it by hand. thats what computers are for. doing itin by hand is inefficient and that's why this text is so long. using string.maketrans() is recommended. now apply on the url."

Put Everything Together

raw = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle grgl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."

a = "abcdefghijklmnopqrstuvwxyz,. '()"
b = "cdefghijklmnopqrstuvwxyzab,. '()"

print("".join([dict(zip(a,b))[x] for x in raw]))

Next Level

As hinted, apply the same function on the url(map), we get ocr.

>>> "map".translate(str.maketrans("abcdefghijklmnopqrstuvwxyz", "cdefghijklmnopqrstuvwxyzab"))
'ocr'

http://www.pythonchallenge.com/pc/def/ocr.html

Python 2 to Python 3

In Python 2, maketrans() is a method in string module, so you need to import it first:

import string

table = string.maketrans("from", "to")

In Python 3, maketrans() is part of either bytes or str:

  • str.maketrans(from, to)
  • bytes.maketrans(from, to)