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