2016-11-17 17:54:24 +03:00
|
|
|
// Copyright 2016 The go-ethereum Authors
|
|
|
|
// This file is part of the go-ethereum library.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2020-03-31 18:17:24 +03:00
|
|
|
package utils
|
2016-11-17 17:54:24 +03:00
|
|
|
|
2020-03-31 18:17:24 +03:00
|
|
|
import "math/rand"
|
2016-11-17 17:54:24 +03:00
|
|
|
|
|
|
|
// wrsItem interface should be implemented by any entries that are to be selected from
|
2020-03-31 18:17:24 +03:00
|
|
|
// a WeightedRandomSelect set. Note that recalculating monotonously decreasing item
|
|
|
|
// weights on-demand (without constantly calling Update) is allowed
|
2016-11-17 17:54:24 +03:00
|
|
|
type wrsItem interface {
|
|
|
|
Weight() int64
|
|
|
|
}
|
|
|
|
|
2020-03-31 18:17:24 +03:00
|
|
|
// WeightedRandomSelect is capable of weighted random selection from a set of items
|
|
|
|
type WeightedRandomSelect struct {
|
2016-11-17 17:54:24 +03:00
|
|
|
root *wrsNode
|
|
|
|
idx map[wrsItem]int
|
|
|
|
}
|
|
|
|
|
2020-03-31 18:17:24 +03:00
|
|
|
// NewWeightedRandomSelect returns a new WeightedRandomSelect structure
|
|
|
|
func NewWeightedRandomSelect() *WeightedRandomSelect {
|
|
|
|
return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)}
|
2016-11-17 17:54:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// setWeight sets an item's weight to a specific value (removes it if zero)
|
2020-03-31 18:17:24 +03:00
|
|
|
func (w *WeightedRandomSelect) setWeight(item wrsItem, weight int64) {
|
2016-11-17 17:54:24 +03:00
|
|
|
idx, ok := w.idx[item]
|
|
|
|
if ok {
|
|
|
|
w.root.setWeight(idx, weight)
|
|
|
|
if weight == 0 {
|
|
|
|
delete(w.idx, item)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if weight != 0 {
|
|
|
|
if w.root.itemCnt == w.root.maxItems {
|
|
|
|
// add a new level
|
|
|
|
newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches}
|
|
|
|
newRoot.items[0] = w.root
|
|
|
|
newRoot.weights[0] = w.root.sumWeight
|
|
|
|
w.root = newRoot
|
|
|
|
}
|
|
|
|
w.idx[item] = w.root.insert(item, weight)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 18:17:24 +03:00
|
|
|
// Update updates an item's weight, adds it if it was non-existent or removes it if
|
|
|
|
// the new weight is zero. Note that explicitly updating decreasing weights is not necessary.
|
|
|
|
func (w *WeightedRandomSelect) Update(item wrsItem) {
|
|
|
|
w.setWeight(item, item.Weight())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove removes an item from the set
|
|
|
|
func (w *WeightedRandomSelect) Remove(item wrsItem) {
|
|
|
|
w.setWeight(item, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose randomly selects an item from the set, with a chance proportional to its
|
2016-11-17 17:54:24 +03:00
|
|
|
// current weight. If the weight of the chosen element has been decreased since the
|
|
|
|
// last stored value, returns it with a newWeight/oldWeight chance, otherwise just
|
|
|
|
// updates its weight and selects another one
|
2020-03-31 18:17:24 +03:00
|
|
|
func (w *WeightedRandomSelect) Choose() wrsItem {
|
2016-11-17 17:54:24 +03:00
|
|
|
for {
|
|
|
|
if w.root.sumWeight == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
val := rand.Int63n(w.root.sumWeight)
|
|
|
|
choice, lastWeight := w.root.choose(val)
|
|
|
|
weight := choice.Weight()
|
|
|
|
if weight != lastWeight {
|
|
|
|
w.setWeight(choice, weight)
|
|
|
|
}
|
|
|
|
if weight >= lastWeight || rand.Int63n(lastWeight) < weight {
|
|
|
|
return choice
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const wrsBranches = 8 // max number of branches in the wrsNode tree
|
|
|
|
|
|
|
|
// wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes.
|
|
|
|
type wrsNode struct {
|
|
|
|
items [wrsBranches]interface{}
|
|
|
|
weights [wrsBranches]int64
|
|
|
|
sumWeight int64
|
|
|
|
level, itemCnt, maxItems int
|
|
|
|
}
|
|
|
|
|
|
|
|
// insert recursively inserts a new item to the tree and returns the item index
|
|
|
|
func (n *wrsNode) insert(item wrsItem, weight int64) int {
|
|
|
|
branch := 0
|
|
|
|
for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) {
|
|
|
|
branch++
|
|
|
|
if branch == wrsBranches {
|
|
|
|
panic(nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
n.itemCnt++
|
|
|
|
n.sumWeight += weight
|
|
|
|
n.weights[branch] += weight
|
|
|
|
if n.level == 0 {
|
|
|
|
n.items[branch] = item
|
|
|
|
return branch
|
2018-05-03 11:35:06 +03:00
|
|
|
}
|
|
|
|
var subNode *wrsNode
|
|
|
|
if n.items[branch] == nil {
|
|
|
|
subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1}
|
|
|
|
n.items[branch] = subNode
|
2016-11-17 17:54:24 +03:00
|
|
|
} else {
|
2018-05-03 11:35:06 +03:00
|
|
|
subNode = n.items[branch].(*wrsNode)
|
2016-11-17 17:54:24 +03:00
|
|
|
}
|
2018-05-03 11:35:06 +03:00
|
|
|
subIdx := subNode.insert(item, weight)
|
|
|
|
return subNode.maxItems*branch + subIdx
|
2016-11-17 17:54:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// setWeight updates the weight of a certain item (which should exist) and returns
|
|
|
|
// the change of the last weight value stored in the tree
|
|
|
|
func (n *wrsNode) setWeight(idx int, weight int64) int64 {
|
|
|
|
if n.level == 0 {
|
|
|
|
oldWeight := n.weights[idx]
|
|
|
|
n.weights[idx] = weight
|
|
|
|
diff := weight - oldWeight
|
|
|
|
n.sumWeight += diff
|
|
|
|
if weight == 0 {
|
|
|
|
n.items[idx] = nil
|
|
|
|
n.itemCnt--
|
|
|
|
}
|
|
|
|
return diff
|
|
|
|
}
|
|
|
|
branchItems := n.maxItems / wrsBranches
|
|
|
|
branch := idx / branchItems
|
|
|
|
diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight)
|
|
|
|
n.weights[branch] += diff
|
|
|
|
n.sumWeight += diff
|
|
|
|
if weight == 0 {
|
|
|
|
n.itemCnt--
|
|
|
|
}
|
|
|
|
return diff
|
|
|
|
}
|
|
|
|
|
2020-03-31 18:17:24 +03:00
|
|
|
// Choose recursively selects an item from the tree and returns it along with its weight
|
2016-11-17 17:54:24 +03:00
|
|
|
func (n *wrsNode) choose(val int64) (wrsItem, int64) {
|
|
|
|
for i, w := range n.weights {
|
|
|
|
if val < w {
|
|
|
|
if n.level == 0 {
|
|
|
|
return n.items[i].(wrsItem), n.weights[i]
|
|
|
|
}
|
2018-05-03 11:35:06 +03:00
|
|
|
return n.items[i].(*wrsNode).choose(val)
|
2016-11-17 17:54:24 +03:00
|
|
|
}
|
2018-05-03 11:35:06 +03:00
|
|
|
val -= w
|
2016-11-17 17:54:24 +03:00
|
|
|
}
|
|
|
|
panic(nil)
|
|
|
|
}
|