Skip to content

Commit

Permalink
Add index based iterator to QList interface (#4083)
Browse files Browse the repository at this point in the history
chore: add index based iterator to the interface

Signed-off-by: Roman Gershman <[email protected]>
  • Loading branch information
romange authored Nov 7, 2024
1 parent 2794239 commit 41d8df6
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 30 deletions.
71 changes: 59 additions & 12 deletions src/core/qlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,6 @@ size_t QList::MallocUsed() const {
return res + count_ * 16; // we account for each member 16 bytes.
}

string QList::Peek(Where where) const {
return {};
}

optional<string> QList::Get(long index) const {
return nullopt;
}

void QList::Iterate(IterateFunc cb, long start, long end) const {
}

Expand Down Expand Up @@ -433,11 +425,61 @@ auto QList::GetIterator(Where where) -> Iterator {
return it;
}

bool QList::Iterator::Next() {
DCHECK(current_);
auto QList::GetIterator(long idx) -> Iterator {
quicklistNode* n;
unsigned long long accum = 0;
unsigned long long index;
int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */

index = forward ? idx : (-idx) - 1;
if (index >= count_)
return {};

/* Seek in the other direction if that way is shorter. */
int seek_forward = forward;
unsigned long long seek_index = index;
if (index > (count_ - 1) / 2) {
seek_forward = !forward;
seek_index = count_ - 1 - index;
}

n = seek_forward ? head_ : tail_;
while (ABSL_PREDICT_TRUE(n)) {
if ((accum + n->count) > seek_index) {
break;
} else {
accum += n->count;
n = seek_forward ? n->next : n->prev;
}
}

if (!n)
return {};

unsigned char* (*nextFn)(unsigned char*, unsigned char*) = NULL;
int offset_update = 0;
/* Fix accum so it looks like we seeked in the other direction. */
if (seek_forward != forward)
accum = count_ - n->count - accum;

Iterator iter;
iter.owner_ = this;
iter.direction_ = forward ? FWD : REV;
iter.current_ = n;

if (forward) {
/* forward = normal head-to-tail offset. */
iter.offset_ = index - accum;
} else {
/* reverse = need negative offset for tail-to-head, so undo
* the result of the original index = (-idx) - 1 above. */
iter.offset_ = (-index) - 1 + accum;
}

return iter;
}

bool QList::Iterator::Next() {
if (!current_)
return false;

int plain = QL_NODE_IS_PLAIN(current_);
if (!zi_) {
Expand All @@ -450,6 +492,9 @@ bool QList::Iterator::Next() {
} else if (ABSL_PREDICT_FALSE(plain)) {
zi_ = NULL;
} else {
unsigned char* (*nextFn)(unsigned char*, unsigned char*) = NULL;
int offset_update = 0;

/* else, use existing iterator offset and get prev/next as necessary. */
if (direction_ == FWD) {
nextFn = lpNext;
Expand Down Expand Up @@ -491,6 +536,8 @@ auto QList::Iterator::Get() const -> Entry {
return Entry(str, current_->sz);
}

DCHECK(zi_);

/* Populate value from existing listpack position */
unsigned int sz = 0;
long long val;
Expand Down
24 changes: 13 additions & 11 deletions src/core/qlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ class QList {

size_t MallocUsed() const;

// Peeks at the head or tail of the list. Precondition: list is not empty.
std::string Peek(Where where) const;

std::optional<std::string> Get(long index) const;

void Iterate(IterateFunc cb, long start, long end) const;

class Iterator {
Expand All @@ -70,19 +65,26 @@ class QList {
bool Next();

private:
QList* owner_;
quicklistNode* current_;
unsigned char* zi_; /* points to the current element */
long offset_; /* offset in current listpack */
uint8_t direction_;
QList* owner_ = nullptr;
quicklistNode* current_ = nullptr;
unsigned char* zi_ = nullptr; /* points to the current element */
long offset_ = 0; /* offset in current listpack */
uint8_t direction_ = 1;

friend class QList;
};

// Returns an iterator to tail or the head of the list.
// To follow the quicklist interface, the iterator is not valid until Next() is called.
// To mirror the quicklist interface, the iterator is not valid until Next() is called.
// TODO: to fix this.
Iterator GetIterator(Where where);

// Returns an iterator at a specific index 'idx',
// or Invalid iterator if index is out of range.
// negative index - means counting from the tail.
// Requires calling subsequent Next() to initialize the iterator.
Iterator GetIterator(long idx);

private:
bool AllowCompression() const {
return compress_ != 0;
Expand Down
18 changes: 11 additions & 7 deletions src/core/qlist_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ TEST_F(QListTest, Basic) {
auto it = ql_.GetIterator(QList::HEAD);
ASSERT_TRUE(it.Next()); // Needed to initialize the iterator.

QList::Entry entry = it.Get();
EXPECT_EQ("abc", entry.view());
EXPECT_EQ("abc", it.Get().view());

ASSERT_FALSE(it.Next());

Expand All @@ -47,13 +46,18 @@ TEST_F(QListTest, Basic) {

it = ql_.GetIterator(QList::TAIL);
ASSERT_TRUE(it.Next());
entry = it.Get();
EXPECT_EQ("def", entry.view());
ASSERT_TRUE(it.Next());
EXPECT_EQ("def", it.Get().view());

entry = it.Get();
EXPECT_EQ("abc", entry.view());
ASSERT_TRUE(it.Next());
EXPECT_EQ("abc", it.Get().view());
ASSERT_FALSE(it.Next());

it = ql_.GetIterator(0);
ASSERT_TRUE(it.Next());
EXPECT_EQ("abc", it.Get().view());
it = ql_.GetIterator(-1);
ASSERT_TRUE(it.Next());
EXPECT_EQ("def", it.Get().view());
}

}; // namespace dfly

0 comments on commit 41d8df6

Please sign in to comment.