memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

2進数で報告する高校生

ことのはじまり

娘が高校で教員をやっていて、担任の生徒さんから0、1が羅列された報告文を受けとったのだそうです。昨夜、教えてくれました。昨日はちょっと仕事が立て込んでいて大学を出たのが21時半でした。帰りの電車でずーっとその2進数と格闘していました😁

鉛筆で書かれたメッセージを読み取りデジタル化?すると以下のような感じで書かれていました。

111001001011101110001010111001101
00101111010010111100011100000101
00000100011000111100110100101111
01001011110100110100000100100011
110010110111100101101011110001110
0000101000101011100011100000011
011111011100011100000011001011111
100011100000011001111111100011100
0000010000010

あれこれ試したこと

直感的にはアスキーコードだと思いましたが、いきなり1から始まっていますので。。。8ビット目が立っていたらおかしいですね。カタカナとか拡張されたテーブルかな?と思いましたが。どうも表を見ただけでは違うようです。

ユニコードか?

とりあえず16進にしてみようと以下のコードを作成。8ビットで区切ってみたのですが、なぜか9ビットの場所もあります。不思議です。

a = "11100100 10111011 10001010 111001101
00101111 01001011 11000111 00000101
00000100 01100011 11001101 00101111
01001011 11010011 01000001 00100011
11001011 01111001 01101011 110001110
00001010 00101011 10001110 0000011
01111101 11000111 00000011 001011111
10001110 00000110 01111111 100011100
00000100 00010"

a.split("\n") do |line|
  c = ""
  line.split(" ") do |b|
    puts sprintf("%03x", b.to_i(2))
  end
end

ググってみると、河野太郎さんが同じように2進数でツイートを受け取り2進数で返した、というのを見つけました。たぶん、これを真似たんだろうな。。。と思い、unicode表を見たのですがどうも上記のプログラムの実行結果にあたる文字が存在しないようです。困った。。。EUCとかシフトJISとかも見てみましたがダメ。

河野太郎氏の「エゴサ力」がすごすぎる 2進数の「暗号」も解読...まさかの返信に「腹よじれた」: J-CAST ニュース【全文表示】

独自暗号か?

既存の文字コードではない可能性が出てきましたので、高校生でも情報とかの授業で知っているような内容かなと思い、独自暗号表のようなものと仮定し、あてずっぽですが8ビットの下位5ビット(アルファベット26文字を表すため)を数値に変換してAを起点として文字を表示してみましたが。。。これもダメ。

a = "11100100
10111011
10001010
11001101
00101111
01001011
11000111
00000101
00000100
01100011
11001101
00101111
01001011
11010011
01000001
00100011
11001011
01111001
01101011
110001110
00001010
00101011
10001110
00000110
01111101
11000111
00000011
001011111
10001110
00000110
01111111
10001100
00000100
00010"

a.split("\n") do |b|
  l = b.size-5
  p ("A".ord + b[l .. b.size-1].to_i(2)).chr
end

もしかするとモールス信号かも?

0が短音、1が長音とすればモールス信号になるかも?と思い、以下のサイトで試してみましたが、和文も欧文もダメでした。 何でもモールス信号変換

やっぱユニコードじゃね?

と思い、ユニコードを表示するには\uをつければいいらしいので、以下のようなコードを書いて実行してみました。しかし。。。ダメ(←ダメな理由は後でわかりますが。。。このときは知らなかった、というかちゃんと理解していなかった。。。)

a = "11100100 10111011
10001010 11001101
00101111 01001011
11000111 00000101
00000100 01100011
11001101 00101111
01001011 11010011
01000001 00100011
11001011 01111001
01101011 110001110
00001010 00101011
10001110 00000110
01111101 11000111
00000011 001011111
10001110 00000110
01111111 10001100
00000100 00010"

a.split("\n") do |line|
  c = ""
  line.split(" ") do |b|
    c += b.to_i(2).to_s(16)
  end
  puts "\u#{c}"
end

諦めの境地

帰宅したのが24時。それからご飯食べて風呂入って、少しやりましたが。。。わからず。うーん。。。諦めのLINEを娘に送り就寝でした。

翌日

やはりわからないのは悔しいもので。。。授業が終わってあれこれやっているうちに、上記でも参照した2進数変換サイトに2進数をそのまま貼り付けてみましたら。。。答えが出てきました(実は生徒さんが書きミスしていて途中が文字化けしていたのですが、1文字ずつ解析して区切りを見つけて上記の2進数に仕上げました)

2進数バイナリ文字列変換 日本語変換 Online - DenCode

全くわからなった状態からすれば1文字でもデコードに成功すればもう十分わかりました。原文のテキストがユニコードで書かれていて、それを2進数に変換したものを報告書に書き出した、ということです。その区切りが文字コードの区切りとは一致していなかったため、報告書に書かれた通りに改行してしまいデータがおかしくなって復号できなかったわけです。

娘に早速報告しました。娘も「今」まで自力で発見したところだったようですので、たぶん自力で解読できたのだと思います。

余談

UTF-8は1文字あたり3バイトで、アスキー文字はバイト」なんですね。すっかり忘れていました。というかユニコードUTF-8の違いをちゃんと理解していませんでした。お恥ずかしい。もっと正確にはこの定義も本では読んでいて字面としては知っていたのだと思いますが、そのコードを書いたことがなかったのですぐには発想できなかったと思われます。やはり手を動かしてちゃんと体験を持って理解しないと使えるスキルにはなりません。

なので、上記の直感は間違っていなかったのですが「3バイトを区切りにする」がわかっていれば答えがすぐに出せたんですね。。。生徒さんのお陰で勉強になりました。二度と忘れないでしょう。1文字ずつをちゃんと書くと以下のようになります。1文字ずつ上記のサイトに入れて確認しました。間違いありません。

普通の文字は上位2ビットが11ですね。仕様は見ていませんがこれをみれば3バイト続くことが予想されます。アスキー文字は上位2ビットが00ですね。これが来たら1バイト読んだら変換すれば良さそうです。

今 111001001011101110001010 
日 111001101001011110100101
も 111000111000001010000010
1      00110001
日 111001101001011110100101
頑 111010011010000010010001
張 111001011011110010110101
り 111000111000001010001010
ま 111000111000000110111110
し 111000111000000110010111
た 111000111000000110011111
。 111000111000000010000010

ということで、最初に書いたコードを改変して上記サイトに頼らずに、かつ、今回の例のように不適切な位置での改行に関わらず、デコードできるコードを書いてみました。愚直です。もっといい方法があるように思いますが。。。とにかく対応しているビットから取り出して3バイトを2バイトに変換して表示する感じにしています。

a = "111001001011101110001010111001101
00101111010010111100011100000101
00000100011000111100110100101111
01001011110100110100000100100011
110010110111100101101011110001110
0000101000101011100011100000011
011111011100011100000011001011111
100011100000011001111111100011100
0000010000010"

a.gsub!("\n", "")

idx = 0
bb = ""

while true
  if a[idx] == '0'
    b = a.slice(idx, 8).to_i(2)
    idx += 8
  else
    b = (a.slice(idx+4, 4) + a.slice(idx+10, 6) + a.slice(idx+18, 6)).to_i(2) #unicode -> UTF-8
    idx += 24
  end
  bb += b.chr('UTF-8')
  break if idx >= a.size
end

puts bb #今日も1日頑張りました。

実行結果がちゃんと表示されました。よかよか。

しかし、最近の高校生は凄いなぁ。高校の先生も大変です。

おしまい。