idnamemap.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package collections
  2. import (
  3. "errors"
  4. "fmt"
  5. "iter"
  6. )
  7. var (
  8. // ErrEmptyID is returned when the provided entry into an IdNameMap returns an empty string
  9. // for ID
  10. ErrEmptyID error = errors.New("id must be non-empty")
  11. // ErrEmptyName is returned when the provided entry into an IdNameMap returns an empty string
  12. // for Name
  13. ErrEmptyName error = errors.New("name must be non-empty")
  14. )
  15. // WithIdName is a generic constraint required for elements added to a `IdNameMap`
  16. type WithIdName interface {
  17. Id() string
  18. Name() string
  19. }
  20. // IdNameMap contains two maps which alias the same element by id and name. It provides O(1) lookups
  21. // by identifier or by name, both a required constraint on the `T` type.
  22. type IdNameMap[T WithIdName] struct {
  23. m map[string]T
  24. r map[string]T
  25. }
  26. func NewIdNameMap[T WithIdName]() *IdNameMap[T] {
  27. return &IdNameMap[T]{
  28. m: make(map[string]T),
  29. r: make(map[string]T),
  30. }
  31. }
  32. // Insert inserts a `T` instance into the map successfully under the following requirements:
  33. //
  34. // Insertion of new Entry:
  35. // 1. IDs and Name for the `T` instance must be non-empty.
  36. // 2. ID and Name must not partially overlap with an existing entry. This would happen if
  37. // you attempted to insert a `T` with a unique ID, but a conflicting Name. Likewise,
  38. // a unique name, but conflicting ID will also fail.
  39. //
  40. // Replacing an existing Entry:
  41. // 1. If there exists an old entry with the id of the new entry, then the name for the new
  42. // entry must also point to the old entry.
  43. // 2. If there exists an old entry with the name of the new entry, then the id for the new
  44. // entry must also point to the old entry.
  45. //
  46. // To summarize, you can replace an existing item as long as the id/name lookups for the entry
  47. // being replaced are the same.
  48. func (rm *IdNameMap[T]) Insert(item T) error {
  49. id := item.Id()
  50. if id == "" {
  51. return ErrEmptyID
  52. }
  53. name := item.Name()
  54. if name == "" {
  55. return ErrEmptyName
  56. }
  57. oldForId, idExists := rm.m[id]
  58. oldForName, nameExists := rm.r[name]
  59. // check partial insertion of id
  60. if idExists && !nameExists {
  61. return fmt.Errorf(
  62. "insertion of new entry: [id: %s, name: %s] would partially overwrite existing entry: [id: %s, name: %s]",
  63. id,
  64. name,
  65. oldForId.Id(),
  66. oldForId.Name(),
  67. )
  68. }
  69. // check partial insertion of name
  70. if !idExists && nameExists {
  71. return fmt.Errorf(
  72. "insertion of new entry: [id: %s, name: %s] would partially overwrite existing entry: [id: %s, name: %s]",
  73. id,
  74. name,
  75. oldForName.Id(),
  76. oldForName.Name(),
  77. )
  78. }
  79. // if we are overwriting, check to ensure that the entities from each map have identical mappings
  80. if idExists && nameExists {
  81. if oldForId.Id() != oldForName.Id() || oldForId.Name() != oldForName.Name() {
  82. return fmt.Errorf(
  83. "attempting to overwrite entries [id: %s, name: %s] and [id: %s, name: %s] with new entry [id: %s, name: %s] creating a multi-entry conflict",
  84. oldForId.Id(),
  85. oldForId.Name(),
  86. oldForName.Id(),
  87. oldForName.Name(),
  88. id,
  89. name,
  90. )
  91. }
  92. }
  93. rm.m[id] = item
  94. rm.r[name] = item
  95. return nil
  96. }
  97. func (rm *IdNameMap[T]) ById(id string) (T, bool) {
  98. item, ok := rm.m[id]
  99. return item, ok
  100. }
  101. func (rm *IdNameMap[T]) ByName(name string) (T, bool) {
  102. item, ok := rm.r[name]
  103. return item, ok
  104. }
  105. func (rm *IdNameMap[T]) RemoveById(id string) bool {
  106. item, ok := rm.ById(id)
  107. if !ok {
  108. return false
  109. }
  110. name := item.Name()
  111. delete(rm.m, id)
  112. delete(rm.r, name)
  113. return true
  114. }
  115. func (rm *IdNameMap[T]) RemoveByName(name string) bool {
  116. item, ok := rm.ByName(name)
  117. if !ok {
  118. return false
  119. }
  120. id := item.Id()
  121. delete(rm.m, id)
  122. delete(rm.r, name)
  123. return true
  124. }
  125. func (rm *IdNameMap[T]) Keys() iter.Seq2[string, string] {
  126. return func(yield func(string, string) bool) {
  127. for id, value := range rm.m {
  128. name := value.Name()
  129. if !yield(id, name) {
  130. return
  131. }
  132. }
  133. }
  134. }
  135. func (rm *IdNameMap[T]) Values() iter.Seq[T] {
  136. return func(yield func(T) bool) {
  137. for _, value := range rm.m {
  138. if !yield(value) {
  139. return
  140. }
  141. }
  142. }
  143. }