diff --git a/core/src/main/scala/cats/collections/Map.scala b/core/src/main/scala/cats/collections/AvlMap.scala similarity index 100% rename from core/src/main/scala/cats/collections/Map.scala rename to core/src/main/scala/cats/collections/AvlMap.scala diff --git a/core/src/main/scala/cats/collections/Set.scala b/core/src/main/scala/cats/collections/AvlSet.scala similarity index 100% rename from core/src/main/scala/cats/collections/Set.scala rename to core/src/main/scala/cats/collections/AvlSet.scala 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..5e9083f2 --- /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