はじめに
あれ?Lispのカレンダーがないよ?! あるとばかり思っていたLispアドヴェントカレンダー。もうロートルLisperは引退してお騒がせしないつもりでいたのですが、これはさすがに寂しいものです。カレンダーを作りしゃしゃり出てみることにしました。さて、題材は何がいいかな? モールス信号を変換、復号するコードをご紹介することにします。SICPにもそんなネタがありましたね。
モールス信号
通信技術の発達した現在、モールス信号を使う場面はほとんどないかもしれません。中学生の頃(昭和40年代)、アマチュア無線をやっている人がいて一生懸命にモールス信号を覚えていましたっけ。ツー、トンの組み合わせで文字を表しこれを電波で送信、受信をしていたものでした。私が唯一覚えてるのはトトトツーツーツートトトがSOSを表すってことだけ(笑い)。ピンクレディーの曲「SOS」のイントロでこのトトトツーツーツートトトが効果音になっていて、それをそのまま放送したらにものすごく怒られます。緊急信号だから迂闊に使っちゃいけないのですね。
【ピンク・レディーの「S・O・S」に隠されたモールス事件の真相 放送局に叱られ、新聞ネタにまで】
https://www.zakzak.co.jp/ent/news/170920/ent1709203106-n1.html
仕様
簡単に済ませるのに扱う文字はアルファベットと1-9の数ということにしました。トンは「.] ツーは「-」で表すことにします。モールス信号は文字列で表すことにします。さきほどのピンクレディーのだと "... --- ... " と表します。文字と文字の間にはスペースを1つ入れることにします。変換の元になるものも文字列で表します。 "SOS" のようにします。単語と単語の間にはもうひとつスペースをいれることにします。
#|
Morse signal encoder and decoder
e.g.
> (encode "sos")
"... --- ... "
> (decode "... --- ... ")
"SOS"
>
> (encode "I love you")
".. .-.. --- ...- . -.-- --- ..- "
> (decode ".. .-.. --- ...- . -.-- --- ..- ")
"I LOVE YOU"
>
|#
ご参考
https://www.benricho.org/symbol/morse.html
符号化
原文の文字列を分解してキャラクタを要素とするリストに変換します。そしてそのキャラクターをひとつひとつモールス信号に変換します。さらにそれらの文字列を全部つなぎ合わせればできあがりです。case構文とmapcarを使うと簡単です。pipeというがでてきますが、これについては後述します。
(import "elixir")
(defun encode (str)
(pipe (convert str <list>) |> (capitalize) |> (list->morse) |> (list->string)))
(defun list->string (ls)
(if (null ls)
""
(string-append (car ls) (list->string (cdr ls)))))
(defun list->morse (ls)
(mapcar (lambda (x) (char->morse x)) ls))
(defun char->morse (c)
(case c
((#\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) "--.. ")
((#\0) "----- ")
((#\1) ".---- ")
((#\2) "..--- ")
((#\3) "...-- ")
((#\4) "....- ")
((#\5) "..... ")
((#\6) "-.... ")
((#\7) "--... ")
((#\8) "---.. ")
((#\9) "----. ")
((#\.) ".-.-.- ")
((#\,) "--..-- ")
((#\?) "..--.. ")
((#\space) " " ) ))
(defun capitalize (ls)
(mapcar (lambda (x) (capitalize1 x)) ls))
(defun capitalize1 (c)
(let ((i (convert c <integer>)))
(if (and (>= i 97)(<= i 122))
(convert (- i 32) <character>)
c)))
復号化
復号化は符号化に比べるとちょっと複雑です。まずはモールス信号の文字列をバラバラにしてキャラクターを要素とするリストに変換します。このリストを先頭から読みつつパターンを探します。トトトときたらSです。 #. #. #. #\space というパターンがきたらこれを文字列”S"に変換します。Lispの機能だけを使うとなるとcond節で1文字目2文字目・・・をcar cadr caddr でみつけてchar=で比較することになります。これだと長くなってかったるいです。そこで自作のパターンマッチライブラリを使いました。自作のISLisp処理のEASY-ISLispではdefpattern というパターンマッチマクロを用意してあります。
また、変換をしていくのにLispでは多重の入れ子になってしまいます。変換がか重なると読みにくいです。
(変換n ...(変換2 (変換1 X)))
そこでElixir風のパイプマクロも用意してあります。
(pipe X |> (変換1) |> (変換2)) |> ... |> (変換n)
のようにあらわすことができるマクロを用意してあります。これらのマクロはElixirという名前のライブラリに入っています。Elixirはあの天才Joseさんの設計した言語名です。Joseさんの天才ぶりに敬意をこめてElixirライブラリと命名しました。
(defun decode (str)
(pipe (convert str <list>) |> (morse->list) |> (char->str) |> (list->string)))
(defpattern morse->list (x)
((empty) nil)
(((#\space :rest _y)) (cons #\space (morse->list _y)))
(((#\. #\- #\space :rest _y)) (cons #\A (morse->list _y)))
(((#\- #\. #\. #\. #\space :rest _y)) (cons #\B (morse->list _y)))
(((#\- #\. #\- #\. #\space :rest _y)) (cons #\C (morse->list _y)))
(((#\- #\. #\. #\space :rest _y)) (cons #\D (morse->list _y)))
(((#\. #\space :rest _y)) (cons #\E (morse->list _y)))
(((#\. #\. #\- #\. #\space :rest _y)) (cons #\F (morse->list _y)))
(((#\- #\- #\. #\space :rest _y)) (cons #\G (morse->list _y)))
(((#\. #\. #\. #\. #\space :rest _y)) (cons #\H (morse->list _y)))
(((#\. #\. #\space :rest _y)) (cons #\I (morse->list _y)))
(((#\. #\- #\- #\- #\space :rest _y)) (cons #\J (morse->list _y)))
(((#\- #\. #\- #\space :rest _y)) (cons #\K (morse->list _y)))
(((#\. #\- #\. #\. #\space :rest _y)) (cons #\L (morse->list _y)))
(((#\- #\- #\space :rest _y)) (cons #\M (morse->list _y)))
(((#\- #\. #\space :rest _y)) (cons #\N (morse->list _y)))
(((#\- #\- #\- #\space :rest _y)) (cons #\O (morse->list _y)))
(((#\. #\- #\- #\. #\space :rest _y)) (cons #\P (morse->list _y)))
(((#\- #\- #\. #\- #\space :rest _y)) (cons #\Q (morse->list _y)))
(((#\. #\- #\. #\space :rest _y)) (cons #\R (morse->list _y)))
(((#\. #\. #\. #\space :rest _y)) (cons #\S (morse->list _y)))
(((#\- #\space :rest _y)) (cons #\T (morse->list _y)))
(((#\. #\. #\- #\space :rest _y)) (cons #\U (morse->list _y)))
(((#\. #\. #\. #\- #\space :rest _y)) (cons #\V (morse->list _y)))
(((#\. #\- #\- #\space :rest _y)) (cons #\W (morse->list _y)))
(((#\- #\. #\. #\- #\space :rest _y)) (cons #\X (morse->list _y)))
(((#\- #\. #\- #\- #\space :rest _y)) (cons #\Y (morse->list _y)))
(((#\- #\- #\. #\. #\space :rest _y)) (cons #\Z (morse->list _y)))
(((#\- #\- #\- #\- #\- #\space :rest _y)) (cons #\0 (morse->list _y)))
(((#\. #\- #\- #\- #\- #\space :rest _y)) (cons #\1 (morse->list _y)))
(((#\. #\. #\- #\- #\- #\space :rest _y)) (cons #\2 (morse->list _y)))
(((#\. #\. #\. #\- #\- #\space :rest _y)) (cons #\3 (morse->list _y)))
(((#\. #\. #\. #\. #\- #\space :rest _y)) (cons #\4 (morse->list _y)))
(((#\. #\. #\. #\. #\. #\space :rest _y)) (cons #\5 (morse->list _y)))
(((#\- #\. #\. #\. #\. #\space :rest _y)) (cons #\6 (morse->list _y)))
(((#\- #\- #\. #\. #\. #\space :rest _y)) (cons #\7 (morse->list _y)))
(((#\- #\- #\- #\. #\. #\space :rest _y)) (cons #\8 (morse->list _y)))
(((#\- #\- #\- #\- #\. #\space :rest _y)) (cons #\9 (morse->list _y)))
(((#\. #\- #\. #\- #\. #\- #\space :rest _y)) (cons #\. (morse->list _y)))
(((#\- #\- #\. #\. #\- #\- #\space :rest _y)) (cons #\, (morse->list _y)))
(((#\. #\. #\- #\- #\. #\. #\space :rest _y)) (cons #\? (morse->list _y)))
((else) (error "morse->list illegal code")))
(defun char->str (ls)
(mapcar (lambda (x) (convert x <string>)) ls))
出現頻度
イーターオインシュルドゥルとは英語の文章でのアルファベットの出現頻度です。etaoinshrdlu eが一番出現頻度が高いのですね。モールス信号をよく見るとeはトン1文字だけになってます。なるほどうまいこと符号化したものです。ちなみに日本語のモールス信号は出現頻度を考慮しないで定めてしまったみたいです。アマチュア無線愛好家にとっては悩ましいとか。
お気楽にどうぞ
Lispというとマニアックな雰囲気で迂闊なことを書いちゃいけないという雰囲気が漂います。でも、アマチュアLisperの私のような人たちも多いことでしょう。どんどんLispについてだべっちゃいましょう。気楽にご参加ください。
それではみなさま、最後はモールス信号でお別れのご挨拶です。
"-- . .-. .-. -.-- -.-. .... .-. .. ... - -- .- ... "