gitlab.com/yannislg/go-pulse@v0.0.0-20210722055913-a3e24e95638d/les/utils/weighted_select.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package utils 18 19 import "math/rand" 20 21 // wrsItem interface should be implemented by any entries that are to be selected from 22 // a WeightedRandomSelect set. Note that recalculating monotonously decreasing item 23 // weights on-demand (without constantly calling Update) is allowed 24 type wrsItem interface { 25 Weight() int64 26 } 27 28 // WeightedRandomSelect is capable of weighted random selection from a set of items 29 type WeightedRandomSelect struct { 30 root *wrsNode 31 idx map[wrsItem]int 32 } 33 34 // NewWeightedRandomSelect returns a new WeightedRandomSelect structure 35 func NewWeightedRandomSelect() *WeightedRandomSelect { 36 return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)} 37 } 38 39 // setWeight sets an item's weight to a specific value (removes it if zero) 40 func (w *WeightedRandomSelect) setWeight(item wrsItem, weight int64) { 41 idx, ok := w.idx[item] 42 if ok { 43 w.root.setWeight(idx, weight) 44 if weight == 0 { 45 delete(w.idx, item) 46 } 47 } else { 48 if weight != 0 { 49 if w.root.itemCnt == w.root.maxItems { 50 // add a new level 51 newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} 52 newRoot.items[0] = w.root 53 newRoot.weights[0] = w.root.sumWeight 54 w.root = newRoot 55 } 56 w.idx[item] = w.root.insert(item, weight) 57 } 58 } 59 } 60 61 // Update updates an item's weight, adds it if it was non-existent or removes it if 62 // the new weight is zero. Note that explicitly updating decreasing weights is not necessary. 63 func (w *WeightedRandomSelect) Update(item wrsItem) { 64 w.setWeight(item, item.Weight()) 65 } 66 67 // Remove removes an item from the set 68 func (w *WeightedRandomSelect) Remove(item wrsItem) { 69 w.setWeight(item, 0) 70 } 71 72 // Choose randomly selects an item from the set, with a chance proportional to its 73 // current weight. If the weight of the chosen element has been decreased since the 74 // last stored value, returns it with a newWeight/oldWeight chance, otherwise just 75 // updates its weight and selects another one 76 func (w *WeightedRandomSelect) Choose() wrsItem { 77 for { 78 if w.root.sumWeight == 0 { 79 return nil 80 } 81 val := rand.Int63n(w.root.sumWeight) 82 choice, lastWeight := w.root.choose(val) 83 weight := choice.Weight() 84 if weight != lastWeight { 85 w.setWeight(choice, weight) 86 } 87 if weight >= lastWeight || rand.Int63n(lastWeight) < weight { 88 return choice 89 } 90 } 91 } 92 93 const wrsBranches = 8 // max number of branches in the wrsNode tree 94 95 // wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes. 96 type wrsNode struct { 97 items [wrsBranches]interface{} 98 weights [wrsBranches]int64 99 sumWeight int64 100 level, itemCnt, maxItems int 101 } 102 103 // insert recursively inserts a new item to the tree and returns the item index 104 func (n *wrsNode) insert(item wrsItem, weight int64) int { 105 branch := 0 106 for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) { 107 branch++ 108 if branch == wrsBranches { 109 panic(nil) 110 } 111 } 112 n.itemCnt++ 113 n.sumWeight += weight 114 n.weights[branch] += weight 115 if n.level == 0 { 116 n.items[branch] = item 117 return branch 118 } 119 var subNode *wrsNode 120 if n.items[branch] == nil { 121 subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} 122 n.items[branch] = subNode 123 } else { 124 subNode = n.items[branch].(*wrsNode) 125 } 126 subIdx := subNode.insert(item, weight) 127 return subNode.maxItems*branch + subIdx 128 } 129 130 // setWeight updates the weight of a certain item (which should exist) and returns 131 // the change of the last weight value stored in the tree 132 func (n *wrsNode) setWeight(idx int, weight int64) int64 { 133 if n.level == 0 { 134 oldWeight := n.weights[idx] 135 n.weights[idx] = weight 136 diff := weight - oldWeight 137 n.sumWeight += diff 138 if weight == 0 { 139 n.items[idx] = nil 140 n.itemCnt-- 141 } 142 return diff 143 } 144 branchItems := n.maxItems / wrsBranches 145 branch := idx / branchItems 146 diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) 147 n.weights[branch] += diff 148 n.sumWeight += diff 149 if weight == 0 { 150 n.itemCnt-- 151 } 152 return diff 153 } 154 155 // Choose recursively selects an item from the tree and returns it along with its weight 156 func (n *wrsNode) choose(val int64) (wrsItem, int64) { 157 for i, w := range n.weights { 158 if val < w { 159 if n.level == 0 { 160 return n.items[i].(wrsItem), n.weights[i] 161 } 162 return n.items[i].(*wrsNode).choose(val) 163 } 164 val -= w 165 } 166 panic(nil) 167 }