たんばりんニュースブログ

当て付け怪文書海老ブログではない

【翻訳】ポッ拳の使用率分析ツール作成

今回はプログラミング記事になります。


突然ですが、ポッ拳のキャラクター使用率が気になった時ってありませんか?

「ランキングは〇〇が多いなー」
「今日は〇〇によく当たるなー」

そう思っていても、母数が少ない以上は気のせいかも?で終わってしまう。
そこで、載っているランキング全てを集計して統計を取るプログラムを作成しました。

1.データを集める

今回の目的であるランキング集計ですが、まずnintendo switchからPCへのデータの受け渡し方法が大きな壁となりました。

前回上位100名では手入力で正の字をメモ用紙に書いて…という感じだったのですが、今回はそうもいきません。

何故なら項目数が前回の7.5倍。

250x3ランキング=750項目もあります。

流石に正の字を書くのはめんどくさそうです…

ここで、単一換字式で1文字に置き換え、辞書型として保持する方法を思いつきました。
人間に分かりづらくても後でデータを簡単に成形できますし、等倍文字にする事で何キャラ分出来たのかチェックしやすいという利点があります。

前回のワークフロー

ランキングを見る
全部の項目を確認するまで以下を繰り返す

  • リストから該当するポケモンを探す
  • 正の字を1画記入してカウント

終わったら全体の何%か計算してメモ
多い順にPCへ手入力

問題点
紙データのため統計時にPCへ入力する必要がある
記入ミスがあった時、書いた順が分からないので整合性チェックが出来ない
ミスする可能性のある課程が多い
ぶっちゃけめんどい

今回挑戦するワークフロー

ランキングを見る
全部の項目を確認するまで以下を繰り返す

  • ポケモン選択画面に対応したキーを押す
  • 10ページ毎に見直し

出来た文字列から文字の出現率を解析
出来たリストを出現率の降順でソート
文字をキャラ名に置換してリストを出力

これなら多くの手順を自動化できそうです。
文字列という形式なので整合性がチェックできます。

早速入力してみました。

累計ポイント世界ランキング(1〜250位)
vgqfzqzzzruhqjzeduaqbgymcrmwzjmgbqfrcgmvgbzhhemuygvwnbugqwcvzveczhbmcnvxvnqqtmjsxzfestgnxezunbqsrzggrsnzgsjdebwzdxqbxzqzjvmfwtznbrzzfqcfsrafdnuuxhafrtgzhgbwhasbmrqwnmxqsqzrarhhmzhmzbdnszfvzushqntzggnwgfsturdwzqunedfbqmuzewxrtfcjgvnwvdqtwyezqxznrvmjtt

累計勝利数世界ランキング(1〜250位)
qqvgzzafzuqrbzzmhxbqjuezwnnzdcyhghhrmzzgbzgqxmjgwfurmwycgemvbvxcvxvesqsgatnvgwvzujmcbrwtqrvmsmvnnbcugxarqeshthfzusfbbzedzqgfmzwqgzzsnxnffgusfdzjsrxfzumwzqrnnrtdnrjgfrwdrqxtgrbrahcndshqmbfwuqrtnhshtgzqamrhbgzzwwqmqyrtzwqfzwvsghzhdtyfebntshqzrnevbbcywc

トレーナー世界ランキング(1〜250位)
zrngzdnqxyxmfmzwxvfqnwwhadfaxnreztburuuzqzrvscvtahzryjmztbgqbsxjcfzrutrwxrnnrzdsucyrsvcscueqvyzbmssbuqtdjzwqhzhbhtmssybhwztvhztztrsscgbsxengqstbmqweqdnzfqztcqdqdjgbrsnrnrrdchmmsfuecjxmftgyqwunbmmmwfuhrznvfdrrfzsjbqxrcgjztxcerfertthrnhhurxjtumwjfxzxyt

1ページ5秒、見直し含めて1ランキング10分以内で終わりました。
ここを自動化したい気持ちがあったのですが、PCとswitchを繋ぐ黒い差込口みたいなパーツが手元になかったので断念。
webカメも無いので手打ちという結果になりました。

2.自動で統計する

前置きが長くなりましたが、これが本記事のメインです。

LINQでソートすると楽かな?とふと思いつき、C#でプログラミングしてみました。

そして出来上がったものはこちらです。

using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleSubstitutionCipher
{
    class ConvertCharacterList
    {
        //Read Users Input
        static string readText()
        {
            Console.WriteLine("\n--------------------------------\n");
            var originalText = Console.ReadLine();
            Console.WriteLine("\n--------------------------------\n");
            return originalText;
        }

        //Output Console
        static void WriteAllStatistics(Dictionary<char, string> name, Dictionary<char, float> stat)
        {
            //var sort = stat.OrderBy((x) => x.Value);
            var sort = stat.OrderByDescending((x) => x.Value);
            foreach (var st in sort)
                Console.WriteLine($"{name[st.Key]}  \t: {stat[st.Key]}%");
        }

        static void Main(string[] args)
        {
            var Statistics = new Dictionary<char, float>();
            var CharacterList
                = new Dictionary<char, string>()
                {
                    {'q',"Darkrai"},{'w',"Blaziken"},{'e',"Pikachu"},{'r',"Lucario"},{'t',"Gardevoir"},{'y',"Masked"},{'u',"Scizor"},
                    {'a',"Crogunk"},{'s',"Sceptile"},{'d',"Genger"},{'f',"Desidueye"},{'g',"Machamp"},{'h',"Braixen"},{'j',"Empoleon"},
                    {'z',"Mewtwo"},{'x',"Chandelure"},{'c',"Suicune"},{'v',"Weavile"},{'b',"Charizard"},{'n',"Garchomp"},{'m',"Dark M2"},
                };

            //1.Read users input
            var originalText = readText();

            //2.Substitution
            foreach (var ch in CharacterList)
            {
                float ratio = (float)originalText.Count(c => c == ch.Key) / (float)originalText.Length * 100;
                Statistics.Add(ch.Key, ratio);
            }

            //3.Output console
            WriteAllStatistics(CharacterList, Statistics);

            Console.WriteLine("\n--------------------------------\n");
            Console.WriteLine("Statistics Complete!! (Press Any Key)");
            Console.ReadKey();
        }
    }
}

動作チェック。

問題なく統計情報が出力できました。

3.解説

using System;
using System.Collections.Generic;
using System.Linq;

今回は辞書型を使うためのSystem.Collections.Generic;と
ソートするためのSystem.Linq;を読み込みました。




        //Read Users Input
        static string readText()
        {
            Console.WriteLine("\n--------------------------------\n");
            var originalText = Console.ReadLine();
            Console.WriteLine("\n--------------------------------\n");
            return originalText;
        } 

ユーザー入力を返すモジュールです。
少し装飾がありますが、やっている事はシンプルですが。
入力を待ち、決定された文字列をstring型で返します。



        //Output Console
        static void WriteAllStatistics(Dictionary<char, string> name, Dictionary<char, float> stat)
        {
            //var sort = stat.OrderBy((x) => x.Value);
            var sort = stat.OrderByDescending((x) => x.Value);
            foreach (var st in sort)
                Console.WriteLine($"{name[st.Key]}  \t: {stat[st.Key]}%");
        }

ソートして出力します。
ラムダ式LINQのwhere拡張を組み合わせると降順ソートが1行で可能です。

sort変数をコメントアウトしている行に切り替えると、昇順になります。
後半は結果出力です。
後述しますが、nameとstatは辞書型の変数です。また、statのKeyがキャラクターに当てはめた文字になります。
そのため、nameとstatの情報をst.Keyで取り出す事でソートした結果が取得できます。




var Statistics = new Dictionary<char, float>();
            var CharacterList
                = new Dictionary<char, string>()
                {
                    {'q',"Darkrai"},{'w',"Blaziken"},{'e',"Pikachu"},{'r',"Lucario"},{'t',"Gardevoir"},{'y',"Masked"},{'u',"Scizor"},
                    {'a',"Crogunk"},{'s',"Sceptile"},{'d',"Genger"},{'f',"Desidueye"},{'g',"Machamp"},{'h',"Braixen"},{'j',"Empoleon"},
                    {'z',"Mewtwo"},{'x',"Chandelure"},{'c',"Suicune"},{'v',"Weavile"},{'b',"Charizard"},{'n',"Garchomp"},{'m',"Dark M2"},
                };

先述した辞書型の中身はこうなっています。
ゲーム内のキャラ選択画面における上段、中段、下段で行を分けてみました。
statには統計結果がfloatで入るので、まだ空の状態です。



            //1.Read users input
            var originalText = readText();

            //2.Substitution ratio
            foreach (var ch in CharacterList)
            {
                float ratio = (float)originalText.Count(c => c == ch.Key) / (float)originalText.Length * 100;
                Statistics.Add(ch.Key, ratio);
            }

            //3.Output console
            WriteAllStatistics(CharacterList, Statistics);

メイン処理です。
先ほどのモジュールを呼び出しています。
2番目が肝となる処理で、ここで統計のパーセンテージを出して辞書型に格納しています。

最後に完了のメッセージを出して終了します。

手入力した部分と例外処理が無いのが反省点でしょうか。
辞書にない文字を入力しても無視する作りにはなっていると思ったので記述はしていません。

ば解説「文字出現率分析と単一換字暗号の応用を用いたキャラクター使用分布リスト作成」

今回ば解説情報処理

ば作キャラ分布

ば集データ

ば集計準備

ばメモ上位250キャラ

然ば大変

ば立作戦

ばキャラ選択画面対応Myキーボード



故1頁5秒。速。



累計ポイント世界ランキング(1〜250位)
vgqfzqzzzruhqjzeduaqbgymcrmwzjmgbqfrcgmvgbzhhemuygvwnbugqwcvzveczhbmcnvxvnqqtmjsxzfestgnxezunbqsrzggrsnzgsjdebwzdxqbxzqzjvmfwtznbrzzfqcfsrafdnuuxhafrtgzhgbwhasbmrqwnmxqsqzrarhhmzhmzbdnszfvzushqntzggnwgfsturdwzqunedfbqmuzewxrtfcjgvnwvdqtwyezqxznrvmjtt

累計勝利数世界ランキング(1〜250位)
qqvgzzafzuqrbzzmhxbqjuezwnnzdcyhghhrmzzgbzgqxmjgwfurmwycgemvbvxcvxvesqsgatnvgwvzujmcbrwtqrvmsmvnnbcugxarqeshthfzusfbbzedzqgfmzwqgzzsnxnffgusfdzjsrxfzumwzqrnnrtdnrjgfrwdrqxtgrbrahcndshqmbfwuqrtnhshtgzqamrhbgzzwwqmqyrtzwqfzwvsghzhdtyfebntshqzrnevbbcywc

トレーナー世界ランキング(1〜250位)
zrngzdnqxyxmfmzwxvfqnwwhadfaxnreztburuuzqzrvscvtahzryjmztbgqbsxjcfzrutrwxrnnrzdsucyrsvcscueqvyzbmssbuqtdjzwqhzhbhtmssybhwztvhztztrsscgbsxengqstbmqweqdnzfqztcqdqdjgbrsnrnrrdchmmsfuecjxmftgyqwunbmmmwfuhrznvfdrrfzsjbqxrcgjztxcerfertthrnhhurxjtumwjfxzxyt


ば考集計方法「単一換字変換と文字出現率を組み合わせれば簡単にランキングが作れるのではないだろうか」


ば作

ばPC立上

ば記C#

ば簡単作成

using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleSubstitutionCipher
{
    class ConvertCharacterList
    {
        //Read Users Input
        static string readText()
        {
            Console.WriteLine("\n--------------------------------\n");
            var originalText = Console.ReadLine();
            Console.WriteLine("\n--------------------------------\n");
            return originalText;
        }

        //Output Console
        static void WriteAllStatistics(Dictionary<char, string> name, Dictionary<char, float> stat)
        {
            //var sort = stat.OrderBy((x) => x.Value);
            var sort = stat.OrderByDescending((x) => x.Value);
            foreach (var st in sort)
                Console.WriteLine($"{name[st.Key]}  \t: {stat[st.Key]}%");
        }

        static void Main(string[] args)
        {
            var Statistics = new Dictionary<char, float>();
            var CharacterList
                = new Dictionary<char, string>()
                {
                    {'q',"Darkrai"},{'w',"Blaziken"},{'e',"Pikachu"},{'r',"Lucario"},{'t',"Gardevoir"},{'y',"Masked"},{'u',"Scizor"},
                    {'a',"Crogunk"},{'s',"Sceptile"},{'d',"Genger"},{'f',"Desidueye"},{'g',"Machamp"},{'h',"Braixen"},{'j',"Empoleon"},
                    {'z',"Mewtwo"},{'x',"Chandelure"},{'c',"Suicune"},{'v',"Weavile"},{'b',"Charizard"},{'n',"Garchomp"},{'m',"Dark M2"},
                };

            //1.Read users input
            var originalText = readText();

            //2.Substitution ratio
            foreach (var ch in CharacterList)
            {
                float ratio = (float)originalText.Count(c => c == ch.Key) / (float)originalText.Length * 100;
                Statistics.Add(ch.Key, ratio);
            }

            //3.Output console
            WriteAllStatistics(CharacterList, Statistics);

            Console.WriteLine("\n--------------------------------\n");
            Console.WriteLine("Statistics Complete!! (Press Any Key)");
            Console.ReadKey();
        }
    }
}

ば試

My job here is done.



ば解説

using System;
using System.Collections.Generic;
using System.Linq;

ばライブラリ宣言
Dictionary故Collections.Generic使用
途中ラムダ式LINQ配列整理故Linq使用



        //Read Users Input
        static string readText()
        {
            Console.WriteLine("\n--------------------------------\n");
            var originalText = Console.ReadLine();
            Console.WriteLine("\n--------------------------------\n");
            return originalText;
        }

ば作成モジュール
コンソール故ユーザー入力orコピペ待



        //Output Console
        static void WriteAllStatistics(Dictionary<char, string> name, Dictionary<char, float> stat)
        {
            //var sort = stat.OrderBy((x) => x.Value);
            var sort = stat.OrderByDescending((x) => x.Value);
            foreach (var st in sort)
                Console.WriteLine($"{name[st.Key]}  \t: {stat[st.Key]}%");
        }

ば再作モ
前半LINQ使用ソート
上米外下米切替、結果昇順。
標準降順。
後半結果出力。



            var Statistics = new Dictionary<char, float>();
            var CharacterList
                = new Dictionary<char, string>()
                {
                    {'q',"Darkrai"},{'w',"Blaziken"},{'e',"Pikachu"},{'r',"Lucario"},{'t',"Gardevoir"},{'y',"Masked"},{'u',"Scizor"},
                    {'a',"Crogunk"},{'s',"Sceptile"},{'d',"Genger"},{'f',"Desidueye"},{'g',"Machamp"},{'h',"Braixen"},{'j',"Empoleon"},
                    {'z',"Mewtwo"},{'x',"Chandelure"},{'c',"Suicune"},{'v',"Weavile"},{'b',"Charizard"},{'n',"Garchomp"},{'m',"Dark M2"},
                };

ば初期化Dictionary
ばCharacterList代入



            //1.Read users input
            var originalText = readText();

            //2.Substitution ratio
            foreach (var ch in CharacterList)
            {
                float ratio = (float)originalText.Count(c => c == ch.Key) / (float)originalText.Length * 100;
                Statistics.Add(ch.Key, ratio);
            }

            //3.Output console
            WriteAllStatistics(CharacterList, Statistics);

ば思キ
1原文string用意
2ば適合及追加Statistics
3コ出力

ば思2重要
ば処理委譲「何個数」
次ば計算個数/全体*100故文字出現率
ば算出後Statistics追加



            Console.WriteLine("\n--------------------------------\n");
            Console.WriteLine("Statistics Complete!! (Press Any Key)");
            Console.ReadKey();

完了コ表示
終了故ば最終入力待

ば非記述例外処理
然非適用文字入力後無視故軽視

ば考桁揃然之良故未変更

ば反省。。。

【特集】ば解説た腹減

た腹減「お腹すいた…」 *1 *2 *3

た思付「すき家でも行くか」*4 *5 *6

た繰返「お腹空いたんだけどこれバグ?」 *7 *8

た変「すき家行く時間でもないからコンビニかな」 *9 *10 *11

た決「すき家なう」 *12 *13 *14

ば震た才



追記:た解説「すき家行く時間でもない」

*1:後半を平仮名にして、段々と脱力していく様を表すプロの表現方法。

*2:「食欲」という誰もが持つテーマで呟くことで、より共感を誘う効果がある。

*3:導入としては618点。読者は今後の動向を知るまでスマホが手放せなくなる事だろう。

*4:ここで「飯でも行くか」とでも言った瞬間に台無しである。すき家のワードを出すことによりテンポを損なわず、最低限の文字数で話題を進行させた

*5:ここで選択肢をすき家に意思を固めている事をアピールすることにより、「メニューも決めているだろう」というミスリードを誘う。

*6:叙述トリックでは片一方の存在感を強調することにより読者を否定する書き方も起こり得るが、ここでは文字の外で表現した事によって読者の好奇心を刺激する

*7:ここで自分でしか出せないこれバグ?のフレーズを用いてバグキャラという要素を読者の思考にチラつかせる

*8:起承転結の2段階に来ておきながら、次はまた1段階へ戻る。ループさせるタイミングを逃さないあたりが流石である

*9:先ほど振り出しに戻ると思いきやこのツイート。話が進んでいることを読者に知らせる

*10:ここで読者は2週目のループが1ツイートでカットされていることを知る。2回見た光景ともなると読者はそこで退屈するが、著者は最初の条件だけ提示して残りの展開を読者の想像に任せている。

*11:前後する飽きさせない展開と、分解して情報を渡す叙述性、それでいて話は確実に進んでいるあたり構成力のレベルが違う事が伺える

*12:すき家という消えた可能性の再提示、死んだ駒の再活躍をここで見せる

*13:読者はすき家の選択肢が無くなった事を知りながら、先ほどのミスリードで脳裏に浮かんだ事もあり納得感を得る。正統派選択肢の復活である

*14:2重ミスリードで翻弄したにも関わらず許される展開を作る作家能力の片鱗を見せる