OSDN Git Service

[Feature] ProbabilityTableクラスのテストプログラム
authorHabu <habu1010+github@gmail.com>
Thu, 1 Aug 2024 11:57:58 +0000 (20:57 +0900)
committerHabu <habu1010+github@gmail.com>
Tue, 6 Aug 2024 07:38:29 +0000 (16:38 +0900)
src/Makefile.am
src/test/test-probability-table.cpp [new file with mode: 0644]

index f100e53..cfe7c7c 100644 (file)
@@ -1064,6 +1064,7 @@ EXTRA_hengband_SOURCES = \
        main-win/stack-trace-win.cpp \
        main-win/wav-reader.cpp main-win/wav-reader.h \
        test/test-sha256.cpp \
+       test/test-probability-table.cpp \
        wall.bmp \
        stdafx.cpp stdafx.h
 
diff --git a/src/test/test-probability-table.cpp b/src/test/test-probability-table.cpp
new file mode 100644 (file)
index 0000000..d7209c9
--- /dev/null
@@ -0,0 +1,137 @@
+/*!
+ * @brief ProbabilityTableクラスのテストプログラム
+ *
+ * srcディレクトリで以下のコマンドでコンパイルして実行する
+ *
+ * g++ -std=c++20 -I. test/test-probability-table.cpp util/rng-xoshiro.cpp term/z-rand.cpp system/angband-system.cpp main-unix/stack-trace-unix.cpp system/angband-version.cpp term/z-form.cpp term/z-util.cpp
+ *
+ * 実行すると永久にテストを繰り返し、想定通りの確率で抽選されていなければassertでプログラムが停止する
+ */
+
+#include <cassert>
+#include <iostream>
+#include <map>
+#include <numeric>
+#include <random>
+
+#include "system/angband-system.h"
+#include "util/probability-table.h"
+
+static void simulate(const ProbabilityTable<int> &table, const std::vector<std::tuple<int, int>> &test_list, int lottery_count)
+{
+    std::vector<int> result;
+    ProbabilityTable<int>::lottery(std::back_inserter(result), table, lottery_count);
+
+    // IDの抽選結果の分布
+    std::map<int, int> m;
+    for (auto i : result) {
+        m[i]++;
+    }
+
+    printf("total_prob = %u\n", table.total_prob());
+    // IDそれぞれに対し、選択された確率が計算上の確率にあっているか調べる
+    for (const auto &[k, v] : m) {
+        // 計算上の確率 → そのIDが選ばれるprob / 全体のprob
+        auto item_prob = 0;
+        for (auto [id, prob] : test_list) {
+            if (id == k) {
+                item_prob += prob;
+            }
+        }
+        auto calc_rate = static_cast<double>(item_prob) / table.total_prob();
+
+        // IDが実際に抽選された確率
+        auto item_rate = static_cast<double>(v) / lottery_count;
+
+        // 計算上の確率との誤差が0.3%未満ならOKとする
+        assert(std::abs(item_rate - calc_rate) < 0.003);
+    }
+}
+
+static void test(std::vector<std::tuple<int, int>> test_list, int lottery_count)
+{
+    ProbabilityTable<int> table;
+    assert(table.empty());
+    for (auto &&i : test_list) {
+        table.entry_item(std::get<0>(i), std::get<1>(i));
+    }
+
+    assert(table.item_count() == test_list.size());
+    auto sum = std::accumulate(
+        test_list.begin(), test_list.end(), std::make_tuple(0, 0), [](const auto &a, const auto &b) { return std::make_tuple(0, std::get<1>(a) + std::get<1>(b)); });
+    assert(table.total_prob() == std::get<1>(sum));
+
+    // test_list をテスト
+    simulate(table, test_list, lottery_count);
+
+    // さらに test_list の前半分を追加したものをテスト
+    std::vector<std::tuple<int, int>> half_test_list(test_list.begin(), test_list.begin() + test_list.size() / 2);
+    for (auto &&i : half_test_list) {
+        table.entry_item(std::get<0>(i), std::get<1>(i));
+    }
+    test_list.insert(test_list.end(), half_test_list.begin(), half_test_list.end());
+    simulate(table, test_list, lottery_count);
+
+    // 一旦クリアして test_list の前半分のみでテスト
+    table.clear();
+    for (auto &&i : half_test_list) {
+        table.entry_item(std::get<0>(i), std::get<1>(i));
+    }
+    if (!half_test_list.empty()) {
+        simulate(table, half_test_list, lottery_count);
+    }
+}
+
+static int test_main()
+{
+    std::random_device rd;
+    std::mt19937 mt(rd());
+    Xoshiro128StarStar xoshiro(rd());
+
+    AngbandSystem::get_instance().set_rng(xoshiro);
+
+    // 選択肢1個のエッジケース
+    test({ { 1, 1 } }, 50000);
+    test({ { 1, 100 } }, 50000);
+
+    // 選択肢3つ
+    test({ { 1, 1 }, { 2, 2 }, { 3, 3 } }, 500000);
+    test({ { 1, 3 }, { 2, 2 }, { 3, 1 } }, 500000); // 逆パターン
+    test({ { 1, 13 }, { 2, 37 }, { 3, 23 } }, 500000); // 乱雑なprob
+
+    // 選択肢1000個
+    std::vector<std::tuple<int, int>> v;
+    for (int i = 0; i < 1000; i++) {
+        v.emplace_back(i + 1, i + 1);
+    }
+
+    test(v, 50000);
+
+    // 逆順
+    std::reverse(v.begin(), v.end());
+    test(v, 50000);
+
+    std::uniform_int_distribution<> dist(1, 100);
+
+    // 選択肢1000個がランダムなprobを持つ
+    v.clear();
+    for (int i = 0; i < 1000; i++) {
+        v.emplace_back(i + 1, dist(mt));
+    }
+    test(v, 50000);
+
+    // その1000個の選択肢 + IDかぶり項目がある場合
+    v.emplace_back(320, dist(mt));
+    v.emplace_back(673, dist(mt));
+
+    test(v, 50000);
+
+    return 0;
+}
+
+int main()
+{
+    while (true) {
+        test_main();
+    }
+}