From 2957c98f8f13557f27ee56325973bd939b768ea0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:20:38 +0000 Subject: [PATCH 1/2] feat: add ofList, ofArray, tryFind, find, choose, collect, partition, sort, sortWith, sortBy, rev to DList Expand the DList module with 11 standard collection functions: - ofList / ofArray: build a DList from an existing list or array - tryFind / find: search for elements matching a predicate - choose: filter-and-map, returning a new DList of Some values - collect: monadic bind / flat-map over DList - partition: split into two DLists based on a predicate - sort / sortWith / sortBy: O(n log n) sorting via Array round-trip - rev: O(n) reversal via fold Add 11 property-based tests covering all new functions, verifying consistency with the equivalent List operations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharpx.Collections/DList.fs | 51 ++++++++++++ src/FSharpx.Collections/DList.fsi | 34 ++++++++ tests/FSharpx.Collections.Tests/DListTest.fs | 88 +++++++++++++++++++- 3 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/FSharpx.Collections/DList.fs b/src/FSharpx.Collections/DList.fs index 7db24add..6b455cb2 100644 --- a/src/FSharpx.Collections/DList.fs +++ b/src/FSharpx.Collections/DList.fs @@ -279,3 +279,54 @@ module DList = let inline toArray(l: DList<'T>) : 'T[] = Seq.toArray l + + ///O(n). Build a DList from the given list. + let inline ofList(l: 'T list) : DList<'T> = ofSeq l + + ///O(n). Build a DList from the given array. + let inline ofArray(a: 'T array) : DList<'T> = ofSeq a + + ///O(n), worst case. Returns the first element for which the given function returns Some. + let tryFind (predicate: 'T -> bool) (l: DList<'T>) : 'T option = + Seq.tryFind predicate l + + ///O(n), worst case. Returns the first element for which the given function returns true. + /// Raises KeyNotFoundException if no such element exists. + let find (predicate: 'T -> bool) (l: DList<'T>) : 'T = + Seq.find predicate l + + ///O(n). Applies the given function to each element and returns a DList of the values returned + /// by the function where the function returned Some. + let choose (mapping: 'T -> 'U option) (l: DList<'T>) : DList<'U> = + foldBack + (fun x acc -> + match mapping x with + | Some v -> cons v acc + | None -> acc) + l + empty + + ///O(n). For each element, applies the given function to produce a DList, then concatenates all results. + let collect (mapping: 'T -> DList<'U>) (l: DList<'T>) : DList<'U> = + foldBack (fun x acc -> append (mapping x) acc) l empty + + ///O(n). Splits the DList into two DLists: the first contains elements for which the predicate + /// returns true; the second contains those for which it returns false. + let partition (predicate: 'T -> bool) (l: DList<'T>) : DList<'T> * DList<'T> = + foldBack (fun x (yes, no) -> if predicate x then cons x yes, no else yes, cons x no) l (empty, empty) + + ///O(n log n). Returns a new DList sorted using the default comparison. + let sort(l: DList<'T>) : DList<'T> = + l |> toArray |> Array.sort |> ofArray + + ///O(n log n). Returns a new DList sorted using the given comparison function. + let sortWith (comparer: 'T -> 'T -> int) (l: DList<'T>) : DList<'T> = + l |> toArray |> Array.sortWith comparer |> ofArray + + ///O(n log n). Returns a new DList sorted by the given projection. + let sortBy (projection: 'T -> 'Key) (l: DList<'T>) : DList<'T> = + l |> toArray |> Array.sortBy projection |> ofArray + + ///O(n). Returns the DList in reversed order. + let rev(l: DList<'T>) : DList<'T> = + fold (fun acc x -> cons x acc) empty l diff --git a/src/FSharpx.Collections/DList.fsi b/src/FSharpx.Collections/DList.fsi index 894f06c1..15cebe9c 100644 --- a/src/FSharpx.Collections/DList.fsi +++ b/src/FSharpx.Collections/DList.fsi @@ -121,3 +121,37 @@ module DList = ///O(n). Returns an array of the DList elements. val inline toArray: DList<'T> -> 'T[] + + ///O(n). Build a DList from the given list. + val inline ofList: list<'T> -> DList<'T> + + ///O(n). Build a DList from the given array. + val inline ofArray: 'T[] -> DList<'T> + + ///O(n), worst case. Returns the first element for which the given function returns Some. + val tryFind: ('T -> bool) -> DList<'T> -> 'T option + + ///O(n), worst case. Returns the first element for which the given function returns true. + /// Raises KeyNotFoundException if no such element exists. + val find: ('T -> bool) -> DList<'T> -> 'T + + ///O(n). Returns a DList of the values v where the mapping function returns Some(v). + val choose: ('T -> 'U option) -> DList<'T> -> DList<'U> + + ///O(n). For each element, applies the mapping to produce a DList, then concatenates all results. + val collect: ('T -> DList<'U>) -> DList<'T> -> DList<'U> + + ///O(n). Splits the DList into two DLists on the predicate: true elements first, false elements second. + val partition: ('T -> bool) -> DList<'T> -> DList<'T> * DList<'T> + + ///O(n log n). Returns a new DList sorted using the default comparison. + val sort: DList<'T> -> DList<'T> when 'T: comparison + + ///O(n log n). Returns a new DList sorted using the given comparison function. + val sortWith: ('T -> 'T -> int) -> DList<'T> -> DList<'T> + + ///O(n log n). Returns a new DList sorted by the given projection. + val sortBy: ('T -> 'Key) -> DList<'T> -> DList<'T> when 'Key: comparison + + ///O(n). Returns the DList in reversed order. + val rev: DList<'T> -> DList<'T> diff --git a/tests/FSharpx.Collections.Tests/DListTest.fs b/tests/FSharpx.Collections.Tests/DListTest.fs index cfa6fdc0..1edda4ed 100644 --- a/tests/FSharpx.Collections.Tests/DListTest.fs +++ b/tests/FSharpx.Collections.Tests/DListTest.fs @@ -494,4 +494,90 @@ module DListTests = config10k "DList.toArray matches Seq.toArray 0" (Prop.forAll(Arb.fromGen intGensStart1.[0]) - <| fun (q, l) -> DList.toArray q = Array.ofList l) ] + <| fun (q, l) -> DList.toArray q = Array.ofList l) + + testPropertyWithConfig + config10k + "DList.ofList round-trips with toList" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (_, l) -> DList.ofList l |> DList.toList = l) + + testPropertyWithConfig + config10k + "DList.ofArray round-trips with toArray" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (_, l) -> DList.ofArray(Array.ofList l) |> DList.toArray = Array.ofList l) + + testPropertyWithConfig + config10k + "DList.tryFind matches List.tryFind" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.tryFind (fun x -> x % 3 = 0) q = List.tryFind (fun x -> x % 3 = 0) l) + + testPropertyWithConfig + config10k + "DList.find matches List.find when element exists" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> + match List.tryFind (fun x -> x % 2 = 0) l with + | Some expected -> DList.find (fun x -> x % 2 = 0) q = expected + | None -> true) + + testPropertyWithConfig + config10k + "DList.choose matches List.choose" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> + let mapping x = + if x % 2 = 0 then Some(x * 2) else None + + DList.choose mapping q |> DList.toList = List.choose mapping l) + + testPropertyWithConfig + config10k + "DList.collect matches List.collect" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> + let mapping x = + DList.ofList [ x; x * 2 ] + + DList.collect mapping q |> DList.toList = List.collect (fun x -> [ x; x * 2 ]) l) + + testPropertyWithConfig + config10k + "DList.partition splits correctly" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> + let trueD, falseD = DList.partition (fun x -> x % 2 = 0) q + let trueL, falseL = List.partition (fun x -> x % 2 = 0) l + DList.toList trueD = trueL && DList.toList falseD = falseL) + + testPropertyWithConfig + config10k + "DList.sort matches List.sort" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.sort q |> DList.toList = List.sort l) + + testPropertyWithConfig + config10k + "DList.sortWith compare matches List.sort" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.sortWith compare q |> DList.toList = List.sort l) + + testPropertyWithConfig + config10k + "DList.sortBy id matches List.sort" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.sortBy id q |> DList.toList = List.sort l) + + testPropertyWithConfig + config10k + "DList.rev reverses the list" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, l) -> DList.rev q |> DList.toList = List.rev l) + + testPropertyWithConfig + config10k + "DList.rev . DList.rev = id" + (Prop.forAll(Arb.fromGen intGensStart1.[0]) + <| fun (q, _) -> DList.rev(DList.rev q) |> DList.toList = DList.toList q) ] From e26d3f62c6f648750bfac9ac21cc5f18c8fa003c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Apr 2026 20:20:41 +0000 Subject: [PATCH 2/2] ci: trigger checks