smallset.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package set
  4. import (
  5. "iter"
  6. "maps"
  7. "tailscale.com/types/structs"
  8. )
  9. // SmallSet is a set that is optimized for reducing memory overhead when the
  10. // expected size of the set is 0 or 1 elements.
  11. //
  12. // The zero value of SmallSet is a usable empty set.
  13. //
  14. // When storing a SmallSet in a map as a value type, it is important to re-assign
  15. // the map entry after calling Add or Delete, as the SmallSet's representation
  16. // may change.
  17. //
  18. // Copying a SmallSet by value may alias the previous value. Use the Clone method
  19. // to create a new SmallSet with the same contents.
  20. type SmallSet[T comparable] struct {
  21. _ structs.Incomparable // to prevent == mistakes
  22. one T // if non-zero, then single item in set
  23. m Set[T] // if non-nil, the set of items, which might be size 1 if it's the zero value of T
  24. }
  25. // Values returns an iterator over the elements of the set.
  26. // The iterator will yield the elements in no particular order.
  27. func (s SmallSet[T]) Values() iter.Seq[T] {
  28. if s.m != nil {
  29. return maps.Keys(s.m)
  30. }
  31. var zero T
  32. return func(yield func(T) bool) {
  33. if s.one != zero {
  34. yield(s.one)
  35. }
  36. }
  37. }
  38. // Contains reports whether e is in the set.
  39. func (s SmallSet[T]) Contains(e T) bool {
  40. if s.m != nil {
  41. return s.m.Contains(e)
  42. }
  43. var zero T
  44. return e != zero && s.one == e
  45. }
  46. // SoleElement returns the single value in the set, if the set has exactly one
  47. // element.
  48. //
  49. // If the set is empty or has more than one element, ok will be false and e will
  50. // be the zero value of T.
  51. func (s SmallSet[T]) SoleElement() (e T, ok bool) {
  52. return s.one, s.Len() == 1
  53. }
  54. // Add adds e to the set.
  55. //
  56. // When storing a SmallSet in a map as a value type, it is important to
  57. // re-assign the map entry after calling Add or Delete, as the SmallSet's
  58. // representation may change.
  59. func (s *SmallSet[T]) Add(e T) {
  60. var zero T
  61. if s.m != nil {
  62. s.m.Add(e)
  63. return
  64. }
  65. // Non-zero elements can go into s.one.
  66. if e != zero {
  67. if s.one == zero {
  68. s.one = e // Len 0 to Len 1
  69. return
  70. }
  71. if s.one == e {
  72. return // dup
  73. }
  74. }
  75. // Need to make a multi map, either
  76. // because we now have two items, or
  77. // because e is the zero value.
  78. s.m = Set[T]{}
  79. if s.one != zero {
  80. s.m.Add(s.one) // move single item to multi
  81. }
  82. s.m.Add(e) // add new item, possibly zero
  83. s.one = zero
  84. }
  85. // Len reports the number of elements in the set.
  86. func (s SmallSet[T]) Len() int {
  87. var zero T
  88. if s.m != nil {
  89. return s.m.Len()
  90. }
  91. if s.one != zero {
  92. return 1
  93. }
  94. return 0
  95. }
  96. // Delete removes e from the set.
  97. //
  98. // When storing a SmallSet in a map as a value type, it is important to
  99. // re-assign the map entry after calling Add or Delete, as the SmallSet's
  100. // representation may change.
  101. func (s *SmallSet[T]) Delete(e T) {
  102. var zero T
  103. if s.m == nil {
  104. if s.one == e {
  105. s.one = zero
  106. }
  107. return
  108. }
  109. s.m.Delete(e)
  110. // If the map size drops to zero, that means
  111. // it only contained the zero value of T.
  112. if s.m.Len() == 0 {
  113. s.m = nil
  114. return
  115. }
  116. // If the map size drops to one element and doesn't
  117. // contain the zero value, we can switch back to the
  118. // single-item representation.
  119. if s.m.Len() == 1 {
  120. for v := range s.m {
  121. if v != zero {
  122. s.one = v
  123. s.m = nil
  124. }
  125. }
  126. }
  127. return
  128. }
  129. // Clone returns a copy of s that doesn't alias the original.
  130. func (s SmallSet[T]) Clone() SmallSet[T] {
  131. return SmallSet[T]{
  132. one: s.one,
  133. m: maps.Clone(s.m), // preserves nilness
  134. }
  135. }