@@ -6,7 +6,7 @@ open FsUnit.Xunit
66open FSharp.Control
77
88//
9- // TaskSeq.distinctUntilChanged
9+ // TaskSeq.distinctUntilChanged / distinctUntilChangedWith / distinctUntilChangedWithAsync
1010//
1111
1212
@@ -23,6 +23,34 @@ module EmptySeq =
2323 |> Task.map ( List.isEmpty >> should be True)
2424 }
2525
26+ [<Fact>]
27+ let ``TaskSeq - distinctUntilChangedWith with null source raises`` () =
28+ assertNullArg
29+ <| fun () -> TaskSeq.distinctUntilChangedWith ( fun _ _ -> false ) null
30+
31+ [<Theory; ClassData( typeof< TestEmptyVariants>) >]
32+ let ``TaskSeq - distinctUntilChangedWith has no effect on empty`` variant = task {
33+ do !
34+ Gen.getEmptyVariant variant
35+ |> TaskSeq.distinctUntilChangedWith ( fun _ _ -> false )
36+ |> TaskSeq.toListAsync
37+ |> Task.map ( List.isEmpty >> should be True)
38+ }
39+
40+ [<Fact>]
41+ let ``TaskSeq - distinctUntilChangedWithAsync with null source raises`` () =
42+ assertNullArg
43+ <| fun () -> TaskSeq.distinctUntilChangedWithAsync ( fun _ _ -> task { return false }) null
44+
45+ [<Theory; ClassData( typeof< TestEmptyVariants>) >]
46+ let ``TaskSeq - distinctUntilChangedWithAsync has no effect on empty`` variant = task {
47+ do !
48+ Gen.getEmptyVariant variant
49+ |> TaskSeq.distinctUntilChangedWithAsync ( fun _ _ -> task { return false })
50+ |> TaskSeq.toListAsync
51+ |> Task.map ( List.isEmpty >> should be True)
52+ }
53+
2654module Functionality =
2755 [<Fact>]
2856 let ``TaskSeq - distinctUntilChanged should return no consecutive duplicates`` () = task {
@@ -90,6 +118,129 @@ module Functionality =
90118 xs |> should equal [ 1 .. 10 ]
91119 }
92120
121+ [<Fact>]
122+ let ``TaskSeq - distinctUntilChangedWith with structural equality comparer behaves like distinctUntilChanged`` () = task {
123+ let ts =
124+ [ 'A' ; 'A' ; 'B' ; 'Z' ; 'C' ; 'C' ; 'Z' ; 'C' ; 'D' ; 'D' ; 'D' ; 'Z' ]
125+ |> TaskSeq.ofList
126+
127+ let! xs =
128+ ts
129+ |> TaskSeq.distinctUntilChangedWith (=)
130+ |> TaskSeq.toListAsync
131+
132+ xs
133+ |> List.map string
134+ |> String.concat " "
135+ |> should equal " ABZCZCDZ"
136+ }
137+
138+ [<Fact>]
139+ let ``TaskSeq - distinctUntilChangedWith with always - true comparer returns only first element`` () = task {
140+ let! xs =
141+ taskSeq { yield ! [ 1 ; 2 ; 3 ; 4 ; 5 ] }
142+ |> TaskSeq.distinctUntilChangedWith ( fun _ _ -> true )
143+ |> TaskSeq.toListAsync
144+
145+ xs |> should equal [ 1 ]
146+ }
147+
148+ [<Fact>]
149+ let ``TaskSeq - distinctUntilChangedWith with always - false comparer returns all elements`` () = task {
150+ let! xs =
151+ taskSeq { yield ! [ 1 ; 1 ; 2 ; 2 ; 3 ] }
152+ |> TaskSeq.distinctUntilChangedWith ( fun _ _ -> false )
153+ |> TaskSeq.toListAsync
154+
155+ xs |> should equal [ 1 ; 1 ; 2 ; 2 ; 3 ]
156+ }
157+
158+ [<Fact>]
159+ let ``TaskSeq - distinctUntilChangedWith can use custom projection for equality`` () = task {
160+ // Treat values as equal if their absolute difference is <= 1
161+ let closeEnough a b = abs ( a - b) <= 1
162+
163+ let! xs =
164+ taskSeq { yield ! [ 10 ; 11 ; 9 ; 20 ; 21 ; 5 ] }
165+ |> TaskSeq.distinctUntilChangedWith closeEnough
166+ |> TaskSeq.toListAsync
167+
168+ // 10≈11 skip; 11≈9 skip (|11-9|=2? no, |11-9|=2>1, so keep 9); 9 vs 20 keep; 20≈21 skip; 21 vs 5 keep
169+ // Wait: |10-11|=1 skip 11; |10-9|=1 skip 9; 10 vs 20 keep 20; |20-21|=1 skip 21; 20 vs 5 keep 5
170+ xs |> should equal [ 10 ; 20 ; 5 ]
171+ }
172+
173+ [<Fact>]
174+ let ``TaskSeq - distinctUntilChangedWith with single element returns singleton`` () = task {
175+ let! xs =
176+ taskSeq { yield 99 }
177+ |> TaskSeq.distinctUntilChangedWith ( fun _ _ -> true )
178+ |> TaskSeq.toListAsync
179+
180+ xs |> should equal [ 99 ]
181+ }
182+
183+ [<Fact>]
184+ let ``TaskSeq - distinctUntilChangedWith case - insensitive string comparison`` () = task {
185+ let! xs =
186+ taskSeq { yield ! [ " Hello" ; " hello" ; " HELLO" ; " World" ; " world" ] }
187+ |> TaskSeq.distinctUntilChangedWith ( fun a b -> System.String.Compare( a, b, System.StringComparison.OrdinalIgnoreCase) = 0 )
188+ |> TaskSeq.toListAsync
189+
190+ xs |> should equal [ " Hello" ; " World" ]
191+ }
192+
193+ [<Fact>]
194+ let ``TaskSeq - distinctUntilChangedWithAsync with structural equality behaves like distinctUntilChanged`` () = task {
195+ let ts = [ 1 ; 1 ; 2 ; 3 ; 3 ; 4 ] |> TaskSeq.ofList
196+
197+ let! xs =
198+ ts
199+ |> TaskSeq.distinctUntilChangedWithAsync ( fun a b -> task { return a = b })
200+ |> TaskSeq.toListAsync
201+
202+ xs |> should equal [ 1 ; 2 ; 3 ; 4 ]
203+ }
204+
205+ [<Fact>]
206+ let ``TaskSeq - distinctUntilChangedWithAsync with always - true async comparer returns only first element`` () = task {
207+ let! xs =
208+ taskSeq { yield ! [ 10 ; 20 ; 30 ] }
209+ |> TaskSeq.distinctUntilChangedWithAsync ( fun _ _ -> task { return true })
210+ |> TaskSeq.toListAsync
211+
212+ xs |> should equal [ 10 ]
213+ }
214+
215+ [<Fact>]
216+ let ``TaskSeq - distinctUntilChangedWithAsync with always - false async comparer returns all elements`` () = task {
217+ let! xs =
218+ taskSeq { yield ! [ 5 ; 5 ; 5 ] }
219+ |> TaskSeq.distinctUntilChangedWithAsync ( fun _ _ -> task { return false })
220+ |> TaskSeq.toListAsync
221+
222+ xs |> should equal [ 5 ; 5 ; 5 ]
223+ }
224+
225+ [<Fact>]
226+ let ``TaskSeq - distinctUntilChangedWithAsync can perform async work in comparer`` () = task {
227+ let mutable comparerCallCount = 0
228+
229+ let asyncComparer a b = task {
230+ comparerCallCount <- comparerCallCount + 1
231+ return a = b
232+ }
233+
234+ let! xs =
235+ taskSeq { yield ! [ 1 ; 1 ; 2 ; 2 ; 3 ] }
236+ |> TaskSeq.distinctUntilChangedWithAsync asyncComparer
237+ |> TaskSeq.toListAsync
238+
239+ xs |> should equal [ 1 ; 2 ; 3 ]
240+ // comparer called for each pair of consecutive elements (4 pairs for 5 elements)
241+ comparerCallCount |> should equal 4
242+ }
243+
93244module SideEffects =
94245 [<Fact>]
95246 let ``TaskSeq - distinctUntilChanged consumes every element exactly once`` () = task {
@@ -132,3 +283,41 @@ module SideEffects =
132283
133284 xs |> should equal [ 1 .. 10 ]
134285 }
286+
287+ [<Fact>]
288+ let ``TaskSeq - distinctUntilChangedWith consumes every element exactly once`` () = task {
289+ let mutable count = 0
290+
291+ let ts = taskSeq {
292+ for i in 1 .. 5 do
293+ count <- count + 1
294+ yield i
295+ }
296+
297+ let! xs =
298+ ts
299+ |> TaskSeq.distinctUntilChangedWith ( fun a b -> a = b)
300+ |> TaskSeq.toListAsync
301+
302+ count |> should equal 5
303+ xs |> should equal [ 1 ; 2 ; 3 ; 4 ; 5 ]
304+ }
305+
306+ [<Fact>]
307+ let ``TaskSeq - distinctUntilChangedWithAsync consumes every element exactly once`` () = task {
308+ let mutable count = 0
309+
310+ let ts = taskSeq {
311+ for i in 1 .. 5 do
312+ count <- count + 1
313+ yield i
314+ }
315+
316+ let! xs =
317+ ts
318+ |> TaskSeq.distinctUntilChangedWithAsync ( fun a b -> task { return a = b })
319+ |> TaskSeq.toListAsync
320+
321+ count |> should equal 5
322+ xs |> should equal [ 1 ; 2 ; 3 ; 4 ; 5 ]
323+ }
0 commit comments