From d711d76a179ae84a76e02f329ef58a5bcdab5d49 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 25 Jul 2024 16:07:03 -0700 Subject: [PATCH] slice: deprecate Dedup, shim to the equivalent stdlib slices.Compact There is a slight semantic change: elements beyond the end of the compacted slice are zeroed in slices.Compact. While this is a semantic change, it should be a strict improvement since people don't generally intend to hide old values (and pin pointers, from the GC's POV) in the unused part of a backing array. --- slice/slice.go | 54 +++++---------------------------------------- slice/slice_test.go | 35 ----------------------------- 2 files changed, 5 insertions(+), 84 deletions(-) diff --git a/slice/slice.go b/slice/slice.go index d15b0a5..5e673fe 100644 --- a/slice/slice.go +++ b/slice/slice.go @@ -1,6 +1,8 @@ // Package slice implements some useful functions for slices. package slice +import "slices" + // Partition rearranges the elements of vs in-place so that all the elements v // for which keep(v) is true precede all those for which it is false. It // returns the prefix of vs that contains the kept elements. It takes time @@ -69,57 +71,11 @@ func Partition[T any](vs []T, keep func(T) bool) []T { // Dedup rearranges the elements of vs in-place to deduplicate consecutive runs // of identical elements. It returns a prefix of vs that contains the first -// element of each run found, in their original relative order. It takes time -// proportional to len(vs) and does not allocate storage outside the slice. +// element of each run found. // -// The returned slice will contain (non-consecutive) duplicates only if -// vs is not in sorted order at input. If vs is sorted at input (in either -// direction), the elements of the prefix are exactly the unique first elements -// of the input. +// Deprecated: Use the equivalent [slices.Compact] instead. func Dedup[T comparable](vs []T) []T { - if len(vs) <= 1 { - return vs - } - - // Setup: - // i : the location of the first element of the next run - // j : runs forward from i looking for the end of the run - // - i, j := 0, 1 - for { - // Scan forward from i for an element different from vs[i]. - for j < len(vs) && vs[i] == vs[j] { - j++ - } - - // If there are no further distinct elements, we're done. The item at - // position i is the beginning of the last run in the slice. - if j == len(vs) { - return vs[:i+1] - } - - // Reaching here, the slice looks like this: - // - // [a b c d d d d d d d e ? ? ?] - // 0 i j n - // - // where a, b, c, d are distinct from their neighbors. - - // Otherwise, we have found the first item of a new run at j. - // Move i forward to the next slot and (if necessary) swap vs[j] into it. - i++ - if j > i { - // A swap is unnecessary (though harmless) if j is already the next slot. - vs[i], vs[j] = vs[j], vs[i] - } - j++ - - // Now: - // swapped - // v-----------v - // [a b c d e d d d d d d ? ? ?] - // 0 i j n - } + return slices.Compact(vs) } // Reverse reverses the contents of vs in-place. diff --git a/slice/slice_test.go b/slice/slice_test.go index f95c9df..1a9845c 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -62,27 +62,6 @@ func TestPartition(t *testing.T) { } } -func TestDedup(t *testing.T) { - for _, test := range []testCase[int]{ - {"Nil", nil, nil, nil}, - {"Empty", []int{}, nil, nil}, - {"One", []int{100}, []int{100}, nil}, - {"NoRuns", []int{1, 3, 2, 4}, []int{1, 3, 2, 4}, nil}, - {"Single", []int{5, 5, 5, 5, 5}, []int{5}, nil}, - {"Two", []int{2, 2, 2, 3, 3, 3}, []int{2, 3}, nil}, - {"Repeat", []int{1, 3, 3, 1}, []int{1, 3, 1}, nil}, - {"NoRunsAsc", []int{0, 1, 2, 3, 4}, []int{0, 1, 2, 3, 4}, nil}, - {"NoRunsDesc", []int{10, 9, 8, 7}, []int{10, 9, 8, 7}, nil}, - {"RunsAsc", []int{0, 1, 1, 1, 2, 2, 3}, []int{0, 1, 2, 3}, nil}, - {"RunsDesc", []int{5, 5, 5, 3, 3, 1, 1, 0}, []int{5, 3, 1, 0}, nil}, - - // Runs: a b--- c b--- d--- e--------- - {"Unsorted", []int{1, 0, 0, 9, 0, 0, 3, 3, 2, 2, 2, 2}, []int{1, 0, 9, 0, 3, 2}, nil}, - } { - t.Run(test.desc, test.dedup) - } -} - func TestReverse(t *testing.T) { tests := []struct { name string @@ -542,20 +521,6 @@ func (tc *testCase[T]) partition(t *testing.T) { } } -func (tc *testCase[T]) dedup(t *testing.T) { - t.Helper() - - cp := copyOf(tc.input) - t.Logf("Input: %+v", cp) - - got := slice.Dedup(cp) - t.Logf("After dedup: %+v ~ %+v", got, cp[len(got):]) - diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()) - if diff != "" { - t.Errorf("Dedup result (-want, +got)\n%s", diff) - } -} - func copyOf[T any](vs []T) []T { out := make([]T, len(vs)) copy(out, vs)