github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/randselect.go (about) 1 package gossip 2 3 import ( 4 "math/rand" 5 ) 6 7 // wrsItem interface should be implemented by any entries that are to be selected from 8 // a weightedRandomSelect set. Note that recalculating monotonously decreasing item 9 // weights on-demand (without constantly calling update) is allowed 10 type wrsItem interface { 11 Weight() int64 12 } 13 14 // weightedRandomSelect is capable of weighted random selection from a set of items 15 type weightedRandomSelect struct { 16 root *wrsNode 17 idx map[wrsItem]int 18 } 19 20 // newWeightedRandomSelect returns a new weightedRandomSelect structure 21 func newWeightedRandomSelect() *weightedRandomSelect { 22 return &weightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)} 23 } 24 25 // update updates an item's weight, adds it if it was non-existent or removes it if 26 // the new weight is zero. Note that explicitly updating decreasing weights is not necessary. 27 func (w *weightedRandomSelect) update(item wrsItem) { 28 w.setWeight(item, item.Weight()) 29 } 30 31 // remove removes an item from the set 32 func (w *weightedRandomSelect) remove(item wrsItem) { 33 w.setWeight(item, 0) 34 } 35 36 // setWeight sets an item's weight to a specific value (removes it if zero) 37 func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) { 38 idx, ok := w.idx[item] 39 if ok { 40 w.root.setWeight(idx, weight) 41 if weight == 0 { 42 delete(w.idx, item) 43 } 44 } else { 45 if weight != 0 { 46 if w.root.itemCnt == w.root.maxItems { 47 // add a new level 48 newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} 49 newRoot.items[0] = w.root 50 newRoot.weights[0] = w.root.sumWeight 51 w.root = newRoot 52 } 53 w.idx[item] = w.root.insert(item, weight) 54 } 55 } 56 } 57 58 // choose randomly selects an item from the set, with a chance proportional to its 59 // current weight. If the weight of the chosen element has been decreased since the 60 // last stored value, returns it with a newWeight/oldWeight chance, otherwise just 61 // updates its weight and selects another one 62 func (w *weightedRandomSelect) choose() wrsItem { 63 for { 64 if w.root.sumWeight == 0 { 65 return nil 66 } 67 val := rand.Int63n(w.root.sumWeight) 68 choice, lastWeight := w.root.choose(val) 69 weight := choice.Weight() 70 if weight != lastWeight { 71 w.setWeight(choice, weight) 72 } 73 if weight >= lastWeight || rand.Int63n(lastWeight) < weight { 74 return choice 75 } 76 } 77 } 78 79 const wrsBranches = 8 // max number of branches in the wrsNode tree 80 81 // wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes. 82 type wrsNode struct { 83 items [wrsBranches]interface{} 84 weights [wrsBranches]int64 85 sumWeight int64 86 level, itemCnt, maxItems int 87 } 88 89 // insert recursively inserts a new item to the tree and returns the item index 90 func (n *wrsNode) insert(item wrsItem, weight int64) int { 91 branch := 0 92 for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) { 93 branch++ 94 if branch == wrsBranches { 95 panic(nil) 96 } 97 } 98 n.itemCnt++ 99 n.sumWeight += weight 100 n.weights[branch] += weight 101 if n.level == 0 { 102 n.items[branch] = item 103 return branch 104 } 105 var subNode *wrsNode 106 if n.items[branch] == nil { 107 subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} 108 n.items[branch] = subNode 109 } else { 110 subNode = n.items[branch].(*wrsNode) 111 } 112 subIdx := subNode.insert(item, weight) 113 return subNode.maxItems*branch + subIdx 114 } 115 116 // setWeight updates the weight of a certain item (which should exist) and returns 117 // the change of the last weight value stored in the tree 118 func (n *wrsNode) setWeight(idx int, weight int64) int64 { 119 if n.level == 0 { 120 oldWeight := n.weights[idx] 121 n.weights[idx] = weight 122 diff := weight - oldWeight 123 n.sumWeight += diff 124 if weight == 0 { 125 n.items[idx] = nil 126 n.itemCnt-- 127 } 128 return diff 129 } 130 branchItems := n.maxItems / wrsBranches 131 branch := idx / branchItems 132 diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) 133 n.weights[branch] += diff 134 n.sumWeight += diff 135 if weight == 0 { 136 n.itemCnt-- 137 } 138 return diff 139 } 140 141 // choose recursively selects an item from the tree and returns it along with its weight 142 func (n *wrsNode) choose(val int64) (wrsItem, int64) { 143 for i, w := range n.weights { 144 if val < w { 145 if n.level == 0 { 146 return n.items[i].(wrsItem), n.weights[i] 147 } 148 return n.items[i].(*wrsNode).choose(val) 149 } 150 val -= w 151 } 152 panic(nil) 153 }