mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-03 17:51:28 +01:00 
			
		
		
		
	This adds SmallSet.SoleElement, which I need in another repo for efficiency. I added tests, but those tests failed because Add(1) + Add(1) was promoting the first Add's sole element to a map of one item. So fix that, and add more tests. Updates tailscale/corp#29093 Change-Id: Iadd5ad08afe39721ee5449343095e389214d8389 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			149 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			3.4 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
 | 
						|
}
 | 
						|
 | 
						|
// SoleElement returns the single value in the set, if the set has exactly one
 | 
						|
// element.
 | 
						|
//
 | 
						|
// If the set is empty or has more than one element, ok will be false and e will
 | 
						|
// be the zero value of T.
 | 
						|
func (s SmallSet[T]) SoleElement() (e T, ok bool) {
 | 
						|
	return s.one, s.Len() == 1
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
	}
 | 
						|
	// Non-zero elements can go into s.one.
 | 
						|
	if e != zero {
 | 
						|
		if s.one == zero {
 | 
						|
			s.one = e // Len 0 to Len 1
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if s.one == e {
 | 
						|
			return // dup
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// 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, possibly zero
 | 
						|
	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
 | 
						|
	}
 | 
						|
}
 |