Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve takewhile, takeWhileInclusive and skipwhile, skipWhileInclusive #235

Merged
merged 3 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading