じゃがいも畑

開発ネタの記録

【C#】一番手っ取り早いSystem.Text.Jsonのデシリアライズ

年に1回ぐらいJsonを扱うんだけど、毎回忘れるので手順を残します
Jsonのデシリアライズでつまずきたくない人向け

※サンプル作成にあたり、以下のWebサイト様&WebAPIを使用させていただきました
ありがとうございました

blog.tsukumijima.net

シリアライズするJson

{ "publicTime": "2021-01-09T17:00:00+09:00", "publicTime_format": "2021/01/09 17:00:00", "title": "東京都 東京 の天気", "link": "https://www.jma.go.jp/jp/yoho/319.html", "description": { "text": "東京都では、強風や高波、急な強い雨、落雷、空気の乾燥した状態が続くため、火の取り扱い、霜に対する農作物の管理に注意してください。\n\n日本付近は、強い冬型の気圧配置となっています。\n\n東京地方は、晴れや曇りとなっています。\n\n9日は、強い冬型の気圧配置が続きますが、気圧の谷や湿った空気の影響を受けるため、晴れ時々曇りとなるでしょう。伊豆諸島では、雷を伴い雪や雨の降る所がある見込みです。\n\n10日は、冬型の気圧配置は次第に緩み、高気圧に覆われますが、気圧の谷や湿った空気の影響を受けるため、晴れで朝晩は曇りとなる見込みです。伊豆諸島では、雷を伴い雪や雨の降る所がある見込みです。\n\n<天気変化等の留意点>\n伊豆諸島南部では、9日は、曇りで雷を伴い雪や雨の降る所があるでしょう。\n10日は、曇りで雷を伴い雪や雨の降る所がある見込みです。\n(雨の予想)\n9日18時から10日18時までに予想される24時間降水量は、多い所で、伊豆諸島南部20ミリの見込みです。\n\n【関東甲信地方】\n関東甲信地方は、晴れや曇りで、雪の降っている所があります。\n\n9日は、強い冬型の気圧配置が続くため、晴れや曇りで、長野県や関東地方北部では雪の降る所があるでしょう。伊豆諸島では、雷を伴って雪や雨の降る所がある見込みです。\n\n10日は、冬型の気圧配置は次第に緩み、高気圧に覆われますが、気圧の谷や湿った空気の影響を受ける見込みです。このため、晴れや曇りで、長野県や関東地方北部では雪の降る所があるでしょう。伊豆諸島では、雷を伴って雪や雨の降る所がある見込みです。\n\n関東地方と伊豆諸島の海上では、9日から10日にかけて、うねりを伴いしけるでしょう。船舶は高波に注意してください。", "publicTime": "2021-01-09T16:35:00+09:00", "publicTime_format": "2021/01/09 16:35:00" }, "forecasts": [ { "date": "2021-01-09", "dateLabel": "今日", "telop": "晴時々曇", "temperature": { "min": null, "max": null }, "chanceOfRain": { "00-06": "--%", "06-12": "--%", "12-18": "--%", "18-24": "10%", "T00_06": "--%", "T06_12": "--%", "T12_18": "--%", "T18_24": "10%" }, "image": { "title": "晴時々曇", "url": "https://weather.tsukumijima.net/icon/2.gif", "width": 50, "height": 31 } }, { "date": "2021-01-10", "dateLabel": "明日", "telop": "晴時々曇", "temperature": { "min": { "celsius": "-2", "fahrenheit": "28.4" }, "max": { "celsius": "6", "fahrenheit": "42.8" } }, "chanceOfRain": { "00-06": "10%", "06-12": "0%", "12-18": "0%", "18-24": "10%", "T00_06": "10%", "T06_12": "0%", "T12_18": "0%", "T18_24": "10%" }, "image": { "title": "晴時々曇", "url": "https://weather.tsukumijima.net/icon/2.gif", "width": 50, "height": 31 } }, { "date": "2021-01-11", "dateLabel": "明後日", "telop": "曇時々晴", "temperature": { "min": null, "max": null }, "chanceOfRain": { "00-06": "--%", "06-12": "--%", "12-18": "--%", "18-24": "--%", "T00_06": "--%", "T06_12": "--%", "T12_18": "--%", "T18_24": "--%" }, "image": { "title": "曇時々晴", "url": "https://weather.tsukumijima.net/icon/9.gif", "width": 50, "height": 31 } } ], "location": { "city": "東京", "area": "関東", "prefecture": "東京都" }, "copyright": { "link": "https://weather.tsukumijima.net/", "title": "(C) 天気予報 APIlivedoor 天気互換)", "image": { "width": 120, "height": 120, "link": "https://weather.tsukumijima.net/", "url": "https://weather.tsukumijima.net/logo.png", "title": "天気予報 APIlivedoor 天気互換)" }, "provider": [ { "link": "https://www.jma.go.jp/jma/", "name": "気象庁 Japan Meteorological Agency", "note": "気象庁 HP にて配信されている天気予報を json データへ編集しています。" } ] } }

NugetパッケージにSystem.Text.Jsonを追加

「text.json」とかで検索をかけてインストールを押す f:id:whitedog0215:20210109233552p:plain

Jsonをデシリアライズするためのクラスを作る

とりあえず、Tenkiとか名前を付けてファイルを作成

f:id:whitedog0215:20210110000656p:plain

Jsonデータをコピーして、Visual Studioの「編集」-> 「形式を選択して貼り付け」-> 「Jsonをクラスとして貼り付ける」を選択

f:id:whitedog0215:20210110000740p:plain

こんな感じでクラスが自動生成される。超便利。
クラス名がRootObjectになっているので修正しておく
f:id:whitedog0215:20210110001128p:plain

「形式を選択して貼り付け」が無い場合

Visual Studio Installerから「ASP.NETとWeb開発」のコンポーネントを追加する

f:id:whitedog0215:20210110000855p:plain

シリアライズする

以下のコードでデシリアライズを実行する。

var tenki = JsonSerializer.Deserialize<Tenki>(jsonStr);

f:id:whitedog0215:20210110002511p:plain

ちゃんとできてますね。

【C#】異なるn個のものからr個選ぶ組み合わせを列挙する(Combination)

作ったもの

入力のリストと選ぶ個数を渡すと組み合わせを列挙してくれるCombinationクラスを作りました
ほんとはyield returnで作って拡張メソッドにしたかったんですが、生成速度が遅くなる(自分の実力不足)のと動きが追っかけにくいのでこの形にしました

再帰とyield return組み合わさると難しすぎんか・・・

ソースコード

static class Combination
{
    private static List<List<int>> _comb;

    public static List<List<int>> Generate(int n, int r, bool dupulication)
    {
        _comb = new List<List<int>>();
        CalcCombination(new List<int>(), n, r, dupulication);
        return _comb;
    }

    private static void CalcCombination(List<int> list, int n, int r, bool dupulication)
    {
        if (list.Count == r)
        {
            _comb.Add(new List<int>(list));
            return;
        }

        var index = 0;
        if (dupulication)
        {
            index = list.Any() ? list.Last() : 0;
        }
        else
        {
            index = list.Any() ? list.Last() + 1 : 0;
        }
        for (int i = index; i < n; i++)
        {
            list.Add(i);
            CalcCombination(list, n, r, dupulication);
            list.Remove(i);
        }
    }
}

使い方

Generateメソッドは異なるn個のものからr個取ってくる組み合わせを列挙した結果を返します
dupulicationには重複あり、無しを指定します

異なる5個のものから3個取ってくる組み合わせを列挙すると

重複ありの場合

Combination.Generate(5, 3, true)

f:id:whitedog0215:20200825225617p:plain

重複無しの場合

Combination.Generate(5, 3, false)

f:id:whitedog0215:20200825225356p:plain

となります
これを配列やリストの添え字に使ってやればなんでも組み合わせ列挙できますね

解説

組み合わせの列挙

CalcCombinationを再帰呼び出ししてlistに要素を追加していきます
listの要素数がr個になったら_combにリストのコピーを追加します
コピーした後は最後の要素を取り除いてから次の要素をlistに追加します

f:id:whitedog0215:20200825235129p:plain

重複ありの場合

列挙の処理の中で{0, 1, 2}, {2, 0, 1}, {1, 2, 0}のような重複を避けたいので、必ず前の要素<= 後ろの要素になるようにします
listに最後に追加した要素からforループが始まるようにして前の要素<= 後ろの要素を実現しています

f:id:whitedog0215:20200826000527p:plain f:id:whitedog0215:20200826000719p:plain

重複無しの場合

必ず前の要素 < 後ろの要素であればよいので、listに最後に追加した要素+1からforループが始まるようにする
f:id:whitedog0215:20200826000935p:plain f:id:whitedog0215:20200826001048p:plain

木での表現

上記の内容を木にするとこんな感じ

重複ありの木

f:id:whitedog0215:20200827001853p:plain

重複無しの木

f:id:whitedog0215:20200827001915p:plain

速度

n=55, r=5, 重複ありの組み合わせ(約5000000通り)を列挙するのに大体1500msでした

C# doubleをintにキャストするときはちゃんとMath.Roundする

タイトルそのままの記事です

docs.microsoft.com

問題のコード

1.01から10.0まで、それぞれに100を掛けた値を整数で出力するプログラム

        double value = 1.01;
        while (value < 10.0)
        {
            var result = value * 100;
            Console.WriteLine($"{result} --- Cast --->{(int)result}");
            value += 0.01;
        }

出力結果は101~1000になってほしいが、これを実行するとこんな感じになる

f:id:whitedog0215:20200818220143p:plain
999.9999999999832はキャストしたら1000になってほしいが小数点以下が切り捨てられて999にされている

対策

Math.Roundを使いましょう

        double value = 1.01;
        while (value < 10.0)
        {
            var result = Math.Round(value * 100);
            Console.WriteLine($"{result} --- Cast --->{(int)result}");
            value += 0.01;
        }

f:id:whitedog0215:20200818220824p:plain
これで良し
初歩的なことだけどたまに忘れるとハマるやつ

C# 2次元リストのコピー

C# でリストAの中身をリストBにコピーしてリストBで値の変更などをしたい場合、以下のようにすれば値渡しでコピーができる

        var listA = new List<int> { 1, 2, 3 };
        var listB = new List<int>(listA);  // 値渡し

        // listB = listAは参照渡し

        listB[0] = 3;
        listB[1] = 3;
        listB[2] = 3;

        Console.WriteLine(string.Join(" ", listA));     // 1 2 3
        Console.WriteLine(string.Join(" ", listB));     // 3 3 3

けど2次元リストで同じ方法を使うと入れ子になったリストが参照渡しになってしまう

        var listA = new List<List<int>> {
            new List<int>{1, 2, 3},
            new List<int>{1, 2, 3},
        };

        // 外のリストは値渡し、入れ子のリストは参照渡し
        var listB = new List<List<int>>(listA);     

        listB[1][0] = 3;
        listB[1][1] = 3;
        listB[1][2] = 3;

        foreach (var item in listA) Console.WriteLine(string.Join(" ", item));  
        foreach (var item in listB) Console.WriteLine(string.Join(" ", item));  

       // 出力結果
       // 1 2 3 3 3 3
       // 1 2 3 3 3 3

listAは1 2 3 1 2 3のままでいてほしいのに1 2 3 3 3 3になってしまっている

しょうがないので2次元リストをDeepCopyする拡張メソッドを作る

namespace Extension
{
    public static class Extensions
    {
        public static List<List<T>> DeepCopy<T>(this List<List<T>> source)
        {
            var copyList = new List<List<T>>();
            foreach (var item in source)
            {
                copyList.Add(new List<T>(item));
            }
            return copyList;
        }
    }
}

これでOK

        var listA = new List<List<int>> {
            new List<int>{1, 2, 3},
            new List<int>{1, 2, 3},
        };

        var listB = listA.DeepCopy();

        listB[1][0] = 3;
        listB[1][1] = 3;
        listB[1][2] = 3;

        foreach (var item in listA) Console.WriteLine(string.Join(" ", item));
        foreach (var item in listB) Console.WriteLine(string.Join(" ", item));

       // 出力結果
       // 1 2 3 1 2 3
       // 1 2 3 3 3 3

C#の文字列連結(+, string.Join, StringBuilder)

最近C#の文字列連結を適当に書いてやらかしちゃったので反省のためにまとめる
検証環境はC# .Net Core 3.1

やらかしたコード

            var outText = "";
            foreach(var i in Enumerable.Range(1, N))
            {
                outText += i.ToString() + " ";
            }

リストの中身を文字列に変換して+=でどんどん連結していくコード
連結の回数が少なければ問題ないけどループ回数が増えると遅すぎて話にならない

N ループにかかる時間
1 0 ms
10 0 ms
100 0 ms
1000 2 ms
10000 67 ms
100000 15553 ms

対策

string.Joinを使う

配列を文字列で連結するときはstring.Joinが正解
上記のコードを書き直すとこんな感じ
これなら1000万データぐらいまでは大丈夫そう

                var list = Enumerable.Range(1, N);
                var outText = string.Join(" ", list);
N ループにかかる時間
1 0 ms
10 0 ms
100 0 ms
1000 0 ms
10000 1 ms
100000 10 ms
1000000 78 ms
10000000 1040 ms
100000000 9260 ms
StringBuilderを使う

配列じゃないときは大体StringBuilderが正解
コードはこんな感じ

                foreach (var i in Enumerable.Range(1, N))
                {
                    sb.Append(i).Append(" ");
                }
                outText = sb.ToString();
N ループにかかる時間
1 0 ms
10 0 ms
100 0 ms
1000 0 ms
10000 0 ms
100000 6 ms
1000000 60 ms
10000000 730 ms
100000000 7061 ms

まとめ

N += string.Join StringBuilder
1 0 ms 0 ms 0 ms
10 0 ms 0 ms 0 ms
100 0 ms 0 ms 0 ms
1000 2 ms 0 ms 0 ms
10000 67 ms 1 ms 0 ms
100000 15553 ms 10 ms 6 ms
1000000 ---- 78 ms 60 ms
10000000 ---- 1040 ms 730 ms
100000000 ---- 9260 ms 7061 ms

どうやらStringBuilderのほうが若干早いっぽい
とにかくループで文字列連結するときは+=はやめよう

【セカボク攻略】8割生き残るための確率分析 イベント編【始まりの海岸】

前回の記事はこちら
whitedog0215.hatenablog.jp

前回に引き続き、始まりの海岸解析でのイベントを見ていきます。
期待値の計算とか合ってる気がしないので、間違ってたら正しい計算を教えてください。

記事を書いていたらバージョンが上がったようです。イベント発生確率が変わってませんように・・・

イベント一覧と発生確率

釣り竿の有無、拠点Lv関係なく集計した場合の結果は以下のような感じ。試行回数は500回です。
イベント名、発生回数、確率の順番で並んでいます。

f:id:whitedog0215:20200728154951p:plain

集計結果を見てみると、イベント無し・釣りをする?に次いで雨が降ってくるが来ていますね。
ケガはしないけど確率で体力20を持っていくイベントの発生確率を最上位にしているあたり、作者様の殺意が伝わってきます。
体力20以下でうろついてるプレイヤーを絶対殺すマンだ。
(Ver1.0.6で救済が用意されたようです)

続いて、食料入手やメンタル面を救済するためのイベントが上位に来てますね。序盤は特に食料入手できないと死に直結するので慈悲を感じます。

あとは野犬とか天井とか穴に落ちる系のイベントは意外と確率が低いようです。
序盤の引きがいい時に転んでケガしてから野犬の群れに囲まれたら泣くしかないですがね・・・

釣り竿、拠点Lv別の集計結果

イベント無しと釣り竿イベントの発生確率を見ていきます。
それぞれの試行回数がさらに減っているので信頼度が低いことに注意。

  • 拠点Lv1以下、釣り竿無し(試行回数 170回) f:id:whitedog0215:20200728160916p:plain

  • 拠点Lv1以下、釣り竿あり(試行回数 59回) f:id:whitedog0215:20200728161353p:plain

  • 拠点Lv2以上、釣り竿無し(試行回数 103回) f:id:whitedog0215:20200728161628p:plain

  • 拠点Lv2以上、釣り竿あり(試行回数 168回) f:id:whitedog0215:20200728162020p:plain

見た感じ、拠点Lv2になるとイベントの発生確率も上がるようです。早めの拠点Lvアップマジ大事。
また、釣り竿イベントは拠点Lvに関係なくだいだい25%ぐらいの確率で発生するように設定されてるんじゃないでしょうか。

また、老人から種もみをもらうイベントと長い棒を使って取っ手のついたケースを拾うイベントは拠点Lv3以上が発生条件になっているようですね。
実験キット、清浄な土を手に入れた状態で野菜の種がなかったら海岸で食料探しも悪くなさそう。

食料入手の期待値分析

毎日の食料消費をいくつにすべきか考えたいので、食料の期待値を計算してみましょう。
計算には拠点Lvと釣り竿の有無をごっちゃにしたデータを使っているので実際にはもう少し低いと思います。

図書館と病院探索に少し食料の貯蓄が欲しいので、始まりの海岸で食料期待値0.5ぐらい目指すのがいいんじゃないでしょうか(適当)

オープニングイベントでゲーム機を選択した場合

オープニングイベントでゲーム機を選択した場合、以下のイベントで食料が入手できます。

  • 鉄製のアタッシュケース(体力-15, 食料1)
  • キレイな倉庫(ケガ、食料4)
  • さび付いたトランク(体力-15, 食料1)
  • いけすの魚(食料3)

これらのイベントで期待値を計算すると0.298になります。1日探索して食料1個手に入るか入らないかぐらい?
ケガしたり体力消費してこの数値なのでアイテム入手やステータスが上がるまでは辛いスタートになりそうです。

オープニングイベントでナイフを選択した場合

ナイフがある場合、食料入手は以下のようになります。

  • 鉄製のアタッシュケース(食料2)
  • キレイな倉庫(食料4)
  • 果実を発見(食料2)
  • さび付いたトランク(体力-15, 食料1)
  • いけすの魚(食料3)

これで期待値を計算すると0.41になります。
やっぱりナイフの存在はでかいですね。知力3と筋力10達成するだけで期待値0.5いけるので序盤の安定が欲しい場合はナイフを取るべきでしょう。

食料期待値を上げる要素

序盤に関係してきそうなやつだけ抜粋

  • ナイフ:0.112
    果実を発見で食料2
    鉄製のアタッシュケースの食料+1

  • 海岸のカギ:0.078
    さび付いたトランクの食料+3

  • ハンマー:0.096
    捨てられた車で食料3

  • 知力3:0.02
    小さなカニを発見で食料1

  • 筋力10:0.076
    瓦礫の下の缶詰を発見で食料2

釣り竿について

釣りをすると、大きな魚(食料3)、小さな魚(食料1)、錆びた空き缶、何もなし、のどれかが入手できます。
ここの確率は調べてないですがそれぞれ25%とすると期待値1、釣り竿イベントの発生確率を25%とすると食料の期待値が0.25上がる計算になります。
ルアーか糸と長い棒を入手したらとりあえず作るべき。
ちなみに釣りをしないを選ぶと精神状態が回復するので釣り竿は神。

鉄片・木片の入手について

このゲームでとても重要な拠点Lv2に必要なアイテムです。
TAの場合は序盤で入手できなかったらリセットですね。

  • ボロボロの樽:2.8%
  • ボロボロの自動車:1.4%

木片と鉄片で確率が結構違いますね。鉄片が全然入手できなくて死ぬってのは実際よくあります。
鉄片が欲しい時は、食料に余裕がない時は始まりの海岸、余裕があれば病院の資材室を狙うって感じがいいんじゃないでしょうか。


とりあえず自分の見たかった情報はこんな感じかな・・・調べるのツカレタ・・・
ほかのステージもまとめようとしてちょっとだけデータ集めてたけど発狂しそうなんでたぶんやらないと思います。
TAとかやる人はがんばれー

【セカボク攻略】8割生き残るための確率分析 アイテム編【始まりの海岸】

最近こちらのゲームにハマっています。
結構理不尽に死ぬゲームですが、慣れればそこそこの確率でトゥルーエンドまでたどり着けるように調整されていて素敵。
アイテムやらイベントやらの発生確率をみればさらに生存率が上がるんじゃないかと思って調べていて、 やっと始まりの海岸のまとめが終わったので結果を書いていきます。
きっと何かの役に立つはず・・・

でも試行回数はたったの500回なので、信頼度はマックで女子高生が話してたというレベルなのでご注意ください。
あと確率の計算を間違ってたら教えてください。

取得できるアイテム一覧

500回試してみたところ画像のような結果になりました。
アイテム名、入手個数、入手確率の順となっています。
f:id:whitedog0215:20200727223619p:plain

見た感じ、食料系の入手率が高く設定されているようです。
海藻、食料、携行食料の出現率は合わせて13%ぐらいなので10回探索すれば75%ぐらいの確率で1個ぐらい出るっぽい

拠点Lv2の探索効率について

拠点をLv2にすると探索効率が上がりますが、アイテム入手確率は以下のように変化します。

  • 拠点Lv1以下
    f:id:whitedog0215:20200727225508p:plain

  • 拠点Lv2以上
    f:id:whitedog0215:20200727225556p:plain

拠点Lv2以上にすると必ず1つ以上のアイテムが入手できるようになるので早めに上げておきたいところ。

木片、鉄片、刃物のツモ率

拠点Lv2以上にするには木片、鉄片、刃物が必要ですが、全部1%台ということもあり探索で全部入手するのは相当厳しい感じですね。
でも確率4.72%のガチャを30回(ゲーム時間で10日分)回すと77%ぐらいの確率で1つ以上出るみたいなので、1つぐらいは期待してもいいのかも?
始まりの海岸には刃物を入手するイベントがないので、早めに見切りをつけて図書館行っちゃうほうが確率は高そうですね。
誰か3種コンプする確率計算シテ・・・

  • 木片: 1.79%
  • 鉄片: 1.63%
  • 刃物: 1.3%

木の棒、ルアー、糸のツモ率

食料入手の要、釣り竿素材の入手率です。

  • 長い棒:2.76%
  • ルアー:3.41%
  • 糸:0.98%

長い棒が全く手に入らないことがよくあるような気がしますが、入手率ランキングでは割りと上のほうですね。
30回探索して長い棒は57%, ルアーは65%ぐらいの確率で1つ以上入手できるみたいです。

イベント編に続く・・・

※追記 続きを書きました

whitedog0215.hatenablog.jp