From 870d5a84a33dd65168c306d26109f63e24975d95 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 12:56:22 +0000 Subject: [PATCH 1/2] feat: add filter, iter, fold, exists, forall, choose, tryFind, toList, toArray, ofList, ofArray, keys, values to PersistentHashMap; 16 new tests All 754 tests pass. Addresses #152 (Align Collection Module functions with FSharp.Collections). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharpx.Collections/PersistentHashMap.fs | 71 +++++++++++++++ .../PersistentHashMapTest.fs | 87 +++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/src/FSharpx.Collections/PersistentHashMap.fs b/src/FSharpx.Collections/PersistentHashMap.fs index a41d598f..b1f1f765 100644 --- a/src/FSharpx.Collections/PersistentHashMap.fs +++ b/src/FSharpx.Collections/PersistentHashMap.fs @@ -929,4 +929,75 @@ module PersistentHashMap = ret.persistent() + ///O(log32n), returns the value option for the given key. + let tryFind key (map: PersistentHashMap<'T, 'S>) = + if map.ContainsKey key then Some map.[key] else None + + ///O(n). Returns a new HashMap containing only the entries for which the predicate returns true. + let filter (predicate: 'T -> 'S -> bool) (map: PersistentHashMap<'T, 'S>) : PersistentHashMap<'T, 'S> = + let mutable ret = TransientHashMap<'T, 'S>.Empty() + + for (key, value) in map do + if predicate key value then + ret <- ret.Add(key, value) + + ret.persistent() + + ///O(n). Applies the supplied function to each element of the HashMap. + let iter (action: 'T -> 'S -> unit) (map: PersistentHashMap<'T, 'S>) = + for (key, value) in map do + action key value + + ///O(n). Applies a function to each entry of the HashMap, threading an accumulator argument. + let fold (folder: 'State -> 'T -> 'S -> 'State) (state: 'State) (map: PersistentHashMap<'T, 'S>) = + let mutable acc = state + + for (key, value) in map do + acc <- folder acc key value + + acc + + ///O(n). Returns true if any entry satisfies the predicate. + let exists (predicate: 'T -> 'S -> bool) (map: PersistentHashMap<'T, 'S>) = + map |> Seq.exists(fun (k, v) -> predicate k v) + + ///O(n). Returns true if all entries satisfy the predicate. + let forall (predicate: 'T -> 'S -> bool) (map: PersistentHashMap<'T, 'S>) = + map |> Seq.forall(fun (k, v) -> predicate k v) + + ///O(n). Builds a new HashMap whose entries are the results of applying the given function to each entry. Entries for which the function returns None are excluded. + let choose (chooser: 'T -> 'S -> 'S1 option) (map: PersistentHashMap<'T, 'S>) : PersistentHashMap<'T, 'S1> = + let mutable ret = TransientHashMap<'T, 'S1>.Empty() + + for (key, value) in map do + match chooser key value with + | Some v -> ret <- ret.Add(key, v) + | None -> () + + ret.persistent() + + ///O(n). Returns a list of all key-value pairs in the HashMap. + let toList(map: PersistentHashMap<'T, 'S>) = + [ for kv in map -> kv ] + + ///O(n). Returns an array of all key-value pairs in the HashMap. + let toArray(map: PersistentHashMap<'T, 'S>) = + [| for kv in map -> kv |] + + ///O(n). Creates a HashMap from a list of key-value pairs. + let ofList(items: ('T * 'S) list) = + PersistentHashMap<'T, 'S>.ofSeq items + + ///O(n). Creates a HashMap from an array of key-value pairs. + let ofArray(items: ('T * 'S) array) = + PersistentHashMap<'T, 'S>.ofSeq items + + ///O(n). Returns a sequence of all keys in the HashMap. + let keys(map: PersistentHashMap<'T, 'S>) = + map |> Seq.map fst + + ///O(n). Returns a sequence of all values in the HashMap. + let values(map: PersistentHashMap<'T, 'S>) = + map |> Seq.map snd + #endif diff --git a/tests/FSharpx.Collections.Tests/PersistentHashMapTest.fs b/tests/FSharpx.Collections.Tests/PersistentHashMapTest.fs index f43a614e..00751a48 100644 --- a/tests/FSharpx.Collections.Tests/PersistentHashMapTest.fs +++ b/tests/FSharpx.Collections.Tests/PersistentHashMapTest.fs @@ -313,4 +313,91 @@ module PersistentHashMapTests = !x |> PersistentHashMap.containsKey((r.Next n).ToString()) |> Expect.isTrue "Next" + } + + test "tryFind returns Some for existing key" { + let m = PersistentHashMap.ofSeq [ ("a", 1); ("b", 2) ] + Expect.equal "tryFind existing" (Some 1) (PersistentHashMap.tryFind "a" m) + } + + test "tryFind returns None for missing key" { + let m = PersistentHashMap.ofSeq [ ("a", 1) ] + Expect.equal "tryFind missing" None (PersistentHashMap.tryFind "z" m) + } + + test "filter keeps only matching entries" { + let m = PersistentHashMap.ofSeq [ (1, "a"); (2, "b"); (3, "c") ] + let result = PersistentHashMap.filter (fun k _ -> k > 1) m + Expect.equal "filter count" 2 (PersistentHashMap.count result) + Expect.isFalse "filter excludes 1" (PersistentHashMap.containsKey 1 result) + Expect.isTrue "filter keeps 2" (PersistentHashMap.containsKey 2 result) + } + + test "iter visits all entries" { + let m = PersistentHashMap.ofSeq [ (1, 10); (2, 20); (3, 30) ] + let seen = System.Collections.Generic.HashSet() + PersistentHashMap.iter (fun k _ -> seen.Add(k) |> ignore) m + Expect.equal "iter visits all" 3 seen.Count + } + + test "fold sums all values" { + let m = PersistentHashMap.ofSeq [ ("a", 1); ("b", 2); ("c", 3) ] + let total = PersistentHashMap.fold (fun acc _ v -> acc + v) 0 m + Expect.equal "fold sum" 6 total + } + + test "exists returns true when predicate matches" { + let m = PersistentHashMap.ofSeq [ (1, "x"); (2, "y") ] + Expect.isTrue "exists" (PersistentHashMap.exists (fun k _ -> k = 2) m) + } + + test "exists returns false when no match" { + let m = PersistentHashMap.ofSeq [ (1, "x"); (2, "y") ] + Expect.isFalse "exists false" (PersistentHashMap.exists (fun k _ -> k = 99) m) + } + + test "forall returns true when all entries match" { + let m = PersistentHashMap.ofSeq [ (1, 10); (2, 20) ] + Expect.isTrue "forall" (PersistentHashMap.forall (fun _ v -> v > 0) m) + } + + test "forall returns false when some entry does not match" { + let m = PersistentHashMap.ofSeq [ (1, 10); (2, -1) ] + Expect.isFalse "forall false" (PersistentHashMap.forall (fun _ v -> v > 0) m) + } + + test "choose keeps and transforms matching entries" { + let m = PersistentHashMap.ofSeq [ (1, 10); (2, 20); (3, 30) ] + + let result = + PersistentHashMap.choose (fun _ v -> if v > 15 then Some(v * 2) else None) m + + Expect.equal "choose count" 2 (PersistentHashMap.count result) + Expect.equal "choose value" 40 (PersistentHashMap.find 2 result) + } + + test "toList round-trips via ofList" { + let pairs = [ (1, "one"); (2, "two"); (3, "three") ] + let m = PersistentHashMap.ofList pairs + let result = PersistentHashMap.toList m |> List.sortBy fst + Expect.equal "toList/ofList round-trip" (pairs |> List.sortBy fst) result + } + + test "toArray round-trips via ofArray" { + let pairs = [| (1, "one"); (2, "two") |] + let m = PersistentHashMap.ofArray pairs + let result = PersistentHashMap.toArray m |> Array.sortBy fst + Expect.equal "toArray/ofArray round-trip" (pairs |> Array.sortBy fst) result + } + + test "keys returns all keys" { + let m = PersistentHashMap.ofSeq [ (1, "a"); (2, "b"); (3, "c") ] + let ks = PersistentHashMap.keys m |> Seq.sort |> Seq.toList + Expect.equal "keys" [ 1; 2; 3 ] ks + } + + test "values returns all values" { + let m = PersistentHashMap.ofSeq [ ("a", 1); ("b", 2); ("c", 3) ] + let vs = PersistentHashMap.values m |> Seq.sort |> Seq.toList + Expect.equal "values" [ 1; 2; 3 ] vs } ] From 2d4c35a97411ab5988ab29901128e05af0762744 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 13:00:16 +0000 Subject: [PATCH 2/2] ci: trigger checks