From 22c30ce46aceb1a39c84162129acbe4e2302a2fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 01:12:21 +0000 Subject: [PATCH 1/2] feat: add fold, map, filter, iter, exists, forall, toList, toArray, ofList, ofArray, choose to Heap module Add 11 missing collection-style functions to the Heap module, consistent with FSharp.Collections patterns and addressing the gap identified in #152. All functions are implemented as inline wrappers around toSeq/ofSeq, which means: - toList/toArray return elements in sorted order (ascending or descending) - ofList/ofArray build a heap from a list/array - fold/iter/exists/forall traverse elements in sorted order - map/filter/choose return new heaps preserving the sort direction 18 new tests covering all added functions, all passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharpx.Collections/PriorityQueue.fs | 44 +++++++++ tests/FSharpx.Collections.Tests/HeapTest.fs | 98 ++++++++++++++++++++- 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/src/FSharpx.Collections/PriorityQueue.fs b/src/FSharpx.Collections/PriorityQueue.fs index 1c9046d2..7938e003 100644 --- a/src/FSharpx.Collections/PriorityQueue.fs +++ b/src/FSharpx.Collections/PriorityQueue.fs @@ -335,6 +335,50 @@ module Heap = let inline tryUncons(xs: Heap<'T>) = xs.TryUncons() + ///O(n). Returns a sorted list of all elements. + let inline toList(xs: Heap<'T>) = + xs |> toSeq |> Seq.toList + + ///O(n). Returns a sorted array of all elements. + let inline toArray(xs: Heap<'T>) = + xs |> toSeq |> Seq.toArray + + ///O(n log n). Returns heap from a list. + let inline ofList isDescending (xs: 'T list) = + ofSeq isDescending xs + + ///O(n log n). Returns heap from an array. + let inline ofArray isDescending (xs: 'T array) = + ofSeq isDescending xs + + ///O(n). Applies a function to each element in sorted order, threading an accumulator. + let inline fold (f: 'State -> 'T -> 'State) (state: 'State) (xs: Heap<'T>) = + xs |> toSeq |> Seq.fold f state + + ///O(n). Applies a function to each element in sorted order. + let inline iter (f: 'T -> unit) (xs: Heap<'T>) = + xs |> toSeq |> Seq.iter f + + ///O(n). Returns true if any element satisfies the predicate. + let inline exists (f: 'T -> bool) (xs: Heap<'T>) = + xs |> toSeq |> Seq.exists f + + ///O(n). Returns true if all elements satisfy the predicate. + let inline forall (f: 'T -> bool) (xs: Heap<'T>) = + xs |> toSeq |> Seq.forall f + + ///O(n log n). Returns a new heap with elements mapped by the function, preserving sort direction. + let inline map (f: 'T -> 'U) (xs: Heap<'T>) : Heap<'U> = + xs |> toSeq |> Seq.map f |> ofSeq(isDescending xs) + + ///O(n log n). Returns a new heap containing only elements satisfying the predicate. + let inline filter (f: 'T -> bool) (xs: Heap<'T>) = + xs |> toSeq |> Seq.filter f |> ofSeq(isDescending xs) + + ///O(n log n). Applies an option-returning function to each element; returns a new heap of the Some values. + let inline choose (f: 'T -> 'U option) (xs: Heap<'T>) : Heap<'U> = + xs |> toSeq |> Seq.choose f |> ofSeq(isDescending xs) + [] module PriorityQueue = ///O(1). Returns a empty queue, with indicated ordering. diff --git a/tests/FSharpx.Collections.Tests/HeapTest.fs b/tests/FSharpx.Collections.Tests/HeapTest.fs index 36e56c5f..c0dce700 100644 --- a/tests/FSharpx.Collections.Tests/HeapTest.fs +++ b/tests/FSharpx.Collections.Tests/HeapTest.fs @@ -482,4 +482,100 @@ module HeapTests = (Prop.forAll(Arb.fromGen intGensStart2.[5]) <| fun (h, l) -> let x, tl = h.Uncons() - x = l.Head && tl.Length = (l.Length - 1)) ] + x = l.Head && tl.Length = (l.Length - 1)) + + test "toList ascending returns sorted list" { + let h = Heap.ofSeq false [ 3; 1; 4; 1; 5; 9; 2; 6 ] + Expect.equal "toList asc" [ 1; 1; 2; 3; 4; 5; 6; 9 ] (Heap.toList h) + } + + test "toList descending returns reverse-sorted list" { + let h = Heap.ofSeq true [ 3; 1; 4; 1; 5; 9; 2; 6 ] + Expect.equal "toList desc" [ 9; 6; 5; 4; 3; 2; 1; 1 ] (Heap.toList h) + } + + test "toArray ascending returns sorted array" { + let h = Heap.ofSeq false [ 5; 3; 1; 4; 2 ] + Expect.equal "toArray asc" [| 1; 2; 3; 4; 5 |] (Heap.toArray h) + } + + test "ofList round-trips through toList" { + let xs = [ 7; 3; 5; 1; 9; 2; 4; 8; 6 ] + let h = Heap.ofList false xs + Expect.equal "ofList round-trip" (List.sort xs) (Heap.toList h) + } + + test "ofArray round-trips through toArray" { + let xs = [| 7; 3; 5; 1; 9; 2; 4; 8; 6 |] + let h = Heap.ofArray false xs + Expect.equal "ofArray round-trip" (Array.sort xs) (Heap.toArray h) + } + + test "fold accumulates in sorted order" { + let h = Heap.ofSeq false [ 3; 1; 2 ] + let result = Heap.fold (fun acc x -> acc @ [ x ]) [] h + Expect.equal "fold ascending" [ 1; 2; 3 ] result + } + + test "iter visits elements in sorted order" { + let h = Heap.ofSeq false [ 3; 1; 2 ] + let mutable result = [] + Heap.iter (fun x -> result <- result @ [ x ]) h + Expect.equal "iter ascending" [ 1; 2; 3 ] result + } + + test "exists returns true when predicate matches" { + let h = Heap.ofSeq false [ 1; 2; 3; 4; 5 ] + Expect.isTrue "exists" (Heap.exists (fun x -> x = 3) h) + } + + test "exists returns false when predicate does not match" { + let h = Heap.ofSeq false [ 1; 2; 3; 4; 5 ] + Expect.isFalse "exists" (Heap.exists (fun x -> x = 6) h) + } + + test "forall returns true when all match" { + let h = Heap.ofSeq false [ 2; 4; 6; 8 ] + Expect.isTrue "forall" (Heap.forall (fun x -> x % 2 = 0) h) + } + + test "forall returns false when some do not match" { + let h = Heap.ofSeq false [ 2; 3; 4 ] + Expect.isFalse "forall" (Heap.forall (fun x -> x % 2 = 0) h) + } + + test "map transforms elements" { + let h = Heap.ofSeq false [ 1; 2; 3; 4; 5 ] + let mapped = Heap.map (fun x -> x * 2) h + Expect.equal "map" [ 2; 4; 6; 8; 10 ] (Heap.toList mapped) + } + + test "map preserves sort direction" { + let h = Heap.ofSeq true [ 1; 2; 3 ] + let mapped = Heap.map (fun x -> x * 10) h + Expect.equal "map desc" [ 30; 20; 10 ] (Heap.toList mapped) + } + + test "filter keeps only matching elements" { + let h = Heap.ofSeq false [ 1; 2; 3; 4; 5; 6 ] + let filtered = Heap.filter (fun x -> x % 2 = 0) h + Expect.equal "filter" [ 2; 4; 6 ] (Heap.toList filtered) + } + + test "filter empty heap returns empty heap" { + let h = Heap.empty false + let filtered = Heap.filter (fun x -> x > 0) h + Expect.isTrue "filter empty" (Heap.isEmpty filtered) + } + + test "choose keeps Some values" { + let h = Heap.ofSeq false [ 1; 2; 3; 4; 5 ] + let chosen = Heap.choose (fun x -> if x % 2 = 0 then Some(x * 10) else None) h + Expect.equal "choose" [ 20; 40 ] (Heap.toList chosen) + } + + test "choose all None returns empty heap" { + let h = Heap.ofSeq false [ 1; 3; 5 ] + let chosen = Heap.choose (fun _ -> None) h + Expect.isTrue "choose none" (Heap.isEmpty chosen) + } ] From 6ce1ba5d43af5e9ade7036437a95b44193e95b00 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 01:25:20 +0000 Subject: [PATCH 2/2] ci: trigger checks