#pragma once #if !defined(MIJIN_UTIL_ITERATORS_HPP_INCLUDED) #define MIJIN_UTIL_ITERATORS_HPP_INCLUDED 1 #include #include #include #include #include #include #include namespace mijin { struct RangeAdapter {}; template struct RangeRef { T& range; decltype(auto) begin() const { return std::begin(range); } decltype(auto) end() const { return std::end(range); } }; template struct RangeRef> { std::pair iterators; TIterator begin() const { return iterators.first; } TIterator end() const { return iterators.second; } }; // in case of pass-by-reference template struct RangeRef&> : RangeRef> {}; static_assert(std::is_same_v>>().iterators.first), int*>, "RangeRef for iterator pair not working."); static_assert(std::is_same_v&>>().iterators.first), std::string_view::iterator>, "RangeRef for iterator pair not working."); template struct is_range_adapter_type : std::false_type {}; template requires std::is_base_of_v struct is_range_adapter_type : std::true_type {}; // allow passing in spans and string_views by value template struct is_range_adapter_type> : std::true_type {}; template struct is_range_adapter_type> : std::true_type {}; template concept RangeAdapterType = is_range_adapter_type>::value; static_assert(!is_range_adapter_type>::value); static_assert(is_range_adapter_type::value); template struct RangeRef { std::remove_reference_t range; decltype(auto) begin() const { return range.begin(); } decltype(auto) end() const { return range.end(); } }; template struct EnumeratingIterator { TIdx idx; TIterator base; EnumeratingIterator(TIdx idx_, TIterator base_) noexcept : idx(idx_), base(base_) {} EnumeratingIterator(const EnumeratingIterator&) noexcept = default; EnumeratingIterator& operator=(const EnumeratingIterator&) noexcept = default; auto operator*() const noexcept { return std::tie(idx, *base); } EnumeratingIterator& operator++() noexcept { ++idx; ++base; return *this; } EnumeratingIterator operator++(int) noexcept { EnumeratingIterator copy(*this); ++(*this); return copy; } EnumeratingIterator& operator--() noexcept { --idx; --base; return *this; } EnumeratingIterator operator--(int) noexcept { EnumeratingIterator copy(*this); --(*this); return copy; } bool operator==(const EnumeratingIterator& other) const noexcept { return base == other.base; // note: ignoring idx so we don't have to find it out for end() } bool operator!=(const EnumeratingIterator& other) const noexcept { return base != other.base; // note: ignoring idx so we don't have to find it out for end() } }; template EnumeratingIterator(TIdx, TIterator) -> EnumeratingIterator; template struct Enumeratable : RangeAdapter { RangeRef base; auto begin() const noexcept { return EnumeratingIterator(TIdx(0), base.begin()); } auto end() const noexcept { return EnumeratingIterator(TIdx(0), base.end()); } }; template Enumeratable enumerate(TIterable&& iterable) { return {.base = {std::forward(iterable)}}; } template struct ReplacingIterator { using difference_type = std::ptrdiff_t; using value_type = typename std::iterator_traits::value_type; using pointer = std::add_const_t*; using reference = std::add_const_t&; using iterator_category = std::bidirectional_iterator_tag; // TODO? TIterator base; value_type what; value_type with; ReplacingIterator(TIterator base_, value_type what_, value_type with_) noexcept : base(base_), what(what_), with(with_) {} ReplacingIterator(const ReplacingIterator&) noexcept = default; ReplacingIterator& operator=(const ReplacingIterator&) noexcept = default; pointer operator->() const noexcept { if (*base == what) { return &with; } return &*base; } reference operator*() const noexcept { if (*base == what) { return with; } return *base; } ReplacingIterator& operator++() noexcept { ++base; return *this; } ReplacingIterator operator++(int) noexcept { ReplacingIterator copy(*this); ++(*this); return copy; } ReplacingIterator& operator--() noexcept { --base; return *this; } ReplacingIterator operator--(int) noexcept { ReplacingIterator copy(*this); --(*this); return copy; } bool operator==(const ReplacingIterator& other) const noexcept { return what == other.what && with == other.with && base == other.base; } bool operator!=(const ReplacingIterator& other) const noexcept { return !(*this == other); } }; template struct ReplacingRange : RangeAdapter { using value_type = typename std::iterator_traits()))>::value_type; RangeRef base; value_type what; value_type with; auto begin() const noexcept { return ReplacingIterator(base.begin(), what, with); } auto end() const noexcept { return ReplacingIterator(base.end(), what, with); } }; template auto replace( TIterable&& iterable, typename std::iterator_traits()))>::value_type what, typename std::iterator_traits()))>::value_type with) { return ReplacingRange{ .base = {std::forward(iterable)}, .what = std::move(what), .with = std::move(with) }; } template struct MappingIterator { using orig_value_type = typename std::iterator_traits::value_type; using difference_type = std::ptrdiff_t; using value_type = std::invoke_result_t; using reference = value_type&; using iterator_category = std::bidirectional_iterator_tag; // TODO? static_assert(!std::is_same_v, "Invalid mapping result type."); TIterator base; TFunctor functor; mutable std::optional result; MappingIterator(TIterator base_, TFunctor functor_) noexcept : base(base_), functor(std::move(functor_)) {} MappingIterator(const MappingIterator&) noexcept = default; MappingIterator(MappingIterator&&) noexcept = default; MappingIterator& operator=(const MappingIterator&) noexcept = default; MappingIterator& operator=(MappingIterator&&) noexcept = default; reference operator*() const noexcept { if (!result.has_value()) { result = functor(*base); } return *result; } MappingIterator& operator++() noexcept { ++base; result = std::nullopt; return *this; } MappingIterator operator++(int) noexcept { MappingIterator copy(*this); ++(*this); return copy; } MappingIterator& operator--() noexcept { --base; result = std::nullopt; return *this; } MappingIterator operator--(int) noexcept { MappingIterator copy(*this); --(*this); return copy; } bool operator==(const MappingIterator& other) const noexcept { return base == other.base; // TODO: compare functor? -> doesn't always work and may be useless } bool operator!=(const MappingIterator& other) const noexcept { return !(*this == other); } }; template struct MappingRange : RangeAdapter { // using value_type = typename std::iterator_traits()))>::value_type; using value_type = typename std::iterator_traits>().begin())>::value_type; RangeRef base; TFunctor functor; auto begin() const noexcept { return MappingIterator(base.begin(), functor); } auto end() const noexcept { return MappingIterator(base.end(), functor); } }; template auto map(TIterable&& iterable, TFunctor&& functor) { return MappingRange{ .base = {std::forward(iterable)}, .functor = std::forward(functor) }; } template struct OptionalMappingIterator { using orig_value_type = typename std::iterator_traits::value_type; using difference_type = std::ptrdiff_t; using optional_type = std::invoke_result_t; using value_type = std::decay_t().value())>; using iterator_category = std::forward_iterator_tag; // TODO? using reference = value_type&; static_assert(!std::is_same_v, "Invalid mapping result type."); TIterator base; TIterator end; TFunctor functor; mutable optional_type result; // must be mutable so dereferencing can stay const, not nice :/ OptionalMappingIterator(TIterator base_, TIterator end_, TFunctor functor_) noexcept : base(base_), end(end_), functor(std::move(functor_)) { if (base != end) { result = functor(*base); if (!result.has_value()) { ++(*this); } } } OptionalMappingIterator(const OptionalMappingIterator&) noexcept = default; OptionalMappingIterator(OptionalMappingIterator&&) noexcept = default; OptionalMappingIterator& operator=(const OptionalMappingIterator&) noexcept = default; OptionalMappingIterator& operator=(OptionalMappingIterator&&) noexcept = default; reference operator*() const noexcept { return *result; } OptionalMappingIterator& operator++() noexcept { do { ++base; result = base != end ? functor(*base) : std::nullopt; } while (base != end && !result.has_value()); return *this; } OptionalMappingIterator operator++(int) noexcept { OptionalMappingIterator copy(*this); ++(*this); return copy; } bool operator==(const OptionalMappingIterator& other) const noexcept { return base == other.base && end == other.end; // TODO: compare functor? -> doesn't always work and may be useless } bool operator!=(const OptionalMappingIterator& other) const noexcept { return !(*this == other); } }; template struct OptionalMappingRange : RangeAdapter { // using value_type = typename std::iterator_traits()))>::value_type; using value_type = typename std::iterator_traits>().begin())>::value_type; RangeRef base; TFunctor functor; auto begin() const noexcept { return OptionalMappingIterator(base.begin(), base.end(), functor); } auto end() const noexcept { return OptionalMappingIterator(base.end(), base.end(), functor); } }; template auto mapOptional(TIterable&& iterable, TFunctor&& functor) { return OptionalMappingRange{ .base = {std::forward(iterable)}, .functor = std::forward(functor) }; } template struct FilteringIterator { using difference_type = typename std::iterator_traits::difference_type; using value_type = typename std::iterator_traits::value_type; using iterator_category = std::forward_iterator_tag; // TODO? using reference = typename std::iterator_traits::reference; TIterator base; TIterator end; TPredicate predicate; FilteringIterator(TIterator base_, TIterator end_, TPredicate predicate_) noexcept : base(base_), end(end_), predicate(std::move(predicate_)) { if (base != end && !predicate(*base)) { ++(*this); } } FilteringIterator(const FilteringIterator&) noexcept = default; FilteringIterator(FilteringIterator&&) noexcept = default; FilteringIterator& operator=(const FilteringIterator&) noexcept = default; FilteringIterator& operator=(FilteringIterator&&) noexcept = default; reference operator*() const noexcept { return *base; } FilteringIterator& operator++() noexcept { do { ++base; } while (base != end && !predicate(*base)); return *this; } FilteringIterator operator++(int) noexcept { FilteringIterator copy(*this); ++(*this); return copy; } bool operator==(const FilteringIterator& other) const noexcept { return base == other.base && end == other.end; // TODO: compare predicate? } bool operator!=(const FilteringIterator& other) const noexcept { return !(*this == other); } }; template struct FilteringRange : RangeAdapter { // using value_type = typename std::iterator_traits()))>::value_type; using value_type = typename std::iterator_traits>().begin())>::value_type; RangeRef base; TPredicate predicate; auto begin() const noexcept { return FilteringIterator(base.begin(), base.end(), predicate); } auto end() const noexcept { return FilteringIterator(base.end(), base.end(), predicate); } }; template auto filter(TIterable&& iterable, TPredicate&& predicate) { return FilteringRange{ .base = {std::forward(iterable)}, .predicate = std::forward(predicate) }; } template struct ChainingIterator { using difference_type = std::ptrdiff_t; using value_type = typename std::iterator_traits::value_type; using pointer = std::add_const_t*; using reference = std::add_const_t&; using iterator_category = std::bidirectional_iterator_tag; // TODO? TFirstIterator firstBase; TFirstIterator firstEnd; TSecondIterator secondBase; TSecondIterator secondBegin; ChainingIterator(TFirstIterator firstBase_, TFirstIterator firstEnd_, TSecondIterator secondBase_, TSecondIterator secondBegin_) noexcept : firstBase(firstBase_), firstEnd(firstEnd_), secondBase(secondBase_), secondBegin(secondBegin_) {} ChainingIterator(const ChainingIterator&) noexcept = default; ChainingIterator& operator=(const ChainingIterator&) noexcept = default; pointer operator->() const noexcept { if (firstBase == firstEnd) { return &*secondBase; } return &*firstBase; } reference operator*() const noexcept { if (firstBase == firstEnd) { return *secondBase; } return *firstBase; } ChainingIterator& operator++() noexcept { if (firstBase == firstEnd) { ++secondBase; } else { ++firstBase; } return *this; } ChainingIterator operator++(int) noexcept { ChainingIterator copy(*this); ++(*this); return copy; } ChainingIterator& operator--() noexcept { if (secondBase == secondBegin) { --firstBase; } else { --secondBase; } return *this; } ChainingIterator operator--(int) noexcept { ChainingIterator copy(*this); --(*this); return copy; } bool operator==(const ChainingIterator& other) const noexcept { return firstBase == other.firstBase && secondBase == other.secondBase; // should be enough } bool operator!=(const ChainingIterator& other) const noexcept { return !(*this == other); } }; template struct ChainedRange : RangeAdapter { using value_type = typename std::iterator_traits()))>::value_type; RangeRef first; RangeRef second; auto begin() const noexcept { return ChainingIterator(first.begin(), first.end(), second.begin(), second.begin()); } auto end() const noexcept { return ChainingIterator(first.end(), first.end(), second.end(), second.begin()); } }; template auto chain(TFirstRange&& firstRange, TSecondRange&& secondRange) { return ChainedRange{ .first = {std::forward(firstRange)}, .second = {std::forward(secondRange)} }; } template auto chain(TFirstRange&& firstRange, TSecondRange&& secondRange, TMoreRanges&&... moreRanges) { return chain(std::forward(firstRange), chain(std::forward(secondRange), std::forward(moreRanges)...)); } template TAs collect(TIterable&& iterable) { return TAs(iterable.begin(), iterable.end()); } namespace pipe { template struct Replace { T what; T with; Replace(T what_, T with_) : what(std::move(what_)), with(std::move(with_)) {} }; template Replace(T, T) -> Replace; template auto operator|(TIterable&& iterable, Replace()))>::value_type> repl) { return replace(std::forward(iterable), std::move(repl.what), std::move(repl.with)); } template struct Collect {}; template auto operator|(TIterable&& iterable, Collect) { return collect(std::forward(iterable)); } struct Chain {}; template struct Chain2 { RangeRef range; }; template auto operator|(TIterable&& iterable, Chain) { return Chain2{.range = {.range = std::forward(iterable)}}; } template auto operator|(Chain2 chain2, TSecondIterable&& secondIterable) { return chain(chain2.range.range, std::forward(secondIterable)); } template struct Map { T functor; explicit Map(T functor_) : functor(std::move(functor_)) {} }; template Map(T) -> Map; template auto operator|(TIterable&& iterable, Map mapper) { return map(std::forward(iterable), std::move(mapper.functor)); } template struct MapOptional { T functor; explicit MapOptional(T functor_) : functor(std::move(functor_)) {} }; template MapOptional(T) -> MapOptional; template auto operator|(TIterable&& iterable, MapOptional mapper) { return mapOptional(std::forward(iterable), std::move(mapper.functor)); } template struct Filter { T predicate; explicit Filter(T predicate_) : predicate(std::move(predicate_)) {} }; template Filter(T) -> Filter; template auto operator|(TIterable&& iterable, Filter filterer) { return filter(std::forward(iterable), std::move(filterer.predicate)); } template struct Xth {}; using First = Xth<0>; using Second = Xth<1>; template auto operator|(TIterable&& iterable, Xth) { return map(std::forward(iterable), [](auto&& element) { return std::get(element); } ); } } } // namespace mijin #endif // MIJIN_UTIL_ITERATORS_HPP_INCLUDED