Skip to content

Commit

Permalink
slice: deprecate Dedup, shim to the equivalent stdlib slices.Compact
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
danderson committed Jul 25, 2024
1 parent c15c30a commit d711d76
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 84 deletions.
54 changes: 5 additions & 49 deletions slice/slice.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand Down
35 changes: 0 additions & 35 deletions slice/slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit d711d76

Please sign in to comment.