From 8ca8a622a750b963a7224ed6960410372fc40bef Mon Sep 17 00:00:00 2001 From: Anthony Cerruti Date: Tue, 7 Jul 2020 20:03:32 -0700 Subject: [PATCH 1/4] Initial apply, empty, get, + for HashMap --- .../src/main/scala/cats/collections/Map.scala | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/core/src/main/scala/cats/collections/Map.scala b/core/src/main/scala/cats/collections/Map.scala index 61eb38be..d5390f86 100644 --- a/core/src/main/scala/cats/collections/Map.scala +++ b/core/src/main/scala/cats/collections/Map.scala @@ -172,3 +172,39 @@ trait AvlMapInstances { } } } + +class HashMap[K, V](val buckets: Vector[List[(K, V)]]) { + + def +(pair: (K, V))(implicit K: Eq[K]): HashMap[K, V] = { + def updateBucket(bucket: List[(K, V)], key: K, value: V): List[(K, V)] = + bucket match { + case Nil => (key, value) :: Nil + case (bk, _) :: tail if bk === key => (key, value) :: tail + case head :: tail => head :: updateBucket(tail, key, value) + } + + val (key, value) = pair + val keyBucketIndex = key.hashCode() % buckets.size + val keyBucket = buckets(keyBucketIndex) + val updatedKeyBucket = updateBucket(keyBucket, key, value) + new HashMap(buckets.updated(keyBucketIndex, updatedKeyBucket)) + } + + def get(key: K)(implicit K: Eq[K]): Option[V] = if(buckets.nonEmpty) { + val bucketIndex = key.hashCode % buckets.size + buckets(bucketIndex).collectFirst { + case (pkey, pvalue) if pkey === key => pvalue + } + } else None + +} + +object HashMap { + + val defaultCapacity = 16 + + def apply[K, V](entries: (K, V)*)(implicit K: Eq[K]): HashMap[K, V] = entries.foldLeft(empty[K, V])(_ + _) + + def empty[K, V]: HashMap[K, V] = new HashMap[K, V](Vector.fill(defaultCapacity)(List.empty)) + +} From 3d7614f4bf43ca9e8055092fd5b043b4fd41cb03 Mon Sep 17 00:00:00 2001 From: Anthony Cerruti Date: Tue, 7 Jul 2020 20:45:37 -0700 Subject: [PATCH 2/4] Add in a HashSet for Eq instance --- .../src/main/scala/cats/collections/Map.scala | 20 ++++++++- .../src/main/scala/cats/collections/Set.scala | 44 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/collections/Map.scala b/core/src/main/scala/cats/collections/Map.scala index d5390f86..a82ffcb9 100644 --- a/core/src/main/scala/cats/collections/Map.scala +++ b/core/src/main/scala/cats/collections/Map.scala @@ -197,14 +197,30 @@ class HashMap[K, V](val buckets: Vector[List[(K, V)]]) { } } else None + def entryHashSet(implicit K: Eq[K], V: Eq[V]): HashSet[(K, V)] = buckets.foldLeft(HashSet.empty[(K, V)]) { + case (oset, bucket) => bucket.foldLeft(oset)(_ + _) + } + } -object HashMap { +object HashMap extends HashMapInstances { val defaultCapacity = 16 - def apply[K, V](entries: (K, V)*)(implicit K: Eq[K]): HashMap[K, V] = entries.foldLeft(empty[K, V])(_ + _) + def apply[K: Eq, V](entries: (K, V)*): HashMap[K, V] = entries.foldLeft(empty[K, V])(_ + _) def empty[K, V]: HashMap[K, V] = new HashMap[K, V](Vector.fill(defaultCapacity)(List.empty)) } + +trait HashMapInstances { + + implicit def eqMap[K, V](implicit K: Eq[K], V: Eq[V]): Eq[HashMap[K, V]] = new Eq[HashMap[K, V]] { + + override def eqv(x: HashMap[K,V], y: HashMap[K,V]): Boolean = { + x.entryHashSet === y.entryHashSet + } + + } + +} diff --git a/core/src/main/scala/cats/collections/Set.scala b/core/src/main/scala/cats/collections/Set.scala index 52e84c78..b7eb6993 100644 --- a/core/src/main/scala/cats/collections/Set.scala +++ b/core/src/main/scala/cats/collections/Set.scala @@ -430,3 +430,47 @@ trait AvlSetInstances { t.toIterator.map(_.show).mkString("AvlSet(", ", ", ")") } } + +class HashSet[A](val buckets: Vector[List[A]]) { + + def +(a: A)(implicit eq: Eq[A]): HashSet[A] = { + def updateBucket(bucket: List[A], value: A): List[A] = + bucket match { + case Nil => a :: Nil + case h :: tail => + if(h === value) h :: tail + else h :: updateBucket(tail, value) + } + + val bucketIndex = a.hashCode() % buckets.size + val bucket = buckets(bucketIndex) + val updatedBucket = updateBucket(bucket, a) + new HashSet(buckets.updated(bucketIndex, updatedBucket)) + } + + def toIterator: Iterator[A] = + buckets.flatten.toIterator + +} + +object HashSet extends HashSetInstances { + + val defaultCapacity = 16 + + def apply[A: Eq](as: A*): HashSet[A] = + as.foldLeft(empty[A])(_ + _) + + def empty[A]: HashSet[A] = new HashSet(Vector.fill(defaultCapacity)(List.empty)) + +} + +trait HashSetInstances { + + implicit def eqSet[A](implicit A: Eq[A]): Eq[HashSet[A]] = new Eq[HashSet[A]] { + + override def eqv(x: HashSet[A], y: HashSet[A]): Boolean = + iteratorEq(x.toIterator, y.toIterator) + + } + +} From d09bdd3e7db98c6bc17975baf3b6d602dafc5206 Mon Sep 17 00:00:00 2001 From: Aly Date: Sun, 29 Nov 2020 10:28:37 -0800 Subject: [PATCH 3/4] Rename existing Map and Set files to AvlMap and AvlSet --- .../collections/{Map.scala => AvlMap.scala} | 52 ----------------- .../collections/{Set.scala => AvlSet.scala} | 44 --------------- .../main/scala/cats/collections/HashMap.scala | 56 +++++++++++++++++++ .../main/scala/cats/collections/HashSet.scala | 48 ++++++++++++++++ .../{SetSpec.scala => AvlSetSpec.scala} | 0 5 files changed, 104 insertions(+), 96 deletions(-) rename core/src/main/scala/cats/collections/{Map.scala => AvlMap.scala} (76%) rename core/src/main/scala/cats/collections/{Set.scala => AvlSet.scala} (92%) create mode 100644 core/src/main/scala/cats/collections/HashMap.scala create mode 100644 core/src/main/scala/cats/collections/HashSet.scala rename tests/src/test/scala/cats/collections/{SetSpec.scala => AvlSetSpec.scala} (100%) diff --git a/core/src/main/scala/cats/collections/Map.scala b/core/src/main/scala/cats/collections/AvlMap.scala similarity index 76% rename from core/src/main/scala/cats/collections/Map.scala rename to core/src/main/scala/cats/collections/AvlMap.scala index a82ffcb9..61eb38be 100644 --- a/core/src/main/scala/cats/collections/Map.scala +++ b/core/src/main/scala/cats/collections/AvlMap.scala @@ -172,55 +172,3 @@ trait AvlMapInstances { } } } - -class HashMap[K, V](val buckets: Vector[List[(K, V)]]) { - - def +(pair: (K, V))(implicit K: Eq[K]): HashMap[K, V] = { - def updateBucket(bucket: List[(K, V)], key: K, value: V): List[(K, V)] = - bucket match { - case Nil => (key, value) :: Nil - case (bk, _) :: tail if bk === key => (key, value) :: tail - case head :: tail => head :: updateBucket(tail, key, value) - } - - val (key, value) = pair - val keyBucketIndex = key.hashCode() % buckets.size - val keyBucket = buckets(keyBucketIndex) - val updatedKeyBucket = updateBucket(keyBucket, key, value) - new HashMap(buckets.updated(keyBucketIndex, updatedKeyBucket)) - } - - def get(key: K)(implicit K: Eq[K]): Option[V] = if(buckets.nonEmpty) { - val bucketIndex = key.hashCode % buckets.size - buckets(bucketIndex).collectFirst { - case (pkey, pvalue) if pkey === key => pvalue - } - } else None - - def entryHashSet(implicit K: Eq[K], V: Eq[V]): HashSet[(K, V)] = buckets.foldLeft(HashSet.empty[(K, V)]) { - case (oset, bucket) => bucket.foldLeft(oset)(_ + _) - } - -} - -object HashMap extends HashMapInstances { - - val defaultCapacity = 16 - - def apply[K: Eq, V](entries: (K, V)*): HashMap[K, V] = entries.foldLeft(empty[K, V])(_ + _) - - def empty[K, V]: HashMap[K, V] = new HashMap[K, V](Vector.fill(defaultCapacity)(List.empty)) - -} - -trait HashMapInstances { - - implicit def eqMap[K, V](implicit K: Eq[K], V: Eq[V]): Eq[HashMap[K, V]] = new Eq[HashMap[K, V]] { - - override def eqv(x: HashMap[K,V], y: HashMap[K,V]): Boolean = { - x.entryHashSet === y.entryHashSet - } - - } - -} diff --git a/core/src/main/scala/cats/collections/Set.scala b/core/src/main/scala/cats/collections/AvlSet.scala similarity index 92% rename from core/src/main/scala/cats/collections/Set.scala rename to core/src/main/scala/cats/collections/AvlSet.scala index 94b47b6d..331ea591 100644 --- a/core/src/main/scala/cats/collections/Set.scala +++ b/core/src/main/scala/cats/collections/AvlSet.scala @@ -430,47 +430,3 @@ trait AvlSetInstances { t.toIterator.map(_.show).mkString("AvlSet(", ", ", ")") } } - -class HashSet[A](val buckets: Vector[List[A]]) { - - def +(a: A)(implicit eq: Eq[A]): HashSet[A] = { - def updateBucket(bucket: List[A], value: A): List[A] = - bucket match { - case Nil => a :: Nil - case h :: tail => - if(h === value) h :: tail - else h :: updateBucket(tail, value) - } - - val bucketIndex = a.hashCode() % buckets.size - val bucket = buckets(bucketIndex) - val updatedBucket = updateBucket(bucket, a) - new HashSet(buckets.updated(bucketIndex, updatedBucket)) - } - - def toIterator: Iterator[A] = - buckets.flatten.toIterator - -} - -object HashSet extends HashSetInstances { - - val defaultCapacity = 16 - - def apply[A: Eq](as: A*): HashSet[A] = - as.foldLeft(empty[A])(_ + _) - - def empty[A]: HashSet[A] = new HashSet(Vector.fill(defaultCapacity)(List.empty)) - -} - -trait HashSetInstances { - - implicit def eqSet[A](implicit A: Eq[A]): Eq[HashSet[A]] = new Eq[HashSet[A]] { - - override def eqv(x: HashSet[A], y: HashSet[A]): Boolean = - iteratorEq(x.toIterator, y.toIterator) - - } - -} diff --git a/core/src/main/scala/cats/collections/HashMap.scala b/core/src/main/scala/cats/collections/HashMap.scala new file mode 100644 index 00000000..57f048d0 --- /dev/null +++ b/core/src/main/scala/cats/collections/HashMap.scala @@ -0,0 +1,56 @@ +package cats.collections + +import cats._ +import cats.implicits._ + +class HashMap[K, V](val buckets: Vector[List[(K, V)]]) { + + def +(pair: (K, V))(implicit K: Hash[K]): HashMap[K, V] = { + def updateBucket(bucket: List[(K, V)], key: K, value: V): List[(K, V)] = + bucket match { + case Nil => (key, value) :: Nil + case (bk, _) :: tail if bk === key => (key, value) :: tail + case head :: tail => head :: updateBucket(tail, key, value) + } + + val (key, value) = pair + val keyBucketIndex = key.hash % buckets.size + val keyBucket = buckets(keyBucketIndex) + val updatedKeyBucket = updateBucket(keyBucket, key, value) + new HashMap(buckets.updated(keyBucketIndex, updatedKeyBucket)) + } + + def get(key: K)(implicit K: Eq[K]): Option[V] = if(buckets.nonEmpty) { + val bucketIndex = key.hashCode % buckets.size + buckets(bucketIndex).collectFirst { + case (pkey, pvalue) if pkey === key => pvalue + } + } else None + + def entryHashSet(implicit K: Hash[K], V: Hash[V]): HashSet[(K, V)] = buckets.foldLeft(HashSet.empty[(K, V)]) { + case (oset, bucket) => bucket.foldLeft(oset)(_ + _) + } + +} + +object HashMap extends HashMapInstances { + + val defaultCapacity = 16 + + def apply[K: Hash, V](entries: (K, V)*): HashMap[K, V] = entries.foldLeft(empty[K, V])(_ + _) + + def empty[K, V]: HashMap[K, V] = new HashMap[K, V](Vector.fill(defaultCapacity)(List.empty)) + +} + +trait HashMapInstances { + + implicit def eqMap[K, V](implicit K: Hash[K], V: Hash[V]): Eq[HashMap[K, V]] = new Eq[HashMap[K, V]] { + + override def eqv(x: HashMap[K,V], y: HashMap[K,V]): Boolean = { + x.entryHashSet === y.entryHashSet + } + + } + +} diff --git a/core/src/main/scala/cats/collections/HashSet.scala b/core/src/main/scala/cats/collections/HashSet.scala new file mode 100644 index 00000000..35811042 --- /dev/null +++ b/core/src/main/scala/cats/collections/HashSet.scala @@ -0,0 +1,48 @@ +package cats.collections + +import cats._ +import cats.implicits._ + +class HashSet[A](val buckets: Vector[List[A]]) { + + def +(a: A)(implicit hash: Hash[A]): HashSet[A] = { + def updateBucket(bucket: List[A], value: A): List[A] = + bucket match { + case Nil => a :: Nil + case h :: tail => + if(h === value) h :: tail + else h :: updateBucket(tail, value) + } + + val bucketIndex = a.hash % buckets.size + val bucket = buckets(bucketIndex) + val updatedBucket = updateBucket(bucket, a) + new HashSet(buckets.updated(bucketIndex, updatedBucket)) + } + + def toIterator: Iterator[A] = + buckets.flatten.toIterator + +} + +object HashSet extends HashSetInstances { + + val defaultCapacity = 16 + + def apply[A: Hash](as: A*): HashSet[A] = + as.foldLeft(empty[A])(_ + _) + + def empty[A]: HashSet[A] = new HashSet(Vector.fill(defaultCapacity)(List.empty)) + +} + +trait HashSetInstances { + + implicit def eqSet[A](implicit A: Eq[A]): Eq[HashSet[A]] = new Eq[HashSet[A]] { + + override def eqv(x: HashSet[A], y: HashSet[A]): Boolean = + iteratorEq(x.toIterator, y.toIterator) + + } + +} diff --git a/tests/src/test/scala/cats/collections/SetSpec.scala b/tests/src/test/scala/cats/collections/AvlSetSpec.scala similarity index 100% rename from tests/src/test/scala/cats/collections/SetSpec.scala rename to tests/src/test/scala/cats/collections/AvlSetSpec.scala From a5f263818d1f80b09622eaad9381aa231a977030 Mon Sep 17 00:00:00 2001 From: Aly Date: Sun, 29 Nov 2020 10:51:10 -0800 Subject: [PATCH 4/4] Remove trailing whitespace --- core/src/main/scala/cats/collections/HashSet.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/collections/HashSet.scala b/core/src/main/scala/cats/collections/HashSet.scala index 35811042..5e9083f2 100644 --- a/core/src/main/scala/cats/collections/HashSet.scala +++ b/core/src/main/scala/cats/collections/HashSet.scala @@ -13,7 +13,7 @@ class HashSet[A](val buckets: Vector[List[A]]) { if(h === value) h :: tail else h :: updateBucket(tail, value) } - + val bucketIndex = a.hash % buckets.size val bucket = buckets(bucketIndex) val updatedBucket = updateBucket(bucket, a)