Skip to content

Commit 5d25e4a

Browse files
authored
Merge pull request #254 from fsprojects/repo-assist/improve-persistenthashmap-functions-2026-03-15-f9da3c6901d0f554
[Repo Assist] feat: add filter, iter, fold, exists, forall, choose, tryFind, toList, toArray, ofList, ofArray, keys, values to PersistentHashM [Content truncated due to length]
2 parents b2ce779 + 2d4c35a commit 5d25e4a

2 files changed

Lines changed: 158 additions & 0 deletions

File tree

src/FSharpx.Collections/PersistentHashMap.fs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,4 +956,75 @@ module PersistentHashMap =
956956

957957
ret.persistent()
958958

959+
///O(log32n), returns the value option for the given key.
960+
let tryFind key (map: PersistentHashMap<'T, 'S>) =
961+
if map.ContainsKey key then Some map.[key] else None
962+
963+
///O(n). Returns a new HashMap containing only the entries for which the predicate returns true.
964+
let filter (predicate: 'T -> 'S -> bool) (map: PersistentHashMap<'T, 'S>) : PersistentHashMap<'T, 'S> =
965+
let mutable ret = TransientHashMap<'T, 'S>.Empty()
966+
967+
for (key, value) in map do
968+
if predicate key value then
969+
ret <- ret.Add(key, value)
970+
971+
ret.persistent()
972+
973+
///O(n). Applies the supplied function to each element of the HashMap.
974+
let iter (action: 'T -> 'S -> unit) (map: PersistentHashMap<'T, 'S>) =
975+
for (key, value) in map do
976+
action key value
977+
978+
///O(n). Applies a function to each entry of the HashMap, threading an accumulator argument.
979+
let fold (folder: 'State -> 'T -> 'S -> 'State) (state: 'State) (map: PersistentHashMap<'T, 'S>) =
980+
let mutable acc = state
981+
982+
for (key, value) in map do
983+
acc <- folder acc key value
984+
985+
acc
986+
987+
///O(n). Returns true if any entry satisfies the predicate.
988+
let exists (predicate: 'T -> 'S -> bool) (map: PersistentHashMap<'T, 'S>) =
989+
map |> Seq.exists(fun (k, v) -> predicate k v)
990+
991+
///O(n). Returns true if all entries satisfy the predicate.
992+
let forall (predicate: 'T -> 'S -> bool) (map: PersistentHashMap<'T, 'S>) =
993+
map |> Seq.forall(fun (k, v) -> predicate k v)
994+
995+
///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.
996+
let choose (chooser: 'T -> 'S -> 'S1 option) (map: PersistentHashMap<'T, 'S>) : PersistentHashMap<'T, 'S1> =
997+
let mutable ret = TransientHashMap<'T, 'S1>.Empty()
998+
999+
for (key, value) in map do
1000+
match chooser key value with
1001+
| Some v -> ret <- ret.Add(key, v)
1002+
| None -> ()
1003+
1004+
ret.persistent()
1005+
1006+
///O(n). Returns a list of all key-value pairs in the HashMap.
1007+
let toList(map: PersistentHashMap<'T, 'S>) =
1008+
[ for kv in map -> kv ]
1009+
1010+
///O(n). Returns an array of all key-value pairs in the HashMap.
1011+
let toArray(map: PersistentHashMap<'T, 'S>) =
1012+
[| for kv in map -> kv |]
1013+
1014+
///O(n). Creates a HashMap from a list of key-value pairs.
1015+
let ofList(items: ('T * 'S) list) =
1016+
PersistentHashMap<'T, 'S>.ofSeq items
1017+
1018+
///O(n). Creates a HashMap from an array of key-value pairs.
1019+
let ofArray(items: ('T * 'S) array) =
1020+
PersistentHashMap<'T, 'S>.ofSeq items
1021+
1022+
///O(n). Returns a sequence of all keys in the HashMap.
1023+
let keys(map: PersistentHashMap<'T, 'S>) =
1024+
map |> Seq.map fst
1025+
1026+
///O(n). Returns a sequence of all values in the HashMap.
1027+
let values(map: PersistentHashMap<'T, 'S>) =
1028+
map |> Seq.map snd
1029+
9591030
#endif

tests/FSharpx.Collections.Tests/PersistentHashMapTest.fs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,91 @@ module PersistentHashMapTests =
333333
!x
334334
|> PersistentHashMap.containsKey((r.Next n).ToString())
335335
|> Expect.isTrue "Next"
336+
}
337+
338+
test "tryFind returns Some for existing key" {
339+
let m = PersistentHashMap.ofSeq [ ("a", 1); ("b", 2) ]
340+
Expect.equal "tryFind existing" (Some 1) (PersistentHashMap.tryFind "a" m)
341+
}
342+
343+
test "tryFind returns None for missing key" {
344+
let m = PersistentHashMap.ofSeq [ ("a", 1) ]
345+
Expect.equal "tryFind missing" None (PersistentHashMap.tryFind "z" m)
346+
}
347+
348+
test "filter keeps only matching entries" {
349+
let m = PersistentHashMap.ofSeq [ (1, "a"); (2, "b"); (3, "c") ]
350+
let result = PersistentHashMap.filter (fun k _ -> k > 1) m
351+
Expect.equal "filter count" 2 (PersistentHashMap.count result)
352+
Expect.isFalse "filter excludes 1" (PersistentHashMap.containsKey 1 result)
353+
Expect.isTrue "filter keeps 2" (PersistentHashMap.containsKey 2 result)
354+
}
355+
356+
test "iter visits all entries" {
357+
let m = PersistentHashMap.ofSeq [ (1, 10); (2, 20); (3, 30) ]
358+
let seen = System.Collections.Generic.HashSet<int>()
359+
PersistentHashMap.iter (fun k _ -> seen.Add(k) |> ignore) m
360+
Expect.equal "iter visits all" 3 seen.Count
361+
}
362+
363+
test "fold sums all values" {
364+
let m = PersistentHashMap.ofSeq [ ("a", 1); ("b", 2); ("c", 3) ]
365+
let total = PersistentHashMap.fold (fun acc _ v -> acc + v) 0 m
366+
Expect.equal "fold sum" 6 total
367+
}
368+
369+
test "exists returns true when predicate matches" {
370+
let m = PersistentHashMap.ofSeq [ (1, "x"); (2, "y") ]
371+
Expect.isTrue "exists" (PersistentHashMap.exists (fun k _ -> k = 2) m)
372+
}
373+
374+
test "exists returns false when no match" {
375+
let m = PersistentHashMap.ofSeq [ (1, "x"); (2, "y") ]
376+
Expect.isFalse "exists false" (PersistentHashMap.exists (fun k _ -> k = 99) m)
377+
}
378+
379+
test "forall returns true when all entries match" {
380+
let m = PersistentHashMap.ofSeq [ (1, 10); (2, 20) ]
381+
Expect.isTrue "forall" (PersistentHashMap.forall (fun _ v -> v > 0) m)
382+
}
383+
384+
test "forall returns false when some entry does not match" {
385+
let m = PersistentHashMap.ofSeq [ (1, 10); (2, -1) ]
386+
Expect.isFalse "forall false" (PersistentHashMap.forall (fun _ v -> v > 0) m)
387+
}
388+
389+
test "choose keeps and transforms matching entries" {
390+
let m = PersistentHashMap.ofSeq [ (1, 10); (2, 20); (3, 30) ]
391+
392+
let result =
393+
PersistentHashMap.choose (fun _ v -> if v > 15 then Some(v * 2) else None) m
394+
395+
Expect.equal "choose count" 2 (PersistentHashMap.count result)
396+
Expect.equal "choose value" 40 (PersistentHashMap.find 2 result)
397+
}
398+
399+
test "toList round-trips via ofList" {
400+
let pairs = [ (1, "one"); (2, "two"); (3, "three") ]
401+
let m = PersistentHashMap.ofList pairs
402+
let result = PersistentHashMap.toList m |> List.sortBy fst
403+
Expect.equal "toList/ofList round-trip" (pairs |> List.sortBy fst) result
404+
}
405+
406+
test "toArray round-trips via ofArray" {
407+
let pairs = [| (1, "one"); (2, "two") |]
408+
let m = PersistentHashMap.ofArray pairs
409+
let result = PersistentHashMap.toArray m |> Array.sortBy fst
410+
Expect.equal "toArray/ofArray round-trip" (pairs |> Array.sortBy fst) result
411+
}
412+
413+
test "keys returns all keys" {
414+
let m = PersistentHashMap.ofSeq [ (1, "a"); (2, "b"); (3, "c") ]
415+
let ks = PersistentHashMap.keys m |> Seq.sort |> Seq.toList
416+
Expect.equal "keys" [ 1; 2; 3 ] ks
417+
}
418+
419+
test "values returns all values" {
420+
let m = PersistentHashMap.ofSeq [ ("a", 1); ("b", 2); ("c", 3) ]
421+
let vs = PersistentHashMap.values m |> Seq.sort |> Seq.toList
422+
Expect.equal "values" [ 1; 2; 3 ] vs
336423
} ]

0 commit comments

Comments
 (0)