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
35 changes: 21 additions & 14 deletions include/rusty_iterators/interperse.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#pragma once

#include "interface.fwd.hpp"
#include "peekable.hpp"

#include <algorithm>
#include <optional>

namespace rusty_iterators::iterator
Expand All @@ -13,35 +15,38 @@ class Interperse : public IterInterface<T, Interperse<T, Other>>
{
public:
explicit Interperse(Other&& it, T&& item)
: it(std::forward<Other>(it)), item(std::forward<T>(item))
: it(std::forward<Peekable<T, Other>>(it.peekable())), item(std::forward<T>(item))
{}

auto next() -> std::optional<T>;
[[nodiscard]] auto sizeHint() const -> std::optional<size_t>;

private:
Other it;
Peekable<T, Other> it;
T item;
bool interperse = false;
bool returnInterperseValue = false;
};
} // namespace rusty_iterators::iterator

template <class T, class Other>
auto rusty_iterators::iterator::Interperse<T, Other>::next() -> std::optional<T>
{
if (interperse)
/// NOTE: 18.01.2025 <@uncommon-nickname>
/// We need to check if iterator is finished. By using a peekable
/// iterator, we can do that without any significant cost, because
/// we will advance the iterator anyway later.
auto peeked = it.peek();

[[unlikely]] if (!peeked.has_value())
return std::nullopt;

if (returnInterperseValue)
{
interperse = false;
returnInterperseValue = false;
return item;
}

auto nextItem = it.next();

if (!nextItem.has_value())
return std::move(nextItem);

interperse = true;
return std::move(nextItem);
returnInterperseValue = true;
return std::move(it.next());
}

template <class T, class Other>
Expand All @@ -52,5 +57,7 @@ auto rusty_iterators::iterator::Interperse<T, Other>::sizeHint() const -> std::o
if (!underlyingSize.has_value())
return std::nullopt;

return underlyingSize.value() * 2;
int32_t potentialSize = (underlyingSize.value() * 2) - 1;

return std::max(0, potentialSize);
}
15 changes: 12 additions & 3 deletions tests/interperse.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ TEST(TestInterperseIterator, TestNextReturnsInterpersed)
ASSERT_EQ(it.next(), 1);
ASSERT_EQ(it.next(), 5);
ASSERT_EQ(it.next(), 2);
ASSERT_EQ(it.next(), 5);
ASSERT_EQ(it.next(), std::nullopt);
}

Expand All @@ -27,7 +26,7 @@ TEST(TestInterperseIterator, TestCollectReturnsAllValues)
auto item = 5;
auto it = LazyIterator{vec}.interperse(std::cref(item));

EXPECT_THAT(it.collect(), ElementsAreArray({1, 5, 2, 5, 3, 5}));
EXPECT_THAT(it.collect(), ElementsAreArray({1, 5, 2, 5, 3}));
}

TEST(TestInterperseIterator, TestSizeHintIsDoubled)
Expand All @@ -37,5 +36,15 @@ TEST(TestInterperseIterator, TestSizeHintIsDoubled)
auto item = 5;
auto it = LazyIterator{vec}.interperse(std::cref(item));

ASSERT_EQ(it.sizeHint(), 6);
ASSERT_EQ(it.sizeHint(), 5);
}

TEST(TestInterperseIterator, TestEmptyIteratorSizeIsZero)
{
auto vec = std::vector<int>{};

auto item = 5;
auto it = LazyIterator{vec}.interperse(std::cref(item));

ASSERT_EQ(it.sizeHint(), 0);
}