Skip to content

Commit

Permalink
Merge pull request #9087 from ProofOfKeags/fn/list-filter
Browse files Browse the repository at this point in the history
fn: add Filter to List
  • Loading branch information
Roasbeef authored Sep 28, 2024
2 parents 6485a81 + f7264e6 commit 1acc839
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 0 deletions.
15 changes: 15 additions & 0 deletions fn/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,18 @@ func (l *List[A]) PushFrontList(other *List[A]) {
n = n.Prev()
}
}

// Filter gives a slice of all of the node values that satisfy the given
// predicate.
func (l *List[A]) Filter(f Pred[A]) []A {
var acc []A

for cursor := l.Front(); cursor != nil; cursor = cursor.Next() {
a := cursor.Value
if f(a) {
acc = append(acc, a)
}
}

return acc
}
93 changes: 93 additions & 0 deletions fn/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"reflect"
"testing"
"testing/quick"

"github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
)

func GenList(r *rand.Rand) *List[uint32] {
Expand Down Expand Up @@ -727,3 +730,93 @@ func TestMoveUnknownMark(t *testing.T) {
checkList(t, &l1, []int{1})
checkList(t, &l2, []int{2})
}

// TestFilterIdempotence ensures that the slice coming out of List.Filter is
// the same as that slice filtered again by the same predicate.
func TestFilterIdempotence(t *testing.T) {
require.NoError(
t, quick.Check(
func(l *List[uint32], modSize uint32) bool {
pred := func(a uint32) bool {
return a%modSize != 0
}

filtered := l.Filter(pred)

filteredAgain := Filter(pred, filtered)

return slices.Equal(filtered, filteredAgain)
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(
r.Uint32()%5 + 1,
)
},
},
),
)
}

// TestFilterShrinks ensures that the length of the slice returned from
// List.Filter is never larger than the length of the List.
func TestFilterShrinks(t *testing.T) {
require.NoError(
t, quick.Check(
func(l *List[uint32], modSize uint32) bool {
pred := func(a uint32) bool {
return a%modSize != 0
}

filteredSize := len(l.Filter(pred))

return filteredSize <= l.Len()
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(
r.Uint32()%5 + 1,
)
},
},
),
)
}

// TestFilterLawOfExcludedMiddle ensures that if we intersect a List.Filter
// with its negation that the intersection is the empty set.
func TestFilterLawOfExcludedMiddle(t *testing.T) {
require.NoError(
t, quick.Check(
func(l *List[uint32], modSize uint32) bool {
pred := func(a uint32) bool {
return a%modSize != 0
}

negatedPred := func(a uint32) bool {
return !pred(a)
}

positive := NewSet(l.Filter(pred)...)
negative := NewSet(l.Filter(negatedPred)...)

return positive.Intersect(negative).Equal(
NewSet[uint32](),
)
},
&quick.Config{
Values: func(vs []reflect.Value, r *rand.Rand) {
l := GenList(r)
vs[0] = reflect.ValueOf(l)
vs[1] = reflect.ValueOf(
r.Uint32()%5 + 1,
)
},
},
),
)
}

0 comments on commit 1acc839

Please sign in to comment.