Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

multiset.gno

5.82 Kb · 213 lines
  1// Package multiset is a bag / frequency counter — a set that allows duplicates
  2// and remembers how many — as a pure, reusable package.
  3//
  4// It is the STL multiset and Python's collections.Counter in one type: Add an
  5// element several times and the count rises; the distinct elements stay sorted
  6// so iteration and rendering are deterministic.
  7//
  8// The interesting operation is MostCommon(n), and the interesting problem with
  9// it is ties. Sorting by count alone leaves elements with equal counts in
 10// whatever order the underlying storage happened to yield — which, if that is a
 11// built-in map, is unspecified in gno and can differ between nodes. Here the
 12// order is total: count descending, then element ascending. Two multisets built
 13// from the same elements always produce the same ranking.
 14//
 15// A live demo of this package is at
 16// [r/moul/x/daily/multisetdemo](/r/moul/x/daily/multisetdemo/v0).
 17package multiset
 18
 19import "sort"
 20
 21// MaxDistinct bounds the number of DISTINCT elements so gas stays predictable.
 22// Counts themselves are unbounded.
 23const MaxDistinct = 4096
 24
 25// MultiSet counts occurrences of string elements.
 26type MultiSet struct {
 27	counts map[string]int
 28	total  int
 29}
 30
 31// New returns an empty MultiSet.
 32func New() *MultiSet { return &MultiSet{counts: map[string]int{}} }
 33
 34// FromSlice builds a MultiSet from elements, counting duplicates.
 35func FromSlice(elems []string) *MultiSet {
 36	m := New()
 37	for _, e := range elems {
 38		m.Add(e)
 39	}
 40	return m
 41}
 42
 43// Add records one occurrence of e. Returns false when e is new and the set
 44// already holds MaxDistinct distinct elements.
 45func (m *MultiSet) Add(e string) bool { return m.AddN(e, 1) }
 46
 47// AddN records n occurrences of e. A non-positive n is a no-op returning true.
 48func (m *MultiSet) AddN(e string, n int) bool {
 49	if n <= 0 {
 50		return true
 51	}
 52	if _, seen := m.counts[e]; !seen && len(m.counts) >= MaxDistinct {
 53		return false
 54	}
 55	m.counts[e] += n
 56	m.total += n
 57	return true
 58}
 59
 60// Count returns how many times e occurs; zero when absent.
 61func (m *MultiSet) Count(e string) int { return m.counts[e] }
 62
 63// Has reports whether e occurs at least once.
 64func (m *MultiSet) Has(e string) bool { return m.counts[e] > 0 }
 65
 66// Remove drops one occurrence of e, deleting it entirely when the count hits
 67// zero. Returns false when e was not present.
 68func (m *MultiSet) Remove(e string) bool { return m.RemoveN(e, 1) }
 69
 70// RemoveN drops up to n occurrences of e. Returns false when e was absent.
 71// Removing more than are present clears the element rather than going negative.
 72func (m *MultiSet) RemoveN(e string, n int) bool {
 73	have, ok := m.counts[e]
 74	if !ok || n <= 0 {
 75		return false
 76	}
 77	if n >= have {
 78		delete(m.counts, e)
 79		m.total -= have
 80		return true
 81	}
 82	m.counts[e] = have - n
 83	m.total -= n
 84	return true
 85}
 86
 87// RemoveAll drops every occurrence of e. Returns false when e was absent.
 88func (m *MultiSet) RemoveAll(e string) bool {
 89	have, ok := m.counts[e]
 90	if !ok {
 91		return false
 92	}
 93	delete(m.counts, e)
 94	m.total -= have
 95	return true
 96}
 97
 98// Distinct returns the number of distinct elements.
 99func (m *MultiSet) Distinct() int { return len(m.counts) }
100
101// Total returns the sum of every count.
102func (m *MultiSet) Total() int { return m.total }
103
104// IsEmpty reports whether the set holds nothing.
105func (m *MultiSet) IsEmpty() bool { return len(m.counts) == 0 }
106
107// Elements returns the distinct elements, sorted.
108func (m *MultiSet) Elements() []string {
109	out := make([]string, 0, len(m.counts))
110	for e := range m.counts {
111		out = append(out, e)
112	}
113	sort.Strings(out)
114	return out
115}
116
117// Expand returns every occurrence, sorted — a multiset flattened back to a
118// slice. Length equals Total.
119func (m *MultiSet) Expand() []string {
120	out := make([]string, 0, m.total)
121	for _, e := range m.Elements() {
122		for i := 0; i < m.counts[e]; i++ {
123			out = append(out, e)
124		}
125	}
126	return out
127}
128
129// Entry pairs an element with its count.
130type Entry struct {
131	Elem  string
132	Count int
133}
134
135// byRank orders entries by count descending, then element ascending — a TOTAL
136// order, so ranking never depends on map iteration.
137type byRank []Entry
138
139func (r byRank) Len() int      { return len(r) }
140func (r byRank) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
141func (r byRank) Less(i, j int) bool {
142	if r[i].Count != r[j].Count {
143		return r[i].Count > r[j].Count
144	}
145	return r[i].Elem < r[j].Elem
146}
147
148// MostCommon returns the n most frequent entries, ranked by count descending
149// then element ascending. n <= 0, or larger than the number of distinct
150// elements, returns them all.
151func (m *MultiSet) MostCommon(n int) []Entry {
152	all := make([]Entry, 0, len(m.counts))
153	for e, c := range m.counts {
154		all = append(all, Entry{Elem: e, Count: c})
155	}
156	sort.Sort(byRank(all))
157	if n <= 0 || n > len(all) {
158		return all
159	}
160	return all[:n]
161}
162
163// Union returns a set where each element's count is the MAXIMUM of the two —
164// the standard multiset union.
165func (m *MultiSet) Union(other *MultiSet) *MultiSet {
166	out := m.Clone()
167	for e, c := range other.counts {
168		if c > out.counts[e] {
169			out.setCount(e, c)
170		}
171	}
172	return out
173}
174
175// Intersect returns a set where each element's count is the MINIMUM of the two,
176// keeping only elements present in both.
177func (m *MultiSet) Intersect(other *MultiSet) *MultiSet {
178	out := New()
179	for e, c := range m.counts {
180		if oc, ok := other.counts[e]; ok {
181			if oc < c {
182				c = oc
183			}
184			out.AddN(e, c)
185		}
186	}
187	return out
188}
189
190// Sum returns a set where each element's count is the SUM of the two.
191func (m *MultiSet) Sum(other *MultiSet) *MultiSet {
192	out := m.Clone()
193	for e, c := range other.counts {
194		out.AddN(e, c)
195	}
196	return out
197}
198
199// Clone returns an independent copy.
200func (m *MultiSet) Clone() *MultiSet {
201	out := New()
202	for e, c := range m.counts {
203		out.counts[e] = c
204	}
205	out.total = m.total
206	return out
207}
208
209// setCount overwrites an element's count, keeping total in step.
210func (m *MultiSet) setCount(e string, c int) {
211	m.total += c - m.counts[e]
212	m.counts[e] = c
213}