gecko/ids/bag.go

157 lines
3.5 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ids
import (
"fmt"
"strings"
)
const (
minBagSize = 16
)
// Bag is a multiset of IDs.
//
// A bag has the ability to split and filter on it's bits for ease of use for
// binary voting.
type Bag struct {
counts map[[32]byte]int
size int
mode ID
modeFreq int
threshold int
metThreshold Set
}
func (b *Bag) init() {
if b.counts == nil {
b.counts = make(map[[32]byte]int, minBagSize)
}
}
// SetThreshold sets the number of times an ID must be added to be contained in
// the threshold set.
func (b *Bag) SetThreshold(threshold int) {
if b.threshold == threshold {
return
}
b.threshold = threshold
b.metThreshold.Clear()
for vote, count := range b.counts {
if count >= threshold {
b.metThreshold.Add(NewID(vote))
}
}
}
// Add increases the number of times each id has been seen by one.
func (b *Bag) Add(ids ...ID) {
for _, id := range ids {
b.AddCount(id, 1)
}
}
// AddCount increases the nubmer of times the id has been seen by count.
//
// count must be >= 1
func (b *Bag) AddCount(id ID, count int) {
b.init()
totalCount := b.counts[*id.ID] + count
b.counts[*id.ID] = totalCount
b.size += count
if totalCount > b.modeFreq {
b.mode = id
b.modeFreq = totalCount
}
if totalCount >= b.threshold {
b.metThreshold.Add(id)
}
}
// Count returns the number of times the id has been added.
func (b *Bag) Count(id ID) int {
b.init()
return b.counts[*id.ID]
}
// Len returns the number of times an id has been added.
func (b *Bag) Len() int { return b.size }
// List returns a list of all ids that have been added.
func (b *Bag) List() []ID {
idList := make([]ID, len(b.counts), len(b.counts))
i := 0
for id := range b.counts {
idList[i] = NewID(id)
i++
}
return idList
}
// Equals returns true if the bags contain the same elements
func (b *Bag) Equals(oIDs Bag) bool {
if b.Len() != oIDs.Len() {
return false
}
for key, value := range b.counts {
if value != oIDs.counts[key] {
return false
}
}
return true
}
// Mode returns the id that has been seen the most and the number of times it
// has been seen. Ties are broken by the first id to be seen the reported number
// of times.
func (b *Bag) Mode() (ID, int) { return b.mode, b.modeFreq }
// Threshold returns the ids that have been seen at least threshold times.
func (b *Bag) Threshold() Set { return b.metThreshold }
// Filter returns the bag of ids with the same counts as this bag, except all
// the ids in the returned bag must have the same bits in the range [start, end)
// as id.
func (b *Bag) Filter(start, end int, id ID) Bag {
newBag := Bag{}
for vote, count := range b.counts {
voteID := NewID(vote)
if EqualSubset(start, end, id, voteID) {
newBag.AddCount(voteID, count)
}
}
return newBag
}
// Split returns the bags of ids with the same counts a this bag, except all ids
// in the 0th index have a 0 at bit [index], and all ids in the 1st index have a
// 1 at bit [index].
func (b *Bag) Split(index uint) [2]Bag {
splitVotes := [2]Bag{}
for vote, count := range b.counts {
voteID := NewID(vote)
bit := voteID.Bit(index)
splitVotes[bit].AddCount(voteID, count)
}
return splitVotes
}
func (b *Bag) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("Bag: (Size = %d)", b.Len()))
for idBytes, count := range b.counts {
id := NewID(idBytes)
sb.WriteString(fmt.Sprintf("\n ID[%s]: Count = %d", id, count))
}
return sb.String()
}