Skip to content

Commit 1acc839

Browse files
authored
Merge pull request #9087 from ProofOfKeags/fn/list-filter
fn: add Filter to List
2 parents 6485a81 + f7264e6 commit 1acc839

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

fn/list.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,18 @@ func (l *List[A]) PushFrontList(other *List[A]) {
300300
n = n.Prev()
301301
}
302302
}
303+
304+
// Filter gives a slice of all of the node values that satisfy the given
305+
// predicate.
306+
func (l *List[A]) Filter(f Pred[A]) []A {
307+
var acc []A
308+
309+
for cursor := l.Front(); cursor != nil; cursor = cursor.Next() {
310+
a := cursor.Value
311+
if f(a) {
312+
acc = append(acc, a)
313+
}
314+
}
315+
316+
return acc
317+
}

fn/list_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"reflect"
66
"testing"
77
"testing/quick"
8+
9+
"github.com/stretchr/testify/require"
10+
"golang.org/x/exp/slices"
811
)
912

1013
func GenList(r *rand.Rand) *List[uint32] {
@@ -727,3 +730,93 @@ func TestMoveUnknownMark(t *testing.T) {
727730
checkList(t, &l1, []int{1})
728731
checkList(t, &l2, []int{2})
729732
}
733+
734+
// TestFilterIdempotence ensures that the slice coming out of List.Filter is
735+
// the same as that slice filtered again by the same predicate.
736+
func TestFilterIdempotence(t *testing.T) {
737+
require.NoError(
738+
t, quick.Check(
739+
func(l *List[uint32], modSize uint32) bool {
740+
pred := func(a uint32) bool {
741+
return a%modSize != 0
742+
}
743+
744+
filtered := l.Filter(pred)
745+
746+
filteredAgain := Filter(pred, filtered)
747+
748+
return slices.Equal(filtered, filteredAgain)
749+
},
750+
&quick.Config{
751+
Values: func(vs []reflect.Value, r *rand.Rand) {
752+
l := GenList(r)
753+
vs[0] = reflect.ValueOf(l)
754+
vs[1] = reflect.ValueOf(
755+
r.Uint32()%5 + 1,
756+
)
757+
},
758+
},
759+
),
760+
)
761+
}
762+
763+
// TestFilterShrinks ensures that the length of the slice returned from
764+
// List.Filter is never larger than the length of the List.
765+
func TestFilterShrinks(t *testing.T) {
766+
require.NoError(
767+
t, quick.Check(
768+
func(l *List[uint32], modSize uint32) bool {
769+
pred := func(a uint32) bool {
770+
return a%modSize != 0
771+
}
772+
773+
filteredSize := len(l.Filter(pred))
774+
775+
return filteredSize <= l.Len()
776+
},
777+
&quick.Config{
778+
Values: func(vs []reflect.Value, r *rand.Rand) {
779+
l := GenList(r)
780+
vs[0] = reflect.ValueOf(l)
781+
vs[1] = reflect.ValueOf(
782+
r.Uint32()%5 + 1,
783+
)
784+
},
785+
},
786+
),
787+
)
788+
}
789+
790+
// TestFilterLawOfExcludedMiddle ensures that if we intersect a List.Filter
791+
// with its negation that the intersection is the empty set.
792+
func TestFilterLawOfExcludedMiddle(t *testing.T) {
793+
require.NoError(
794+
t, quick.Check(
795+
func(l *List[uint32], modSize uint32) bool {
796+
pred := func(a uint32) bool {
797+
return a%modSize != 0
798+
}
799+
800+
negatedPred := func(a uint32) bool {
801+
return !pred(a)
802+
}
803+
804+
positive := NewSet(l.Filter(pred)...)
805+
negative := NewSet(l.Filter(negatedPred)...)
806+
807+
return positive.Intersect(negative).Equal(
808+
NewSet[uint32](),
809+
)
810+
},
811+
&quick.Config{
812+
Values: func(vs []reflect.Value, r *rand.Rand) {
813+
l := GenList(r)
814+
vs[0] = reflect.ValueOf(l)
815+
vs[1] = reflect.ValueOf(
816+
r.Uint32()%5 + 1,
817+
)
818+
},
819+
},
820+
),
821+
)
822+
}

0 commit comments

Comments
 (0)