mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-08 16:58:35 +00:00

Updates tailscale/corp#29093 Change-Id: I0e07e83dee51b4915597a913b0583c99756d90e2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
135 lines
3.1 KiB
Go
135 lines
3.1 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package set
|
|
|
|
import (
|
|
"iter"
|
|
"maps"
|
|
|
|
"tailscale.com/types/structs"
|
|
)
|
|
|
|
// SmallSet is a set that is optimized for reducing memory overhead when the
|
|
// expected size of the set is 0 or 1 elements.
|
|
//
|
|
// The zero value of SmallSet is a usable empty set.
|
|
//
|
|
// When storing a SmallSet in a map as a value type, it is important to re-assign
|
|
// the map entry after calling Add or Delete, as the SmallSet's representation
|
|
// may change.
|
|
//
|
|
// Copying a SmallSet by value may alias the previous value. Use the Clone method
|
|
// to create a new SmallSet with the same contents.
|
|
type SmallSet[T comparable] struct {
|
|
_ structs.Incomparable // to prevent == mistakes
|
|
one T // if non-zero, then single item in set
|
|
m Set[T] // if non-nil, the set of items, which might be size 1 if it's the zero value of T
|
|
}
|
|
|
|
// Values returns an iterator over the elements of the set.
|
|
// The iterator will yield the elements in no particular order.
|
|
func (s SmallSet[T]) Values() iter.Seq[T] {
|
|
if s.m != nil {
|
|
return maps.Keys(s.m)
|
|
}
|
|
var zero T
|
|
return func(yield func(T) bool) {
|
|
if s.one != zero {
|
|
yield(s.one)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Contains reports whether e is in the set.
|
|
func (s SmallSet[T]) Contains(e T) bool {
|
|
if s.m != nil {
|
|
return s.m.Contains(e)
|
|
}
|
|
var zero T
|
|
return e != zero && s.one == e
|
|
}
|
|
|
|
// Add adds e to the set.
|
|
//
|
|
// When storing a SmallSet in a map as a value type, it is important to
|
|
// re-assign the map entry after calling Add or Delete, as the SmallSet's
|
|
// representation may change.
|
|
func (s *SmallSet[T]) Add(e T) {
|
|
var zero T
|
|
if s.m != nil {
|
|
s.m.Add(e)
|
|
return
|
|
}
|
|
// Size zero to one non-zero element.
|
|
if s.one == zero && e != zero {
|
|
s.one = e
|
|
return
|
|
}
|
|
// Need to make a multi map, either
|
|
// because we now have two items, or
|
|
// because e is the zero value.
|
|
s.m = Set[T]{}
|
|
if s.one != zero {
|
|
s.m.Add(s.one) // move single item to multi
|
|
}
|
|
s.m.Add(e) // add new item
|
|
s.one = zero
|
|
}
|
|
|
|
// Len reports the number of elements in the set.
|
|
func (s SmallSet[T]) Len() int {
|
|
var zero T
|
|
if s.m != nil {
|
|
return s.m.Len()
|
|
}
|
|
if s.one != zero {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Delete removes e from the set.
|
|
//
|
|
// When storing a SmallSet in a map as a value type, it is important to
|
|
// re-assign the map entry after calling Add or Delete, as the SmallSet's
|
|
// representation may change.
|
|
func (s *SmallSet[T]) Delete(e T) {
|
|
var zero T
|
|
if s.m == nil {
|
|
if s.one == e {
|
|
s.one = zero
|
|
}
|
|
return
|
|
}
|
|
s.m.Delete(e)
|
|
|
|
// If the map size drops to zero, that means
|
|
// it only contained the zero value of T.
|
|
if s.m.Len() == 0 {
|
|
s.m = nil
|
|
return
|
|
}
|
|
|
|
// If the map size drops to one element and doesn't
|
|
// contain the zero value, we can switch back to the
|
|
// single-item representation.
|
|
if s.m.Len() == 1 {
|
|
for v := range s.m {
|
|
if v != zero {
|
|
s.one = v
|
|
s.m = nil
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Clone returns a copy of s that doesn't alias the original.
|
|
func (s SmallSet[T]) Clone() SmallSet[T] {
|
|
return SmallSet[T]{
|
|
one: s.one,
|
|
m: maps.Clone(s.m), // preserves nilness
|
|
}
|
|
}
|