github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/les/randselect.go (about)

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