はじめに
これはCSV Parserを実装するわけではありません。 vincentlaucsb/csv-parserに乗っかって、iteratorを実装します。
国民的人気ブラウザゲーム艦隊これくしょん-艦これ-
では、ユーザ有志による検証が非常に盛んに行われています。
その中でも、ダメージ検証は特に活発であり、専用のデータ採取ツール(赤仮、74式EN)や、データ解析用計算ツール(ダメージ検証用スプレ)、異常ダメージ検知などの様々なツールが開発・保守・運用されています。
艦娘は、最大でconstexpr auto slot_size = 6;
個のスロットがあります。
各スロットには装備を載せることができ、各装備は、少なくとも以下の要素を持ちます。
- 名前
- 改修値
- 熟練度
- 搭載数
- 戦闘後搭載数
これらの要素は、前述のデータ採取ツールによって、戦闘結果として単位攻防ごとにCSVデータの形式で得られます。 このCSVデータの列名は以下のようになっています。列数は150です。
No.,日付,海域,マス,出撃,ランク,敵艦隊,提督レベル,自陣形,敵陣形,自索敵,敵索敵,制空権,会敵,自触接,敵触接,自照明弾,敵照明弾,戦闘種別,艦名1,艦名2,艦名3,艦名4,艦名5,艦名6,自艦隊,巡目,攻撃艦,砲撃種別,砲撃回数,表示装備1,表示装備2,表示装備3,クリティカル,ダメージ,かばう,攻撃艦.編成順,攻撃艦.ID,攻撃艦.名前,攻撃艦.種別,攻撃艦.疲労,攻撃艦.戦闘後疲労,攻撃艦.残耐久,攻撃艦.最大耐久,攻撃艦.損傷,攻撃艦.残燃料,攻撃艦.戦闘後残燃料,攻撃艦.最大燃料,攻撃艦.残弾薬,攻撃艦.戦闘後残弾薬,攻撃艦.最大弾薬,攻撃艦.Lv,攻撃艦.速力,攻撃艦.火力,攻撃艦.雷装,攻撃艦.対空,攻撃艦.装甲,攻撃艦.回避,攻撃艦.対潜,攻撃艦.索敵,攻撃艦.運,攻撃艦.射程,攻撃艦.装備1.名前,攻撃艦.装備1.改修,攻撃艦.装備1.熟練度,攻撃艦.装備1.搭載数,攻撃艦.装備1.戦闘後搭載数,攻撃艦.装備2.名前,攻撃艦.装備2.改修,攻撃艦.装備2.熟練度,攻撃艦.装備2.搭載数,攻撃艦.装備2.戦闘後搭載数,攻撃艦.装備3.名前,攻撃艦.装備3.改修,攻撃艦.装備3.熟練度,攻撃艦.装備3.搭載数,攻撃艦.装備3.戦闘後搭載数,攻撃艦.装備4.名前,攻撃艦.装備4.改修,攻撃艦.装備4.熟練度,攻撃艦.装備4.搭載数,攻撃艦.装備4.戦闘後搭載数,攻撃艦.装備5.名前,攻撃艦.装備5.改修,攻撃艦.装備5.熟練度,攻撃艦.装備5.搭載数,攻撃艦.装備5.戦闘後搭載数,攻撃艦.装備6.名前,攻撃艦.装備6.改修,攻撃艦.装備6.熟練度,攻撃艦.装備6.搭載数,攻撃艦.装備6.戦闘後搭載数,防御艦.編成順,防御艦.ID,防御艦.名前,防御艦.種別,防御艦.疲労,防御艦.戦闘後疲労,防御艦.残耐久,防御艦.最大耐久,防御艦.損傷,防御艦.残燃料,防御艦.戦闘後残燃料,防御艦.最大燃料,防御艦.残弾薬,防御艦.戦闘後残弾薬,防御艦.最大弾薬,防御艦.Lv,防御艦.速力,防御艦.火力,防御艦.雷装,防御艦.対空,防御艦.装甲,防御艦.回避,防御艦.対潜,防御艦.索敵,防御艦.運,防御艦.射程,防御艦.装備1.名前,防御艦.装備1.改修,防御艦.装備1.熟練度,防御艦.装備1.搭載数,防御艦.装備1.戦闘後搭載数,防御艦.装備2.名前,防御艦.装備2.改修,防御艦.装備2.熟練度,防御艦.装備2.搭載数,防御艦.装備2.戦闘後搭載数,防御艦.装備3.名前,防御艦.装備3.改修,防御艦.装備3.熟練度,防御艦.装備3.搭載数,防御艦.装備3.戦闘後搭載数,防御艦.装備4.名前,防御艦.装備4.改修,防御艦.装備4.熟練度,防御艦.装備4.搭載数,防御艦.装備4.戦闘後搭載数,防御艦.装備5.名前,防御艦.装備5.改修,防御艦.装備5.熟練度,防御艦.装備5.搭載数,防御艦.装備5.戦闘後搭載数,防御艦.装備6.名前,防御艦.装備6.改修,防御艦.装備6.熟練度,防御艦.装備6.搭載数,防御艦.装備6.戦闘後搭載数,艦隊種類,敵艦隊種類
ここで本題です。 このCSVデータについて、攻撃艦の装備の名前1-6をイテレートしたいです。 おっと、防御艦の装備の改修値1-6もイテレートしたいです。
はて、どうしたものか。 まず、Data Oriented的な観点でいうと、装備の各要素
- 名前
- 改修値
- 熟練度
- 搭載数
- 戦闘後搭載数
をメンバとする構造体の配列として扱うのは不適でしょう。
第1スロットの装備の名前 -> 第2スロットの装備の名前 -> ...
とイテレートしたいのです。
AoSではなく、SoAライクな設計にします。
コード
今回利用するvincentlaucsb/csv-parser
は、row[index]
やrow[key]
のようにフィールドにアクセスできます。
ただし、index
はint
で、key
はconst std::string&
です。
フィールドへのアクセスは、ここでは、可読性のために整数ではなく文字列を使うことにします。
keyとなる文字列は、直積(?) {"攻撃艦", "防御艦"} * {1, 2, 3, 4, 5, 6} * {"名前", "改修値", "熟練度", "搭載数", "戦闘後搭載数"}だけ必要です。
key
はconst char*
ではなく、const std::string&
ですから、std::format
やfmt::format
を利用して、静的実行時定数としてコンストラクトします。
くわえて、スロット1-6でイテレートできるように、std::array
を使います。
すなわち、static const std::array<std::string, slot_size> keys;
を定義します。
型にキーとなる文字列("攻撃艦"や"名前")を結び付ければ、templateを利用して、型ごとにkeysを上手に定義できます。 ということで、タグ型のようなものを定義します:
攻撃艦と防御艦のタグ型
#include <string_view> namespace kcv { struct attacker { static constexpr auto key = std::string_view{"攻撃艦"}; }; struct defender { static constexpr auto key = std::string_view{"防御艦"}; }; } // namespace kcv
装備の各要素の型
#include <string_view> namespace kcv { namespace slotitem { class name { public: static constexpr auto key = std::string_view{"名前"}; // fmtライブラリのために friend constexpr auto format_as(const name& name) noexcept -> std::string_view { return name.value_; } constexpr name(std::string_view name) noexcept : value_{name} {} private: std::string_view value_; }; class improvement; class proficiency; // ... } // namespace slotitem } // namespace kcv
イテレータ
ここまで来れば、あとは典型的なiteratorの実装です。
とりあえずstd::input_iterator
を満たすようにします。
std::random_access_iterator
も満たすことができますが、記事が長くなるのでね。
ちなみに、std::filesystem::directory_iterator
を参考にしました。
#include <fmt/format.h> #include <array> #include <cstddef> #include <string> #include <string_view> #include <utility> #include "vincentlaucsb/csv.hpp" namespace kcv { inline constexpr auto slot_size = std::size_t{6}; template <typename S, typename T> class slot_iterator final { public: using ship_type = S; using value_type = T; using difference_type = std::ptrdiff_t; using iterator_concept = std::input_iterator_tag; friend constexpr auto begin(const slot_iterator itr) noexcept -> slot_iterator { return itr; } friend constexpr auto end(const slot_iterator) noexcept -> slot_iterator { return slot_iterator{}; } friend constexpr bool operator==(const slot_iterator &lhs, const slot_iterator &rhs) noexcept { return lhs.i_ == rhs.i_; } friend constexpr bool operator!=(const slot_iterator &lhs, const slot_iterator &rhs) noexcept { return lhs.i_ != rhs.i_; } friend constexpr auto operator++(slot_iterator &itr) noexcept -> slot_iterator & { ++itr.i_; return itr; } friend constexpr auto operator++(slot_iterator &itr, int) noexcept -> slot_iterator { auto self = itr; itr.i_++; return self; } friend auto operator*(const slot_iterator &itr) -> value_type { const auto &key = keys[itr.i_]; const auto &row = *itr.row_; const auto &field = row[key]; return value_type{field.get_sv()}; } // end constexpr slot_iterator() noexcept : row_{nullptr} , i_{slot_size} {} // begin constexpr slot_iterator(const csv::CSVRow &row) noexcept : row_{std::addressof(row)} , i_{0} {} private: static inline const auto keys = []<std::size_t... i>(std::index_sequence<i...>) { return std::array<std::string, sizeof...(i)>{ fmt::format("{}.装備{}.{}", ship_type::key, 1 + i, value_type::key)... }; }(std::make_index_sequence<slot_size>{}); private: const csv::CSVRow *row_; std::size_t i_; }; } // namespace kcv
使用例.cpp
サンプルデータ:
赤仮砲撃戦.csv
No.,日付,海域,マス,出撃,ランク,敵艦隊,提督レベル,自陣形,敵陣形,自索敵,敵索敵,制空権,会敵,自触接,敵触接,自照明弾,敵照明弾,戦闘種別,艦名1,艦名2,艦名3,艦名4,艦名5,艦名6,自艦隊,巡目,攻撃艦,砲撃種別,砲撃回数,表示装備1,表示装備2,表示装備3,クリティカル,ダメージ,かばう,攻撃艦.編成順,攻撃艦.ID,攻撃艦.名前,攻撃艦.種別,攻撃艦.疲労,攻撃艦.戦闘後疲労,攻撃艦.残耐久,攻撃艦.最大耐久,攻撃艦.損傷,攻撃艦.残燃料,攻撃艦.戦闘後残燃料,攻撃艦.最大燃料,攻撃艦.残弾薬,攻撃艦.戦闘後残弾薬,攻撃艦.最大弾薬,攻撃艦.Lv,攻撃艦.速力,攻撃艦.火力,攻撃艦.雷装,攻撃艦.対空,攻撃艦.装甲,攻撃艦.回避,攻撃艦.対潜,攻撃艦.索敵,攻撃艦.運,攻撃艦.射程,攻撃艦.装備1.名前,攻撃艦.装備1.改修,攻撃艦.装備1.熟練度,攻撃艦.装備1.搭載数,攻撃艦.装備1.戦闘後搭載数,攻撃艦.装備2.名前,攻撃艦.装備2.改修,攻撃艦.装備2.熟練度,攻撃艦.装備2.搭載数,攻撃艦.装備2.戦闘後搭載数,攻撃艦.装備3.名前,攻撃艦.装備3.改修,攻撃艦.装備3.熟練度,攻撃艦.装備3.搭載数,攻撃艦.装備3.戦闘後搭載数,攻撃艦.装備4.名前,攻撃艦.装備4.改修,攻撃艦.装備4.熟練度,攻撃艦.装備4.搭載数,攻撃艦.装備4.戦闘後搭載数,攻撃艦.装備5.名前,攻撃艦.装備5.改修,攻撃艦.装備5.熟練度,攻撃艦.装備5.搭載数,攻撃艦.装備5.戦闘後搭載数,攻撃艦.装備6.名前,攻撃艦.装備6.改修,攻撃艦.装備6.熟練度,攻撃艦.装備6.搭載数,攻撃艦.装備6.戦闘後搭載数,防御艦.編成順,防御艦.ID,防御艦.名前,防御艦.種別,防御艦.疲労,防御艦.戦闘後疲労,防御艦.残耐久,防御艦.最大耐久,防御艦.損傷,防御艦.残燃料,防御艦.戦闘後残燃料,防御艦.最大燃料,防御艦.残弾薬,防御艦.戦闘後残弾薬,防御艦.最大弾薬,防御艦.Lv,防御艦.速力,防御艦.火力,防御艦.雷装,防御艦.対空,防御艦.装甲,防御艦.回避,防御艦.対潜,防御艦.索敵,防御艦.運,防御艦.射程,防御艦.装備1.名前,防御艦.装備1.改修,防御艦.装備1.熟練度,防御艦.装備1.搭載数,防御艦.装備1.戦闘後搭載数,防御艦.装備2.名前,防御艦.装備2.改修,防御艦.装備2.熟練度,防御艦.装備2.搭載数,防御艦.装備2.戦闘後搭載数,防御艦.装備3.名前,防御艦.装備3.改修,防御艦.装備3.熟練度,防御艦.装備3.搭載数,防御艦.装備3.戦闘後搭載数,防御艦.装備4.名前,防御艦.装備4.改修,防御艦.装備4.熟練度,防御艦.装備4.搭載数,防御艦.装備4.戦闘後搭載数,防御艦.装備5.名前,防御艦.装備5.改修,防御艦.装備5.熟練度,防御艦.装備5.搭載数,防御艦.装備5.戦闘後搭載数,防御艦.装備6.名前,防御艦.装備6.改修,防御艦.装備6.熟練度,防御艦.装備6.搭載数,防御艦.装備6.戦闘後搭載数,艦隊種類,敵艦隊種類 1,2023/03/10 20:40:07,ブルネイ泊地沖,マップ:7-1 セル:4,出撃,勝利S,深海潜水艦隊 II群,120,単横陣,梯形陣,発見!(索敵機なし),発見!(索敵機なし),制空権確保,同航戦,,,,,砲撃戦,丹陽,Верный,夕張改二特,時雨改二,早潮改二,,通常艦隊,先制対潜,自軍,0,0,二式爆雷改二,,,2,237,0,4,145,時雨改二,駆逐艦,52,63,36,36,小破未満,15,14,15,20,20,20,137,高速,63,89,72,55,120,143,62,55,短,三式水中探信儀改,10,0,0,0,四式水中聴音機,6,0,0,0,二式爆雷改二,5,0,0,0,,,,,,,,,,,応急修理要員,0,0,,,3,1530,潜水カ級,潜水艦,,,19,19,小破未満,,,0,,,0,50,低速,0,46,0,7,1,0,6,1,短,21inch魚雷前期型,,,,,21inch魚雷前期型,,,,,,,,,,,,,,,,,,,,,,,,,通常艦隊,通常艦隊 2,2023/03/10 20:40:07,ブルネイ泊地沖,マップ:7-1 セル:4,出撃,勝利S,深海潜水艦隊 II群,120,単横陣,梯形陣,発見!(索敵機なし),発見!(索敵機なし),制空権確保,同航戦,,,,,砲撃戦,丹陽,Верный,夕張改二特,時雨改二,早潮改二,,通常艦隊,先制対潜,自軍,0,0,,,,1,62,0,3,623,夕張改二特,軽巡洋艦,49,50,44,47,小破未満,30,28,30,40,40,40,161,低速,57,130,87,67,112,109,75,35,短,甲標的 丙型,5,0,0,0,試製61cm六連装(酸素)魚雷,9,0,0,0,試製61cm六連装(酸素)魚雷,9,0,0,0,四式水中聴音機,6,0,0,0,2cm 四連装FlaK 38,8,0,0,0,応急修理要員,0,0,,,2,1530,潜水カ級,潜水艦,,,19,19,小破未満,,,0,,,0,50,低速,0,46,0,7,1,0,6,1,短,21inch魚雷前期型,,,,,21inch魚雷前期型,,,,,,,,,,,,,,,,,,,,,,,,,通常艦隊,通常艦隊 3,2023/03/10 20:40:07,ブルネイ泊地沖,マップ:7-1 セル:4,出撃,勝利S,深海潜水艦隊 II群,120,単横陣,梯形陣,発見!(索敵機なし),発見!(索敵機なし),制空権確保,同航戦,,,,,砲撃戦,丹陽,Верный,夕張改二特,時雨改二,早潮改二,,通常艦隊,先制対潜,自軍,0,0,Hedgehog(初期型),,,1,146,0,1,651,丹陽,駆逐艦,49,53,40,40,小破未満,15,14,15,20,20,20,139,高速,68,56,88,61,110,116,64,63,短,四式水中聴音機,6,0,0,0,四式水中聴音機,6,0,0,0,Hedgehog(初期型),2,0,0,0,,,,,,,,,,,応急修理要員,0,0,,,4,1530,潜水カ級,潜水艦,,,19,19,小破未満,,,0,,,0,50,低速,0,46,0,7,1,0,6,1,短,21inch魚雷前期型,,,,,21inch魚雷前期型,,,,,,,,,,,,,,,,,,,,,,,,,通常艦隊,通常艦隊
#include <fmt/core.h> #include <concepts> #include "vincentlaucsb/csv.hpp" // #include "slot_iterator.hpp" // #include "slotitem/name.hpp" using slot_iterator = kcv::slot_iterator<kcv::attacker, kcv::slotitem::name>; static_assert(std::input_iterator<slot_iterator>); static_assert(std::ranges::range<slot_iterator>); auto main() -> int { for (auto reader = csv::CSVReader{"赤仮砲撃戦.csv"}; const auto& row : reader) { for (const auto& e : slot_iterator{row}) { fmt::print("{}, ", e); } fmt::println(""); } return 0; }