-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
compact: Introduce Range intersection and merge methods #13
base: main
Are you sure you want to change the base?
Changes from all commits
a450657
34e848d
68f0a98
5232032
b368c6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,7 +96,8 @@ func (r *Range) Append(hash []byte, visitor VisitFn) error { | |
|
||
// AppendRange extends the compact range by merging in the other compact range | ||
// from the right. It uses the tree hasher to calculate hashes of newly created | ||
// nodes, and reports them through the visitor function (if non-nil). | ||
// nodes, and reports them through the visitor function (if non-nil). The other | ||
// range must begin where the current range ends. | ||
func (r *Range) AppendRange(other *Range, visitor VisitFn) error { | ||
if other.f != r.f { | ||
return errors.New("incompatible ranges") | ||
|
@@ -110,6 +111,29 @@ func (r *Range) AppendRange(other *Range, visitor VisitFn) error { | |
return r.appendImpl(other.end, other.hashes[0], other.hashes[1:], visitor) | ||
} | ||
|
||
// Merge extends the compact range by merging in the other compact range from | ||
// the right. It uses the tree hasher to calculate hashes of newly created | ||
// nodes, and reports them through the visitor function (if non-nil). The other | ||
// range must begin between the current range's begin and end. | ||
// | ||
// Warning: This method modifies both this and the other Range. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems unnecessary to modify the other range, no? Or at least this feels like it could easily have some awkward and unintended side effects. Why not implement it in a more "pure" way? |
||
// Warning: This method is experimental. | ||
func (r *Range) Merge(other *Range, visitor VisitFn) error { | ||
if _, err := r.intersectImpl(other); err != nil { | ||
return err | ||
} | ||
return r.AppendRange(other, visitor) | ||
} | ||
|
||
// Intersect returns the intersection of two compact ranges. The other range | ||
// must begin between the current range's begin and end. | ||
// | ||
// Warning: This method modifies both this and the other Range. | ||
// Warning: This method is experimental. | ||
func (r *Range) Intersect(other *Range) (*Range, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The modification of both ranges feels especially confusing here. Really after you call this function you have three compact ranges:
|
||
return r.intersectImpl(other) | ||
} | ||
|
||
// GetRootHash returns the root hash of the Merkle tree represented by this | ||
// compact range. Requires the range to start at index 0. If the range is | ||
// empty, returns nil. | ||
|
@@ -208,6 +232,48 @@ func (r *Range) appendImpl(end uint64, seed []byte, hashes [][]byte, visitor Vis | |
return nil | ||
} | ||
|
||
// intersectImpl returns the intersection of two compact ranges. It also | ||
// modifies the `r` and `other` compact ranges in such a way that they become | ||
// adjacent, and a subsequent AppendRange operation between them will result in | ||
// a compact range that represents the union of the two original ranges. | ||
func (r *Range) intersectImpl(other *Range) (*Range, error) { | ||
if other.f != r.f { | ||
return nil, errors.New("incompatible ranges") | ||
} else if other.begin < r.begin { | ||
return nil, errors.New("ranges unordered") | ||
} | ||
|
||
if other.end <= r.end { // The other range is nested. | ||
intersection := *other // Note: Force the clone. | ||
*other = *r.f.NewEmptyRange(r.end) // Note: Force the rewrite. | ||
return &intersection, nil | ||
} | ||
|
||
begin, end := other.begin, r.end | ||
if begin > end { // The other range is disjoint. | ||
return nil, fmt.Errorf("ranges are disjoint: other.begin=%d, want <= %d", begin, end) | ||
} else if begin == end { // The ranges touch ends. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see the distinction here, since the intersection of two disjoint ranges is the empty set regardless of whether or not they "almost" intersect. |
||
return r.f.NewEmptyRange(begin), nil // The intersection is empty. | ||
} | ||
|
||
// Decompose the intersection range, allocate the resulting slice of hashes. | ||
left, right := Decompose(begin, end) | ||
leftBits, rightBits := bits.OnesCount64(left), bits.OnesCount64(right) | ||
hashes := make([][]byte, 0, leftBits+rightBits) | ||
|
||
// Cut off the intersection hashes from the `other` range. | ||
hashes = append(hashes, other.hashes[:leftBits]...) | ||
other.begin += left | ||
other.hashes = other.hashes[leftBits:] | ||
|
||
// Cut off the intersection hashes from the `r` range. | ||
hashes = append(hashes, r.hashes[len(r.hashes)-rightBits:]...) | ||
r.end -= right | ||
r.hashes = r.hashes[:len(r.hashes)-rightBits] | ||
|
||
return &Range{f: r.f, begin: begin, end: end, hashes: hashes}, nil | ||
} | ||
|
||
// getMergePath returns the merging path between the compact range [begin, mid) | ||
// and [mid, end). The path is represented as a range of bits within mid, with | ||
// bit indices [low, high). A bit value of 1 on level i of mid means that the | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: there is no reason for "range" to be capitalized here and throughout.