diff --git a/include/rusty_iterators/interface.hpp b/include/rusty_iterators/interface.hpp index ded0dbd..179a27a 100644 --- a/include/rusty_iterators/interface.hpp +++ b/include/rusty_iterators/interface.hpp @@ -16,6 +16,7 @@ #include "step_by.hpp" #include "take.hpp" #include "zip.hpp" +#include "zip_longest.hpp" #include #include @@ -58,6 +59,7 @@ using iterator::Skip; using iterator::StepBy; using iterator::Take; using iterator::Zip; +using iterator::ZipLongest; template class IterInterface @@ -202,6 +204,10 @@ class IterInterface template [[nodiscard]] auto zip(Second&& it) -> Zip; + template + [[nodiscard]] auto zipLongest(Second&& it) + -> ZipLongest; + private: [[nodiscard]] inline auto self() -> Derived& { return static_cast(*this); } auto sizeHintChecked() -> size_t; @@ -626,6 +632,15 @@ auto rusty_iterators::interface::IterInterface::zip(Second&& it) std::forward(it)}; } +template +template +auto rusty_iterators::interface::IterInterface::zipLongest(Second&& it) + -> ZipLongest +{ + return ZipLongest{std::forward(self()), + std::forward(it)}; +} + template auto rusty_iterators::interface::IterInterface::sizeHintChecked() -> size_t { diff --git a/include/rusty_iterators/zip_longest.hpp b/include/rusty_iterators/zip_longest.hpp new file mode 100644 index 0000000..ba5a3fc --- /dev/null +++ b/include/rusty_iterators/zip_longest.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "interface.fwd.hpp" + +#include +#include +#include + +namespace rusty_iterators::iterator +{ +using interface::IterInterface; + +template +class ZipLongest : public IterInterface, std::optional>, + ZipLongest> +{ + public: + explicit ZipLongest(First&& f, Second&& s) + : first(std::forward(f)), second(std::forward(s)) + {} + + auto advanceBy(size_t amount) -> void; + auto next() -> std::optional, std::optional>>; + [[nodiscard]] auto sizeHint() const -> std::optional; + + private: + First first; + Second second; +}; +} // namespace rusty_iterators::iterator + +template +auto rusty_iterators::iterator::ZipLongest::advanceBy(size_t amount) -> void +{ + first.advanceBy(amount); + second.advanceBy(amount); +} + +template +auto rusty_iterators::iterator::ZipLongest::next() + -> std::optional, std::optional>> +{ + auto firstItem = first.next(); + auto secondItem = second.next(); + + if (!firstItem.has_value() && !secondItem.has_value()) + return std::nullopt; + + return std::make_tuple(std::move(firstItem), std::move(secondItem)); +} + +template +auto rusty_iterators::iterator::ZipLongest::sizeHint() const + -> std::optional +{ + auto firstSize = first.sizeHint(); + auto secondSize = second.sizeHint(); + + if (!firstSize.has_value() || !secondSize.has_value()) + return std::nullopt; + + return std::max(firstSize.value(), secondSize.value()); +} diff --git a/tests/zip_longest.test.cpp b/tests/zip_longest.test.cpp new file mode 100644 index 0000000..c2e8e3e --- /dev/null +++ b/tests/zip_longest.test.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include + +using ::rusty_iterators::iterator::LazyIterator; +using ::testing::FieldsAre; + +TEST(TestZipLongestIterator, TestCollectedTuples) +{ + auto v1 = std::vector{1, 2, 3}; + auto v2 = std::vector{4, 5, 6}; + + auto result = LazyIterator{v1}.zipLongest(LazyIterator{v2}).collect(); + + ASSERT_EQ(result.size(), 3); + EXPECT_THAT(result[0], FieldsAre(1, 4)); + EXPECT_THAT(result[1], FieldsAre(2, 5)); + EXPECT_THAT(result[2], FieldsAre(3, 6)); +} + +TEST(ZipLongestIterator, TestLongerIteratorDefinesEnd) +{ + auto v1 = std::vector{1, 2, 3}; + auto v2 = std::vector{4}; + + auto it = LazyIterator{v1}.zipLongest(LazyIterator{v2}); + + EXPECT_THAT(it.next().value(), FieldsAre(1, 4)); + EXPECT_THAT(it.next().value(), FieldsAre(2, std::nullopt)); + EXPECT_THAT(it.next().value(), FieldsAre(3, std::nullopt)); + ASSERT_EQ(it.next(), std::nullopt); +} + +TEST(ZipLongestIterator, TestSizeHintWhenOneIsInf) +{ + auto v1 = std::vector{1, 2, 3}; + auto v2 = std::vector{4, 5}; + + auto it = LazyIterator{v1}.zipLongest(LazyIterator{v2}.cycle()); + + ASSERT_EQ(it.sizeHint(), std::nullopt); +} + +TEST(ZipLongestIterator, TestSizeHintWhenOneLonger) +{ + auto v1 = std::vector{1, 2, 3}; + auto v2 = std::vector{1}; + + auto it = LazyIterator{v1}.zipLongest(LazyIterator{v2}); + + ASSERT_EQ(it.sizeHint(), 3); +} + +TEST(TestZipLongestIterator, TestDifferentTypesOfZippedIterators) +{ + auto v1 = std::vector{1, 2}; + auto v2 = std::vector{"a"}; + + auto it = LazyIterator{v1}.zipLongest(LazyIterator{v2}); + + auto firstItem = it.next().value(); + ASSERT_EQ(std::get<0>(firstItem), 1); + ASSERT_EQ(std::get<1>(firstItem).value().get(), "a"); + + auto secondItem = it.next().value(); + ASSERT_EQ(std::get<0>(secondItem), 2); + ASSERT_EQ(std::get<1>(secondItem), std::nullopt); +} + +TEST(TestZipLongestIterator, TestAdvanceBy) +{ + auto v1 = std::vector{1, 2, 3}; + auto v2 = std::vector{4, 5, 6}; + + auto it = LazyIterator{v1}.zipLongest(LazyIterator{v2}); + + it.advanceBy(2); + + EXPECT_THAT(it.next().value(), FieldsAre(3, 6)); +}