github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/les/randselect.go (about)

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