diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index dbf1cfc..240ef00 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -19,6 +19,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.DistinctUntilChanged.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.DistinctUntilChanged.Tests.fs new file mode 100644 index 0000000..e26e50d --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.DistinctUntilChanged.Tests.fs @@ -0,0 +1,39 @@ +module TaskSeq.Tests.DistinctUntilChanged + +open Xunit +open FsUnit.Xunit + +open FSharp.Control + +// +// TaskSeq.distinctUntilChanged +// + + +module EmptySeq = + [] + let ``TaskSeq-distinctUntilChanged with null source raises`` () = assertNullArg <| fun () -> TaskSeq.distinctUntilChanged null + + [)>] + let ``TaskSeq-distinctUntilChanged has no effect`` variant = task { + do! + Gen.getEmptyVariant variant + |> TaskSeq.distinctUntilChanged + |> TaskSeq.toListAsync + |> Task.map (List.isEmpty >> should be True) + } + +module Functionality = + [] + let ``TaskSeq-distinctUntilChanged should return no consecutive duplicates`` () = task { + let ts = + [ 'A'; 'A'; 'B'; 'Z'; 'C'; 'C'; 'Z'; 'C'; 'D'; 'D'; 'D'; 'Z' ] + |> TaskSeq.ofList + + let! xs = ts |> TaskSeq.distinctUntilChanged |> TaskSeq.toListAsync + + xs + |> List.map string + |> String.concat "" + |> should equal "ABZCZCDZ" + } diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index 710dadd..32c159f 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -358,6 +358,8 @@ type TaskSeq private () = static member except itemsToExclude source = Internal.except itemsToExclude source static member exceptOfSeq itemsToExclude source = Internal.exceptOfSeq itemsToExclude source + static member distinctUntilChanged source = Internal.distinctUntilChanged source + static member forall predicate source = Internal.forall (Predicate predicate) source static member forallAsync predicate source = Internal.forall (PredicateAsync predicate) source diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index ff98586..76e90c2 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -1297,6 +1297,16 @@ type TaskSeq = /// Thrown when either of the two input task sequences is null. static member exceptOfSeq<'T when 'T: equality> : itemsToExclude: seq<'T> -> source: TaskSeq<'T> -> TaskSeq<'T> + /// + /// Returns a new task sequence without consecutive duplicate elements. + /// + /// + /// The input task sequence whose consecutive duplicates will be removed. + /// A sequence without consecutive duplicates elements. + /// + /// Thrown when the input task sequences is null. + static member distinctUntilChanged<'T when 'T: equality> : source: TaskSeq<'T> -> TaskSeq<'T> + /// /// Combines the two task sequences into a new task sequence of pairs. The two sequences need not have equal lengths: /// when one sequence is exhausted any remaining elements in the other sequence are ignored. diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index 66a92f2..2509a28 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -1097,3 +1097,22 @@ module internal TaskSeqInternal = go <- step } + + let distinctUntilChanged (source: TaskSeq<_>) = + checkNonNull (nameof source) source + + taskSeq { + let mutable maybePrevious = ValueNone + + for current in source do + match maybePrevious with + | ValueNone -> + yield current + maybePrevious <- ValueSome current + | ValueSome previous -> + if previous = current then + () // skip + else + yield current + maybePrevious <- ValueSome current + }