У моей 8-летней племянницы вчера был урок по коду Морзе в школе, и ее назначение состояло в том, чтобы преобразовать различные фразы в код Морзе. Одна из фраз включала ее возраст, и вместо того, чтобы писать ---..
, она написала 3-2.
, потому что (по ее словам), "это меньше написано именно так". Этот рудиментарный "алгоритм сжатия" вызвал мое любопытство, поэтому я написал немного кода для его реализации.
Однако мы сделали несколько изменений на этом пути. Я указал ей, что если вы написали только .....-----
, нет никакого способа сказать, означал ли автор 50
или eeeeettttt
. На самом деле между каждой буквой каждого слова и каждым словом происходит пауза, поэтому это не проблема, но наша схема не имела этого. Я вытащил какую-нибудь графическую бумагу и предложил заполнить код Морзе для каждого символа другим символом, чтобы облегчить кодирование и устранить неоднозначность из схемы. Мне понравилось использовать +
, потому что "никто никогда не записывает их в предложениях". (Ой, я недавно закончил с математикой, но достаточно честный.)
Поскольку некоторые из нас пишут с помощью +
, и все мы используем дефисы и периоды/точки, что противоречило бы с нашим стандартным определением кода Морзе, эти символы заменяются на p
, h
и d
, соответственно. Конечно, это приводит нас к проблеме того, что делать с символами, которые не определены в нашем расширенном коде Морзе. Моя племянница хотела просто игнорировать их, так, что мы сделали. Для удобства хранения текстового сообщения с учетом регистра, буквы верхнего регистра не опускаются ниже в коде; они просто переносятся как есть и дополняются с помощью +
.
Резюме алгоритма:
- Коды Морзе имеют право на 5 символов с
+
- Мы расширили код Морзе, чтобы подставить
p
для+
,d
для.
иh
для-
. - Символы, которые не определены в нашем "расширенном" морском коде, передаются без изменений.
- Замены символов заменяются, если не встречается только один последовательный символ, и в этом случае число опущено.
Потенциальные ловушки:
- Моя схема заполнения, вероятно, снижает эффективность сжатия.
- Сжатие может быть улучшено за счет использования блоков размером более 5 символов.
- Если бы моя племянница или я знали что-нибудь об алгоритмах сжатия, мы, вероятно, могли бы использовать это, чтобы сделать их успешными.
- Это явно не подходит для производства, но поскольку для таких целей существует множество эффективных алгоритмов сжатия, я пока игнорирую эту проблему.
- ???
Пример:
В нашем алгоритме "Hello, World" переводится на
H++++.++++.-..+.-..+---++,+++++++++W++++---++.-.++.-..+-..++
и сжимается до
H4+.4+.-2.+.-2.+3-2+,9+W4+3-2+.-.2+.-2.+-2.2+
Вот код Python, который я сбросил вместе:
#!/usr/bin/python3
import itertools
import string
class MorseString(str):
def __init__(self, string):
# or, pad values during iteration but this seems neater
self._char_morse_map = {"a":".-+++", "b":"-...+", "c":"-.-.+", "d":"-..++", \
"e":".++++", "f":"..-.+", "g":"--.++", "h":"....+", \
"i":"..+++", "j":".---+", "k":"-.-++", "l":".-..+", \
"m":"--+++", "n":"-.+++", "o":"---++", "p":".--.+", \
"q":"--.-+", "r":".-.++", "s":"...++", "t":"-++++", \
"u":"..-++", "v":"...-+", "w":".--++", "x":"-..-+", \
"y":"-.--+", "z":"--..+", "1":".----", "2":"..---", \
"3":"...--", "4":"....-", "5":".....", "6":"-....", \
"7":"--...", "8":"---..", "9":"----.", "0":"-----",
" ":"+++++", ".":"d++++", "+":"p++++", "-":"h++++"}
self._morse_char_map = dict()
for letter, code in self._char_morse_map.items():
self._morse_char_map[code] = letter
self._string = string
# convert the string to "Morse code". Could also use c.lower()
self._morse_string = "".join([self._char_morse_map.get(c, c.ljust(5, "+")) for c in self._string])
def compress(self):
def grouper(n, k):
return str(n) + k if n > 1 else k
# could just use lambda
return "".join([grouper(len(list(g)), k) for k, g in itertools.groupby(self._morse_string)])
def decompress(self):
i = 0
start = 0
chars = list()
sc = self.compress()
while i < len(sc):
curr = sc[i]
i += 1
if not(curr in string.digits):
num = 1 if start + 1 == i else int(sc[start:i-1])
chars.append("".join(curr * num))
start = i
code = "".join(chars)
chars = list()
for i in range(0, len(code), 5):
piece = "".join(code[i:i+5])
chars.append(self._morse_char_map.get(piece, piece[0]))
return "".join(chars)
def main():
s0 = "Hello, World"
ms0 = MorseString(s0)
print(ms0._morse_string)
print(ms0.compress())
assert(s0 == ms0.decompress())
s1 = "Hello 2 world."
ms1 = MorseString(s1)
assert(s1 == ms1.decompress())
s2 = "The quick brown fox jumped over the lazy dog."
ms2 = MorseString(s2)
assert(s2 == ms2.decompress())
s3 = "abc -.+"
ms3 = MorseString(s3)
ms3
assert(s3 == ms3.decompress())
if __name__ == "__main__":
main()
Каковы некоторые простые методы, которые: а) улучшат наш алгоритм и б) относительно легко объяснить моей 8-летней племяннице? Хотя последний момент явно субъективен, я все же стараюсь побаловать свое любопытство как можно больше.
Я приветствую любые улучшения кода, так как он не структурирован ужасно хорошо (я довольно уверен, что он структурирован довольно плохо, на самом деле, но это быстро и грязно), хотя это строго для меня, так как я не получил моя племянница использует Python (YET).
Update
Вот обновленная версия кода, которая пытается включить модификацию пользователя1884905 в алгоритм и улучшения Karl для самого кода.
import itertools
import string
_char_morse_map = {"a":".-", "b":"-...", "c":"-.-.", "d":"-..", \
"e":".", "f":"..-.", "g":"--.", "h":"....", \
"i":"..", "j":".---", "k":"-.-", "l":".-..", \
"m":"--", "n":"-.", "o":"---", "p":".--.", \
"q":"--.-", "r":".-.", "s":"...", "t":"-", \
"u":"..-", "v":"...-", "w":".--", "x":"-..-", \
"y":"-.--", "z":"--..", "1":".----", "2":"..---", \
"3":"...--", "4":"....-", "5":".....", "6":"-....", \
"7":"--...", "8":"---..", "9":"----.", "0":"-----",
" ":"", ".":"d", "+":"p", "-":"h"}
_morse_char_map = {
code: letter
for letter, code in _char_morse_map.items()
}
def encode(s):
return "".join(_char_morse_map.get(c, c) + "+" for c in s)
def decode(encoded):
return "".join(decode_gen(encoded))
def decode_gen(encoded):
word = ""
for c in encoded:
if c != "+":
word += c
else:
yield _morse_char_map.get(word, word) if word != "" else " "
word = ""
def compress(s):
def grouper(n, k):
return str(n) + k if n > 1 else k
return "".join(grouper(len(list(g)), k) for k, g in itertools.groupby(s))
def decompress(compressed):
return "".join(decompress_gen(compressed))
def decompress_gen(compressed):
digits = ""
for c in compressed:
if c in string.digits:
digits += c
else:
number = int(digits) if digits else 1
yield "".join(c * number)
digits = ""