OISEAU
A modern DGTD framework
Loading...
Searching...
No Matches
jagged_array.hpp
1// Copyright (C) 2025 Tiago V. L. Amorim (@tiagovla)
2//
3// This file is part of oiseau (https://github.com/tiagovla/oiseau)
4//
5// SPDX-License-Identifier: GPL-3.0-or-later
6
7#pragma once
8
9#include <algorithm> // For std::copy, std::move, std::next
10#include <initializer_list> // For std::initializer_list
11#include <iostream> // For std::ostream and operator<<
12#include <iterator> // For std::iterator related tags
13#include <span> // For std::span
14#include <stdexcept> // For std::out_of_range
15#include <string> // For std::to_string in error messages
16#include <vector>
17
18namespace oiseau::utils {
19
20template <typename T>
21class JaggedArray;
22
23template <typename T>
24std::ostream& operator<<(std::ostream& os, const JaggedArray<T>& arr);
25
26template <typename T>
27class JaggedArray {
28 private:
29 std::vector<T> m_data;
30 std::vector<std::size_t> m_row_offsets;
31
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.");
36 }
37 }
38
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.");
44 }
45 }
46
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();
51 return total;
52 }
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();
56 return total;
57 }
58
59 public:
60 JaggedArray() { m_row_offsets.push_back(0); }
61
62 explicit JaggedArray(std::size_t num_r) : m_row_offsets(num_r + 1, 0) {}
63
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);
72 }
73 }
74
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);
83 }
84 }
85
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;
90
91 std::size_t num_rows() const noexcept {
92 if (m_row_offsets.empty()) return 0;
93 return m_row_offsets.size() - 1;
94 }
95
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];
99 }
100
101 std::size_t total_elements() const noexcept { return m_data.size(); }
102
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) +
106 ") out of bounds.");
107 }
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);
111 }
112
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.");
117 }
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);
121 }
122
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) + ".");
131 }
132 return m_data[start_offset + c_idx];
133 }
134
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) + ".");
143 }
144 return m_data[start_offset + c_idx];
145 }
146
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());
150 }
151
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());
155 }
156
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();
162 }
163
164 void insert_row(std::size_t r_idx, std::initializer_list<T> il) {
165 check_row_insert_bounds(r_idx, "insert_row");
166
167 std::size_t insert_data_pos = m_row_offsets[r_idx];
168 std::size_t new_row_len = il.size();
169
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);
172
173 for (std::size_t i = r_idx + 2; i < m_row_offsets.size(); ++i) {
174 m_row_offsets[i] += new_row_len;
175 }
176 }
177
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");
180
181 std::size_t insert_data_pos = m_row_offsets[r_idx];
182 std::size_t new_row_len = new_row_data.size();
183
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;
188 }
189 }
190
191 void remove_row(std::size_t r_idx) {
192 check_row_bounds(r_idx, "remove_row");
193
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;
197
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);
200
201 for (std::size_t i = r_idx + 1; i < m_row_offsets.size(); ++i) {
202 m_row_offsets[i] -= removed_len;
203 }
204 }
205
206 void add_element(std::size_t r_idx, const T& value) {
207 check_row_bounds(r_idx, "add_element");
208
209 std::size_t insert_data_pos = m_row_offsets[r_idx + 1];
210 m_data.insert(m_data.begin() + insert_data_pos, value);
211
212 for (std::size_t i = r_idx + 1; i < m_row_offsets.size(); ++i) {
213 m_row_offsets[i] += 1;
214 }
215 }
216
217 void add_element(std::size_t r_idx, T&& value) {
218 check_row_bounds(r_idx, "add_element");
219
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;
224 }
225 }
226
227 void clear() noexcept {
228 m_data.clear();
229 m_row_offsets.assign(1, 0);
230 }
231
232 bool is_empty() const noexcept { return num_rows() == 0; }
233
234 template <bool IsConstIter>
235 class RowIterator {
236 public:
237 using iterator_category = std::random_access_iterator_tag;
238 using difference_type = std::ptrdiff_t;
239 using value_type =
240 typename std::conditional<IsConstIter, std::span<const T>, std::span<T>>::type;
241 using pointer = value_type*;
242 using reference = value_type;
243
244 using ParentArrayPtr =
245 typename std::conditional<IsConstIter, const JaggedArray<T>*, JaggedArray<T>*>::type;
246
247 private:
248 ParentArrayPtr m_parent_array;
249 std::size_t m_current_row_idx;
250
251 public:
252 RowIterator(ParentArrayPtr parent, std::size_t r_idx)
253 : m_parent_array(parent), m_current_row_idx(r_idx) {}
254
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>();
259 } else {
260 return std::span<T>();
261 }
262 }
263 // CORRECTED: Accessing members without trailing underscore
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;
266
267 if constexpr (IsConstIter) {
268 return std::span<const T>(m_parent_array->m_data.data() + start_offset, length);
269 } else {
270 return std::span<T>(m_parent_array->m_data.data() + start_offset, length);
271 }
272 }
273
274 RowIterator& operator++() {
275 ++m_current_row_idx;
276 return *this;
277 }
278 RowIterator operator++(int) {
279 RowIterator tmp = *this;
280 ++(*this);
281 return tmp;
282 }
283 RowIterator& operator--() {
284 --m_current_row_idx;
285 return *this;
286 }
287 RowIterator operator--(int) {
288 RowIterator tmp = *this;
289 --(*this);
290 return tmp;
291 }
292
293 RowIterator& operator+=(difference_type n) {
294 m_current_row_idx += n;
295 return *this;
296 }
297 RowIterator operator+(difference_type n) const {
298 RowIterator temp = *this;
299 temp += n;
300 return temp;
301 }
302 friend RowIterator operator+(difference_type n, const RowIterator& iter) { return iter + n; }
303
304 RowIterator& operator-=(difference_type n) {
305 m_current_row_idx -= n;
306 return *this;
307 }
308 RowIterator operator-(difference_type n) const {
309 RowIterator temp = *this;
310 temp -= n;
311 return temp;
312 }
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);
316 }
317
318 reference operator[](difference_type n) const { return *(*this + n); }
319
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;
322 }
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;
326 }
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); }
330 };
331
332 using iterator = RowIterator<false>;
333 using const_iterator = RowIterator<true>;
334
335 iterator begin() { return iterator(this, 0); }
336 iterator end() { return iterator(this, num_rows()); }
337
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()); }
342
343 friend std::ostream& operator<< <>(std::ostream& os, const JaggedArray<T>& arr);
344};
345
346template <typename T>
347std::ostream& operator<<(std::ostream& os, const JaggedArray<T>& arr) {
348 os << "JaggedArray (" << arr.num_rows() << " rows, " << arr.total_elements()
349 << " total elements):\n";
350 if (arr.is_empty()) {
351 os << " (empty)\n";
352 return os;
353 }
354 for (std::size_t i = 0; i < arr.num_rows(); ++i) {
355 os << " [" << i << "] (" << arr.num_cols(i) << " elements): {";
356 std::span<const T> current_row_span = arr[i];
357 for (std::size_t j = 0; j < current_row_span.size(); ++j) {
358 os << current_row_span[j];
359 if (j < current_row_span.size() - 1) {
360 os << ", ";
361 }
362 }
363 os << "}\n";
364 }
365 return os;
366}
367
368} // namespace oiseau::utils
Definition jagged_array.hpp:235
Definition jagged_array.hpp:27