@@ -157,13 +157,13 @@ class RunEndEncodedArraySpan {
157157 // / The values array can be addressed with this index to get the value
158158 // / that makes up the run.
159159 // /
160- // / NOTE: if this Iterator was produced by RunEndEncodedArraySpan::end(),
160+ // / NOTE: if this Iterator is equal to RunEndEncodedArraySpan::end(),
161161 // / the value returned is undefined.
162162 int64_t index_into_array () const { return physical_pos_; }
163163
164164 // / \brief Return the initial logical position of the run
165165 // /
166- // / If this Iterator was produced by RunEndEncodedArraySpan::end(), this is
166+ // / If this Iterator is equal to RunEndEncodedArraySpan::end(), this is
167167 // / the same as RunEndEncodedArraySpan::length().
168168 int64_t logical_position () const { return logical_pos_; }
169169
@@ -177,6 +177,16 @@ class RunEndEncodedArraySpan {
177177 // / Pre-condition: *this != RunEndEncodedArraySpan::end()
178178 int64_t run_length () const { return run_end () - logical_pos_; }
179179
180+ // / \brief Check if the iterator is at the end of the array.
181+ // /
182+ // / This can be used to avoid paying the cost of a call to
183+ // / RunEndEncodedArraySpan::end().
184+ // /
185+ // / \return true if the iterator is at the end of the array
186+ bool is_end (const RunEndEncodedArraySpan& span) const {
187+ return logical_pos_ >= span.length ();
188+ }
189+
180190 Iterator& operator ++() {
181191 logical_pos_ = span.run_end (physical_pos_);
182192 physical_pos_ += 1 ;
@@ -189,6 +199,18 @@ class RunEndEncodedArraySpan {
189199 return prev;
190200 }
191201
202+ Iterator& operator --() {
203+ physical_pos_ -= 1 ;
204+ logical_pos_ = (physical_pos_ > 0 ) ? span.run_end (physical_pos_ - 1 ) : 0 ;
205+ return *this ;
206+ }
207+
208+ Iterator operator --(int ) {
209+ const Iterator prev = *this ;
210+ --(*this );
211+ return prev;
212+ }
213+
192214 bool operator ==(const Iterator& other) const {
193215 return logical_pos_ == other.logical_pos_ ;
194216 }
@@ -234,31 +256,52 @@ class RunEndEncodedArraySpan {
234256 // /
235257 // / \param logical_pos is an index in the [0, length()] range
236258 Iterator iterator (int64_t logical_pos) const {
237- return iterator (logical_pos, logical_pos < length ()
238- ? PhysicalIndex (logical_pos)
239- : RunEndsArray (array_span).length );
259+ if (logical_pos < length ()) {
260+ return iterator (logical_pos, PhysicalIndex (logical_pos));
261+ }
262+ // If logical_pos is above the valid range, use length() as the logical
263+ // position and calculate the physical address right after the last valid
264+ // physical position. Which is the physical index of the last logical
265+ // position, plus 1.
266+ return (length () == 0 ) ? iterator (0 , PhysicalIndex (0 ))
267+ : iterator (length (), PhysicalIndex (length () - 1 ) + 1 );
240268 }
241269
242270 // / \brief Create an iterator representing the logical begin of the run-end
243271 // / encoded array
244- Iterator begin () const { return iterator (0 ); }
272+ Iterator begin () const { return iterator (0 , PhysicalIndex ( 0 ) ); }
245273
246274 // / \brief Create an iterator representing the first invalid logical position
247275 // / of the run-end encoded array
248276 // /
249- // / The Iterator returned by end() should not be
277+ // / \warning Avoid calling end() in a loop, as it will recompute the physical
278+ // / length of the array on each call (O(log N) cost per call).
279+ // /
280+ // / \par You can write your loops like this instead:
281+ // / \code
282+ // / for (auto it = array.begin(), end = array.end(); it != end; ++it) {
283+ // / // ...
284+ // / }
285+ // / \endcode
286+ // /
287+ // / \par Or this version that does not look like idiomatic C++, but removes
288+ // / the need for calling end() completely:
289+ // / \code
290+ // / for (auto it = array.begin(); !it.is_end(array); ++it) {
291+ // / // ...
292+ // / }
293+ // / \endcode
250294 Iterator end () const {
251- // NOTE: the run ends array length is not necessarily what
252- // PhysicalIndex(length()) would return but it is a cheap to obtain
253- // physical offset that is invalid.
254- return iterator (length (), RunEndsArray (array_span).length );
295+ return iterator (length (),
296+ (length () == 0 ) ? PhysicalIndex (0 ) : PhysicalIndex (length () - 1 ) + 1 );
255297 }
256298
257299 // Pre-condition: physical_pos < RunEndsArray(array_span).length);
258300 inline int64_t run_end (int64_t physical_pos) const {
259301 assert (physical_pos < RunEndsArray (array_span).length );
260- // Logical index of the end of the currently active run
261- const int64_t logical_run_end = run_ends_[physical_pos] - offset ();
302+ // Logical index of the end of the run at physical_pos with offset applied
303+ const int64_t logical_run_end =
304+ std::max<int64_t >(static_cast <int64_t >(run_ends_[physical_pos]) - offset (), 0 );
262305 // The current run may go further than the logical length, cap it
263306 return std::min (logical_run_end, length ());
264307 }
@@ -281,12 +324,37 @@ class MergedRunsIterator {
281324 using LeftIterator = typename Left::Iterator;
282325 using RightIterator = typename Right::Iterator;
283326
327+ MergedRunsIterator (LeftIterator left_it, RightIterator right_it,
328+ int64_t common_logical_length, int64_t common_logical_pos)
329+ : ree_iterators_{std::move (left_it), std::move (right_it)},
330+ logical_length_ (common_logical_length),
331+ logical_pos_ (common_logical_pos) {}
332+
284333 public:
334+ // / \brief Construct a MergedRunsIterator positioned at logical position 0.
335+ // /
336+ // / Pre-condition: left.length() == right.length()
285337 MergedRunsIterator (const Left& left, const Right& right)
286- : ree_iterators_{ left.begin (), right.begin ()}, logical_length_( left.length()) {
338+ : MergedRunsIterator( left.begin(), right.begin(), left.length(), 0 ) {
287339 assert (left.length () == right.length ());
288340 }
289341
342+ static Result<MergedRunsIterator> MakeBegin (const Left& left, const Right& right) {
343+ if (left.length () != right.length ()) {
344+ return Status::Invalid (
345+ " MergedRunsIterator expects RunEndEncodedArraySpans of the same length" );
346+ }
347+ return MergedRunsIterator (left, right);
348+ }
349+
350+ static Result<MergedRunsIterator> MakeEnd (const Left& left, const Right& right) {
351+ if (left.length () != right.length ()) {
352+ return Status::Invalid (
353+ " MergedRunsIterator expects RunEndEncodedArraySpans of the same length" );
354+ }
355+ return MergedRunsIterator (left.end (), right.end (), left.length (), left.length ());
356+ }
357+
290358 // / \brief Return the left RunEndEncodedArraySpan child
291359 const Left& left () const { return std::get<0 >(ree_iterators_).span ; }
292360
@@ -295,15 +363,18 @@ class MergedRunsIterator {
295363
296364 // / \brief Return the initial logical position of the run
297365 // /
298- // / If isEnd (), this is the same as length().
366+ // / If is_end (), this is the same as length().
299367 int64_t logical_position () const { return logical_pos_; }
300368
369+ // / \brief Whether the iterator is at logical position 0.
370+ bool is_begin () const { return logical_pos_ == 0 ; }
371+
301372 // / \brief Whether the iterator has reached the end of both arrays
302- bool isEnd () const { return logical_pos_ == logical_length_; }
373+ bool is_end () const { return logical_pos_ == logical_length_; }
303374
304375 // / \brief Return the logical position immediately after the run.
305376 // /
306- // / Pre-condition: !isEnd ()
377+ // / Pre-condition: !is_end ()
307378 int64_t run_end () const {
308379 const auto & left_it = std::get<0 >(ree_iterators_);
309380 const auto & right_it = std::get<1 >(ree_iterators_);
@@ -312,7 +383,7 @@ class MergedRunsIterator {
312383
313384 // / \brief returns the logical length of the current run
314385 // /
315- // / Pre-condition: !isEnd ()
386+ // / Pre-condition: !is_end ()
316387 int64_t run_length () const { return run_end () - logical_pos_; }
317388
318389 // / \brief Return a physical index into the values array of a given input,
@@ -352,6 +423,34 @@ class MergedRunsIterator {
352423 return prev;
353424 }
354425
426+ MergedRunsIterator& operator --() {
427+ auto & left_it = std::get<0 >(ree_iterators_);
428+ auto & right_it = std::get<1 >(ree_iterators_);
429+
430+ // The logical position of each iterator is the run_end() of the previous run.
431+ const int64_t left_logical_pos = left_it.logical_position ();
432+ const int64_t right_logical_pos = right_it.logical_position ();
433+
434+ if (left_logical_pos < right_logical_pos) {
435+ --right_it;
436+ logical_pos_ = std::max (left_logical_pos, right_it.logical_position ());
437+ } else if (left_logical_pos > right_logical_pos) {
438+ --left_it;
439+ logical_pos_ = std::max (left_it.logical_position (), right_logical_pos);
440+ } else {
441+ --left_it;
442+ --right_it;
443+ logical_pos_ = std::max (left_it.logical_position (), right_it.logical_position ());
444+ }
445+ return *this ;
446+ }
447+
448+ MergedRunsIterator operator --(int ) {
449+ MergedRunsIterator prev = *this ;
450+ --(*this );
451+ return prev;
452+ }
453+
355454 bool operator ==(const MergedRunsIterator& other) const {
356455 return logical_pos_ == other.logical_position ();
357456 }
@@ -361,7 +460,7 @@ class MergedRunsIterator {
361460 private:
362461 std::tuple<LeftIterator, RightIterator> ree_iterators_;
363462 const int64_t logical_length_;
364- int64_t logical_pos_ = 0 ;
463+ int64_t logical_pos_;
365464};
366465
367466} // namespace ree_util
0 commit comments