6.7800-project

✨ Convolve me, senpai!!! ✨

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
  119. 119
  120. 120
  121. 121
  122. 122
  123. 123
  124. 124
  125. 125
"""
Script for generating ciphertexts.
Usage: python3 encode.py plaintext.out ciphertext.out has_breakpoint [seed]

Behavior:
    1. Reads in standard input (until EOF).
    2. Cleans text to satisfy requirements given in the project handout.
    3. Writes the cleaned text to `plaintext.out`.
    4. Encodes the cleaned text and writes the ciphertext to `ciphertext.out`.

Setting has_breakpoint to true encodes with a breakpoint.
Passing a seed as the optional last argument makes the encoding deterministic.

Example invocations:
    python3 encode.py plaintext.txt ciphertext.txt false 42 < data/texts/feynman.txt
    python3 encode.py plaintext.txt ciphertext.txt true < data/texts/tolstoy.txt

Can also be used for just cleaning text in the following way:
    python3 encode.py clean.txt /dev/null 0 < dirty.txt
"""

from typing import Tuple

import sys
import string
import random
import typing
import unicodedata

ALPHABET = list(string.ascii_lowercase) + [" ", "."]
LETTER_TO_IDX = dict(map(reversed, enumerate(ALPHABET)))


def _clean_text(text: typing.AnyStr) -> str:
    # try and approximate unicode with ascii
    text = unicodedata.normalize("NFKD", text).encode("ascii",
                                                      "ignore").decode()

    text = text.lower()  # make lowercase
    text = text.replace("?", ".").replace("!", ".")
    for c in "/-\n\r":
        text = text.replace(c, " ")
    text = "".join(filter(ALPHABET.__contains__,
                          text))  # filter to alphabet chars

    text = text.lstrip(" .")  # filter out leading spaces and periods
    if text == "":
        return text
        # raise ValueError("text needs to have at least one letter")

    ret = ""
    for x in text:
        # ret is a valid string after every iteration
        if x == ".":
            ret = ret.rstrip(". ") + ". "
        elif x == " ":
            ret = ret.rstrip(" ") + " "
        else:
            ret += x

    ret = ret.rstrip(" ")  # strip trailing spaces
    return ret


def assert_clean(text: str):
    assert _clean_text(text) == text

    assert len(text) > 0
    assert all(x in ALPHABET for x in text)
    # assert text[0] in string.ascii_lowercase
    for i, x in enumerate(text):
        if x == ".":
            assert text[i - 1] in string.ascii_lowercase
            if i + 1 < len(text):
                assert text[i + 1] == " "
        elif x == " ":
            assert text[i + 1] in string.ascii_lowercase

def clean_text(text: str) -> str:
    clean = _clean_text(text)
    assert_clean(clean)
    return clean


def encode(plaintext: str) -> str:
    cipherbet = ALPHABET.copy()
    random.shuffle(cipherbet)

    ciphertext = "".join(cipherbet[LETTER_TO_IDX[c]] for c in plaintext)
    return ciphertext


def encode_with_breakpoint(plaintext: str) -> Tuple[str, int]:
    bpoint = random.randint(0, len(plaintext))
    return encode(plaintext[:bpoint]) + encode(plaintext[bpoint:]), bpoint


def main():
    plaintext_out = sys.argv[1]
    ciphertext_out = sys.argv[2]
    has_breakpoint = (sys.argv[3].lower() == "true")
    if len(sys.argv) > 4:
        random.seed(sys.argv[4])

    raw_text = sys.stdin.read()

    plaintext = clean_text(raw_text)
    print(f"Clean plaintext length: {len(plaintext)}")
    with open(plaintext_out, "w") as f:
        f.write(plaintext)

    if has_breakpoint:
        print("Encoding with breakpoint...")
        ciphertext, bpoint = encode_with_breakpoint(plaintext)
        print(f"Breakpoint at position {bpoint}")
    else:
        print("Encoding without breakpoint")
        ciphertext = encode(plaintext)

    with open(ciphertext_out, "w") as f:
        f.write(ciphertext)


if __name__ == "__main__":
    main()