Skip to content

Commit

Permalink
Merge pull request #235 from fsprojects/improve-takewhile-skipwhile
Browse files Browse the repository at this point in the history
Improve `takewhile`, `takeWhileInclusive` and `skipwhile`, `skipWhileInclusive`
  • Loading branch information
abelbraaksma committed Mar 15, 2024
2 parents 9338100 + 56f4dbe commit 988ac7f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 125 deletions.
16 changes: 8 additions & 8 deletions src/FSharp.Control.TaskSeq/TaskSeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,14 @@ type TaskSeq private () =
static member take count source = Internal.skipOrTake Take count source
static member truncate count source = Internal.skipOrTake Truncate count source

static member takeWhile predicate source = Internal.takeWhile Exclusive (Predicate predicate) source
static member takeWhileAsync predicate source = Internal.takeWhile Exclusive (PredicateAsync predicate) source
static member takeWhileInclusive predicate source = Internal.takeWhile Inclusive (Predicate predicate) source
static member takeWhileInclusiveAsync predicate source = Internal.takeWhile Inclusive (PredicateAsync predicate) source
static member skipWhile predicate source = Internal.skipWhile Exclusive (Predicate predicate) source
static member skipWhileAsync predicate source = Internal.skipWhile Exclusive (PredicateAsync predicate) source
static member skipWhileInclusive predicate source = Internal.skipWhile Inclusive (Predicate predicate) source
static member skipWhileInclusiveAsync predicate source = Internal.skipWhile Inclusive (PredicateAsync predicate) source
static member takeWhile predicate source = Internal.takeWhile false (Predicate predicate) source
static member takeWhileAsync predicate source = Internal.takeWhile false (PredicateAsync predicate) source
static member takeWhileInclusive predicate source = Internal.takeWhile true (Predicate predicate) source
static member takeWhileInclusiveAsync predicate source = Internal.takeWhile true (PredicateAsync predicate) source
static member skipWhile predicate source = Internal.skipWhile false (Predicate predicate) source
static member skipWhileAsync predicate source = Internal.skipWhile false (PredicateAsync predicate) source
static member skipWhileInclusive predicate source = Internal.skipWhile true (Predicate predicate) source
static member skipWhileInclusiveAsync predicate source = Internal.skipWhile true (PredicateAsync predicate) source

static member tryPick chooser source = Internal.tryPick (TryPick chooser) source
static member tryPickAsync chooser source = Internal.tryPick (TryPickAsync chooser) source
Expand Down
163 changes: 46 additions & 117 deletions src/FSharp.Control.TaskSeq/TaskSeqInternal.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ type internal AsyncEnumStatus =
| WithCurrent
| AfterAll

[<Struct>]
type internal WhileKind =
/// The item under test is included (or skipped) even when the predicate returns false
| Inclusive
/// The item under test is always excluded (or not skipped)
| Exclusive

[<Struct>]
type internal TakeOrSkipKind =
/// use the Seq.take semantics, raises exception if not enough elements
Expand Down Expand Up @@ -796,140 +789,76 @@ module internal TaskSeqInternal =

}

let takeWhile whileKind predicate (source: TaskSeq<_>) =
let takeWhile isInclusive predicate (source: TaskSeq<_>) =
checkNonNull (nameof source) source

taskSeq {
use e = source.GetAsyncEnumerator CancellationToken.None
let! notEmpty = e.MoveNextAsync()
let mutable more = notEmpty

match whileKind, predicate with
| Exclusive, Predicate predicate -> // takeWhile
while more do
let value = e.Current
more <- predicate value

if more then
// yield ONLY if predicate is true
yield value
let! hasMore = e.MoveNextAsync()
more <- hasMore
let mutable hasMore = notEmpty

| Inclusive, Predicate predicate -> // takeWhileInclusive
while more do
let value = e.Current
more <- predicate value

// yield regardless of result of predicate
yield value

if more then
let! hasMore = e.MoveNextAsync()
more <- hasMore
match predicate with
| Predicate synchronousPredicate ->
while hasMore && synchronousPredicate e.Current do
yield e.Current
let! cont = e.MoveNextAsync()
hasMore <- cont

| Exclusive, PredicateAsync predicate -> // takeWhileAsync
while more do
let value = e.Current
let! passed = predicate value
more <- passed
| PredicateAsync asyncPredicate ->
let mutable predicateHolds = true

if more then
// yield ONLY if predicate is true
yield value
let! hasMore = e.MoveNextAsync()
more <- hasMore
while hasMore && predicateHolds do // TODO: check perf if `while!` is going to be better or equal
let! predicateIsTrue = asyncPredicate e.Current

| Inclusive, PredicateAsync predicate -> // takeWhileInclusiveAsync
while more do
let value = e.Current
let! passed = predicate value
more <- passed
if predicateIsTrue then
yield e.Current
let! cont = e.MoveNextAsync()
hasMore <- cont

// yield regardless of predicate
yield value
predicateHolds <- predicateIsTrue

if more then
let! hasMore = e.MoveNextAsync()
more <- hasMore
// "inclusive" means: always return the item that we pulled, regardless of the result of applying the predicate
// and only stop thereafter. The non-inclusive versions, in contrast, do not return the item under which the predicate is false.
if hasMore && isInclusive then
yield e.Current
}

let skipWhile whileKind predicate (source: TaskSeq<_>) =
let skipWhile isInclusive predicate (source: TaskSeq<_>) =
checkNonNull (nameof source) source

taskSeq {
use e = source.GetAsyncEnumerator CancellationToken.None
let! moveFirst = e.MoveNextAsync()
let mutable more = moveFirst

match whileKind, predicate with
| Exclusive, Predicate predicate -> // skipWhile
while more && predicate e.Current do
let! hasMore = e.MoveNextAsync()
more <- hasMore

if more then
// yield the last one where the predicate was false
// (this ensures we skip 0 or more)
yield e.Current

while! e.MoveNextAsync() do // get the rest
yield e.Current

| Inclusive, Predicate predicate -> // skipWhileInclusive
while more && predicate e.Current do
let! hasMore = e.MoveNextAsync()
more <- hasMore

if more then
// yield the rest (this ensures we skip 1 or more)
while! e.MoveNextAsync() do
yield e.Current

| Exclusive, PredicateAsync predicate -> // skipWhileAsync
let mutable cont = true

if more then
let! hasMore = predicate e.Current
cont <- hasMore

while more && cont do
let! moveNext = e.MoveNextAsync()

if moveNext then
let! hasMore = predicate e.Current
cont <- hasMore

more <- moveNext

if more then
// yield the last one where the predicate was false
// (this ensures we skip 0 or more)
yield e.Current
let! notEmpty = e.MoveNextAsync()
let mutable hasMore = notEmpty

while! e.MoveNextAsync() do // get the rest
yield e.Current
match predicate with
| Predicate synchronousPredicate ->
while hasMore && synchronousPredicate e.Current do
// keep skipping
let! cont = e.MoveNextAsync()
hasMore <- cont

| Inclusive, PredicateAsync predicate -> // skipWhileInclusiveAsync
let mutable cont = true
| PredicateAsync asyncPredicate ->
let mutable predicateHolds = true

if more then
let! hasMore = predicate e.Current
cont <- hasMore
while hasMore && predicateHolds do // TODO: check perf if `while!` is going to be better or equal
let! predicateIsTrue = asyncPredicate e.Current

while more && cont do
let! moveNext = e.MoveNextAsync()
if predicateIsTrue then
// keep skipping
let! cont = e.MoveNextAsync()
hasMore <- cont

if moveNext then
let! hasMore = predicate e.Current
cont <- hasMore
predicateHolds <- predicateIsTrue

more <- moveNext
// "inclusive" means: always skip the item that we pulled, regardless of the result of applying the predicate
// and only stop thereafter. The non-inclusive versions, in contrast, do not skip the item under which the predicate is false.
if hasMore && not isInclusive then
yield e.Current // don't skip, unless inclusive

if more then
// get the rest, this gives 1 or more semantics
while! e.MoveNextAsync() do
yield e.Current
// propagate the rest
while! e.MoveNextAsync() do
yield e.Current
}

// Consider turning using an F# version of this instead?
Expand Down

0 comments on commit 988ac7f

Please sign in to comment.