Skip to content

Commit 6132174

Browse files
authored
Merge pull request #644 from projectdiscovery/add_dedupefunc
add `DedupeFunc`
2 parents d73b206 + 520a98e commit 6132174

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

slice/sliceutil.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,28 @@ func Dedupe[T comparable](inputSlice []T) (result []T) {
3737
return
3838
}
3939

40+
// DedupeFunc removes duplicates from a slice of elements using a key function
41+
// The key function is used to return a key for each element in the slice
42+
// and then the key is used to check for duplicates
43+
// If the key function is nil, we return the original input slice
44+
func DedupeFunc[T any](inputSlice []T, keyFunc func(T) any) (result []T) {
45+
if keyFunc == nil {
46+
return inputSlice
47+
}
48+
seen := make(map[any]struct{})
49+
for _, inputValue := range inputSlice {
50+
key := keyFunc(inputValue)
51+
if _, ok := seen[key]; !ok {
52+
seen[key] = struct{}{}
53+
result = append(result, inputValue)
54+
}
55+
}
56+
57+
return
58+
}
59+
60+
// DedupeFuncDeep checks for deep equality of two elements
61+
4062
// PickRandom item from a slice of elements
4163
func PickRandom[T any](v []T) T {
4264
return v[rand.Intn(len(v))]

slice/sliceutil_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sliceutil
22

33
import (
4+
"strings"
45
"testing"
56

67
osutils "github.com/projectdiscovery/utils/os"
@@ -308,3 +309,76 @@ func TestVisitRandomZero(t *testing.T) {
308309
}
309310
require.True(t, timesDifferent > 0)
310311
}
312+
313+
func TestDedupeFunc(t *testing.T) {
314+
t.Run("basic-string-deduplication", func(t *testing.T) {
315+
input := []string{"hello", "HELLO", "world", "WORLD"}
316+
result := DedupeFunc(input, func(s string) any { return strings.ToLower(s) })
317+
require.Equal(t, []string{"hello", "world"}, result)
318+
})
319+
320+
t.Run("struct-deduplication", func(t *testing.T) {
321+
type Person struct {
322+
ID int
323+
Name string
324+
}
325+
input := []Person{
326+
{ID: 1, Name: "Alice"},
327+
{ID: 2, Name: "Bob"},
328+
{ID: 1, Name: "Alice Different"}, // Same ID, different name
329+
{ID: 3, Name: "Charlie"},
330+
}
331+
result := DedupeFunc(input, func(p Person) any {
332+
return p.ID
333+
})
334+
require.Equal(t, []Person{
335+
{ID: 1, Name: "Alice"},
336+
{ID: 2, Name: "Bob"},
337+
{ID: 3, Name: "Charlie"},
338+
}, result)
339+
})
340+
341+
t.Run("empty-slice", func(t *testing.T) {
342+
var input []int
343+
result := DedupeFunc(input, func(i int) any { return i })
344+
require.Empty(t, result)
345+
})
346+
347+
t.Run("multiple-field-key", func(t *testing.T) {
348+
type Event struct {
349+
Date string
350+
Category string
351+
Value int
352+
}
353+
input := []Event{
354+
{"2024-01-01", "A", 1},
355+
{"2024-01-01", "A", 2}, // Same date and category
356+
{"2024-01-01", "B", 3},
357+
{"2024-01-02", "A", 4},
358+
}
359+
result := DedupeFunc(input, func(e Event) any {
360+
return e.Date + "-" + e.Category
361+
})
362+
require.Equal(t, []Event{
363+
{"2024-01-01", "A", 1},
364+
{"2024-01-01", "B", 3},
365+
{"2024-01-02", "A", 4},
366+
}, result)
367+
})
368+
369+
t.Run("nil-key-function", func(t *testing.T) {
370+
input := []int{1, 2, 2, 3}
371+
result := DedupeFunc(input, func(i int) any {
372+
if i == 2 {
373+
return nil
374+
}
375+
return i
376+
})
377+
require.Equal(t, []int{1, 2, 3}, result)
378+
})
379+
t.Run("nil-key-func", func(t *testing.T) {
380+
input := []string{"a", "b", "c"}
381+
res := DedupeFunc(input, nil)
382+
require.Equal(t, input, res)
383+
})
384+
}

0 commit comments

Comments
 (0)