Reintegrating optimizations originally proposed by @fy0 in issue:60 for set instantiation to utilize capacity hint where possible (#113)

diff --git a/set.go b/set.go
index a315c3a..803c8ea 100644
--- a/set.go
+++ b/set.go
@@ -145,6 +145,9 @@
 	// Remove removes a single element from the set.
 	Remove(i T)
 
+	// RemoveAll removes multiple elements from the set.
+	RemoveAll(i ...T)
+
 	// String provides a convenient string representation
 	// of the current state of the set.
 	String() string
@@ -182,27 +185,41 @@
 // NewSet creates and returns a new set with the given elements.
 // Operations on the resulting set are thread-safe.
 func NewSet[T comparable](vals ...T) Set[T] {
-	s := newThreadSafeSet[T]()
+	s := newThreadSafeSetWithSize[T](len(vals))
 	for _, item := range vals {
 		s.Add(item)
 	}
 	return s
 }
 
+// NewSetWithSize creates and returns a reference to an empty set with a specified
+// capacity. Operations on the resulting set are thread-safe.
+func NewSetWithSize[T comparable](cardinality int) Set[T] {
+	s := newThreadSafeSetWithSize[T](cardinality)
+	return s
+}
+
 // NewThreadUnsafeSet creates and returns a new set with the given elements.
 // Operations on the resulting set are not thread-safe.
 func NewThreadUnsafeSet[T comparable](vals ...T) Set[T] {
-	s := newThreadUnsafeSet[T]()
+	s := newThreadUnsafeSetWithSize[T](len(vals))
 	for _, item := range vals {
 		s.Add(item)
 	}
 	return s
 }
 
-// Creates and returns a new set with the given keys of the map.
+// NewThreadUnsafeSetWithSize creates and returns a reference to an empty set with
+// a specified capacity. Operations on the resulting set are not thread-safe.
+func NewThreadUnsafeSetWithSize[T comparable](cardinality int) Set[T] {
+	s := newThreadUnsafeSetWithSize[T](cardinality)
+	return s
+}
+
+// NewSetFromMapKeys creates and returns a new set with the given keys of the map.
 // Operations on the resulting set are thread-safe.
 func NewSetFromMapKeys[T comparable, V any](val map[T]V) Set[T] {
-	s := NewSet[T]()
+	s := NewSetWithSize[T](len(val))
 
 	for k := range val {
 		s.Add(k)
@@ -211,10 +228,10 @@
 	return s
 }
 
-// Creates and returns a new set with the given keys of the map.
+// NewThreadUnsafeSetFromMapKeys creates and returns a new set with the given keys of the map.
 // Operations on the resulting set are not thread-safe.
 func NewThreadUnsafeSetFromMapKeys[T comparable, V any](val map[T]V) Set[T] {
-	s := NewThreadUnsafeSet[T]()
+	s := NewThreadUnsafeSetWithSize[T](len(val))
 
 	for k := range val {
 		s.Add(k)
diff --git a/set_test.go b/set_test.go
index 5c54c39..6017f45 100644
--- a/set_test.go
+++ b/set_test.go
@@ -185,6 +185,26 @@
 	}
 }
 
+func Test_RemoveAllSet(t *testing.T) {
+	a := makeSetInt([]int{6, 3, 1, 8, 9})
+
+	a.RemoveAll(3, 1)
+
+	if a.Cardinality() != 3 {
+		t.Error("RemoveAll should only have 2 items in the set")
+	}
+
+	if !a.Contains(6, 8, 9) {
+		t.Error("RemoveAll should have only items (6,8,9) in the set")
+	}
+
+	a.RemoveAll(6, 8, 9)
+
+	if a.Cardinality() != 0 {
+		t.Error("RemoveSet should be an empty set after removing 6 and 1")
+	}
+}
+
 func Test_RemoveUnsafeSet(t *testing.T) {
 	a := makeUnsafeSetInt([]int{6, 3, 1})
 
@@ -206,6 +226,26 @@
 	}
 }
 
+func Test_RemoveAllUnsafeSet(t *testing.T) {
+	a := makeUnsafeSetInt([]int{6, 3, 1, 8, 9})
+
+	a.RemoveAll(3, 1)
+
+	if a.Cardinality() != 3 {
+		t.Error("RemoveAll should only have 2 items in the set")
+	}
+
+	if !a.Contains(6, 8, 9) {
+		t.Error("RemoveAll should have only items (6,8,9) in the set")
+	}
+
+	a.RemoveAll(6, 8, 9)
+
+	if a.Cardinality() != 0 {
+		t.Error("RemoveSet should be an empty set after removing 6 and 1")
+	}
+}
+
 func Test_ContainsSet(t *testing.T) {
 	a := NewSet[int]()
 
diff --git a/threadsafe.go b/threadsafe.go
index 6178d5f..9e3a0ca 100644
--- a/threadsafe.go
+++ b/threadsafe.go
@@ -38,6 +38,12 @@
 	}
 }
 
+func newThreadSafeSetWithSize[T comparable](cardinality int) *threadSafeSet[T] {
+	return &threadSafeSet[T]{
+		uss: newThreadUnsafeSetWithSize[T](cardinality),
+	}
+}
+
 func (t *threadSafeSet[T]) Add(v T) bool {
 	t.Lock()
 	ret := t.uss.Add(v)
@@ -155,6 +161,12 @@
 	t.Unlock()
 }
 
+func (t *threadSafeSet[T]) RemoveAll(i ...T) {
+	t.Lock()
+	t.uss.RemoveAll(i...)
+	t.Unlock()
+}
+
 func (t *threadSafeSet[T]) Cardinality() int {
 	t.RLock()
 	defer t.RUnlock()
diff --git a/threadunsafe.go b/threadunsafe.go
index ea5635c..e5f4629 100644
--- a/threadunsafe.go
+++ b/threadunsafe.go
@@ -41,6 +41,10 @@
 	return make(threadUnsafeSet[T])
 }
 
+func newThreadUnsafeSetWithSize[T comparable](cardinality int) threadUnsafeSet[T] {
+	return make(threadUnsafeSet[T], cardinality)
+}
+
 func (s threadUnsafeSet[T]) Add(v T) bool {
 	prevLen := len(s)
 	s[v] = struct{}{}
@@ -74,7 +78,7 @@
 }
 
 func (s threadUnsafeSet[T]) Clone() Set[T] {
-	clonedSet := make(threadUnsafeSet[T], s.Cardinality())
+	clonedSet := newThreadUnsafeSetWithSize[T](s.Cardinality())
 	for elem := range s {
 		clonedSet.add(elem)
 	}
@@ -220,6 +224,12 @@
 	delete(s, v)
 }
 
+func (s threadUnsafeSet[T]) RemoveAll(i ...T) {
+	for _, elem := range i {
+		delete(s, elem)
+	}
+}
+
 func (s threadUnsafeSet[T]) String() string {
 	items := make([]string, 0, len(s))