Skip to content
Open
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
47 changes: 31 additions & 16 deletions src/FSharp.Collections.Immutable/SortedSet/SortedSet.fs
Original file line number Diff line number Diff line change
Expand Up @@ -146,40 +146,55 @@ let contains (element : 'T) (set : SortedSet<'T>) =
checkNotNull (nameof set) set
set.Contains element

// exists and forall loop over the struct enumerator of the sorted set, which allocates nothing. LINQ Any and All allocated
// 152 bytes per call, a delegate and the boxed enumerator, and Seq.exists and Seq.forall 64 bytes, the boxed enumerator
// (measured in Release; a measured faster path replaces a higher priority, D5). Dispose returns the pooled stack of the
// enumerator.
// exists and forall loop over the struct enumerator of the sorted set for a value type, which allocates nothing. LINQ Any and
// All allocated 152 bytes per call, a delegate and the boxed enumerator, and Seq.exists and Seq.forall 64 bytes, the boxed
// enumerator (measured in Release; a measured faster path replaces a higher priority, D5). A set of a reference type is walked
// through the interface instead, at the price of that boxed enumerator: the code that all reference types share needs a
// lookup of the exact type at run time for each call of a member of the generic struct enumerator, and for strings of 100 to
// 100 000 elements exists and forall took 0.47 to 0.57 times as long, where for integers the interface took 1.3 to 1.7 times
// as long. Dispose returns the pooled stack of the enumerator.

[<CompiledName "Exists">]
let exists (predicate : 'T -> bool) (set : SortedSet<'T>) =
checkNotNull (nameof set) set
let mutable enumerator = set.GetEnumerator ()
let mutable found = false

try
let mutable found = false
if typeof<'T>.IsValueType then
let mutable enumerator = set.GetEnumerator ()

try
while not found && enumerator.MoveNext () do
found <- predicate enumerator.Current
finally
enumerator.Dispose ()
else
use enumerator = (set :> seq<'T>).GetEnumerator()

while not found && enumerator.MoveNext () do
found <- predicate enumerator.Current

found
finally
enumerator.Dispose ()
found

[<CompiledName "ForAll">]
let forall (predicate : 'T -> bool) (set : SortedSet<'T>) =
checkNotNull (nameof set) set
let mutable enumerator = set.GetEnumerator ()
let mutable satisfied = true

try
let mutable satisfied = true
if typeof<'T>.IsValueType then
let mutable enumerator = set.GetEnumerator ()

try
while satisfied && enumerator.MoveNext () do
satisfied <- predicate enumerator.Current
finally
enumerator.Dispose ()
else
use enumerator = (set :> seq<'T>).GetEnumerator()

while satisfied && enumerator.MoveNext () do
satisfied <- predicate enumerator.Current

satisfied
finally
enumerator.Dispose ()
satisfied

[<CompiledName "IsProperSubset">]
let isProperSubset (set1 : SortedSet<'T>) (set2 : SortedSet<'T>) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,87 @@ type SortedSetTests () =
"SortedSet.maxElement of an empty sorted set must throw"
)

// ---- Search ----

// exists and forall walk a set of a reference type through the interface and a set of a value type through the struct
// enumerator, and the contracts run on integers, so strings have tests of their own

[<TestMethod;
DataRow(0, "absent");
DataRow(1, "first");
DataRow(5, "first");
DataRow(5, "middle");
DataRow(5, "last");
DataRow(5, "absent");
DataRow(2000, "middle");
DataRow(2000, "absent")>]
member _.``exists and forall of strings give the results of Set`` (count : int, position : string) =
let items = Array.init count (fun index -> "x" + index.ToString ())

let target =
match position with
| "first" -> "x0"
| "middle" -> "x" + (count / 2).ToString()
| "last" -> "x" + (count - 1).ToString()
| _ -> "absent"

let set = SortedSet.ofArray items
let expected = Set.ofArray items

Assert.AreEqual (
Set.exists ((=) target) expected,
SortedSet.exists ((=) target) set,
"SortedSet.exists must find the element as Set.exists does"
)

Assert.AreEqual (
Set.forall ((<>) target) expected,
SortedSet.forall ((<>) target) set,
"SortedSet.forall must reject the element as Set.forall does"
)

Assert.AreEqual (
Set.exists (fun (text : string) -> text.Length > 0) expected,
SortedSet.exists (fun (text : string) -> text.Length > 0) set,
"SortedSet.exists must answer for a predicate that fits some elements as Set.exists does"
)

Assert.AreEqual (
Set.forall (fun (text : string) -> text.Length > 0) expected,
SortedSet.forall (fun (text : string) -> text.Length > 0) set,
"SortedSet.forall must answer for a predicate that fits all elements as Set.forall does"
)

[<TestMethod>]
member _.``exists of strings stops at the first match`` () =
let calls = ref 0

let found =
SortedSet.exists
(fun word ->
calls.Value <- calls.Value + 1
word = "A"
)
(SortedSet.ofArray words)

Assert.IsTrue (found, "SortedSet.exists must find the first string in the order")
Assert.AreEqual (1, calls.Value, "SortedSet.exists must not call the predicate after the first match")

[<TestMethod>]
member _.``forall of strings stops at the first element that fails`` () =
let calls = ref 0

let holds =
SortedSet.forall
(fun word ->
calls.Value <- calls.Value + 1
word <> "A"
)
(SortedSet.ofArray words)

Assert.IsFalse (holds, "SortedSet.forall must fail on the first string in the order")
Assert.AreEqual (1, calls.Value, "SortedSet.forall must not call the predicate after the first failure")

// ---- Null inputs ----

[<TestMethod; DynamicData(nameof SortedSetTests.NullRejections)>]
Expand Down