Skip to content

Commit

Permalink
added some combinators:
Browse files Browse the repository at this point in the history
* HashMap.intersect(With)
* HashSet.intersectionCount
* HashMap.intersectionCount
  • Loading branch information
krauthaufen committed May 14, 2023
1 parent 5e92ab4 commit b5af6cf
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 2 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### 1.2.14
* added some combinators for HashSet/HashMap

### 1.2.13
* made FSharp.Data.Adaptive trimmable via `<IsTrimmable>true</IsTrimmable>`

Expand Down
169 changes: 167 additions & 2 deletions src/FSharp.Data.Adaptive/Datastructures/HashCollections.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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>)
Expand Down Expand Up @@ -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>) =
Expand Down Expand Up @@ -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>) =
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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)

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member x.IntersectionCount(other : HashSet<'K>) =
SetNode.intersectionCount comparer 0 root other.Root

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member x.ComputeDeltaAsHashMap(other : HashSet<'K>) =
let delta = SetNode.computeDelta comparer remOp addOp root other.Root
Expand Down Expand Up @@ -3695,6 +3833,21 @@ and [<Struct; DebuggerDisplay("Count = {Count}"); DebuggerTypeProxy(typedefof<Ha
let mapping = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt mapping
HashMap<'K, 'U>(comparer, MapNode.choose2V comparer mapping root other.Root)

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
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)

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
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)


[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member x.IntersectionCount(other : HashMap<'K, 'T>) =
SetNode.intersectionCount comparer 0 root other.Root

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
member x.Choose2(other : HashMap<'K, 'T>, mapping : 'K -> option<'V> -> option<'T> -> option<'U>) =
let mapping = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt mapping
Expand Down Expand Up @@ -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

Expand All @@ -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<HashSet<'T>>) =
use e = sets.GetEnumerator()
Expand Down Expand Up @@ -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)

Expand Down
25 changes: 25 additions & 0 deletions src/Test/FSharp.Data.Adaptive.Tests/HashMap.fs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,31 @@ let ``[HashMap] map2/choose2`` (lm : Map<int, int>) (rm : Map<int, int>) =
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)
]


[<Property(EndSize = 1000)>]
let ``[HashMap] intersect`` (lm : Map<int, int>) (rm : Map<int, int>) =
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)
]

[<Property(EndSize = 10000)>]
let ``[HashMap] enumerator correct`` (m : Map<int, int>) =
Expand Down
14 changes: 14 additions & 0 deletions src/Test/FSharp.Data.Adaptive.Tests/HashSet.fs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,20 @@ let ``[HashSet] intersect`` (fset1 : Set<int>) (fset2 : Set<int>) =
// A ^ 0 = 0
HashSet.intersect set1 empty |> should setequal empty

[<Property(EndSize = 10000)>]
let ``[HashSet] intersectionCount`` (fset1 : Set<int>) (fset2 : Set<int>) =
let empty : HashSet<int> = 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



[<Property(EndSize = 10000)>]
let ``[HashSet] xor`` (fset1 : Set<int>) (fset2 : Set<int>) =
let empty : HashSet<int> = HashSet.empty
Expand Down

0 comments on commit b5af6cf

Please sign in to comment.