29 std::vector<T> m_data;
30 std::vector<std::size_t> m_row_offsets;
32 void check_row_bounds(std::size_t r_idx,
const char* func_name)
const {
33 if (r_idx >= num_rows()) {
34 throw std::out_of_range(std::string(func_name) +
" - Row index (" + std::to_string(r_idx) +
35 ") out of bounds for " + std::to_string(num_rows()) +
" rows.");
39 void check_row_insert_bounds(std::size_t r_idx,
const char* func_name)
const {
40 if (r_idx > num_rows()) {
41 throw std::out_of_range(std::string(func_name) +
" - Row index for insertion (" +
42 std::to_string(r_idx) +
") out of bounds for " +
43 std::to_string(num_rows()) +
" rows.");
47 static std::size_t get_total_elements_hint(
48 const std::initializer_list<std::initializer_list<T>>& il) {
49 std::size_t total = 0;
50 for (
const auto& inner : il) total += inner.size();
53 static std::size_t get_total_elements_hint(
const std::initializer_list<std::vector<T>>& il) {
54 std::size_t total = 0;
55 for (
const auto& inner : il) total += inner.size();
60 JaggedArray() { m_row_offsets.push_back(0); }
62 explicit JaggedArray(std::size_t num_r) : m_row_offsets(num_r + 1, 0) {}
64 JaggedArray(std::initializer_list<std::initializer_list<T>> il) {
65 m_row_offsets.push_back(0);
66 std::size_t current_offset = 0;
67 m_data.reserve(get_total_elements_hint(il));
68 for (
const auto& inner_list : il) {
69 m_data.insert(m_data.end(), inner_list.begin(), inner_list.end());
70 current_offset += inner_list.size();
71 m_row_offsets.push_back(current_offset);
75 JaggedArray(std::initializer_list<std::vector<T>> il) {
76 m_row_offsets.push_back(0);
77 std::size_t current_offset = 0;
78 m_data.reserve(get_total_elements_hint(il));
79 for (
const auto& row_vec : il) {
80 m_data.insert(m_data.end(), row_vec.begin(), row_vec.end());
81 current_offset += row_vec.size();
82 m_row_offsets.push_back(current_offset);
86 JaggedArray(
const JaggedArray& other) =
default;
87 JaggedArray(JaggedArray&& other)
noexcept =
default;
88 JaggedArray& operator=(
const JaggedArray& other) =
default;
89 JaggedArray& operator=(JaggedArray&& other)
noexcept =
default;
91 std::size_t num_rows()
const noexcept {
92 if (m_row_offsets.empty())
return 0;
93 return m_row_offsets.size() - 1;
96 std::size_t num_cols(std::size_t r_idx)
const {
97 check_row_bounds(r_idx,
"num_cols");
98 return m_row_offsets[r_idx + 1] - m_row_offsets[r_idx];
101 std::size_t total_elements()
const noexcept {
return m_data.size(); }
103 std::span<T> operator[](std::size_t r_idx) {
104 if (r_idx >= num_rows()) {
105 throw std::out_of_range(
"JaggedArray::operator[] - Row index (" + std::to_string(r_idx) +
108 std::size_t start_offset = m_row_offsets[r_idx];
109 std::size_t length = m_row_offsets[r_idx + 1] - start_offset;
110 return std::span<T>(m_data.data() + start_offset, length);
113 std::span<const T> operator[](std::size_t r_idx)
const {
114 if (r_idx >= num_rows()) {
115 throw std::out_of_range(
"JaggedArray::operator[] const - Row index (" +
116 std::to_string(r_idx) +
") out of bounds.");
118 std::size_t start_offset = m_row_offsets[r_idx];
119 std::size_t length = m_row_offsets[r_idx + 1] - start_offset;
120 return std::span<const T>(m_data.data() + start_offset, length);
123 T& at(std::size_t r_idx, std::size_t c_idx) {
124 check_row_bounds(r_idx,
"at (row)");
125 std::size_t start_offset = m_row_offsets[r_idx];
126 std::size_t row_len = m_row_offsets[r_idx + 1] - start_offset;
127 if (c_idx >= row_len) {
128 throw std::out_of_range(
"JaggedArray::at - Column index (" + std::to_string(c_idx) +
129 ") out of bounds for row " + std::to_string(r_idx) +
" with length " +
130 std::to_string(row_len) +
".");
132 return m_data[start_offset + c_idx];
135 const T& at(std::size_t r_idx, std::size_t c_idx)
const {
136 check_row_bounds(r_idx,
"at (row)");
137 std::size_t start_offset = m_row_offsets[r_idx];
138 std::size_t row_len = m_row_offsets[r_idx + 1] - start_offset;
139 if (c_idx >= row_len) {
140 throw std::out_of_range(
"JaggedArray::at - Column index (" + std::to_string(c_idx) +
141 ") out of bounds for row " + std::to_string(r_idx) +
" with length " +
142 std::to_string(row_len) +
".");
144 return m_data[start_offset + c_idx];
147 void add_row(std::initializer_list<T> il) {
148 m_data.insert(m_data.end(), il.begin(), il.end());
149 m_row_offsets.push_back(m_data.size());
152 void add_row(
const std::vector<T>& new_row_data) {
153 m_data.insert(m_data.end(), new_row_data.begin(), new_row_data.end());
154 m_row_offsets.push_back(m_data.size());
157 void add_row(std::vector<T>&& new_row_data) {
158 m_data.insert(m_data.end(), std::make_move_iterator(new_row_data.begin()),
159 std::make_move_iterator(new_row_data.end()));
160 m_row_offsets.push_back(m_data.size());
161 new_row_data.clear();
164 void insert_row(std::size_t r_idx, std::initializer_list<T> il) {
165 check_row_insert_bounds(r_idx,
"insert_row");
167 std::size_t insert_data_pos = m_row_offsets[r_idx];
168 std::size_t new_row_len = il.size();
170 m_data.insert(m_data.begin() + insert_data_pos, il.begin(), il.end());
171 m_row_offsets.insert(m_row_offsets.begin() + r_idx + 1, m_row_offsets[r_idx] + new_row_len);
173 for (std::size_t i = r_idx + 2; i < m_row_offsets.size(); ++i) {
174 m_row_offsets[i] += new_row_len;
178 void insert_row(std::size_t r_idx,
const std::vector<T>& new_row_data) {
179 check_row_insert_bounds(r_idx,
"insert_row");
181 std::size_t insert_data_pos = m_row_offsets[r_idx];
182 std::size_t new_row_len = new_row_data.size();
184 m_data.insert(m_data.begin() + insert_data_pos, new_row_data.begin(), new_row_data.end());
185 m_row_offsets.insert(m_row_offsets.begin() + r_idx + 1, m_row_offsets[r_idx] + new_row_len);
186 for (std::size_t i = r_idx + 2; i < m_row_offsets.size(); ++i) {
187 m_row_offsets[i] += new_row_len;
191 void remove_row(std::size_t r_idx) {
192 check_row_bounds(r_idx,
"remove_row");
194 std::size_t start_data_pos = m_row_offsets[r_idx];
195 std::size_t end_data_pos = m_row_offsets[r_idx + 1];
196 std::size_t removed_len = end_data_pos - start_data_pos;
198 m_data.erase(m_data.begin() + start_data_pos, m_data.begin() + end_data_pos);
199 m_row_offsets.erase(m_row_offsets.begin() + r_idx + 1);
201 for (std::size_t i = r_idx + 1; i < m_row_offsets.size(); ++i) {
202 m_row_offsets[i] -= removed_len;
206 void add_element(std::size_t r_idx,
const T& value) {
207 check_row_bounds(r_idx,
"add_element");
209 std::size_t insert_data_pos = m_row_offsets[r_idx + 1];
210 m_data.insert(m_data.begin() + insert_data_pos, value);
212 for (std::size_t i = r_idx + 1; i < m_row_offsets.size(); ++i) {
213 m_row_offsets[i] += 1;
217 void add_element(std::size_t r_idx, T&& value) {
218 check_row_bounds(r_idx,
"add_element");
220 std::size_t insert_data_pos = m_row_offsets[r_idx + 1];
221 m_data.insert(m_data.begin() + insert_data_pos, std::move(value));
222 for (std::size_t i = r_idx + 1; i < m_row_offsets.size(); ++i) {
223 m_row_offsets[i] += 1;
227 void clear()
noexcept {
229 m_row_offsets.assign(1, 0);
232 bool is_empty()
const noexcept {
return num_rows() == 0; }
234 template <
bool IsConstIter>
237 using iterator_category = std::random_access_iterator_tag;
238 using difference_type = std::ptrdiff_t;
240 typename std::conditional<IsConstIter, std::span<const T>, std::span<T>>::type;
241 using pointer = value_type*;
242 using reference = value_type;
244 using ParentArrayPtr =
245 typename std::conditional<IsConstIter, const JaggedArray<T>*, JaggedArray<T>*>::type;
248 ParentArrayPtr m_parent_array;
249 std::size_t m_current_row_idx;
252 RowIterator(ParentArrayPtr parent, std::size_t r_idx)
253 : m_parent_array(parent), m_current_row_idx(r_idx) {}
255 reference operator*()
const {
256 if (m_current_row_idx == m_parent_array->num_rows()) {
257 if constexpr (IsConstIter) {
258 return std::span<const T>();
260 return std::span<T>();
264 std::size_t start_offset = m_parent_array->m_row_offsets[m_current_row_idx];
265 std::size_t length = m_parent_array->m_row_offsets[m_current_row_idx + 1] - start_offset;
267 if constexpr (IsConstIter) {
268 return std::span<const T>(m_parent_array->m_data.data() + start_offset, length);
270 return std::span<T>(m_parent_array->m_data.data() + start_offset, length);
274 RowIterator& operator++() {
278 RowIterator operator++(
int) {
279 RowIterator tmp = *
this;
283 RowIterator& operator--() {
287 RowIterator operator--(
int) {
288 RowIterator tmp = *
this;
293 RowIterator& operator+=(difference_type n) {
294 m_current_row_idx += n;
297 RowIterator operator+(difference_type n)
const {
298 RowIterator temp = *
this;
302 friend RowIterator operator+(difference_type n,
const RowIterator& iter) {
return iter + n; }
304 RowIterator& operator-=(difference_type n) {
305 m_current_row_idx -= n;
308 RowIterator operator-(difference_type n)
const {
309 RowIterator temp = *
this;
313 difference_type operator-(
const RowIterator& other)
const {
314 return static_cast<difference_type
>(m_current_row_idx) -
315 static_cast<difference_type
>(other.m_current_row_idx);
318 reference operator[](difference_type n)
const {
return *(*
this + n); }
320 bool operator==(
const RowIterator& other)
const {
321 return m_parent_array == other.m_parent_array && m_current_row_idx == other.m_current_row_idx;
323 bool operator!=(
const RowIterator& other)
const {
return !(*
this == other); }
324 bool operator<(
const RowIterator& other)
const {
325 return m_current_row_idx < other.m_current_row_idx;
327 bool operator>(
const RowIterator& other)
const {
return other < *
this; }
328 bool operator<=(
const RowIterator& other)
const {
return !(other < *
this); }
329 bool operator>=(
const RowIterator& other)
const {
return !(*
this < other); }
335 iterator begin() {
return iterator(
this, 0); }
336 iterator end() {
return iterator(
this, num_rows()); }
338 const_iterator begin()
const {
return const_iterator(
this, 0); }
339 const_iterator end()
const {
return const_iterator(
this, num_rows()); }
340 const_iterator cbegin()
const {
return const_iterator(
this, 0); }
341 const_iterator cend()
const {
return const_iterator(
this, num_rows()); }
343 friend std::ostream& operator<< <>(std::ostream& os,
const JaggedArray<T>& arr);