github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/randselect.go (about)

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