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