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