クソアプリアドベントカレンダー2024の技術ネタ:サロゲートペア・ハフマン符号化・頻出ひらがな
公開 2024/12/05 05:59
最終更新
-
クソアプリアドベントカレンダー2024作品【真名を示す暗黒ツール『‡虚空叡智の変換機‡』】の技術ネタです。
https://simblo.net/u/WKZXBT/post/150175
それらの方式だと、変換後の文字が以下のようになってしまうためです。
(前提として、変換元として指定可能な文字は、ひらがなと「、」「。」「ー」です。)
・あり得ない文字から始まる「ュパセーチ」「、ピリスカ」「ーエグオイ」「ンミコソザ」
・あり得ない小文字のつながり「アョンテスル」「キュォテン」
・あり得ない文字の連続「ヤ。ーラブ」
こういうのが発生するためです。
「の→ア」
「に→サ」
・・・
ただ、これだと「単一換字式暗号」になってしまうので工夫が必要です。
全ての「変換後」の言葉を、小文字や記号や[ン]で始まらないようにします。
そのために、一部の文字については「変換後の文字を2〜3文字」にします。
具体的には、「ツ」「リ」「ウ」「ネ」など、一部の文字を上位サロゲートにして、これらの文字の場合は必ず「変換後2〜3文字の言葉」で定義します。
「の→ア」
「に→サ」
・・・
「ゆ→ウラ」
「ふ→ウル」
・・・
こうすることで、必ず「変換後の言葉が読める状態のもの」になります。
ただここで問題なのが、「変換したらやたら長い文字になった」だとちょっとイケてない感じがすることです。
ただ、じゃあ実際どの文字の出現頻度が高いのか?ですが、
ネットにある頻出ひらがなの情報だと全てのひらがなを網羅していない感じだったので、自分で調べてみます。
今回はWikipediaのデータを使わせていただきます。
手前味噌ですが、
https://github.com/hoku/splitWikipediaPerPage
こちらのツールで1記事1xmlファイルに分割。
各ファイルをパースして、ひたすら出現数をカウントします。
本当はMediawikiの記法に則ってちゃんと処理してからカウントした方がより良いですし、そもそも「Wikiの場合の出現頻度」という前提になるわけですが、今回はざっくり頻度が分かれば良いのであまり気にせずカウントします。
実際のカウント処理はこんな感じです。
実際にカウントした結果が以下になります。
ちゃんとカウントできていますね!
あとはこの出現数順を考慮して、頻度が高い文字は変換後1文字に、頻度が低い文字は変換後2〜3文字にしていった感じです。
https://simblo.net/u/WKZXBT/post/150175
NGな変換ルール #
今回のような変換では、「単一換字式暗号」や「シーザー暗号」的な変換は使えませんでした。それらの方式だと、変換後の文字が以下のようになってしまうためです。
(前提として、変換元として指定可能な文字は、ひらがなと「、」「。」「ー」です。)
・あり得ない文字から始まる「ュパセーチ」「、ピリスカ」「ーエグオイ」「ンミコソザ」
・あり得ない小文字のつながり「アョンテスル」「キュォテン」
・あり得ない文字の連続「ヤ。ーラブ」
こういうのが発生するためです。
サロゲートペア的変換ルールにする #
「全ての文字に対して、対応する言葉」を1つずつ当てていきます。「の→ア」
「に→サ」
・・・
ただ、これだと「単一換字式暗号」になってしまうので工夫が必要です。
全ての「変換後」の言葉を、小文字や記号や[ン]で始まらないようにします。
そのために、一部の文字については「変換後の文字を2〜3文字」にします。
具体的には、「ツ」「リ」「ウ」「ネ」など、一部の文字を上位サロゲートにして、これらの文字の場合は必ず「変換後2〜3文字の言葉」で定義します。
「の→ア」
「に→サ」
・・・
「ゆ→ウラ」
「ふ→ウル」
・・・
こうすることで、必ず「変換後の言葉が読める状態のもの」になります。
ただここで問題なのが、「変換したらやたら長い文字になった」だとちょっとイケてない感じがすることです。
ハフマン符号化的アプローチ #
そこで、「頻出文字はなるべく1文字」で、「出現頻度が低い文字は2~3文字」にすることで、変換前と変換後の文字数の増加量を抑えるようにします。ただ、じゃあ実際どの文字の出現頻度が高いのか?ですが、
ネットにある頻出ひらがなの情報だと全てのひらがなを網羅していない感じだったので、自分で調べてみます。
頻出ひらがなの調査 #
ということで、自分で集計してみます。今回はWikipediaのデータを使わせていただきます。
手前味噌ですが、
https://github.com/hoku/splitWikipediaPerPage
こちらのツールで1記事1xmlファイルに分割。
各ファイルをパースして、ひたすら出現数をカウントします。
本当はMediawikiの記法に則ってちゃんと処理してからカウントした方がより良いですし、そもそも「Wikiの場合の出現頻度」という前提になるわけですが、今回はざっくり頻度が分かれば良いのであまり気にせずカウントします。
実際のカウント処理はこんな感じです。
<?php
// https://github.com/hoku/splitWikipediaPerPage.git
// で分割したxmlファイルから、[ひらがな]を文字毎にカウントする。
define('WIKI_FILES_DIR', './out');
define('OUT_FILE', './result.json');
echo 'Start!('.date('Y/m/d H:i:s').")\n";
$countsPerChara = [];
foreach (glob(WIKI_FILES_DIR . '/*') as $outDir) {
if (!is_dir($outDir)) { continue; }
foreach (glob($outDir . '/*.xml') as $pageXml) {
if (!is_file($pageXml)) { continue; }
// 本文のひらがなを文字毎にカウントする
$xml = simplexml_load_file($pageXml);
$bodyText = trim($xml->page->revision->text);
preg_match_all('/[ぁ-ゖー、。]/u', $bodyText, $matches);
foreach ($matches[0] as $chara) {
if (array_key_exists($chara, $countsPerChara)) {
$countsPerChara[$chara]++;
} else {
$countsPerChara[$chara] = 1;
}
}
}
echo $outDir." -> Done\n";
}
// 多い順に並び替えて出力
arsort($countsPerChara);
file_put_contents(OUT_FILE, json_encode($countsPerChara));
echo 'Finish!('.date('Y/m/d H:i:s').")\n";
実際にカウントした結果が以下になります。
{
"の": 94902446,
"ー": 81016486,
"、": 66910764,
"に": 51726769,
"。": 47943741,
"る": 42211681,
"た": 41790080,
"と": 38184105,
"し": 38069515,
"い": 36859625,
"は": 36486516,
"を": 35373531,
"で": 33231624,
"て": 32818337,
"が": 30182278,
"な": 25583329,
"れ": 23126747,
"か": 17814469,
"ら": 16294210,
"さ": 16263151,
"す": 15852872,
"り": 15692655,
"っ": 15446688,
"あ": 14236256,
"も": 13267461,
"こ": 12879166,
"う": 12304974,
"ま": 12244326,
"よ": 9937923,
"く": 9554993,
"き": 8564425,
"ん": 7985015,
"お": 7052682,
"め": 6844567,
"け": 6493180,
"つ": 6162208,
"そ": 5940536,
"だ": 5271260,
"え": 5187000,
"や": 5090813,
"ち": 4742223,
"わ": 4613303,
"ど": 4538246,
"み": 4429072,
"せ": 3597357,
"へ": 2740176,
"じ": 2658185,
"ば": 2564891,
"び": 2307353,
"ず": 1969986,
"ろ": 1792898,
"ほ": 1695347,
"げ": 1331530,
"む": 1311501,
"ひ": 1263080,
"べ": 1208729,
"ゆ": 1199121,
"ふ": 1177456,
"ご": 1025260,
"ょ": 1008229,
"ぶ": 975894,
"ね": 881724,
"ゃ": 871815,
"ぐ": 703633,
"ぎ": 672675,
"ぼ": 479487,
"づ": 466282,
"ざ": 429467,
"ゅ": 419541,
"ぞ": 398791,
"ぬ": 220262,
"ぜ": 198194,
"ぱ": 184610,
"ぽ": 154691,
"ぷ": 145419,
"ぴ": 96578,
"ぃ": 68134,
"ぁ": 53970,
"ぇ": 44578,
"ぺ": 41156,
"ぉ": 13145,
"ぅ": 12573,
"ゐ": 11960,
"ぢ": 11897,
"ゑ": 7373,
"ゔ": 4298,
"ゎ": 610,
"ゕ": 36,
"ゖ": 20
}
ちゃんとカウントできていますね!
あとはこの出現数順を考慮して、頻度が高い文字は変換後1文字に、頻度が低い文字は変換後2〜3文字にしていった感じです。
シンプルブログの運営者です。
色々作ってます。タイッツーも運営しています。
https://taittsuu.com/
よろしくお願いします〜🙇✨
色々作ってます。タイッツーも運営しています。
https://taittsuu.com/
よろしくお願いします〜🙇✨
最近の記事
タグ