Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions include/rusty_iterators/interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "step_by.hpp"
#include "take.hpp"
#include "zip.hpp"
#include "zip_longest.hpp"

#include <stdexcept>
#include <type_traits>
Expand Down Expand Up @@ -58,6 +59,7 @@ using iterator::Skip;
using iterator::StepBy;
using iterator::Take;
using iterator::Zip;
using iterator::ZipLongest;

template <class T, class Derived>
class IterInterface
Expand Down Expand Up @@ -202,6 +204,10 @@ class IterInterface
template <class Second>
[[nodiscard]] auto zip(Second&& it) -> Zip<T, typename Second::Type, Derived, Second>;

template <class Second>
[[nodiscard]] auto zipLongest(Second&& it)
-> ZipLongest<T, typename Second::Type, Derived, Second>;

private:
[[nodiscard]] inline auto self() -> Derived& { return static_cast<Derived&>(*this); }
auto sizeHintChecked() -> size_t;
Expand Down Expand Up @@ -626,6 +632,15 @@ auto rusty_iterators::interface::IterInterface<T, Derived>::zip(Second&& it)
std::forward<Second>(it)};
}

template <class T, class Derived>
template <class Second>
auto rusty_iterators::interface::IterInterface<T, Derived>::zipLongest(Second&& it)
-> ZipLongest<T, typename Second::Type, Derived, Second>
{
return ZipLongest<T, typename Second::Type, Derived, Second>{std::forward<Derived>(self()),
std::forward<Second>(it)};
}

template <class T, class Derived>
auto rusty_iterators::interface::IterInterface<T, Derived>::sizeHintChecked() -> size_t
{
Expand Down
63 changes: 63 additions & 0 deletions include/rusty_iterators/zip_longest.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include "interface.fwd.hpp"

#include <algorithm>
#include <optional>
#include <tuple>

namespace rusty_iterators::iterator
{
using interface::IterInterface;

template <class T, class R, class First, class Second>
class ZipLongest : public IterInterface<std::tuple<std::optional<T>, std::optional<R>>,
ZipLongest<T, R, First, Second>>
{
public:
explicit ZipLongest(First&& f, Second&& s)
: first(std::forward<First>(f)), second(std::forward<Second>(s))
{}

auto advanceBy(size_t amount) -> void;
auto next() -> std::optional<std::tuple<std::optional<T>, std::optional<R>>>;
[[nodiscard]] auto sizeHint() const -> std::optional<size_t>;

private:
First first;
Second second;
};
} // namespace rusty_iterators::iterator

template <class T, class R, class First, class Second>
auto rusty_iterators::iterator::ZipLongest<T, R, First, Second>::advanceBy(size_t amount) -> void
{
first.advanceBy(amount);
second.advanceBy(amount);
}

template <class T, class R, class First, class Second>
auto rusty_iterators::iterator::ZipLongest<T, R, First, Second>::next()
-> std::optional<std::tuple<std::optional<T>, std::optional<R>>>
{
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 <class T, class R, class First, class Second>
auto rusty_iterators::iterator::ZipLongest<T, R, First, Second>::sizeHint() const
-> std::optional<size_t>
{
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());
}
81 changes: 81 additions & 0 deletions tests/zip_longest.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <rusty_iterators/iterator.hpp>

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<std::string>{"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));
}