From c36bd9ebb696f88ee2cb515571b1ff26d09740db Mon Sep 17 00:00:00 2001 From: Florine de Geus Date: Thu, 26 Feb 2026 15:09:19 +0100 Subject: [PATCH 1/4] Add test for `std::map` --- types/README.md | 5 +- types/map/README.md | 4 ++ types/map/fundamental/README.md | 15 ++++++ types/map/fundamental/read.C | 68 +++++++++++++++++++++++++ types/map/fundamental/write.C | 67 +++++++++++++++++++++++++ types/map/nested/LinkDef.h | 6 +++ types/map/nested/Makefile | 26 ++++++++++ types/map/nested/NestedMap.hxx | 4 ++ types/map/nested/README.md | 18 +++++++ types/map/nested/read.C | 88 +++++++++++++++++++++++++++++++++ types/map/nested/write.C | 80 ++++++++++++++++++++++++++++++ 11 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 types/map/README.md create mode 100644 types/map/fundamental/README.md create mode 100644 types/map/fundamental/read.C create mode 100644 types/map/fundamental/write.C create mode 100644 types/map/nested/LinkDef.h create mode 100644 types/map/nested/Makefile create mode 100644 types/map/nested/NestedMap.hxx create mode 100644 types/map/nested/README.md create mode 100644 types/map/nested/read.C create mode 100644 types/map/nested/write.C diff --git a/types/README.md b/types/README.md index 0c6525a..d779f62 100644 --- a/types/README.md +++ b/types/README.md @@ -4,6 +4,7 @@ * [`atomic`](atomic): `std::atomic` * [`bitset`](bitset): `std::bitset` * [`fundamental`](fundamental): fundamental column types + * [`map`](map): `std::map` with all `[Split]Index{32,64}` column types * [`multiset`](multiset): `std::multiset` with all `[Split]Index{32,64}` column types * [`optional`](optional): `std::optional` with different element types * [`pair`](pair): `std::pair` with different element types @@ -39,8 +40,8 @@ If `C` is a container type with a single item field, we test the following nesti * `C>` If `M` is an dictionary container type, we test the following nestings: - * `M` - * `M, M>` + * `M` + * `M, M>` ### Record Fields diff --git a/types/map/README.md b/types/map/README.md new file mode 100644 index 0000000..e17152e --- /dev/null +++ b/types/map/README.md @@ -0,0 +1,4 @@ +# `std::map` + +- [`fundamental`](fundamental): `std::map` +- [`nested`](nested): `std::map>` diff --git a/types/map/fundamental/README.md b/types/map/fundamental/README.md new file mode 100644 index 0000000..081bff2 --- /dev/null +++ b/types/map/fundamental/README.md @@ -0,0 +1,15 @@ +# `std::map` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the collection parent field. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the map: + one key-value pair in the first field, two key-value pairs in the second field, etc. diff --git a/types/map/fundamental/read.C b/types/map/fundamental/read.C new file mode 100644 index 0000000..6197876 --- /dev/null +++ b/types/map/fundamental/read.C @@ -0,0 +1,68 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include +#include +#include +#include +#include +#include + +using Map = std::map; + +static void PrintMapValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + Map &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool first = true; + for (auto &[key, value] : item) { + if (first) { + first = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": " << value; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.map.fundamental.root", + std::string_view output = "types.map.fundamental.json") { + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintMapValue(entry, "Index32", os); + PrintMapValue(entry, "Index64", os); + PrintMapValue(entry, "SplitIndex32", os); + PrintMapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/map/fundamental/write.C b/types/map/fundamental/write.C new file mode 100644 index 0000000..5447f7b --- /dev/null +++ b/types/map/fundamental/write.C @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include +#include +#include +#include + +using Map = std::map; + +static std::shared_ptr MakeMapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.map.fundamental.root") { + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeMapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeMapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeMapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeMapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}}; + *SplitIndex32 = {{"c", 3}}; + *SplitIndex64 = {{"d", 4}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the map + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}, {"c", 3}}; + *SplitIndex32 = {{"d", 4}, {"e", 5}, {"f", 6}}; + *SplitIndex64 = {{"g", 7}, {"h", 8}, {"i", 9}, {"j", 10}}; + writer->Fill(); +} diff --git a/types/map/nested/LinkDef.h b/types/map/nested/LinkDef.h new file mode 100644 index 0000000..a258912 --- /dev/null +++ b/types/map/nested/LinkDef.h @@ -0,0 +1,6 @@ +#include +#include + +#ifdef __CLING__ +#pragma link C++ class std::map>+; +#endif diff --git a/types/map/nested/Makefile b/types/map/nested/Makefile new file mode 100644 index 0000000..f401079 --- /dev/null +++ b/types/map/nested/Makefile @@ -0,0 +1,26 @@ +ROOT_CONFIG ?= $(shell which root-config) +ifeq ($(ROOT_CONFIG),) +$(error Could not find root-config) +endif +ROOTCLING ?= $(shell which rootcling) +ifeq ($(ROOTCLING),) +$(error Could not find rootcling) +endif + +CXX := $(shell $(ROOT_CONFIG) --cxx) +CXXFLAGS_ROOT := $(shell $(ROOT_CONFIG) --cflags) +CXXFLAGS := -Wall $(CXXFLAGS_ROOT) +LDFLAGS := $(shell $(ROOT_CONFIG) --libs) + +.PHONY: all clean + +all: libNestedMap.so + +NestedMap.cxx: NestedMap.hxx LinkDef.h + $(ROOTCLING) -f $@ $^ + +libNestedMap.so: NestedMap.cxx + $(CXX) -shared -fPIC -o $@ $^ $(CXXFLAGS) $(LDFLAGS) + +clean: + $(RM) NestedMap.cxx NestedMap_rdict.pcm libNestedMap.so diff --git a/types/map/nested/NestedMap.hxx b/types/map/nested/NestedMap.hxx new file mode 100644 index 0000000..cf43459 --- /dev/null +++ b/types/map/nested/NestedMap.hxx @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/types/map/nested/README.md b/types/map/nested/README.md new file mode 100644 index 0000000..4694d75 --- /dev/null +++ b/types/map/nested/README.md @@ -0,0 +1,18 @@ +# `std::map>` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the two collection parent fields. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the outer map, with arbitrary lengths of the inner maps + +## Dictionaries + +These tests require ROOT dictionaries, which can be generated with the provided `Makefile` in this directory. This will create a `libNestedMap` shared object. diff --git a/types/map/nested/read.C b/types/map/nested/read.C new file mode 100644 index 0000000..bd72266 --- /dev/null +++ b/types/map/nested/read.C @@ -0,0 +1,88 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include + +#include +#include +#include +#include +#include +#include +#include + +using Map = std::map>; + +static void PrintNestedMapValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + Map &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool outerFirst = true; + for (auto &[key, inner] : item) { + if (outerFirst) { + outerFirst = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": {"; + bool innerFirst = true; + for (auto &[innerKey, value] : inner) { + if (innerFirst) { + innerFirst = false; + } else { + os << ","; + } + os << "\n \"" << innerKey << "\": " << value; + } + if (!inner.empty()) { + os << "\n "; + } + os << "}"; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.map.nested.root", + std::string_view output = "types.map.nested.json") { + if (gSystem->Load("libNestedMap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintNestedMapValue(entry, "Index32", os); + PrintNestedMapValue(entry, "Index64", os); + PrintNestedMapValue(entry, "SplitIndex32", os); + PrintNestedMapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/map/nested/write.C b/types/map/nested/write.C new file mode 100644 index 0000000..b63a83f --- /dev/null +++ b/types/map/nested/write.C @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include + +#include +#include +#include +#include +#include + +using Map = std::map>; + +static std::shared_ptr MakeMapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + field->GetSubFields()[0]->GetSubFields()[1]->SetColumnRepresentatives( + {{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.map.nested.root") { + if (gSystem->Load("libNestedMap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeMapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeMapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeMapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeMapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"bb", 2}}}}; + *SplitIndex32 = {{"c", {{"cc", 3}}}}; + *SplitIndex64 = {{"d", {{"dd", 4}}}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the outer map + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"ba", 2}}}, {"c", {{"ca", 3}}}}; + *SplitIndex32 = { + {"d", {{"da", 4}}}, {"e", {{"ea", 5}, {"eb", 6}}}, {"f", {}}}; + *SplitIndex64 = {{"g", {{"ga", 7}, {"gb", 8}}}, + {"h", {}}, + {"i", {{"ia", 9}}}, + {"j", {{"ja", 10}, {"jb", 11}, {"jc", 12}}}}; + writer->Fill(); +} From 0f4f306cc38e7da1dca54382901d5891630c47f3 Mon Sep 17 00:00:00 2001 From: Florine de Geus Date: Thu, 26 Feb 2026 15:36:16 +0100 Subject: [PATCH 2/4] Add test for `std::unordered_map` --- types/README.md | 1 + types/unordered_map/README.md | 4 + types/unordered_map/fundamental/README.md | 15 +++ types/unordered_map/fundamental/read.C | 68 ++++++++++++++ types/unordered_map/fundamental/write.C | 67 ++++++++++++++ types/unordered_map/nested/LinkDef.h | 6 ++ types/unordered_map/nested/Makefile | 26 ++++++ .../nested/NestedUnorderedMap.hxx | 4 + types/unordered_map/nested/README.md | 18 ++++ types/unordered_map/nested/read.C | 91 +++++++++++++++++++ types/unordered_map/nested/write.C | 81 +++++++++++++++++ 11 files changed, 381 insertions(+) create mode 100644 types/unordered_map/README.md create mode 100644 types/unordered_map/fundamental/README.md create mode 100644 types/unordered_map/fundamental/read.C create mode 100644 types/unordered_map/fundamental/write.C create mode 100644 types/unordered_map/nested/LinkDef.h create mode 100644 types/unordered_map/nested/Makefile create mode 100644 types/unordered_map/nested/NestedUnorderedMap.hxx create mode 100644 types/unordered_map/nested/README.md create mode 100644 types/unordered_map/nested/read.C create mode 100644 types/unordered_map/nested/write.C diff --git a/types/README.md b/types/README.md index d779f62..cf3c6c8 100644 --- a/types/README.md +++ b/types/README.md @@ -13,6 +13,7 @@ * [`string`](string): `std::string` with all `[Split]Index{32,64}` column types * [`tuple`](tuple): `std::tuple` with different element types * [`unique_ptr`](unique_ptr): `std::unique_ptr` with different element types + * [`unordered_map`](unordered_map): `std::unordered_map` with all `[Split]Index{32,64}` column types * [`unordered_multiset`](unordered_multiset): `std::unordered_multiset` with all `[Split]Index{32,64}` column types * [`unordered_set`](unordered_set): `std::unordered_set` with all `[Split]Index{32,64}` column types * [`user`](user): user-defined types, such as classes and enums diff --git a/types/unordered_map/README.md b/types/unordered_map/README.md new file mode 100644 index 0000000..753c3eb --- /dev/null +++ b/types/unordered_map/README.md @@ -0,0 +1,4 @@ +# `std::unordered_map` + +- [`fundamental`](fundamental): `std::unordered_map` +- [`nested`](nested): `std::unordered_map>` diff --git a/types/unordered_map/fundamental/README.md b/types/unordered_map/fundamental/README.md new file mode 100644 index 0000000..539ce74 --- /dev/null +++ b/types/unordered_map/fundamental/README.md @@ -0,0 +1,15 @@ +# `std::unordered_map` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the collection parent field. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the map: + one key-value pair in the first field, two key-value pairs in the second field, etc. diff --git a/types/unordered_map/fundamental/read.C b/types/unordered_map/fundamental/read.C new file mode 100644 index 0000000..be38ff2 --- /dev/null +++ b/types/unordered_map/fundamental/read.C @@ -0,0 +1,68 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include +#include +#include +#include +#include +#include + +using UnorderedMap = std::unordered_map; + +static void PrintMapValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + UnorderedMap &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool first = true; + for (auto &[key, value] : item) { + if (first) { + first = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": " << value; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.unordered_map.fundamental.root", + std::string_view output = "types.unordered_map.fundamental.json") { + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintMapValue(entry, "Index32", os); + PrintMapValue(entry, "Index64", os); + PrintMapValue(entry, "SplitIndex32", os); + PrintMapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/unordered_map/fundamental/write.C b/types/unordered_map/fundamental/write.C new file mode 100644 index 0000000..58f102a --- /dev/null +++ b/types/unordered_map/fundamental/write.C @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include +#include +#include +#include + +using UnorderedMap = std::unordered_map; + +static std::shared_ptr MakeMapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.unordered_map.fundamental.root") { + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeMapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeMapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeMapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeMapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}}; + *SplitIndex32 = {{"c", 3}}; + *SplitIndex64 = {{"d", 4}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the map + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}, {"c", 3}}; + *SplitIndex32 = {{"d", 4}, {"e", 5}, {"f", 6}}; + *SplitIndex64 = {{"g", 7}, {"h", 8}, {"i", 9}, {"j", 10}}; + writer->Fill(); +} diff --git a/types/unordered_map/nested/LinkDef.h b/types/unordered_map/nested/LinkDef.h new file mode 100644 index 0000000..e788d1e --- /dev/null +++ b/types/unordered_map/nested/LinkDef.h @@ -0,0 +1,6 @@ +#include +#include + +#ifdef __CLING__ +#pragma link C++ class std::unordered_map>+; +#endif diff --git a/types/unordered_map/nested/Makefile b/types/unordered_map/nested/Makefile new file mode 100644 index 0000000..0be969d --- /dev/null +++ b/types/unordered_map/nested/Makefile @@ -0,0 +1,26 @@ +ROOT_CONFIG ?= $(shell which root-config) +ifeq ($(ROOT_CONFIG),) +$(error Could not find root-config) +endif +ROOTCLING ?= $(shell which rootcling) +ifeq ($(ROOTCLING),) +$(error Could not find rootcling) +endif + +CXX := $(shell $(ROOT_CONFIG) --cxx) +CXXFLAGS_ROOT := $(shell $(ROOT_CONFIG) --cflags) +CXXFLAGS := -Wall $(CXXFLAGS_ROOT) +LDFLAGS := $(shell $(ROOT_CONFIG) --libs) + +.PHONY: all clean + +all: libNestedUnorderedMap.so + +NestedUnorderedMap.cxx: NestedUnorderedMap.hxx LinkDef.h + $(ROOTCLING) -f $@ $^ + +libNestedUnorderedMap.so: NestedUnorderedMap.cxx + $(CXX) -shared -fPIC -o $@ $^ $(CXXFLAGS) $(LDFLAGS) + +clean: + $(RM) NestedUnorderedMap.cxx NestedUnorderedMap_rdict.pcm libNestedUnorderedMap.so diff --git a/types/unordered_map/nested/NestedUnorderedMap.hxx b/types/unordered_map/nested/NestedUnorderedMap.hxx new file mode 100644 index 0000000..982dfac --- /dev/null +++ b/types/unordered_map/nested/NestedUnorderedMap.hxx @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/types/unordered_map/nested/README.md b/types/unordered_map/nested/README.md new file mode 100644 index 0000000..8b08635 --- /dev/null +++ b/types/unordered_map/nested/README.md @@ -0,0 +1,18 @@ +# `std::unordered_map>` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the two collection parent fields. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the outer map, with arbitrary lengths of the inner maps + +## Dictionaries + +These tests require ROOT dictionaries, which can be generated with the provided `Makefile` in this directory. This will create a `libNestedUnorderedMap` shared object. diff --git a/types/unordered_map/nested/read.C b/types/unordered_map/nested/read.C new file mode 100644 index 0000000..6c2d2d5 --- /dev/null +++ b/types/unordered_map/nested/read.C @@ -0,0 +1,91 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include + +#include +#include +#include +#include +#include +#include +#include + +using UnorderedMap = + std::unordered_map>; + +static void PrintNestedUnorderedMapValue(const REntry &entry, + std::string_view name, + std::ostream &os, bool last = false) { + UnorderedMap &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool outerFirst = true; + for (auto &[key, inner] : item) { + if (outerFirst) { + outerFirst = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": {"; + bool innerFirst = true; + for (auto &[innerKey, value] : inner) { + if (innerFirst) { + innerFirst = false; + } else { + os << ","; + } + os << "\n \"" << innerKey << "\": " << value; + } + if (!inner.empty()) { + os << "\n "; + } + os << "}"; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.unordered_map.nested.root", + std::string_view output = "types.unordered_map.nested.json") { + if (gSystem->Load("libNestedUnorderedMap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintNestedUnorderedMapValue(entry, "Index32", os); + PrintNestedUnorderedMapValue(entry, "Index64", os); + PrintNestedUnorderedMapValue(entry, "SplitIndex32", os); + PrintNestedUnorderedMapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/unordered_map/nested/write.C b/types/unordered_map/nested/write.C new file mode 100644 index 0000000..2e53014 --- /dev/null +++ b/types/unordered_map/nested/write.C @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include + +#include +#include +#include +#include +#include + +using UnorderedMap = std::unordered_map>; + +static std::shared_ptr MakeUnorderedMapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + field->GetSubFields()[0]->GetSubFields()[1]->SetColumnRepresentatives( + {{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.unordered_map.nested.root") { + if (gSystem->Load("libNestedUnorderedMap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeUnorderedMapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeUnorderedMapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeUnorderedMapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeUnorderedMapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"bb", 2}}}}; + *SplitIndex32 = {{"c", {{"cc", 3}}}}; + *SplitIndex64 = {{"d", {{"dd", 4}}}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the outer map + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"ba", 2}}}, {"c", {{"ca", 3}}}}; + *SplitIndex32 = { + {"d", {{"da", 4}}}, {"e", {{"ea", 5}, {"eb", 6}}}, {"f", {}}}; + *SplitIndex64 = {{"g", {{"ga", 7}, {"gb", 8}}}, + {"h", {}}, + {"i", {{"ia", 9}}}, + {"j", {{"ja", 10}, {"jb", 11}, {"jc", 12}}}}; + writer->Fill(); +} From 4aa52f606a12ebf0b06b0dbcc62fe8b97c7f142a Mon Sep 17 00:00:00 2001 From: Florine de Geus Date: Thu, 26 Feb 2026 15:47:02 +0100 Subject: [PATCH 3/4] Add test for `std::multimap` --- types/README.md | 1 + types/multimap/README.md | 4 ++ types/multimap/fundamental/README.md | 15 ++++ types/multimap/fundamental/read.C | 68 ++++++++++++++++++ types/multimap/fundamental/write.C | 67 ++++++++++++++++++ types/multimap/nested/LinkDef.h | 6 ++ types/multimap/nested/Makefile | 26 +++++++ types/multimap/nested/NestedMultimap.hxx | 4 ++ types/multimap/nested/README.md | 18 +++++ types/multimap/nested/read.C | 89 ++++++++++++++++++++++++ types/multimap/nested/write.C | 81 +++++++++++++++++++++ 11 files changed, 379 insertions(+) create mode 100644 types/multimap/README.md create mode 100644 types/multimap/fundamental/README.md create mode 100644 types/multimap/fundamental/read.C create mode 100644 types/multimap/fundamental/write.C create mode 100644 types/multimap/nested/LinkDef.h create mode 100644 types/multimap/nested/Makefile create mode 100644 types/multimap/nested/NestedMultimap.hxx create mode 100644 types/multimap/nested/README.md create mode 100644 types/multimap/nested/read.C create mode 100644 types/multimap/nested/write.C diff --git a/types/README.md b/types/README.md index cf3c6c8..5a49de7 100644 --- a/types/README.md +++ b/types/README.md @@ -5,6 +5,7 @@ * [`bitset`](bitset): `std::bitset` * [`fundamental`](fundamental): fundamental column types * [`map`](map): `std::map` with all `[Split]Index{32,64}` column types + * [`multimap`](multimap): `std::multimap` with all `[Split]Index{32,64}` column types * [`multiset`](multiset): `std::multiset` with all `[Split]Index{32,64}` column types * [`optional`](optional): `std::optional` with different element types * [`pair`](pair): `std::pair` with different element types diff --git a/types/multimap/README.md b/types/multimap/README.md new file mode 100644 index 0000000..f6878c4 --- /dev/null +++ b/types/multimap/README.md @@ -0,0 +1,4 @@ +# `std::multimap` + +- [`fundamental`](fundamental): `std::multimap` +- [`nested`](nested): `std::multimap>` diff --git a/types/multimap/fundamental/README.md b/types/multimap/fundamental/README.md new file mode 100644 index 0000000..081bff2 --- /dev/null +++ b/types/multimap/fundamental/README.md @@ -0,0 +1,15 @@ +# `std::map` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the collection parent field. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the map: + one key-value pair in the first field, two key-value pairs in the second field, etc. diff --git a/types/multimap/fundamental/read.C b/types/multimap/fundamental/read.C new file mode 100644 index 0000000..441940c --- /dev/null +++ b/types/multimap/fundamental/read.C @@ -0,0 +1,68 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include +#include +#include +#include +#include +#include + +using Multimap = std::multimap; + +static void PrintMultimapValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + Multimap &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool first = true; + for (auto &[key, value] : item) { + if (first) { + first = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": " << value; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.multimap.fundamental.root", + std::string_view output = "types.multimap.fundamental.json") { + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintMultimapValue(entry, "Index32", os); + PrintMultimapValue(entry, "Index64", os); + PrintMultimapValue(entry, "SplitIndex32", os); + PrintMultimapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/multimap/fundamental/write.C b/types/multimap/fundamental/write.C new file mode 100644 index 0000000..3ace02e --- /dev/null +++ b/types/multimap/fundamental/write.C @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include +#include +#include +#include + +using Multimap = std::multimap; + +static std::shared_ptr MakeMultimapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.multimap.fundamental.root") { + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeMultimapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeMultimapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeMultimapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeMultimapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}}; + *SplitIndex32 = {{"c", 3}}; + *SplitIndex64 = {{"d", 4}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the map + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}, {"c", 3}}; + *SplitIndex32 = {{"d", 4}, {"e", 5}, {"f", 6}}; + *SplitIndex64 = {{"g", 7}, {"h", 8}, {"i", 9}, {"j", 10}}; + writer->Fill(); +} diff --git a/types/multimap/nested/LinkDef.h b/types/multimap/nested/LinkDef.h new file mode 100644 index 0000000..29c1f0f --- /dev/null +++ b/types/multimap/nested/LinkDef.h @@ -0,0 +1,6 @@ +#include +#include + +#ifdef __CLING__ +#pragma link C++ class std::multimap>+; +#endif diff --git a/types/multimap/nested/Makefile b/types/multimap/nested/Makefile new file mode 100644 index 0000000..38ecaa5 --- /dev/null +++ b/types/multimap/nested/Makefile @@ -0,0 +1,26 @@ +ROOT_CONFIG ?= $(shell which root-config) +ifeq ($(ROOT_CONFIG),) +$(error Could not find root-config) +endif +ROOTCLING ?= $(shell which rootcling) +ifeq ($(ROOTCLING),) +$(error Could not find rootcling) +endif + +CXX := $(shell $(ROOT_CONFIG) --cxx) +CXXFLAGS_ROOT := $(shell $(ROOT_CONFIG) --cflags) +CXXFLAGS := -Wall $(CXXFLAGS_ROOT) +LDFLAGS := $(shell $(ROOT_CONFIG) --libs) + +.PHONY: all clean + +all: libNestedMultimap.so + +NestedMultimap.cxx: NestedMultimap.hxx LinkDef.h + $(ROOTCLING) -f $@ $^ + +libNestedMultimap.so: NestedMultimap.cxx + $(CXX) -shared -fPIC -o $@ $^ $(CXXFLAGS) $(LDFLAGS) + +clean: + $(RM) NestedMultimap.cxx NestedMultimap_rdict.pcm libNestedMultimap.so diff --git a/types/multimap/nested/NestedMultimap.hxx b/types/multimap/nested/NestedMultimap.hxx new file mode 100644 index 0000000..cf43459 --- /dev/null +++ b/types/multimap/nested/NestedMultimap.hxx @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/types/multimap/nested/README.md b/types/multimap/nested/README.md new file mode 100644 index 0000000..889c357 --- /dev/null +++ b/types/multimap/nested/README.md @@ -0,0 +1,18 @@ +# `std::multimap>` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the two collection parent fields. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the outer map, with arbitrary lengths of the inner maps + +## Dictionaries + +These tests require ROOT dictionaries, which can be generated with the provided `Makefile` in this directory. This will create a `libNestedMultimap` shared object. diff --git a/types/multimap/nested/read.C b/types/multimap/nested/read.C new file mode 100644 index 0000000..74c4580 --- /dev/null +++ b/types/multimap/nested/read.C @@ -0,0 +1,89 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include + +#include +#include +#include +#include +#include +#include +#include + +using Multimap = + std::multimap>; + +static void PrintNestedMultimapValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + Multimap &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool outerFirst = true; + for (auto &[key, inner] : item) { + if (outerFirst) { + outerFirst = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": {"; + bool innerFirst = true; + for (auto &[innerKey, value] : inner) { + if (innerFirst) { + innerFirst = false; + } else { + os << ","; + } + os << "\n \"" << innerKey << "\": " << value; + } + if (!inner.empty()) { + os << "\n "; + } + os << "}"; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.multimap.nested.root", + std::string_view output = "types.multimap.nested.json") { + if (gSystem->Load("libNestedMultimap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintNestedMultimapValue(entry, "Index32", os); + PrintNestedMultimapValue(entry, "Index64", os); + PrintNestedMultimapValue(entry, "SplitIndex32", os); + PrintNestedMultimapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/multimap/nested/write.C b/types/multimap/nested/write.C new file mode 100644 index 0000000..144dabb --- /dev/null +++ b/types/multimap/nested/write.C @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include + +#include +#include +#include +#include +#include + +using Multimap = + std::multimap>; + +static std::shared_ptr MakeMultimapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + field->GetSubFields()[0]->GetSubFields()[1]->SetColumnRepresentatives( + {{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.multimap.nested.root") { + if (gSystem->Load("libNestedMultimap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeMultimapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeMultimapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeMultimapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeMultimapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"bb", 2}}}}; + *SplitIndex32 = {{"c", {{"cc", 3}}}}; + *SplitIndex64 = {{"d", {{"dd", 4}}}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the outer map + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"ba", 2}}}, {"c", {{"ca", 3}}}}; + *SplitIndex32 = { + {"d", {{"da", 4}}}, {"e", {{"ea", 5}, {"eb", 6}}}, {"f", {}}}; + *SplitIndex64 = {{"g", {{"ga", 7}, {"gb", 8}}}, + {"h", {}}, + {"i", {{"ia", 9}}}, + {"j", {{"ja", 10}, {"jb", 11}, {"jc", 12}}}}; + writer->Fill(); +} From 092feb30ecc021bff0bef9996101b84546df677d Mon Sep 17 00:00:00 2001 From: Florine de Geus Date: Thu, 26 Feb 2026 17:17:38 +0100 Subject: [PATCH 4/4] Add test for `std::unordered_multimap` --- types/README.md | 1 + types/unordered_multimap/README.md | 4 + .../unordered_multimap/fundamental/README.md | 15 +++ types/unordered_multimap/fundamental/read.C | 68 ++++++++++++++ types/unordered_multimap/fundamental/write.C | 67 ++++++++++++++ types/unordered_multimap/nested/LinkDef.h | 6 ++ types/unordered_multimap/nested/Makefile | 26 ++++++ .../nested/NestedUnorderedMultimap.hxx | 4 + types/unordered_multimap/nested/README.md | 18 ++++ types/unordered_multimap/nested/read.C | 91 +++++++++++++++++++ types/unordered_multimap/nested/write.C | 81 +++++++++++++++++ 11 files changed, 381 insertions(+) create mode 100644 types/unordered_multimap/README.md create mode 100644 types/unordered_multimap/fundamental/README.md create mode 100644 types/unordered_multimap/fundamental/read.C create mode 100644 types/unordered_multimap/fundamental/write.C create mode 100644 types/unordered_multimap/nested/LinkDef.h create mode 100644 types/unordered_multimap/nested/Makefile create mode 100644 types/unordered_multimap/nested/NestedUnorderedMultimap.hxx create mode 100644 types/unordered_multimap/nested/README.md create mode 100644 types/unordered_multimap/nested/read.C create mode 100644 types/unordered_multimap/nested/write.C diff --git a/types/README.md b/types/README.md index 5a49de7..3c49e6c 100644 --- a/types/README.md +++ b/types/README.md @@ -15,6 +15,7 @@ * [`tuple`](tuple): `std::tuple` with different element types * [`unique_ptr`](unique_ptr): `std::unique_ptr` with different element types * [`unordered_map`](unordered_map): `std::unordered_map` with all `[Split]Index{32,64}` column types + * [`unordered_multimap`](unordered_multimap): `std::unordered_multimap` with all `[Split]Index{32,64}` column types * [`unordered_multiset`](unordered_multiset): `std::unordered_multiset` with all `[Split]Index{32,64}` column types * [`unordered_set`](unordered_set): `std::unordered_set` with all `[Split]Index{32,64}` column types * [`user`](user): user-defined types, such as classes and enums diff --git a/types/unordered_multimap/README.md b/types/unordered_multimap/README.md new file mode 100644 index 0000000..753c3eb --- /dev/null +++ b/types/unordered_multimap/README.md @@ -0,0 +1,4 @@ +# `std::unordered_map` + +- [`fundamental`](fundamental): `std::unordered_map` +- [`nested`](nested): `std::unordered_map>` diff --git a/types/unordered_multimap/fundamental/README.md b/types/unordered_multimap/fundamental/README.md new file mode 100644 index 0000000..42a4fce --- /dev/null +++ b/types/unordered_multimap/fundamental/README.md @@ -0,0 +1,15 @@ +# `std::unordered_multimap` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the collection parent field. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the map: + one key-value pair in the first field, two key-value pairs in the second field, etc. diff --git a/types/unordered_multimap/fundamental/read.C b/types/unordered_multimap/fundamental/read.C new file mode 100644 index 0000000..346d328 --- /dev/null +++ b/types/unordered_multimap/fundamental/read.C @@ -0,0 +1,68 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include +#include +#include +#include +#include +#include + +using UnorderedMultimap = std::unordered_multimap; + +static void PrintMultimapValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + UnorderedMultimap &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool first = true; + for (auto &[key, value] : item) { + if (first) { + first = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": " << value; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.unordered_multimap.fundamental.root", + std::string_view output = "types.unordered_multimap.fundamental.json") { + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintMultimapValue(entry, "Index32", os); + PrintMultimapValue(entry, "Index64", os); + PrintMultimapValue(entry, "SplitIndex32", os); + PrintMultimapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/unordered_multimap/fundamental/write.C b/types/unordered_multimap/fundamental/write.C new file mode 100644 index 0000000..bdebb90 --- /dev/null +++ b/types/unordered_multimap/fundamental/write.C @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include +#include +#include +#include + +using UnorderedMultimap = std::unordered_multimap; + +static std::shared_ptr MakeMultimapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.unordered_multimap.fundamental.root") { + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeMultimapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeMultimapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeMultimapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeMultimapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}}; + *SplitIndex32 = {{"c", 3}}; + *SplitIndex64 = {{"d", 4}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the map + *Index32 = {{"a", 1}}; + *Index64 = {{"b", 2}, {"c", 3}}; + *SplitIndex32 = {{"d", 4}, {"e", 5}, {"f", 6}}; + *SplitIndex64 = {{"g", 7}, {"h", 8}, {"i", 9}, {"j", 10}}; + writer->Fill(); +} diff --git a/types/unordered_multimap/nested/LinkDef.h b/types/unordered_multimap/nested/LinkDef.h new file mode 100644 index 0000000..b9608d9 --- /dev/null +++ b/types/unordered_multimap/nested/LinkDef.h @@ -0,0 +1,6 @@ +#include +#include + +#ifdef __CLING__ +#pragma link C++ class std::unordered_multimap>+; +#endif diff --git a/types/unordered_multimap/nested/Makefile b/types/unordered_multimap/nested/Makefile new file mode 100644 index 0000000..268e35a --- /dev/null +++ b/types/unordered_multimap/nested/Makefile @@ -0,0 +1,26 @@ +ROOT_CONFIG ?= $(shell which root-config) +ifeq ($(ROOT_CONFIG),) +$(error Could not find root-config) +endif +ROOTCLING ?= $(shell which rootcling) +ifeq ($(ROOTCLING),) +$(error Could not find rootcling) +endif + +CXX := $(shell $(ROOT_CONFIG) --cxx) +CXXFLAGS_ROOT := $(shell $(ROOT_CONFIG) --cflags) +CXXFLAGS := -Wall $(CXXFLAGS_ROOT) +LDFLAGS := $(shell $(ROOT_CONFIG) --libs) + +.PHONY: all clean + +all: libNestedUnorderedMultimap.so + +NestedUnorderedMultimap.cxx: NestedUnorderedMultimap.hxx LinkDef.h + $(ROOTCLING) -f $@ $^ + +libNestedUnorderedMultimap.so: NestedUnorderedMultimap.cxx + $(CXX) -shared -fPIC -o $@ $^ $(CXXFLAGS) $(LDFLAGS) + +clean: + $(RM) NestedUnorderedMultimap.cxx NestedUnorderedMultimap_rdict.pcm libNestedUnorderedMultimap.so diff --git a/types/unordered_multimap/nested/NestedUnorderedMultimap.hxx b/types/unordered_multimap/nested/NestedUnorderedMultimap.hxx new file mode 100644 index 0000000..982dfac --- /dev/null +++ b/types/unordered_multimap/nested/NestedUnorderedMultimap.hxx @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/types/unordered_multimap/nested/README.md b/types/unordered_multimap/nested/README.md new file mode 100644 index 0000000..8f941e3 --- /dev/null +++ b/types/unordered_multimap/nested/README.md @@ -0,0 +1,18 @@ +# `std::unordered_multimap>` + +## Fields + +- `[Split]Index{32,64}` + +with the corresponding column type for the offset column of the two collection parent fields. +All child fields use the default column types for `std::string` (`SplitIndex64` for the principal column, `Char` for the second column) and `std::int32_t` (`SplitInt32`). + +## Entries + +1. Single-element maps, with ascending values for both key and value +2. Empty maps +3. Increasing number of elements in the outer map, with arbitrary lengths of the inner maps + +## Dictionaries + +These tests require ROOT dictionaries, which can be generated with the provided `Makefile` in this directory. This will create a `libNestedUnorderedMultimap` shared object. diff --git a/types/unordered_multimap/nested/read.C b/types/unordered_multimap/nested/read.C new file mode 100644 index 0000000..77f375d --- /dev/null +++ b/types/unordered_multimap/nested/read.C @@ -0,0 +1,91 @@ +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include + +#include +#include +#include +#include +#include +#include +#include + +using UnorderedMultimap = + std::unordered_multimap>; + +static void PrintNestedUnorderedMultimapValue(const REntry &entry, + std::string_view name, + std::ostream &os, bool last = false) { + UnorderedMultimap &item = *entry.GetPtr(name); + os << " \"" << name << "\": {"; + bool outerFirst = true; + for (auto &[key, inner] : item) { + if (outerFirst) { + outerFirst = false; + } else { + os << ","; + } + os << "\n \"" << key << "\": {"; + bool innerFirst = true; + for (auto &[innerKey, value] : inner) { + if (innerFirst) { + innerFirst = false; + } else { + os << ","; + } + os << "\n \"" << innerKey << "\": " << value; + } + if (!inner.empty()) { + os << "\n "; + } + os << "}"; + } + if (!item.empty()) { + os << "\n "; + } + os << "}"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.unordered_multimap.nested.root", + std::string_view output = "types.unordered_multimap.nested.json") { + if (gSystem->Load("libNestedUnorderedMultimap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintNestedUnorderedMultimapValue(entry, "Index32", os); + PrintNestedUnorderedMultimapValue(entry, "Index64", os); + PrintNestedUnorderedMultimapValue(entry, "SplitIndex32", os); + PrintNestedUnorderedMultimapValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/unordered_multimap/nested/write.C b/types/unordered_multimap/nested/write.C new file mode 100644 index 0000000..74c54b5 --- /dev/null +++ b/types/unordered_multimap/nested/write.C @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include + +#include +#include +#include +#include +#include + +using UnorderedMultimap = std::unordered_multimap>; + +static std::shared_ptr MakeUnorderedMultimapField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + field->GetSubFields()[0]->GetSubFields()[1]->SetColumnRepresentatives( + {{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.unordered_multimap.nested.root") { + if (gSystem->Load("libNestedUnorderedMultimap") == -1) + throw std::runtime_error("could not find the required ROOT dictionaries, " + "please make sure to run `make` first"); + + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeUnorderedMultimapField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeUnorderedMultimapField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeUnorderedMultimapField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeUnorderedMultimapField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element maps, with ascending values + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"bb", 2}}}}; + *SplitIndex32 = {{"c", {{"cc", 3}}}}; + *SplitIndex64 = {{"d", {{"dd", 4}}}}; + writer->Fill(); + + // Second entry: empty maps + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the outer map + *Index32 = {{"a", {{"aa", 1}}}}; + *Index64 = {{"b", {{"ba", 2}}}, {"c", {{"ca", 3}}}}; + *SplitIndex32 = { + {"d", {{"da", 4}}}, {"e", {{"ea", 5}, {"eb", 6}}}, {"f", {}}}; + *SplitIndex64 = {{"g", {{"ga", 7}, {"gb", 8}}}, + {"h", {}}, + {"i", {{"ia", 9}}}, + {"j", {{"ja", 10}, {"jb", 11}, {"jc", 12}}}}; + writer->Fill(); +}