Skip to content

Commit f7264e6

Browse files
committed
fn: add Filter to List
This commit adds an immutable Filter method to the linked List API. This is useful because there are several instances wherein we iterate through the linked List and only process a subset of it in some way or another.
1 parent 779903a commit f7264e6

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)