MISJA 006 goo.gl/te47XT DIFFICULTY: ███░░░░░░░ [3/10] ┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ Przeglądając stare dokumenty z lat '60 zeszłego wieku natrafiliśmy na taką oto notatkę: c5 c2 c3 c4 c9 c3 40 82 a8 93 82 a8 40 86 81 91 95 a8 40 87 84 a8 82 a8 40 82 a8 93 40 93 96 87 89 83 a9 95 a8 4b 40 c1 93 85 40 95 89 85 40 91 85 a2 a3 4b Przypuszczamy, że to jakieś zdanie w języku Polskim, ale nie udało nam się tego zdekodować. Zrzucimy to więc na Ciebie. Powodzenia! -- Odzyskaną wiadomość umieśc w komentarzu pod tym video :) Linki do kodu/wpisów na blogu/etc z opisem rozwiązania są również mile widziane! P.S. Rozwiązanie zadania przedstawie na początku kolejnego livestreama.
Przede wszystkim, bardzo mi wstyd, że rozwiązanie tej zagadki zajęło mi ±90 minut. Przy czym chciałbym tutaj zwalić winę na Gynvaela, ponieważ wprowadził w zadaniu zmyłkę, ale o tym za chwilę.
Metoda ręczno-statystyczna
Jeśli to polskie zdanie, to na pewno znaki powinny występować z częstością zgodną z językiem polskim:
a 8.91% w 4.65% p 3.13% g 1.42% ć 0.40% i 8.21% s 4.32% m 2.80% ę 1.11% f 0.30% o 7.75% t 3.98% u 2.50% h 1.08% ń 0.20% e 7.66% c 3.96% j 2.28% ą 0.99% q 0.14% z 5.64% y 3.76% l 2.10% ó 0.85% ź 0.06% n 5.52% k 3.51% ł 1.82% ż 0.83% v 0.04% r 4.69% d 3.25% b 1.47% ś 0.66% x 0.02%
Na szybko zapisałem podstawowe wartości skryptu w Rubym:
TXT = <<TXT c5 c2 c3 c4 c9 c3 40 82 a8 93 82 a8 40 86 81 91 95 a8 40 87 84 a8 82 a8 40 82 a8 93 40 93 96 87 89 83 a9 95 a8 4b 40 c1 93 85 40 95 89 85 40 91 85 a2 a3 4b TXT NUMS = TXT.split.map{ |n| n.to_i(16) } |
Policzenie częstotliwości występowania znaków okazało się proste (ale jeśli jakiś Ruby-golfista umie ładniej to bardzo zapraszam do komentarzy):
COUNTS = NUMS.inject({}){ |t, el| t[el] = t.fetch(el, 0) + 1; t } COUNTS_BY_FREQ = COUNTS.to_a.sort_by{|a,b| b }.reverse |
Najczęściej występującymi okazały się:
[64, 8], [168, 7], [130, 4], [147, 4], [133, 3], [149, 3] |
64 założyłem, że jest spacją (alternatywnie – 168, a 64 to byłoby a). Naprędce sprawdziłem kilka rozwiązań:
[ ' aio', 'a io', ' iao', 'i ao' ].each do |s| chars = s.each_codepoint.to_a.map(&:chr) replace = COUNTS_BY_FREQ.map(&:first).zip(chars).select{ |a,b| b }.to_h p NUMS.map{ |n| replace.fetch(n, '.') }.join end |
Wyniki:
...... iaoia ....a ..aia iao o......a. .o. ... ..... ......ai oi a.... a.. i ai oao...... .a.o.a...a..... ...... aioai ....i ..iai aio o......i. .o. ... ..... ......ia oa i.... i.. a ia oio...... .i.o.i...i..... |
Nope. Spacja wydaje się faktycznie najczęstsza (patrząc po wielkości wyrazów), ale poza tym tworzone są zbitki znakowe niemające sensu w języku polskim. Podjąłem jeszcze kilka prób, dodając znaki interpunkcyjne, używając innych map frekwencji, ale to nie pomogło.
Bardziej matematycznie
Może to jedna wielka liczba, którą należy pomnożyć lub podzielić?
VAL = TXT.split.join.to_i(16) (1..10000).each do |n| x = VAL / n str = x.to_s(16).each_codepoint.each_slice(2).to_a.map{|n| n.map(&:chr).join.to_i(16) }.map(&:chr).join p str unless str =~ /[^[:print:]]/ end |
Nic.
VAL = TXT.split.join.to_i(16) (1..10000).each do |n| x = VAL * n str = x.to_s(16).each_codepoint.each_slice(2).to_a.map{|n| n.map(&:chr).join.to_i(16) }.map(&:chr).join p str unless str =~ /[^[:print:]]/ end |
Nic. Swoją drogą, zamiana stringa na codepointy i z powrotem to jakaś masakra, pomimo tego, że to oneliner.
Może pierwsze 6 lub 7 znaków to klucz xor? (dość znacząco wyróżniają się)
p NUMS[7..-1].each_with_index.map{|e, i| e ^ NUMS[i % 6]}.map(&:chr).join p NUMS[6..-1].each_with_index.map{|e, i| e ^ NUMS[i % 6]}.map(&:chr).join |
"GjPFa\x83CCRQa\x83BFkFa\x83GjP\x84ZUBK@m\\k\x8E\x82\x02WL\x83PKF\x84XFga\x88" "\x85@kWKk\x85DBU\\k\x85EGlKk\x85@kW\x89PSEJG`Vm\x89\x83\x05ZF\x85WJA\x89R@``\x8F" |
Nope. Gdyby nie zgadzała się długość klucza, to chociaż pierwsze znaki by były sensowne.
A co jeśli pytanie wprowadza w błąd?
Równie dobrze mógłbym to opisać jako błędne założenia poczynione przeze mnie, ale zdecydowanie preferuję zwalić na kogoś winę. Polskie zadanie dla polskiego odbiorcy oznajmiło, że polski tekst jest ukryty w zakodowanej wiadomości, prawdopodobnie z lat 60-tych XX w.
Oczywistym dla mnie było, że wszystko odbyło się w Polsce. Wobec tego ruszyłem na poszukiwania kodowania używanego w komputerach Odra. Najbliższe co zauważyłem to kodowanie Mazovia, ale niezbyt pasowało: ani wartości się nie zgadzały, ani dekady.
Rozwiązanie
Finalnie, poddałem się i zacząłem szukać kodowań “z dekady”, niezależnie od przypisanej im lokacji geograficznej. Pierwsze na ruszt poszło EBCDIC. Już na pierwszy rzut oka wydawało się mieć sens, ponieważ EBCDIC są odpowiednio 5., 2., 3., 4., 9. i 3. literami alfabetu, a szyfrogram zaczyna się od “c5 c2 c3 c4 c9 c3” – czyli dolne nibble się zgadzają. Dla pewności:
require 'iconv' p Iconv.conv('ASCII', 'EBCDIC-US', NUMS.map(&:chr).join) |
EBCDIC bylby fajny gdyby byl logiczny. Ale nie jest. |
W końcu! No cóż, ostatnio pisałem “liczę, że czasem pojawią się zadania o wyższym poziomie trudności”, ale liczyłem, że to będzie obiektywnie większa trudność, a nie mój problem z prostym zadaniem.