github.com/snowblossomcoin/go-ethereum@v1.9.25/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  }