Skip to content

Commit

Permalink
Improvements to List and Set in ds package (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
mokiat authored Aug 23, 2023
1 parent a3317dc commit 040457e
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 4 deletions.
46 changes: 42 additions & 4 deletions ds/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ func NewList[T comparable](initialCapacity int) *List[T] {
return &List[T]{}
}

// ListFromSlice constructs a new List that is based on the items from the
// specified slice.
//
// It is safe to modify the slice afterwards, as the list creates its own
// internal copy.
func ListFromSlice[T comparable](items []T) *List[T] {
return &List[T]{
items: slices.Clone(items),
}
}

// List represents a sequence of items.
//
// Currently a List can only store comparable items. This restriction
Expand Down Expand Up @@ -44,15 +55,37 @@ func (l *List[T]) Remove(item T) bool {
}

// Get returns the item in this list that is located at the specified index
// (starting from zero). This method will panic if the index is invalid.
// (starting from zero).
//
// This method will panic if the index is outside the list bounds.
func (l *List[T]) Get(index int) T {
return l.items[index]
}

// Item returns all items stored in this List as a slice. The returned
// slice should not be mutated.
// Set modifies the item at the specified index.
//
// This method will panic if the index is outside the list bounds.
func (l *List[T]) Set(index int, value T) {
l.items[index] = value
}

// Unbox provides direct access to the inner representation of the list.
// The returned slice should not be modified, otherwise there is a risk that
// the List might not work correctly afterwards. Even if it works now, a future
// version might break that behavior.
//
// This method should only be used when performance is critical and memory
// allocation is not desired.
func (l *List[T]) Unbox() []T {
return l.items
}

// Items returns all items stored in this List as a slice. It is safe to mutate
// the returned slice as it is a copy of the inner representation.
//
// If performance is needed, consider using Unbox method instead.
func (l *List[T]) Items() []T {
return l.items[0:len(l.items):len(l.items)]
return slices.Clone(l.items)
}

// Contains checks whether this List has the specified item and returns true
Expand All @@ -75,6 +108,11 @@ func (l *List[T]) Each(iterator func(item T)) {
}
}

// Equals returns whether this list matches exactly the provided list.
func (l *List[T]) Equals(other *List[T]) bool {
return slices.Equal(l.items, other.items)
}

// Clear removes all items from this List.
func (l *List[T]) Clear() {
l.items = l.items[:0]
Expand Down
80 changes: 80 additions & 0 deletions ds/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var _ = Describe("List", func() {
Expect(list.Items()).To(BeEmpty())
})

It("equals an empty list", func() {
other := ds.NewList[string](0)
Expect(list.Equals(other)).To(BeTrue())
})

When("items are added", func() {
BeforeEach(func() {
list.Add("first")
Expand All @@ -49,7 +54,25 @@ var _ = Describe("List", func() {
Expect(list.Get(2)).To(Equal("third"))
})

It("is possible to unbox the list", func() {
items := list.Unbox()
Expect(items).To(Equal([]string{
"first", "second", "third",
}))

items[0] = "modified"
Expect(list.Items()).To(Equal([]string{
"modified", "second", "third",
}))
})

It("is possible to get all items", func() {
items := list.Items()
Expect(items).To(Equal([]string{
"first", "second", "third",
}))

items[0] = "modified"
Expect(list.Items()).To(Equal([]string{
"first", "second", "third",
}))
Expand Down Expand Up @@ -79,6 +102,36 @@ var _ = Describe("List", func() {
}))
})

It("equals another list with same items", func() {
other := ds.ListFromSlice([]string{"first", "second", "third"})
Expect(list.Equals(other)).To(BeTrue())
})

It("does not equal another list with reordered items", func() {
other := ds.ListFromSlice([]string{"second", "first", "third"})
Expect(list.Equals(other)).To(BeFalse())
})

It("does not equal another list with more items", func() {
other := ds.ListFromSlice([]string{"first", "second", "third", "fourth"})
Expect(list.Equals(other)).To(BeFalse())
})

It("does not equal another list with fewer items", func() {
other := ds.ListFromSlice([]string{"first", "second"})
Expect(list.Equals(other)).To(BeFalse())
})

When("an item is overwritten", func() {
BeforeEach(func() {
list.Set(1, "modified")
})

It("is reflected in the items", func() {
Expect(list.Items()).To(Equal([]string{"first", "modified", "third"}))
})
})

When("the list is clipped", func() {
BeforeEach(func() {
list.Clip()
Expand Down Expand Up @@ -125,4 +178,31 @@ var _ = Describe("List", func() {
})
})
})

When("constructed from a slice", func() {
BeforeEach(func() {
list = ds.ListFromSlice([]string{"a", "b", "c"})
})

It("has the correct size", func() {
Expect(list.Size()).To(Equal(3))
})

It("contains the elements of the slice", func() {
Expect(list.Items()).To(Equal([]string{
"a", "b", "c",
}))
})

When("the slice is nil", func() {
BeforeEach(func() {
var slice []string
list = ds.ListFromSlice(slice)
})

It("is empty", func() {
Expect(list.IsEmpty()).To(BeTrue())
})
})
})
})
16 changes: 16 additions & 0 deletions ds/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ func (s *Set[T]) ContainsSet(other *Set[T]) bool {
return true
}

// Unbox provides direct access to the inner representation of the set.
// The returned map should not be modified, otherwise there is a risk that
// the Set might not work correctly afterwards. Even if it works now, a future
// version might break that behavior.
//
// This method should only be used when performance is critical and memory
// allocation is not desired.
func (s *Set[T]) Unbox() map[T]struct{} {
return s.items
}

// Items returns a slice containing all of the items from this Set.
//
// Note: The items are returned in a random order which can differ
Expand All @@ -170,6 +181,11 @@ func (s *Set[T]) Items() []T {
return result
}

// Equals return whether this set is equal to the provided set.
func (s *Set[T]) Equals(other *Set[T]) bool {
return maps.Equal(s.items, other.items)
}

// Clear removes all items from this Set.
func (s *Set[T]) Clear() {
for v := range s.items {
Expand Down
35 changes: 35 additions & 0 deletions ds/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var _ = Describe("Set", func() {
Expect(set.Items()).To(BeEmpty())
})

It("equals an empty set", func() {
other := ds.NewSet[string](0)
Expect(set.Equals(other)).To(BeTrue())
})

When("items are added", func() {
BeforeEach(func() {
set.Add("first")
Expand All @@ -43,6 +48,21 @@ var _ = Describe("Set", func() {
Expect(set.Size()).To(Equal(3))
})

It("is possible to unbox the set", func() {
items := set.Unbox()
Expect(items).To(Equal(map[string]struct{}{
"first": {},
"second": {},
"third": {},
}))

delete(items, "first")
Expect(set.Unbox()).To(Equal(map[string]struct{}{
"second": {},
"third": {},
}))
})

It("is possible to get all items", func() {
Expect(set.Items()).To(ContainElements("first", "second", "third"))
})
Expand Down Expand Up @@ -74,6 +94,21 @@ var _ = Describe("Set", func() {
Expect(set.Remove("missing")).To(BeFalse())
})

It("equals another set with same items", func() {
other := ds.SetFromSlice([]string{"first", "second", "third"})
Expect(set.Equals(other)).To(BeTrue())
})

It("does not equal another set with additional items", func() {
other := ds.SetFromSlice([]string{"first", "second", "third", "extra"})
Expect(set.Equals(other)).To(BeFalse())
})

It("does not equal another set with insufficient items", func() {
other := ds.SetFromSlice([]string{"first", "third"})
Expect(set.Equals(other)).To(BeFalse())
})

When("clipped", func() {
BeforeEach(func() {
set.Clip()
Expand Down

0 comments on commit 040457e

Please sign in to comment.