diff --git a/dedupe.go b/dedupe.go new file mode 100644 index 0000000..3803732 --- /dev/null +++ b/dedupe.go @@ -0,0 +1,23 @@ +package util + +// DedupeStrings returns a new slice holding each value of input once, in the +// order in which each value first appears. The input slice is not modified. +// A nil input returns nil. +func DedupeStrings(input []string) []string { + if input == nil { + return nil + } + + seen := make(map[string]struct{}, len(input)) + out := make([]string, 0, len(input)) + + for _, value := range input { + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + + return out +} diff --git a/dedupe_test.go b/dedupe_test.go new file mode 100644 index 0000000..56e9963 --- /dev/null +++ b/dedupe_test.go @@ -0,0 +1,40 @@ +package util + +import ( + "reflect" + "testing" +) + +func TestDedupeStrings(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + {"nil stays nil", nil, nil}, + {"empty slice", []string{}, []string{}}, + {"nothing repeated", []string{"a", "b", "c"}, []string{"a", "b", "c"}}, + {"all one value", []string{"a", "a", "a"}, []string{"a"}}, + {"repeats next to each other", []string{"a", "a", "b", "b"}, []string{"a", "b"}}, + {"repeats far apart", []string{"a", "b", "a", "c", "b"}, []string{"a", "b", "c"}}, + {"empty string is a value", []string{"", "a", ""}, []string{"", "a"}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := DedupeStrings(test.input) + if !reflect.DeepEqual(got, test.expected) { + t.Errorf("expected %#v got %#v", test.expected, got) + } + }) + } +} + +func TestDedupeStringsLeavesInputAlone(t *testing.T) { + input := []string{"b", "a", "b"} + DedupeStrings(input) + + if !reflect.DeepEqual(input, []string{"b", "a", "b"}) { + t.Errorf("expected the input to be unchanged, got %#v", input) + } +}