From 040457e65e87d0f7e35f0ba80f535c668f8b1932 Mon Sep 17 00:00:00 2001 From: Momchil Atanasov Date: Wed, 23 Aug 2023 19:44:02 +0300 Subject: [PATCH] Improvements to List and Set in ds package (#24) --- ds/list.go | 46 +++++++++++++++++++++++++--- ds/list_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ ds/set.go | 16 ++++++++++ ds/set_test.go | 35 ++++++++++++++++++++++ 4 files changed, 173 insertions(+), 4 deletions(-) diff --git a/ds/list.go b/ds/list.go index 10a9c37..7f60afc 100644 --- a/ds/list.go +++ b/ds/list.go @@ -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 @@ -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 @@ -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] diff --git a/ds/list_test.go b/ds/list_test.go index 16e00f6..5d3483f 100644 --- a/ds/list_test.go +++ b/ds/list_test.go @@ -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") @@ -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", })) @@ -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() @@ -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()) + }) + }) + }) }) diff --git a/ds/set.go b/ds/set.go index 8ae5706..5f5127a 100644 --- a/ds/set.go +++ b/ds/set.go @@ -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 @@ -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 { diff --git a/ds/set_test.go b/ds/set_test.go index 329d13d..c39d4ef 100644 --- a/ds/set_test.go +++ b/ds/set_test.go @@ -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") @@ -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")) }) @@ -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()