Skip to content

Commit

Permalink
v2: enable custom functions on hashset
Browse files Browse the repository at this point in the history
  • Loading branch information
shoenig committed Nov 4, 2023
1 parent f3c9084 commit e50b8f5
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 37 deletions.
17 changes: 11 additions & 6 deletions common.go → collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@ package set
// Collection is a minimal common interface that all sets implement.
type Collection[T any] interface {

// Slice returns a slice of all elements in the set.
//
// Note: order of elements depends on the underlying implementation.
Slice() []T

// Insert an element into the set.
//
// Returns true if the set is modified as a result.
Insert(T) bool

// InsertSlice inserts all elements from the slice into the set.
// InsertSlice will insert each element of a given slice.
//
// Returns true if the set was modified as a result.
InsertSlice([]T) bool

// InsertSet will insert each element of a given set.
//
// Returns true if the set was modified as a result.
InsertSet(Collection[T]) bool

// Slice returns a slice of all elements in the set.
//
// Note: order of elements depends on the underlying implementation.
Slice() []T

// Size returns the number of elements in the set.
Size() int

Expand Down
File renamed without changes.
77 changes: 53 additions & 24 deletions hashset.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,72 @@ type Hash interface {
~string | ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8
}

// HashFunc is a generic type constraint for any type that implements a Hash()
// method with a Hash return type.
type HashFunc[H Hash] interface {
// Hasher represents a type that implements a Hash() method. Types that wish to
// cache a hash value with an internal field should implement Hash accordingly.
type Hasher[H Hash] interface {
Hash() H
}

// HasherFunc creates a closure around the T.Hash function so that the type can
// be used as the HashFunc for a HashSet.
func HasherFunc[T Hasher[H], H Hash]() HashFunc[T, H] {
return func(t T) H {
return t.Hash()
}
}

// HashFunc represents a function that that produces a hash value when applied
// to a given T. Typically this will be implemented as T.Hash but by separating
// HashFunc a HashSet can be made to make use of any hash implementation.
type HashFunc[T any, H Hash] func(T) H

// HashSet is a generic implementation of the mathematical data structure, oriented
// around the use of a HashFunc to make hash values from other types.
type HashSet[T HashFunc[H], H Hash] struct {
type HashSet[T any, H Hash] struct {
fn HashFunc[T, H]
items map[H]T
}

// NewHashSet creates a HashSet with underlying capacity of size.
// NewHashSet creates a HashSet with underlying capacity of size and will compute
// hash values from the T.Hash method.
func NewHashSet[T Hasher[H], H Hash](size int) *HashSet[T, H] {
return NewHashSetFunc[T, H](size, HasherFunc[T, H]())
}

// NewHashSetFunc creates a HashSet with underlying capacity of size and uses
// the given hashing function to compute hashes on elements.
//
// A HashSet will automatically grow or shrink its capacity as items are added
// or removed.
//
// T must implement HashFunc[H], where H is of Hash type. This allows custom types
// that include non-comparable fields to provide their own hash algorithm.
func NewHashSet[T HashFunc[H], H Hash](size int) *HashSet[T, H] {
func NewHashSetFunc[T any, H Hash](size int, fn HashFunc[T, H]) *HashSet[T, H] {
return &HashSet[T, H]{
fn: fn,
items: make(map[H]T, max(0, size)),
}
}

// HashSetFrom creates a new HashSet containing each item in items.
// HashSetFrom creates a new HashSet containing each element in items.
//
// T must implement HashFunc[H], where H is of type Hash. This allows custom types
// that include non-comparable fields to provide their own hash algorithm.
func HashSetFrom[T HashFunc[H], H Hash](items []T) *HashSet[T, H] {
func HashSetFrom[T Hasher[H], H Hash](items []T) *HashSet[T, H] {
s := NewHashSet[T, H](len(items))
s.InsertSlice(items)
return s
}

// NewHashSetFromFunc creates a new HashSet containing each element in items.
func HashSetFromFunc[T any, H Hash](items []T, hash HashFunc[T, H]) *HashSet[T, H] {
s := NewHashSetFunc[T, H](len(items), hash)
s.InsertSlice(items)
return s
}

// Insert item into s.
//
// Return true if s was modified (item was not already in s), false otherwise.
func (s *HashSet[T, H]) Insert(item T) bool {
key := item.Hash()
key := s.fn(item)
if _, exists := s.items[key]; exists {
return false
}
Expand All @@ -80,22 +106,22 @@ func (s *HashSet[T, H]) InsertSlice(items []T) bool {
// InsertSet will insert each element of o into s.
//
// Return true if s was modified (at least one item of o was not already in s), false otherwise.
func (s *HashSet[T, H]) InsertSet(o *HashSet[T, H]) bool {
func (s *HashSet[T, H]) InsertSet(o Collection[T]) bool {
modified := false
for key, value := range o.items {
if _, exists := s.items[key]; !exists {
o.ForEach(func(item T) bool {
if s.Insert(item) {
modified = true
}
s.items[key] = value
}
return true
})
return modified
}

// Remove will remove item from s.
//
// Return true if s was modified (item was present), false otherwise.
func (s *HashSet[T, H]) Remove(item T) bool {
key := item.Hash()
key := s.fn(item)
if _, exists := s.items[key]; !exists {
return false
}
Expand Down Expand Up @@ -146,7 +172,8 @@ func (s *HashSet[T, H]) RemoveFunc(f func(item T) bool) bool {

// Contains returns whether item is present in s.
func (s *HashSet[T, H]) Contains(item T) bool {
_, exists := s.items[item.Hash()]
hash := s.fn(item)
_, exists := s.items[hash]
return exists
}

Expand All @@ -169,7 +196,7 @@ func (s *HashSet[T, H]) ContainsAll(items []T) bool {
// If the slice is known to be set-like (no duplicates), EqualSlice provides
// a more efficient implementation.
func (s *HashSet[T, H]) ContainsSlice(items []T) bool {
return s.Equal(HashSetFrom[T, H](items))
return s.Equal(HashSetFromFunc[T, H](items, s.fn))
}

// Subset returns whether o is a subset of s.
Expand Down Expand Up @@ -197,7 +224,7 @@ func (s *HashSet[T, H]) Empty() bool {

// Union returns a set that contains all elements of s and o combined.
func (s *HashSet[T, H]) Union(o *HashSet[T, H]) *HashSet[T, H] {
result := NewHashSet[T, H](s.Size())
result := NewHashSetFunc[T, H](s.Size(), s.fn)
for key, item := range s.items {
result.items[key] = item
}
Expand All @@ -209,7 +236,7 @@ func (s *HashSet[T, H]) Union(o *HashSet[T, H]) *HashSet[T, H] {

// Difference returns a set that contains elements of s that are not in o.
func (s *HashSet[T, H]) Difference(o *HashSet[T, H]) *HashSet[T, H] {
result := NewHashSet[T, H](max(0, s.Size()-o.Size()))
result := NewHashSetFunc[T, H](max(0, s.Size()-o.Size()), s.fn)
for key, item := range s.items {
if _, exists := o.items[key]; !exists {
result.items[key] = item
Expand All @@ -220,7 +247,7 @@ func (s *HashSet[T, H]) Difference(o *HashSet[T, H]) *HashSet[T, H] {

// Intersect returns a set that contains elements that are present in both s and o.
func (s *HashSet[T, H]) Intersect(o *HashSet[T, H]) *HashSet[T, H] {
result := NewHashSet[T, H](0)
result := NewHashSetFunc[T, H](0, s.fn)
big, small := s, o
if s.Size() < o.Size() {
big, small = o, s
Expand All @@ -235,7 +262,7 @@ func (s *HashSet[T, H]) Intersect(o *HashSet[T, H]) *HashSet[T, H] {

// Copy creates a shallow copy of s.
func (s *HashSet[T, H]) Copy() *HashSet[T, H] {
result := NewHashSet[T, H](s.Size())
result := NewHashSetFunc[T, H](s.Size(), s.fn)
for key, item := range s.items {
result.items[key] = item
}
Expand Down Expand Up @@ -308,6 +335,8 @@ func (s *HashSet[T, H]) UnmarshalJSON(data []byte) error {
return unmarshalJSON[T](s, data)
}

// ForEach iterates the set calling visit for each element. If visit returns
// false the iteration is halted.
func (s *HashSet[T, H]) ForEach(visit func(T) bool) {
for _, item := range s.items {
if !visit(item) {
Expand Down
7 changes: 4 additions & 3 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,14 @@ func (s *Set[T]) InsertSlice(items []T) bool {
// InsertSet will insert each element of o into s.
//
// Return true if s was modified (at least one item of o was not already in s), false otherwise.
func (s *Set[T]) InsertSet(o *Set[T]) bool {
func (s *Set[T]) InsertSet(o Collection[T]) bool {
modified := false
for item := range o.items {
o.ForEach(func(item T) bool {
if s.Insert(item) {
modified = true
}
}
return true
})
return modified
}

Expand Down
7 changes: 3 additions & 4 deletions treeset.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,14 @@ func (s *TreeSet[T]) InsertSlice(items []T) bool {
// InsertSet will insert each element of o into s.
//
// Return true if s was modified (at least one item of o was not already in s), false otherwise.
func (s *TreeSet[T]) InsertSet(o *TreeSet[T]) bool {
func (s *TreeSet[T]) InsertSet(o Collection[T]) bool {
modified := false
insert := func(item T) bool {
o.ForEach(func(item T) bool {
if s.Insert(item) {
modified = true
}
return true
}
o.ForEach(insert)
})
return modified
}

Expand Down

0 comments on commit e50b8f5

Please sign in to comment.