diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 99aa461..7e2ebfb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,6 @@ +### 1.2.14 +* added some combinators for HashSet/HashMap + ### 1.2.13 * made FSharp.Data.Adaptive trimmable via `true` diff --git a/src/FSharp.Data.Adaptive/Datastructures/HashCollections.fs b/src/FSharp.Data.Adaptive/Datastructures/HashCollections.fs index 12f5e92..4f8e905 100644 --- a/src/FSharp.Data.Adaptive/Datastructures/HashCollections.fs +++ b/src/FSharp.Data.Adaptive/Datastructures/HashCollections.fs @@ -320,6 +320,19 @@ module internal HashImplementation = else intersect cmp a.SetNext b + let rec intersectionCount + (cmp : IEqualityComparer<'K>) + (acc : int) + (a : SetLinked<'K>) (b : SetLinked<'K>) = + if isNull a || isNull b then + acc + else + let struct(ok, b) = tryRemove cmp a.Key b + if ok then intersectionCount cmp (acc + 1) a.SetNext b + else intersectionCount cmp acc a.SetNext b + + + let rec computeDelta (cmp : IEqualityComparer<'K>) (onlyLeft : 'K -> voption<'OP>) @@ -595,7 +608,23 @@ module internal HashImplementation = MapLinked(node.Key, v, chooseV mapping node.MapNext) | ValueNone -> chooseV mapping node.MapNext - + + + let rec intersect + (cmp : IEqualityComparer<'K>) + (resolve : OptimizedClosures.FSharpFunc<'K, 'A, 'B, 'C>) + (a : MapLinked<'K, 'A>) (b : MapLinked<'K, 'B>) = + if isNull a || isNull b then null + else + let struct(ok, b) = tryRemove cmp a.Key b + match ok with + | ValueSome vb -> + let value = resolve.Invoke(a.Key, a.Value, vb) + MapLinked(a.Key, value, intersect cmp resolve a.MapNext b) + | ValueNone -> + intersect cmp resolve a.MapNext b + + let rec choose2VLeft (mapping : OptimizedClosures.FSharpFunc<'K, voption<'A>, voption<'B>, voption<'C>>) (a : MapLinked<'K, 'A>) = @@ -1378,6 +1407,58 @@ module internal HashImplementation = else null + let rec intersectionCount + (cmp : IEqualityComparer<'K>) + (acc : int) + (na : SetNode<'K>) (nb : SetNode<'K>) = + + if isNull na || isNull nb then acc + elif System.Object.ReferenceEquals(na, nb) then acc + size na + elif na.IsLeaf then + let a = na :?> SetLeaf<'K> + if nb.IsLeaf then + let b = nb :?> SetLeaf<'K> + if a.Hash = b.Hash then + SetLinked.intersectionCount cmp acc (SetLinked(a.Key, a.SetNext)) (SetLinked(b.Key, b.SetNext)) + else + acc + else + let b = nb :?> Inner<'K> + match matchPrefixAndGetBit a.Hash b.Prefix b.Mask with + | 0u -> intersectionCount cmp acc na b.Left + | 1u -> intersectionCount cmp acc na b.Right + | _ -> acc + elif nb.IsLeaf then + let a = na :?> Inner<'K> + let b = nb :?> SetLeaf<'K> + match matchPrefixAndGetBit b.Hash a.Prefix a.Mask with + | 0u -> intersectionCount cmp acc a.Left nb + | 1u -> intersectionCount cmp acc a.Right nb + | _ -> acc + else + let a = na :?> Inner<'K> + let b = nb :?> Inner<'K> + + let cc = compareMasks a.Mask b.Mask + if cc > 0 then + // a in b + match matchPrefixAndGetBit a.Prefix b.Prefix b.Mask with + | 0u -> intersectionCount cmp acc na b.Left + | 1u -> intersectionCount cmp acc na b.Right + | _ -> acc + elif cc < 0 then + // b in a + match matchPrefixAndGetBit b.Prefix a.Prefix a.Mask with + | 0u -> intersectionCount cmp acc a.Left nb + | 1u -> intersectionCount cmp acc a.Right nb + | _ -> acc + elif a.Prefix = b.Prefix then + let acc = intersectionCount cmp acc a.Left b.Left + intersectionCount cmp acc a.Right b.Right + else + acc + + let rec xor (cmp : IEqualityComparer<'K>) (na : SetNode<'K>) (nb : SetNode<'K>) = @@ -2453,6 +2534,59 @@ module internal HashImplementation = let vb = choose2VRight mapping nb join a.Prefix va b.Prefix vb + let rec intersect + (cmp : IEqualityComparer<'K>) + (resolve : OptimizedClosures.FSharpFunc<'K, 'A, 'B, 'C>) + (na : SetNode<'K>) (nb : SetNode<'K>) = + + if isNull na || isNull nb then null + elif na.IsLeaf then + let a = na :?> MapLeaf<'K, 'A> + if nb.IsLeaf then + let b = nb :?> MapLeaf<'K, 'B> + if a.Hash = b.Hash then + // TODO: avoid allocating SetLinkeds + let la = MapLinked(a.Key, a.Value, a.MapNext) + let lb = MapLinked(b.Key, b.Value, b.MapNext) + let res = MapLinked.intersect cmp resolve la lb + if isNull res then null + else MapLeaf(a.Hash, res.Key, res.Value, res.MapNext) :> SetNode<_> + else + null + else + let b = nb :?> Inner<'K> + match matchPrefixAndGetBit a.Hash b.Prefix b.Mask with + | 0u -> intersect cmp resolve na b.Left + | 1u -> intersect cmp resolve na b.Right + | _ -> null + elif nb.IsLeaf then + let a = na :?> Inner<'K> + let b = nb :?> SetLeaf<'K> + match matchPrefixAndGetBit b.Hash a.Prefix a.Mask with + | 0u -> intersect cmp resolve a.Left nb + | 1u -> intersect cmp resolve a.Right nb + | _ -> null + else + let a = na :?> Inner<'K> + let b = nb :?> Inner<'K> + + let cc = compareMasks a.Mask b.Mask + if cc > 0 then + // a in b + match matchPrefixAndGetBit a.Prefix b.Prefix b.Mask with + | 0u -> intersect cmp resolve na b.Left + | 1u -> intersect cmp resolve na b.Right + | _ -> null + elif cc < 0 then + // b in a + match matchPrefixAndGetBit b.Prefix a.Prefix a.Mask with + | 0u -> intersect cmp resolve a.Left nb + | 1u -> intersect cmp resolve a.Right nb + | _ -> null + elif a.Prefix = b.Prefix then + newInner a.Prefix a.Mask (intersect cmp resolve a.Left b.Left) (intersect cmp resolve a.Right b.Right) + else + null let rec unionWithSelfV<'K, 'V> @@ -3398,6 +3532,10 @@ type HashSet<'K> internal(comparer : IEqualityComparer<'K>, root : SetNode<'K>) member x.IntersectWith(other : HashSet<'K>) = HashSet<'K>(comparer, SetNode.intersect comparer root other.Root) + [] + member x.IntersectionCount(other : HashSet<'K>) = + SetNode.intersectionCount comparer 0 root other.Root + [] member x.ComputeDeltaAsHashMap(other : HashSet<'K>) = let delta = SetNode.computeDelta comparer remOp addOp root other.Root @@ -3695,6 +3833,21 @@ and [.Adapt mapping HashMap<'K, 'U>(comparer, MapNode.choose2V comparer mapping root other.Root) + [] + member x.IntersectWith(other : HashMap<'K, 'T>, resolve : 'K -> 'V -> 'T -> 'U) = + let mapping = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt resolve + HashMap<'K, 'U>(comparer, MapNode.intersect comparer mapping root other.Root) + + [] + member x.Intersect(other : HashMap<'K, 'T>) = + let mapping = OptimizedClosures.FSharpFunc<'K,'V,'T,_>.Adapt (fun _ a b -> (a, b)) + HashMap<'K, 'V * 'T>(comparer, MapNode.intersect comparer mapping root other.Root) + + + [] + member x.IntersectionCount(other : HashMap<'K, 'T>) = + SetNode.intersectionCount comparer 0 root other.Root + [] member x.Choose2(other : HashMap<'K, 'T>, mapping : 'K -> option<'V> -> option<'T> -> option<'U>) = let mapping = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt mapping @@ -4063,7 +4216,7 @@ module HashSet = let inline toArray (set : HashSet<'K>) = set.ToArray() /// Creates a Set holding all entries contained in the HashSet. - /// `O(N)` + /// `O(N * log N)` let inline toSet (set: HashSet<'T>) = set |> Set.ofSeq @@ -4083,6 +4236,9 @@ module HashSet = /// `O(N + M)` let inline difference (set1 : HashSet<'T>) (set2 : HashSet<'T>) = set1.ExceptWith set2 + /// Returns the number of elements that are in both sets. + let inline intersectionCount (set1 : HashSet<'T>) (set2 : HashSet<'T>) = set1.IntersectionCount set2 + /// Creates a new set containing all elements that are in at least one of the given sets. let unionMany (sets : #seq>) = use e = sets.GetEnumerator() @@ -4299,6 +4455,15 @@ module HashMap = /// Applies the given mapping function to all elements of the two maps. `O(N + M)` let inline choose2 (mapping : 'K -> option<'T1> -> option<'T2> -> option<'R>) (l : HashMap<'K, 'T1>) (r : HashMap<'K, 'T2>) = l.Choose2(r, mapping) + /// returns only the keys that are in both maps together with their tuples values. `O(N + M)` + let inline intersect (l : HashMap<'K, 'T1>) (r : HashMap<'K, 'T2>) = l.Intersect(r) + + /// Applies the given mapping function to overlapping elements of the two maps. `O(N + M)` + let inline intersectWith (mapping : 'K -> 'T1 -> 'T2 -> 'R) (l : HashMap<'K, 'T1>) (r : HashMap<'K, 'T2>) = l.IntersectWith(r, mapping) + + /// Returns the number of elements that are in both sets. + let inline intersectionCount (map1 : HashMap<'K, 'T1>) (map2 : HashMap<'K, 'T2>) = map1.IntersectionCount map2 + /// Applies the given mapping function to all elements of the two maps. `O(N + M)` let inline map2V (mapping : 'K -> voption<'T1> -> voption<'T2> -> 'R) (l : HashMap<'K, 'T1>) (r : HashMap<'K, 'T2>) = l.Map2V(r, mapping) diff --git a/src/Test/FSharp.Data.Adaptive.Tests/HashMap.fs b/src/Test/FSharp.Data.Adaptive.Tests/HashMap.fs index 9b255cf..f01b57a 100644 --- a/src/Test/FSharp.Data.Adaptive.Tests/HashMap.fs +++ b/src/Test/FSharp.Data.Adaptive.Tests/HashMap.fs @@ -208,6 +208,31 @@ let ``[HashMap] map2/choose2`` (lm : Map) (rm : Map) = equal (HashMap.choose2 (fun k l r -> add k l r |> Some) l r) (map2 add lm rm) equal (HashMap.choose2 add2 l r) (choose2 add2 lm rm) ] + + +[] +let ``[HashMap] intersect`` (lm : Map) (rm : Map) = + let l = lm |> Map.toList |> HashMap.ofList + let r = rm |> Map.toList |> HashMap.ofList + + let fintersect (l : Map<'K, 'A>) (r : Map<'K, 'B>) = + let mutable res = Map.empty + + for (lk, lv) in Map.toSeq l do + match Map.tryFind lk r with + | Some rv -> res <- Map.add lk (lv, rv) res + | None -> () + + res + + let equal (l : HashMap<'K, 'V>) (r : Map<'K, 'V>) = + let l = l |> HashMap.toList |> List.sortBy fst + let r = r |> Map.toList + l = r + + List.all [ + equal (HashMap.intersect l r) (fintersect lm rm) + ] [] let ``[HashMap] enumerator correct`` (m : Map) = diff --git a/src/Test/FSharp.Data.Adaptive.Tests/HashSet.fs b/src/Test/FSharp.Data.Adaptive.Tests/HashSet.fs index 3bf937e..6e300bc 100644 --- a/src/Test/FSharp.Data.Adaptive.Tests/HashSet.fs +++ b/src/Test/FSharp.Data.Adaptive.Tests/HashSet.fs @@ -742,6 +742,20 @@ let ``[HashSet] intersect`` (fset1 : Set) (fset2 : Set) = // A ^ 0 = 0 HashSet.intersect set1 empty |> should setequal empty +[] +let ``[HashSet] intersectionCount`` (fset1 : Set) (fset2 : Set) = + let empty : HashSet = HashSet.empty + let set1 = HashSet.ofSeq fset1 + let set2 = HashSet.ofSeq fset2 + let cnt = Set.intersect fset1 fset2 |> Set.count + + HashSet.intersectionCount set1 set2 |> should equal cnt + HashSet.intersectionCount set1 empty |> should equal 0 + HashSet.intersectionCount empty set2 |> should equal 0 + HashSet.intersectionCount set2 set2 |> should equal set2.Count + + + [] let ``[HashSet] xor`` (fset1 : Set) (fset2 : Set) = let empty : HashSet = HashSet.empty