Skip to content

Commit ea0d2ef

Browse files
AsterlessLampese
andauthored
feat: add set operations to @immut/hash{map, set} and @internal/sparse_array (#2145)
* feat: add four new functions to HAMT and their tests * fix(union_with): fix union_with for HAMT,now it can handle branch * feat(sparse_array): add intersection and difference methods * fix(HAMT): fix union_with, intersection, intersection_with and difference methods, now they can handle branchs * feat(hashset): add intersection and difference methods to hashset * commit other files * feat: add four new functions to HAMT and their tests * fix(union_with): fix union_with for HAMT,now it can handle branch * feat(sparse_array): add intersection and difference methods * fix(HAMT): fix union_with, intersection, intersection_with and difference methods, now they can handle branchs * feat(hashset): add intersection and difference methods to hashset * style: change the position of some function declarations * fix: fix formatting of the code * feat: Update the function declarations of hash tables and sparse arrays * refactor:update mbti * refactor: update hashmap and hashset function signatures to include type prefix --------- Co-authored-by: 东灯 <[email protected]>
1 parent df2288b commit ea0d2ef

File tree

8 files changed

+372
-9
lines changed

8 files changed

+372
-9
lines changed

immut/hashmap/HAMT.mbt

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ pub fn[K, V] size(self : T[K, V]) -> Int {
311311

312312
///|
313313
/// Union two hashmaps
314-
pub fn[K : Eq + Hash, V] union(self : T[K, V], other : T[K, V]) -> T[K, V] {
314+
pub fn[K : Eq + Hash, V] T::union(self : T[K, V], other : T[K, V]) -> T[K, V] {
315315
match (self, other) {
316316
(_, Empty) => self
317317
(Empty, _) => other
@@ -329,6 +329,136 @@ pub fn[K : Eq + Hash, V] union(self : T[K, V], other : T[K, V]) -> T[K, V] {
329329
}
330330
}
331331

332+
///|
333+
/// Union two hashmaps with a function
334+
pub fn[K : Eq + Hash, V] T::union_with(
335+
self : T[K, V],
336+
other : T[K, V],
337+
f : (K, V, V) -> V
338+
) -> T[K, V] {
339+
match (self, other) {
340+
(_, Empty) => self
341+
(Empty, _) => other
342+
(_, Leaf(k, v)) =>
343+
match self.get(k) {
344+
Some(v1) => self.add(k, f(k, v1, v))
345+
None => self.add(k, v)
346+
}
347+
(Leaf(k, v), _) =>
348+
match other.get(k) {
349+
Some(v2) => other.add(k, f(k, v, v2))
350+
None => other.add(k, v)
351+
}
352+
(Branch(sa1), Branch(sa2)) =>
353+
Branch(sa1.union(sa2, fn(m1, m2) { m1.union_with(m2, f) }))
354+
(_, _) =>
355+
self
356+
.iter()
357+
.fold(init=other, fn(m, kv) {
358+
match m.get(kv.0) {
359+
Some(v2) => m.add(kv.0, f(kv.0, kv.1, v2))
360+
None => m.add(kv.0, kv.1)
361+
}
362+
})
363+
}
364+
}
365+
366+
///|
367+
/// Intersect two hashmaps
368+
pub fn[K : Eq + Hash, V] T::intersection(
369+
self : T[K, V],
370+
other : T[K, V]
371+
) -> T[K, V] {
372+
match (self, other) {
373+
(_, Empty) => Empty
374+
(Empty, _) => Empty
375+
(Leaf(k, v), _) =>
376+
match other.get(k) {
377+
Some(_) => Leaf(k, v)
378+
None => Empty
379+
}
380+
(_, Leaf(k, _)) =>
381+
match self.get(k) {
382+
Some(v) => Leaf(k, v)
383+
None => Empty
384+
}
385+
(Branch(sa1), Branch(sa2)) =>
386+
Branch(sa1.intersection(sa2, fn(m1, m2) { m1.intersection(m2) }))
387+
(_, _) =>
388+
self
389+
.iter()
390+
.fold(init=Empty, fn(m, kv) {
391+
if other.get(kv.0) is Some(_) {
392+
m.add(kv.0, kv.1)
393+
} else {
394+
m
395+
}
396+
})
397+
}
398+
}
399+
400+
///|
401+
/// Intersection two hashmaps with a function
402+
pub fn[K : Eq + Hash, V] T::intersection_with(
403+
self : T[K, V],
404+
other : T[K, V],
405+
f : (K, V, V) -> V
406+
) -> T[K, V] {
407+
match (self, other) {
408+
(_, Empty) => Empty
409+
(Empty, _) => Empty
410+
(Leaf(k, v), _) =>
411+
match other.get(k) {
412+
Some(v2) => Leaf(k, f(k, v, v2))
413+
None => Empty
414+
}
415+
(_, Leaf(k, v2)) =>
416+
match self.get(k) {
417+
Some(v1) => Leaf(k, f(k, v1, v2))
418+
None => Empty
419+
}
420+
(Branch(sa1), Branch(sa2)) =>
421+
Branch(sa1.intersection(sa2, fn(m1, m2) { m1.intersection_with(m2, f) }))
422+
(_, _) =>
423+
self
424+
.iter()
425+
.fold(init=Empty, fn(m, kv) {
426+
match other.get(kv.0) {
427+
Some(v2) => m.add(kv.0, f(kv.0, kv.1, v2))
428+
None => m
429+
}
430+
})
431+
}
432+
}
433+
434+
///|
435+
/// Difference of two hashmaps: elements in `self` but not in `other`
436+
pub fn[K : Eq + Hash, V] T::difference(
437+
self : T[K, V],
438+
other : T[K, V]
439+
) -> T[K, V] {
440+
match (self, other) {
441+
(Empty, _) => Empty
442+
(_, Empty) => self
443+
(Leaf(k, v), _) =>
444+
match other.get(k) {
445+
Some(_) => Empty
446+
None => Leaf(k, v)
447+
}
448+
(Branch(sa1), Branch(sa2)) => Branch(sa1.difference(sa2))
449+
(_, _) =>
450+
self
451+
.iter()
452+
.fold(init=Empty, fn(m, kv) {
453+
if other.get(kv.0) is None {
454+
m.add(kv.0, kv.1)
455+
} else {
456+
m
457+
}
458+
})
459+
}
460+
}
461+
332462
///|
333463
/// Iterate through the elements in a hash map
334464
pub fn[K, V] each(self : T[K, V], f : (K, V) -> Unit) -> Unit {

immut/hashmap/HAMT_test.mbt

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,99 @@ test "HAMT::union leaf to non-overlapping map" {
580580
assert_eq(u.get(2), Some(2))
581581
}
582582

583+
///|
584+
test "HAMT::intersection with empty" {
585+
let m1 = @hashmap.of([(1, 1)])
586+
let m2 = @hashmap.new()
587+
assert_eq!(m1.intersection(m2).size(), 0)
588+
assert_eq!(m2.intersection(m1).size(), 0)
589+
}
590+
591+
///|
592+
test "HAMT::intersection with leaf" {
593+
let m1 = @hashmap.of([(1, 1), (2, 2)])
594+
let m2 = @hashmap.singleton(2, 2)
595+
let m3 = @hashmap.singleton(3, 3)
596+
assert_eq!(m1.intersection(m2).get(2), Some(2))
597+
assert_eq!(m1.intersection(m3).get(3), None)
598+
assert_eq!(m2.intersection(m1).get(2), Some(2))
599+
}
600+
601+
///|
602+
test "HAMT::intersection with branch" {
603+
let m1 = @hashmap.of([(1, 1), (2, 2), (3, 3)])
604+
let m2 = @hashmap.of([(2, 2), (3, 30), (4, 4)])
605+
let inter = m1.intersection(m2)
606+
assert_eq!(inter.get(1), None)
607+
assert_eq!(inter.get(2), Some(2))
608+
assert_eq!(inter.get(3), Some(3))
609+
assert_eq!(inter.get(4), None)
610+
}
611+
612+
///|
613+
test "HAMT::intersection_with with empty" {
614+
let m1 = @hashmap.of([(1, 1)])
615+
let m2 = @hashmap.new()
616+
assert_eq!(m1.intersection_with(m2, fn(_k, v1, v2) { v1 + v2 }).size(), 0)
617+
assert_eq!(m2.intersection_with(m1, fn(_k, v1, v2) { v1 + v2 }).size(), 0)
618+
}
619+
620+
///|
621+
test "HAMT::intersection_with with leaf" {
622+
let m1 = @hashmap.of([(1, 1), (2, 2)])
623+
let m2 = @hashmap.singleton(2, 20)
624+
let m3 = @hashmap.singleton(3, 30)
625+
assert_eq!(
626+
m1.intersection_with(m2, fn(_k, v1, v2) { v1 + v2 }).get(2),
627+
Some(22),
628+
)
629+
assert_eq!(m1.intersection_with(m3, fn(_k, v1, v2) { v1 + v2 }).get(3), None)
630+
assert_eq!(
631+
m2.intersection_with(m1, fn(_k, v1, v2) { v1 + v2 }).get(2),
632+
Some(22),
633+
)
634+
}
635+
636+
///|
637+
test "HAMT::intersection_with with branch" {
638+
let m1 = @hashmap.of([(1, 1), (2, 2), (3, 3)])
639+
let m2 = @hashmap.of([(2, 20), (3, 30), (4, 4)])
640+
let inter = m1.intersection_with(m2, fn(_k, v1, v2) { v1 * v2 })
641+
assert_eq!(inter.get(1), None)
642+
assert_eq!(inter.get(2), Some(40))
643+
assert_eq!(inter.get(3), Some(90))
644+
assert_eq!(inter.get(4), None)
645+
}
646+
647+
///|
648+
test "HAMT::difference with empty" {
649+
let m1 = @hashmap.of([(1, 1)])
650+
let m2 = @hashmap.new()
651+
assert_eq!(m1.difference(m2), m1)
652+
assert_eq!(m2.difference(m1).size(), 0)
653+
}
654+
655+
///|
656+
test "HAMT::difference with leaf" {
657+
let m1 = @hashmap.of([(1, 1), (2, 2)])
658+
let m2 = @hashmap.singleton(2, 2)
659+
let m3 = @hashmap.singleton(3, 3)
660+
assert_eq!(m1.difference(m2).get(2), None)
661+
assert_eq!(m1.difference(m3).get(1), Some(1))
662+
assert_eq!(m2.difference(m1).size(), 0)
663+
}
664+
665+
///|
666+
test "HAMT::difference with branch" {
667+
let m1 = @hashmap.of([(1, 1), (2, 2), (3, 3)])
668+
let m2 = @hashmap.of([(2, 2), (3, 30), (4, 4)])
669+
let diff = m1.difference(m2)
670+
assert_eq!(diff.get(1), Some(1))
671+
assert_eq!(diff.get(2), None)
672+
assert_eq!(diff.get(3), None)
673+
assert_eq!(diff.get(4), None)
674+
}
675+
583676
///|
584677
test "HAMT::each" {
585678
let empty = @hashmap.new()

immut/hashmap/hashmap.mbti

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,13 @@ fn[K, V] size(T[K, V]) -> Int
5454

5555
fn[K, V] to_array(T[K, V]) -> Array[(K, V)]
5656

57-
fn[K : Eq + Hash, V] union(T[K, V], T[K, V]) -> T[K, V]
58-
5957
fn[K, V] values(T[K, V]) -> Iter[V]
6058

6159
// Types and methods
6260
type T[K, V]
6361
fn[K : Eq + Hash, V] T::add(Self[K, V], K, V) -> Self[K, V]
6462
fn[K : Eq + Hash, V] T::contains(Self[K, V], K) -> Bool
63+
fn[K : Eq + Hash, V] T::difference(Self[K, V], Self[K, V]) -> Self[K, V]
6564
fn[K, V] T::each(Self[K, V], (K, V) -> Unit) -> Unit
6665
#deprecated
6766
fn[K, V] T::elems(Self[K, V]) -> Iter[V]
@@ -71,6 +70,8 @@ fn[K : Eq + Hash, V] T::find(Self[K, V], K) -> V?
7170
fn[K, V, A] T::fold(Self[K, V], init~ : A, (A, V) -> A) -> A
7271
fn[K, V, A] T::fold_with_key(Self[K, V], init~ : A, (A, K, V) -> A) -> A
7372
fn[K : Eq + Hash, V] T::get(Self[K, V], K) -> V?
73+
fn[K : Eq + Hash, V] T::intersection(Self[K, V], Self[K, V]) -> Self[K, V]
74+
fn[K : Eq + Hash, V] T::intersection_with(Self[K, V], Self[K, V], (K, V, V) -> V) -> Self[K, V]
7475
fn[K, V] T::iter(Self[K, V]) -> Iter[(K, V)]
7576
fn[K, V] T::iter2(Self[K, V]) -> Iter2[K, V]
7677
fn[K, V] T::keys(Self[K, V]) -> Iter[K]
@@ -82,6 +83,7 @@ fn[K : Eq + Hash, V] T::remove(Self[K, V], K) -> Self[K, V]
8283
fn[K, V] T::size(Self[K, V]) -> Int
8384
fn[K, V] T::to_array(Self[K, V]) -> Array[(K, V)]
8485
fn[K : Eq + Hash, V] T::union(Self[K, V], Self[K, V]) -> Self[K, V]
86+
fn[K : Eq + Hash, V] T::union_with(Self[K, V], Self[K, V], (K, V, V) -> V) -> Self[K, V]
8587
fn[K, V] T::values(Self[K, V]) -> Iter[V]
8688
impl[K : Eq + Hash, V : Eq] Eq for T[K, V]
8789
impl[K : Hash, V : Hash] Hash for T[K, V]

immut/hashset/HAMT.mbt

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub fn[A] size(self : T[A]) -> Int {
180180

181181
///|
182182
/// Union two hashsets
183-
pub fn[K : Eq + Hash] union(self : T[K], other : T[K]) -> T[K] {
183+
pub fn[K : Eq + Hash] T::union(self : T[K], other : T[K]) -> T[K] {
184184
match (self, other) {
185185
(_, Empty) => self
186186
(Empty, _) => other
@@ -192,6 +192,56 @@ pub fn[K : Eq + Hash] union(self : T[K], other : T[K]) -> T[K] {
192192
}
193193
}
194194

195+
///|
196+
/// Intersect two hashsets
197+
pub fn[K : Eq + Hash] T::intersection(self : T[K], other : T[K]) -> T[K] {
198+
match (self, other) {
199+
(_, Empty) => Empty
200+
(Empty, _) => Empty
201+
(Leaf(k), _) => if other.contains(k) { Leaf(k) } else { Empty }
202+
(_, Leaf(k)) => if self.contains(k) { Leaf(k) } else { Empty }
203+
(Branch(sa1), Branch(sa2)) => {
204+
let res = sa1.intersection(sa2, fn(m1, m2) { m1.intersection(m2) })
205+
if res.size() == 0 {
206+
Empty
207+
} else {
208+
Branch(res)
209+
}
210+
}
211+
(_, _) =>
212+
self
213+
.iter()
214+
.fold(init=Empty, fn(m, k) {
215+
if other.contains(k) {
216+
m.add(k)
217+
} else {
218+
m
219+
}
220+
})
221+
}
222+
}
223+
224+
///|
225+
/// Difference of two hashsets: elements in `self` but not in `other`
226+
pub fn[K : Eq + Hash] T::difference(self : T[K], other : T[K]) -> T[K] {
227+
match (self, other) {
228+
(Empty, _) => Empty
229+
(_, Empty) => self
230+
(Leaf(k), _) => if other.contains(k) { Empty } else { Leaf(k) }
231+
(Branch(sa1), Branch(sa2)) => Branch(sa1.difference(sa2))
232+
(_, _) =>
233+
self
234+
.iter()
235+
.fold(init=Empty, fn(m, k) {
236+
if other.contains(k) {
237+
m
238+
} else {
239+
m.add(k)
240+
}
241+
})
242+
}
243+
}
244+
195245
///|
196246
/// Returns true if the hash set is empty.
197247
pub fn[A] is_empty(self : T[A]) -> Bool {

immut/hashset/HAMT_test.mbt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,43 @@ test "union 2 hashsets" {
217217
let set4 = @hashset.of([1, 2, 3, 4, 5])
218218
inspect(set3 == set4, content="true")
219219
}
220+
221+
///|
222+
test "@hashset.intersection with overlapping sets" {
223+
let set1 = @hashset.of([1, 2, 3, 4])
224+
let set2 = @hashset.of([3, 4, 5, 6])
225+
let result = set1.intersection(set2)
226+
inspect!(result, content="@immut/hashset.of([3, 4])")
227+
}
228+
229+
///|
230+
test "@hashset.intersection with disjoint sets" {
231+
let set1 = @hashset.of([1, 2])
232+
let set2 = @hashset.of([3, 4])
233+
let result = set1.intersection(set2)
234+
inspect!(result.is_empty(), content="true")
235+
}
236+
237+
///|
238+
test "@hashset.intersection with one empty set" {
239+
let set1 = @hashset.of([1, 2, 3])
240+
let set2 = @hashset.new()
241+
let result = set1.intersection(set2)
242+
inspect!(result.is_empty(), content="true")
243+
}
244+
245+
///|
246+
test "@hashset.intersection with identical sets" {
247+
let set1 = @hashset.of([1, 2, 3])
248+
let set2 = @hashset.of([1, 2, 3])
249+
let result = set1.intersection(set2)
250+
inspect!(result == set1, content="true")
251+
}
252+
253+
///|
254+
test "@hashset.intersection with subset" {
255+
let set1 = @hashset.of([1, 2, 3, 4])
256+
let set2 = @hashset.of([2, 3])
257+
let result = set1.intersection(set2)
258+
inspect!(result == @hashset.of([2, 3]), content="true")
259+
}

0 commit comments

Comments
 (0)