scala.collection.immutable.ListMap.scala Maven / Gradle / Ivy
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala
package collection
package immutable
import scala.annotation.{nowarn, tailrec}
import scala.collection.mutable.ReusableBuilder
import scala.collection.generic.DefaultSerializable
import scala.runtime.Statics.releaseFence
import scala.util.hashing.MurmurHash3
/**
* This class implements immutable maps using a list-based data structure. List map iterators and
* traversal methods visit key-value pairs in the order they were first inserted.
*
* Entries are stored internally in reversed insertion order, which means the newest key is at the
* head of the list. As such, methods such as `head` and `tail` are O(n), while `last` and `init`
* are O(1). Other operations, such as inserting or removing entries, are also O(n), which makes
* this collection suitable only for a small number of elements.
*
* Instances of `ListMap` represent empty maps; they can be either created by calling the
* constructor directly, or by applying the function `ListMap.empty`.
*
* @tparam K the type of the keys contained in this list map
* @tparam V the type of the values associated with the keys
*
* @define Coll ListMap
* @define coll list map
* @define mayNotTerminateInf
* @define willNotTerminateInf
*/
sealed class ListMap[K, +V]
extends AbstractMap[K, V]
with SeqMap[K, V]
with StrictOptimizedMapOps[K, V, ListMap, ListMap[K, V]]
with MapFactoryDefaults[K, V, ListMap, Iterable]
with DefaultSerializable {
override def mapFactory: MapFactory[ListMap] = ListMap
override def size: Int = 0
override def isEmpty: Boolean = true
override def knownSize: Int = 0
def get(key: K): Option[V] = None
def updated[V1 >: V](key: K, value: V1): ListMap[K, V1] = new ListMap.Node[K, V1](key, value, this)
def removed(key: K): ListMap[K, V] = this
def iterator: Iterator[(K, V)] = {
var curr: ListMap[K, V] = this
var res: List[(K, V)] = Nil
while (curr.nonEmpty) {
res = (curr.key, curr.value) :: res
curr = curr.next
}
res.iterator
}
@nowarn("msg=overriding method keys")
override def keys: Iterable[K] = {
var curr: ListMap[K, V] = this
var res: List[K] = Nil
while (curr.nonEmpty) {
res = curr.key :: res
curr = curr.next
}
res
}
override def hashCode(): Int = {
if (isEmpty) MurmurHash3.emptyMapHash
else {
// Can't efficiently override foreachEntry directly in ListMap because it would need to preserve iteration
// order be reversing the list first. But mapHash is symmetric so the reversed order is fine here.
val _reversed = new immutable.AbstractMap[K, V] {
override def isEmpty: Boolean = ListMap.this.isEmpty
override def removed(key: K): Map[K, V] = ListMap.this.removed(key)
override def updated[V1 >: V](key: K, value: V1): Map[K, V1] = ListMap.this.updated(key, value)
override def get(key: K): Option[V] = ListMap.this.get(key)
override def iterator: Iterator[(K, V)] = ListMap.this.iterator
override def foreachEntry[U](f: (K, V) => U): Unit = {
var curr: ListMap[K, V] = ListMap.this
while (curr.nonEmpty) {
f(curr.key, curr.value)
curr = curr.next
}
}
}
MurmurHash3.mapHash(_reversed)
}
}
private[immutable] def key: K = throw new NoSuchElementException("key of empty map")
private[immutable] def value: V = throw new NoSuchElementException("value of empty map")
private[immutable] def next: ListMap[K, V] = throw new NoSuchElementException("next of empty map")
override def foldRight[Z](z: Z)(op: ((K, V), Z) => Z): Z = ListMap.foldRightInternal(this, z, op)
override protected[this] def className = "ListMap"
}
/**
* $factoryInfo
*
* Note that each element insertion takes O(n) time, which means that creating a list map with
* n elements will take O(n^2^) time. This makes the builder suitable only for a small number of
* elements.
*
* @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#list-maps "Scala's Collection Library overview"]]
* section on `List Maps` for more information.
* @define Coll ListMap
* @define coll list map
*/
@SerialVersionUID(3L)
object ListMap extends MapFactory[ListMap] {
/**
* Represents an entry in the `ListMap`.
*/
private[immutable] final class Node[K, V](
override private[immutable] val key: K,
private[immutable] var _value: V,
private[immutable] var _init: ListMap[K, V]
) extends ListMap[K, V] {
releaseFence()
override private[immutable] def value: V = _value
override def size: Int = sizeInternal(this, 0)
@tailrec private[this] def sizeInternal(cur: ListMap[K, V], acc: Int): Int =
if (cur.isEmpty) acc
else sizeInternal(cur.next, acc + 1)
override def isEmpty: Boolean = false
override def knownSize: Int = -1
@throws[NoSuchElementException]
override def apply(k: K): V = applyInternal(this, k)
@tailrec private[this] def applyInternal(cur: ListMap[K, V], k: K): V =
if (cur.isEmpty) throw new NoSuchElementException("key not found: " + k)
else if (k == cur.key) cur.value
else applyInternal(cur.next, k)
override def get(k: K): Option[V] = getInternal(this, k)
@tailrec private[this] def getInternal(cur: ListMap[K, V], k: K): Option[V] =
if (cur.isEmpty) None
else if (k == cur.key) Some(cur.value)
else getInternal(cur.next, k)
override def contains(k: K): Boolean = containsInternal(this, k)
@tailrec private[this] def containsInternal(cur: ListMap[K, V], k: K): Boolean =
if (cur.isEmpty) false
else if (k == cur.key) true
else containsInternal(cur.next, k)
override def updated[V1 >: V](k: K, v: V1): ListMap[K, V1] = {
var index = -1 // the index (in reverse) where the key to update exists, if it is found
var found = false // true if the key is found int he map
var isDifferent = false // true if the key was found and the values are different
{
var curr: ListMap[K, V] = this
while (curr.nonEmpty && !found) {
if (k == curr.key) {
found = true
isDifferent = v.asInstanceOf[AnyRef] ne curr.value.asInstanceOf[AnyRef]
}
index += 1
curr = curr.init
}
}
if (found) {
if (isDifferent) {
var newHead: ListMap.Node[K, V1] = null
var prev: ListMap.Node[K, V1] = null
var curr: ListMap[K, V1] = this
var i = 0
while (i < index) {
val temp = new ListMap.Node(curr.key, curr.value, null)
if (prev ne null) {
prev._init = temp
}
prev = temp
curr = curr.init
if (newHead eq null) {
newHead = prev
}
i += 1
}
val newNode = new ListMap.Node(curr.key, v, curr.init)
if (prev ne null) {
prev._init = newNode
}
releaseFence()
if (newHead eq null) newNode else newHead
} else {
this
}
} else {
new ListMap.Node(k, v, this)
}
}
@tailrec private[this] def removeInternal(k: K, cur: ListMap[K, V], acc: List[ListMap[K, V]]): ListMap[K, V] =
if (cur.isEmpty) acc.last
else if (k == cur.key) acc.foldLeft(cur.next) { (t, h) => new Node(h.key, h.value, t) }
else removeInternal(k, cur.next, cur :: acc)
override def removed(k: K): ListMap[K, V] = removeInternal(k, this, Nil)
override private[immutable] def next: ListMap[K, V] = _init
override def last: (K, V) = (key, value)
override def init: ListMap[K, V] = next
}
def empty[K, V]: ListMap[K, V] = EmptyListMap.asInstanceOf[ListMap[K, V]]
private object EmptyListMap extends ListMap[Any, Nothing]
def from[K, V](it: collection.IterableOnce[(K, V)]): ListMap[K, V] =
it match {
case lm: ListMap[K, V] => lm
case lhm: collection.mutable.LinkedHashMap[K, V] =>
// by directly iterating through LinkedHashMap entries, we save creating intermediate tuples for each
// key-value pair
var current: ListMap[K, V] = empty[K, V]
var firstEntry = lhm._firstEntry
while (firstEntry ne null) {
current = new Node(firstEntry.key, firstEntry.value, current)
firstEntry = firstEntry.later
}
current
case _: collection.Map[K, V] | _: collection.MapView[K, V] =>
// when creating from a map, we need not handle duplicate keys, so we can just append each key-value to the end
var current: ListMap[K, V] = empty[K, V]
val iter = it.iterator
while (iter.hasNext) {
val (k, v) = iter.next()
current = new Node(k, v, current)
}
current
case _ => (newBuilder[K, V] ++= it).result()
}
/** Returns a new ListMap builder
*
* The implementation safely handles additions after `result()` without calling `clear()`
*
* @tparam K the map key type
* @tparam V the map value type
*/
def newBuilder[K, V]: ReusableBuilder[(K, V), ListMap[K, V]] = new ListMapBuilder[K, V]
@tailrec private def foldRightInternal[K, V, Z](map: ListMap[K, V], prevValue: Z, op: ((K, V), Z) => Z): Z = {
if (map.isEmpty) prevValue
else foldRightInternal(map.init, op(map.last, prevValue), op)
}
}
/** Builder for ListMap.
* $multipleResults
*/
private[immutable] final class ListMapBuilder[K, V] extends mutable.ReusableBuilder[(K, V), ListMap[K, V]] {
private[this] var isAliased: Boolean = false
private[this] var underlying: ListMap[K, V] = ListMap.empty
override def clear(): Unit = {
underlying = ListMap.empty
isAliased = false
}
override def result(): ListMap[K, V] = {
isAliased = true
releaseFence()
underlying
}
override def addOne(elem: (K, V)): this.type = addOne(elem._1, elem._2)
@tailrec
private[this] def insertValueAtKeyReturnFound(m: ListMap[K, V], key: K, value: V): Boolean = m match {
case n: ListMap.Node[K, V] =>
if (n.key == key) {
n._value = value
true
} else {
insertValueAtKeyReturnFound(n.init, key, value)
}
case _ => false
}
def addOne(key: K, value: V): this.type = {
if (isAliased) {
underlying = underlying.updated(key, value)
} else {
if (!insertValueAtKeyReturnFound(underlying, key, value)) {
underlying = new ListMap.Node(key, value, underlying)
}
}
this
}
override def addAll(xs: IterableOnce[(K, V)]): this.type = {
if (isAliased) {
super.addAll(xs)
} else if (underlying.nonEmpty) {
xs match {
case m: collection.Map[K, V] =>
// if it is a map, then its keys will not collide with themselves.
// therefor we only need to check the already-existing elements for collisions.
// No need to check the entire list
val iter = m.iterator
var newUnderlying = underlying
while (iter.hasNext) {
val next = iter.next()
if (!insertValueAtKeyReturnFound(underlying, next._1, next._2)) {
newUnderlying = new ListMap.Node[K, V](next._1, next._2, newUnderlying)
}
}
underlying = newUnderlying
this
case _ =>
super.addAll(xs)
}
} else xs match {
case lhm: collection.mutable.LinkedHashMap[K, V] =>
// special-casing LinkedHashMap avoids creating of Iterator and tuples for each key-value
var firstEntry = lhm._firstEntry
while (firstEntry ne null) {
underlying = new ListMap.Node(firstEntry.key, firstEntry.value, underlying)
firstEntry = firstEntry.later
}
this
case _: collection.Map[K, V] | _: collection.MapView[K, V] =>
val iter = xs.iterator
while (iter.hasNext) {
val (k, v) = iter.next()
underlying = new ListMap.Node(k, v, underlying)
}
this
case _ =>
super.addAll(xs)
}
}
}