Skip to content

Commit 1a07aa1

Browse files
fix: use peekable iterator to force interperse to work properly (#110)
1 parent 168a4f7 commit 1a07aa1

File tree

2 files changed

+33
-17
lines changed

2 files changed

+33
-17
lines changed
Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#pragma once
22

33
#include "interface.fwd.hpp"
4+
#include "peekable.hpp"
45

6+
#include <algorithm>
57
#include <optional>
68

79
namespace rusty_iterators::iterator
@@ -13,35 +15,38 @@ class Interperse : public IterInterface<T, Interperse<T, Other>>
1315
{
1416
public:
1517
explicit Interperse(Other&& it, T&& item)
16-
: it(std::forward<Other>(it)), item(std::forward<T>(item))
18+
: it(std::forward<Peekable<T, Other>>(it.peekable())), item(std::forward<T>(item))
1719
{}
1820

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

2224
private:
23-
Other it;
25+
Peekable<T, Other> it;
2426
T item;
25-
bool interperse = false;
27+
bool returnInterperseValue = false;
2628
};
2729
} // namespace rusty_iterators::iterator
2830

2931
template <class T, class Other>
3032
auto rusty_iterators::iterator::Interperse<T, Other>::next() -> std::optional<T>
3133
{
32-
if (interperse)
34+
/// NOTE: 18.01.2025 <@uncommon-nickname>
35+
/// We need to check if iterator is finished. By using a peekable
36+
/// iterator, we can do that without any significant cost, because
37+
/// we will advance the iterator anyway later.
38+
auto peeked = it.peek();
39+
40+
[[unlikely]] if (!peeked.has_value())
41+
return std::nullopt;
42+
43+
if (returnInterperseValue)
3344
{
34-
interperse = false;
45+
returnInterperseValue = false;
3546
return item;
3647
}
37-
38-
auto nextItem = it.next();
39-
40-
if (!nextItem.has_value())
41-
return std::move(nextItem);
42-
43-
interperse = true;
44-
return std::move(nextItem);
48+
returnInterperseValue = true;
49+
return std::move(it.next());
4550
}
4651

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

55-
return underlyingSize.value() * 2;
60+
int32_t potentialSize = (underlyingSize.value() * 2) - 1;
61+
62+
return std::max(0, potentialSize);
5663
}

tests/interperse.test.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ TEST(TestInterperseIterator, TestNextReturnsInterpersed)
1616
ASSERT_EQ(it.next(), 1);
1717
ASSERT_EQ(it.next(), 5);
1818
ASSERT_EQ(it.next(), 2);
19-
ASSERT_EQ(it.next(), 5);
2019
ASSERT_EQ(it.next(), std::nullopt);
2120
}
2221

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

30-
EXPECT_THAT(it.collect(), ElementsAreArray({1, 5, 2, 5, 3, 5}));
29+
EXPECT_THAT(it.collect(), ElementsAreArray({1, 5, 2, 5, 3}));
3130
}
3231

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

40-
ASSERT_EQ(it.sizeHint(), 6);
39+
ASSERT_EQ(it.sizeHint(), 5);
40+
}
41+
42+
TEST(TestInterperseIterator, TestEmptyIteratorSizeIsZero)
43+
{
44+
auto vec = std::vector<int>{};
45+
46+
auto item = 5;
47+
auto it = LazyIterator{vec}.interperse(std::cref(item));
48+
49+
ASSERT_EQ(it.sizeHint(), 0);
4150
}

0 commit comments

Comments
 (0)