はじめに
これはCSV Parserを実装するわけではありません。
vincentlaucsb/csv-parser に乗っかって、iterator を実装します。
国民的人気ブラウザゲーム 艦隊これくしょん-艦これ-
では、ユーザ有志による検証が非常に盛んに行われています。
その中でも、ダメージ検証は特に活発であり、専用のデータ採取ツール(赤仮、74式EN)や、データ解析用計算ツール(ダメージ検証用スプレ)、異常ダメージ検知などの様々なツールが開発・保守・運用されています。
艦娘は、最大でconstexpr auto slot_size = 6;
個のスロットがあります。
艦娘の6個のslot
各スロットには装備を載せることができ、各装備は、少なくとも以下の要素を持ちます。
これらの要素は、前述のデータ採取ツールによって、戦闘結果として単位攻防ごとに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 {"防御艦" };
};
}
装備の各要素の型
#include <string_view>
namespace kcv {
namespace slotitem {
class name {
public :
static constexpr auto key = std ::string_view {"名前" };
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;
}
}
ここまで来れば、あとは典型的な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 ()};
}
constexpr slot_iterator () noexcept
: row_{nullptr }
, i_{slot_size} {}
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_;
};
}
使用例.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"
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 ;
}