// Copyright 2025 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package token// tree is a self-balancing AVL tree; see// Lewis & Denenberg, Data Structures and Their Algorithms.//// An AVL tree is a binary tree in which the difference between the// heights of a node's two subtrees--the node's "balance factor"--is// at most one. It is more strictly balanced than a red/black tree,// and thus favors lookups at the expense of updates, which is the// appropriate trade-off for FileSet.//// Insertion at a node may cause its ancestors' balance factors to// temporarily reach ±2, requiring rebalancing of each such ancestor// by a rotation.//// Each key is the pos-end range of a single File.// All Files in the tree must have disjoint ranges.//// The implementation is simplified from Russ Cox's github.com/rsc/omap.import ()// A tree is a tree-based ordered map:// each value is a *File, keyed by its Pos range.// All map entries cover disjoint ranges.//// The zero value of tree is an empty map ready to use.type tree struct { root *node}type node struct {// We use the notation (parent left right) in many comments. parent *node left *node right *node file *File key key// = file.key(), but improves locality (25% faster) balance int32// at most ±2 height int32}// A key represents the Pos range of a File.type key struct{ start, end int }func ( *File) () key {returnkey{.base, .base + .size}}// compareKey reports whether x is before y (-1),// after y (+1), or overlapping y (0).// This is a total order so long as all// files in the tree have disjoint ranges.//// All files are separated by at least one unit.// This allows us to use strict < comparisons.// Use key{p, p} to search for a zero-width position// even at the start or end of a file.func compareKey(, key) int {switch {case .end < .start:return -1case .end < .start:return +1 }return0}// check asserts that each node's height, subtree, and parent link is// correct.func ( *node) ( *node) {const = falseif {if == nil {return }if .parent != {panic("bad parent") } .left.() .right.() .checkBalance() }}func ( *node) () { , := .left.safeHeight(), .right.safeHeight() := - if != .balance {panic("bad node.balance") }if !(-2 <= && <= +2) {panic(fmt.Sprintf("node.balance out of range: %d", )) } := 1 + max(, )if != .height {panic("bad node.height") }}// locate returns a pointer to the variable that holds the node// identified by k, along with its parent, if any. If the key is not// present, it returns a pointer to the node where the key should be// inserted by a subsequent call to [tree.set].func ( *tree) ( key) ( **node, *node) { , := &.root, .rootfor != nil { := compareKey(, .key)if < 0 { , , = &.left, .left, } elseif > 0 { , , = &.right, .right, } else {break } }return , }// all returns an iterator over the tree t.// If t is modified during the iteration,// some files may not be visited.// No file will be visited multiple times.func ( *tree) () iter.Seq[*File] {returnfunc( func(*File) bool) {if == nil {return } := .rootif != nil {for .left != nil { = .left } }for != nil && (.file) {if .height >= 0 {// still in tree = .next() } else {// deleted = .nextAfter(.locate(.key)) } } }}// nextAfter returns the node in the key sequence following// (pos, parent), a result pair from [tree.locate].func ( *tree) ( **node, *node) *node {switch {case * != nil:return (*).next()case == nil:returnnilcase == &.left:returndefault:return .next() }}func ( *node) () *node {if .right == nil {for .parent != nil && .parent.right == { = .parent }return .parent } = .rightfor .left != nil { = .left }return}func ( *tree) ( *node) { .root = if != nil { .parent = nil }}func ( *node) ( *node) { .left = if != nil { .parent = }}func ( *node) ( *node) { .right = if != nil { .parent = }}func ( *node) () int32 {if == nil {return -1 }return .height}func ( *node) () { , := .left.safeHeight(), .right.safeHeight() .height = max(, ) + 1 .balance = - }func ( *tree) (, , *node) {switch {case == nil:if .root != {panic("corrupt tree") } .setRoot()case .left == : .setLeft()case .right == : .setRight()default:panic("corrupt tree") }}// rebalanceUp visits each excessively unbalanced ancestor// of x, restoring balance by rotating it.//// x is a node that has just been mutated, and so the height and// balance of x and its ancestors may be stale, but the children of x// must be in a valid state.func ( *tree) ( *node) {for != nil { := .height .update()switch .balance {case -2:if .left.balance == 1 { .rotateLeft(.left) } = .rotateRight()case +2:if .right.balance == -1 { .rotateRight(.right) } = .rotateLeft() }if .height == {// x's height has not changed, so the height // and balance of its ancestors have not changed; // no further rebalancing is required.return } = .parent }}// rotateRight rotates the subtree rooted at node y.// turning (y (x a b) c) into (x a (y b c)).func ( *tree) ( *node) *node {// p -> (y (x a b) c) := .parent := .left := .right .checkBalance() .checkBalance() .setRight() .setLeft() .replaceChild(, , ) .update() .update()return}// rotateLeft rotates the subtree rooted at node x.// turning (x a (y b c)) into (y (x a b) c).func ( *tree) ( *node) *node {// p -> (x a (y b c)) := .parent := .right := .left .checkBalance() .checkBalance() .setLeft() .setRight() .replaceChild(, , ) .update() .update()return}// add inserts file into the tree, if not present.// It panics if file overlaps with another.func ( *tree) ( *File) { , := .locate(.key())if * == nil { .set(, , ) // missing; insertreturn }if := (*).file; != {panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)", .Name(), .Base(), .Base()+.Size(), .Name(), .Base(), .Base()+.Size())) }}// set updates the existing node at (pos, parent) if present, or// inserts a new node if not, so that it refers to file.func ( *tree) ( *File, **node, *node) {if := *; != nil {// This code path isn't currently needed // because FileSet never updates an existing entry. // Remove this assertion if things change.iftrue {panic("unreachable according to current FileSet requirements") } .file = return } := &node{file: , key: .key(), parent: , height: -1} * = .rebalanceUp()}// delete deletes the node at pos.func ( *tree) ( **node) { .root.check(nil) := *switch {case == nil:// This code path isn't currently needed because FileSet // only calls delete after a positive locate. // Remove this assertion if things change.iftrue {panic("unreachable according to current FileSet requirements") }returncase .left == nil:if * = .right; * != nil { (*).parent = .parent } .rebalanceUp(.parent)case .right == nil: * = .left .left.parent = .parent .rebalanceUp(.parent)default: .deleteSwap() } .balance = -100 .parent = nil .left = nil .right = nil .height = -1 .root.check(nil)}// deleteSwap deletes a node that has two children by replacing// it by its in-order successor, then triggers a rebalance.func ( *tree) ( **node) { := * := .deleteMin(&.right) * = := .parent// lowest potentially unbalanced nodeif == { = // (x a (z nil b)) -> (z a b) } .parent = .parent .height = .height .balance = .balance .setLeft(.left) .setRight(.right) .rebalanceUp()}// deleteMin updates the subtree rooted at *zpos to delete its minimum// (leftmost) element, which may be *zpos itself. It returns the// deleted node.func ( *tree) ( **node) ( *node) {for (*).left != nil { = &(*).left } = * * = .rightif * != nil { (*).parent = .parent }return}
The pages are generated with Goldsv0.7.7-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.