Skip to content

Commit b827575

Browse files
committed
feat: add PopN method to remove multiple arbitrary items from set
1 parent 9480c3e commit b827575

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

set.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ type Set[T comparable] interface {
189189
// Pop removes and returns an arbitrary item from the set.
190190
Pop() (T, bool)
191191

192+
// PopN removes and returns up to n arbitrary items from the set.
193+
// It returns a slice of the removed items and the actual number of items removed.
194+
// If the set is empty or n is less than or equal to 0s, it returns an empty slice and 0.
195+
// If n is greater than the set's size, all items are
196+
PopN(n int) ([]T, int)
197+
192198
// ToSlice returns the members of the set as a slice.
193199
ToSlice() []T
194200

set_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,118 @@ func Test_PopUnsafe(t *testing.T) {
12421242
}
12431243
}
12441244

1245+
func Test_PopNSafe(t *testing.T) {
1246+
a := NewSet[string]()
1247+
a.Add("a")
1248+
a.Add("b")
1249+
a.Add("c")
1250+
a.Add("d")
1251+
1252+
// Test pop with n <= 0
1253+
items, count := a.PopN(0)
1254+
if count != 0 {
1255+
t.Errorf("expected 0 items popped, got %d", count)
1256+
}
1257+
items, count = a.PopN(-1)
1258+
if count != 0 {
1259+
t.Errorf("expected 0 items popped, got %d", count)
1260+
}
1261+
1262+
captureSet := NewSet[string]()
1263+
1264+
// Test pop 2 items
1265+
items, count = a.PopN(2)
1266+
if count != 2 {
1267+
t.Errorf("expected 2 items popped, got %d", count)
1268+
}
1269+
if len(items) != 2 {
1270+
t.Errorf("expected 2 items in slice, got %d", len(items))
1271+
}
1272+
if a.Cardinality() != 2 {
1273+
t.Errorf("expected 2 items remaining, got %d", a.Cardinality())
1274+
}
1275+
captureSet.Append(items...)
1276+
1277+
// Test pop more than remaining
1278+
items, count = a.PopN(3)
1279+
if count != 2 {
1280+
t.Errorf("expected 2 items popped, got %d", count)
1281+
}
1282+
if a.Cardinality() != 0 {
1283+
t.Errorf("expected 0 items remaining, got %d", a.Cardinality())
1284+
}
1285+
captureSet.Append(items...)
1286+
1287+
// Test pop from empty set
1288+
items, count = a.PopN(1)
1289+
if count != 0 {
1290+
t.Errorf("expected 0 items popped, got %d", count)
1291+
}
1292+
if len(items) != 0 {
1293+
t.Errorf("expected empty slice, got %d items", len(items))
1294+
}
1295+
1296+
if !captureSet.Contains("c", "a", "d", "b") {
1297+
t.Error("unexpected result set; should be a,b,c,d (any order is fine")
1298+
}
1299+
}
1300+
1301+
func Test_PopNUnsafe(t *testing.T) {
1302+
a := NewThreadUnsafeSet[string]()
1303+
a.Add("a")
1304+
a.Add("b")
1305+
a.Add("c")
1306+
a.Add("d")
1307+
1308+
// Test pop with n <= 0
1309+
items, count := a.PopN(0)
1310+
if count != 0 {
1311+
t.Errorf("expected 0 items popped, got %d", count)
1312+
}
1313+
items, count = a.PopN(-1)
1314+
if count != 0 {
1315+
t.Errorf("expected 0 items popped, got %d", count)
1316+
}
1317+
1318+
captureSet := NewThreadUnsafeSet[string]()
1319+
1320+
// Test pop 2 items
1321+
items, count = a.PopN(2)
1322+
if count != 2 {
1323+
t.Errorf("expected 2 items popped, got %d", count)
1324+
}
1325+
if len(items) != 2 {
1326+
t.Errorf("expected 2 items in slice, got %d", len(items))
1327+
}
1328+
if a.Cardinality() != 2 {
1329+
t.Errorf("expected 2 items remaining, got %d", a.Cardinality())
1330+
}
1331+
captureSet.Append(items...)
1332+
1333+
// Test pop more than remaining
1334+
items, count = a.PopN(3)
1335+
if count != 2 {
1336+
t.Errorf("expected 2 items popped, got %d", count)
1337+
}
1338+
if a.Cardinality() != 0 {
1339+
t.Errorf("expected 0 items remaining, got %d", a.Cardinality())
1340+
}
1341+
captureSet.Append(items...)
1342+
1343+
// Test pop from empty set
1344+
items, count = a.PopN(1)
1345+
if count != 0 {
1346+
t.Errorf("expected 0 items popped, got %d", count)
1347+
}
1348+
if len(items) != 0 {
1349+
t.Errorf("expected empty slice, got %d items", len(items))
1350+
}
1351+
1352+
if !captureSet.Contains("c", "a", "d", "b") {
1353+
t.Error("unexpected result set; should be a,b,c,d (any order is fine")
1354+
}
1355+
}
1356+
12451357
func Test_EmptySetProperties(t *testing.T) {
12461358
empty := NewSet[string]()
12471359

threadsafe.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ func (t *threadSafeSet[T]) Pop() (T, bool) {
285285
return t.uss.Pop()
286286
}
287287

288+
func (t *threadSafeSet[T]) PopN(n int) ([]T, int) {
289+
t.Lock()
290+
defer t.Unlock()
291+
return t.uss.PopN(n)
292+
}
293+
288294
func (t *threadSafeSet[T]) ToSlice() []T {
289295
t.RLock()
290296
l := len(*t.uss)

threadunsafe.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,27 @@ func (s *threadUnsafeSet[T]) Pop() (v T, ok bool) {
259259
return v, false
260260
}
261261

262+
func (s *threadUnsafeSet[T]) PopN(n int) (items []T, count int) {
263+
if n <= 0 || len(*s) == 0 {
264+
return make([]T, 0), 0
265+
}
266+
sn := s.Cardinality()
267+
if n > sn {
268+
n = sn
269+
}
270+
271+
items = make([]T, 0, sn)
272+
for item := range *s {
273+
if count >= n {
274+
break
275+
}
276+
delete(*s, item)
277+
items = append(items, item)
278+
count++
279+
}
280+
return items, count
281+
}
282+
262283
func (s threadUnsafeSet[T]) Remove(v T) {
263284
delete(s, v)
264285
}

0 commit comments

Comments
 (0)