From 940071a5f25789fcd971a9916c201c8fc077ecc8 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 14 May 2025 18:06:50 +0100 Subject: [PATCH] Codechange: Add alternating iterator to take elements from middle of range. --- src/misc/CMakeLists.txt | 1 + src/misc/alternating_iterator.hpp | 145 +++++++++++++++++++++++++++++ src/tests/CMakeLists.txt | 1 + src/tests/alternating_iterator.cpp | 48 ++++++++++ 4 files changed, 195 insertions(+) create mode 100644 src/misc/alternating_iterator.hpp create mode 100644 src/tests/alternating_iterator.cpp diff --git a/src/misc/CMakeLists.txt b/src/misc/CMakeLists.txt index f45fa06960..031ad3e000 100644 --- a/src/misc/CMakeLists.txt +++ b/src/misc/CMakeLists.txt @@ -1,4 +1,5 @@ add_files( + alternating_iterator.hpp binaryheap.hpp dbg_helpers.cpp dbg_helpers.h diff --git a/src/misc/alternating_iterator.hpp b/src/misc/alternating_iterator.hpp new file mode 100644 index 0000000000..a5b6d6f591 --- /dev/null +++ b/src/misc/alternating_iterator.hpp @@ -0,0 +1,145 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file alternating_iterator.hpp Iterator adaptor that takes items alternating from a middle position. */ + +#ifndef ALTERNATING_ITERATOR_HPP +#define ALTERNATING_ITERATOR_HPP + +#include + +/** + * Iterator that alternately takes from the "middle" of a range. + * @tparam Titer Type of iterator. + */ +template +class AlternatingIterator { +public: + using value_type = typename Titer::value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + using pointer = typename Titer::pointer; + using reference = typename Titer::reference; + + AlternatingIterator() = default; + + /** + * Construct an AlternatingIterator. + * @param first Iterator to first element. + * @param last Iterator to last element. + * @param middle Iterator to "middle" element, from where to start. + * @param begin Whether this iterator points to the first or last elements. + */ + AlternatingIterator(Titer first, Titer last, Titer middle, bool begin) : first(first), last(last), middle(middle) + { + /* Starting from the end is not supported, unless the range is empty. */ + assert(first == last || middle != last); + + this->position = begin ? 0 : std::distance(this->first, this->last); + this->before = middle; + this->after = middle; + this->next_state = this->before == this->first; + this->state = this->next_state; + } + + bool operator==(const AlternatingIterator &rhs) const + { + assert(this->first == rhs.first); + assert(this->last == rhs.last); + assert(this->middle == rhs.middle); + return this->position == rhs.position; + } + + std::strong_ordering operator<=>(const AlternatingIterator &rhs) const + { + assert(this->first == rhs.first); + assert(this->last == rhs.last); + assert(this->middle == rhs.middle); + return this->position <=> rhs.position; + } + + inline reference operator*() const + { + return *this->Base(); + } + + AlternatingIterator &operator++() + { + size_t size = static_cast(std::distance(this->first, this->last)); + assert(this->position < size); + + ++this->position; + if (this->position < size) this->Next(); + + return *this; + } + + AlternatingIterator operator++(int) + { + AlternatingIterator result = *this; + ++*this; + return result; + } + + inline Titer Base() const + { + return this->state ? this->after : this->before; + } + +private: + Titer first; ///< Initial first iterator. + Titer last; ///< Initial last iterator. + Titer middle; ///< Initial middle iterator. + + Titer after; ///< Current iterator after the middle. + Titer before; ///< Current iterator before the middle. + + size_t position; ///< Position within the entire range. + + bool next_state; ///< Next state for advancing iterator. If true take from after middle, otherwise take from before middle. + bool state; ///< Current state for reading iterator. If true take from after middle, otherwise take from before middle. + + void Next() + { + this->state = this->next_state; + if (this->next_state) { + assert(this->after != this->last); + ++this->after; + this->next_state = this->before == this->first; + } else { + assert(this->before != this->first); + --this->before; + this->next_state = std::next(this->after) != this->last; + } + } +}; + +template +class AlternatingView : public std::ranges::view_interface> { +public: + AlternatingView(std::ranges::viewable_range auto &&range, Titer middle) : + first(std::ranges::begin(range)), last(std::ranges::end(range)), middle(middle) + { + } + + auto begin() const + { + return AlternatingIterator{first, last, middle, true}; + } + + auto end() const + { + return AlternatingIterator{first, last, middle, false}; + } + +private: + Titer first; ///< Iterator to first element. + Titer last; ///< Iterator to last element. + Titer middle; ///< Iterator to middle element. +}; + +#endif /* ALTERNATING_ITERATOR_HPP */ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index e2d5f11ad6..c603eb99d3 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,4 +1,5 @@ add_test_files( + alternating_iterator.cpp bitmath_func.cpp enum_over_optimisation.cpp flatset_type.cpp diff --git a/src/tests/alternating_iterator.cpp b/src/tests/alternating_iterator.cpp new file mode 100644 index 0000000000..93775685f5 --- /dev/null +++ b/src/tests/alternating_iterator.cpp @@ -0,0 +1,48 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file bitmath_func.cpp Test functionality from core/bitmath_func. */ + +#include "../stdafx.h" + +#include "../3rdparty/catch2/catch.hpp" + +#include "../misc/alternating_iterator.hpp" + +#include "../safeguards.h" + +TEST_CASE("AlternatingIterator tests") +{ + auto test_case = [&](auto input, std::initializer_list expected) { + return std::ranges::equal(input, expected); + }; + + /* Sequence includes sentinel markers to detect out-of-bounds reads without relying on UB. */ + std::initializer_list raw_sequence_even = {INT_MAX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, INT_MAX}; + const std::span sequence_even = std::span{raw_sequence_even.begin() + 1, raw_sequence_even.end() - 1}; + + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 0), { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 1), { 1, 0, 2, 3, 4, 5, 6, 7, 8, 9 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 2), { 2, 1, 3, 0, 4, 5, 6, 7, 8, 9 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 3), { 3, 2, 4, 1, 5, 0, 6, 7, 8, 9 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 4), { 4, 3, 5, 2, 6, 1, 7, 0, 8, 9 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 5), { 5, 4, 6, 3, 7, 2, 8, 1, 9, 0 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 6), { 6, 5, 7, 4, 8, 3, 9, 2, 1, 0 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 7), { 7, 6, 8, 5, 9, 4, 3, 2, 1, 0 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 8), { 8, 7, 9, 6, 5, 4, 3, 2, 1, 0 })); + CHECK(test_case(AlternatingView(sequence_even, sequence_even.begin() + 9), { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 })); + + /* Sequence includes sentinel markers to detect out-of-bounds reads without relying on UB. */ + std::initializer_list raw_sequence_odd = {INT_MAX, 0, 1, 2, 3, 4, INT_MAX}; + const std::span sequence_odd = std::span{raw_sequence_odd.begin() + 1, raw_sequence_odd.end() - 1}; + + CHECK(test_case(AlternatingView(sequence_odd, sequence_odd.begin() + 0), { 0, 1, 2, 3, 4 })); + CHECK(test_case(AlternatingView(sequence_odd, sequence_odd.begin() + 1), { 1, 0, 2, 3, 4 })); + CHECK(test_case(AlternatingView(sequence_odd, sequence_odd.begin() + 2), { 2, 1, 3, 0, 4 })); + CHECK(test_case(AlternatingView(sequence_odd, sequence_odd.begin() + 3), { 3, 2, 4, 1, 0 })); + CHECK(test_case(AlternatingView(sequence_odd, sequence_odd.begin() + 4), { 4, 3, 2, 1, 0 })); +}