// (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() }