123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- // 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/>.
- // Package les implements the Light Ethereum Subprotocol.
- package les
- import (
- "math/rand"
- )
- // wrsItem interface should be implemented by any entries that are to be selected from
- // a weightedRandomSelect set. Note that recalculating monotonously decreasing item
- // weights on-demand (without constantly calling update) is allowed
- type wrsItem interface {
- Weight() int64
- }
- // weightedRandomSelect is capable of weighted random selection from a set of items
- type weightedRandomSelect struct {
- root *wrsNode
- idx map[wrsItem]int
- }
- // newWeightedRandomSelect returns a new weightedRandomSelect structure
- func newWeightedRandomSelect() *weightedRandomSelect {
- return &weightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)}
- }
- // 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)
- }
- // setWeight sets an item's weight to a specific value (removes it if zero)
- func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) {
- 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)
- }
- }
- }
- // choose randomly selects an item from the set, with a chance proportional to its
- // 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
- func (w *weightedRandomSelect) choose() wrsItem {
- 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
- }
- var subNode *wrsNode
- if n.items[branch] == nil {
- subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1}
- n.items[branch] = subNode
- } else {
- subNode = n.items[branch].(*wrsNode)
- }
- subIdx := subNode.insert(item, weight)
- return subNode.maxItems*branch + subIdx
- }
- // 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
- }
- // choose recursively selects an item from the tree and returns it along with its weight
- 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]
- }
- return n.items[i].(*wrsNode).choose(val)
- }
- val -= w
- }
- panic(nil)
- }
|