mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +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
 | |
| 	}
 | |
| }
 |