Skip to content

Commit 511c02c

Browse files
authored
Merge pull request scala#7509 from joshlemer/issue/11297-seqmap-builders-2
[11296] Implement mutating ListMapBuilder
2 parents 284a9c0 + b549561 commit 511c02c

File tree

2 files changed

+131
-46
lines changed

2 files changed

+131
-46
lines changed

src/library/scala/collection/immutable/ListMap.scala

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ package scala
1414
package collection
1515
package immutable
1616

17-
import java.io.{ObjectInputStream, ObjectOutputStream}
18-
19-
import collection.{Iterator, MapFactory}
2017
import scala.annotation.tailrec
2118

22-
import scala.collection.mutable.{Builder, ImmutableBuilder}
19+
import scala.collection.mutable.Builder
20+
import scala.runtime.Statics.releaseFence
2321

2422
/**
2523
* This class implements immutable maps using a list-based data structure. List map iterators and
@@ -60,7 +58,7 @@ sealed class ListMap[K, +V]
6058
override def knownSize: Int = 0
6159
def get(key: K): Option[V] = None
6260

63-
def updated[B1 >: V](key: K, value: B1): ListMap[K, B1] = new Node[B1](key, value)
61+
def updated[V1 >: V](key: K, value: V1): ListMap[K, V1] = new ListMap.Node[K, V1](key, value, this)
6462

6563
def removed(key: K): ListMap[K, V] = this
6664

@@ -84,82 +82,90 @@ sealed class ListMap[K, +V]
8482
res
8583
}
8684

87-
protected def key: K = throw new NoSuchElementException("key of empty map")
88-
protected def value: V = throw new NoSuchElementException("value of empty map")
89-
protected def next: ListMap[K, V] = throw new NoSuchElementException("next of empty map")
85+
private[immutable] def key: K = throw new NoSuchElementException("key of empty map")
86+
private[immutable] def value: V = throw new NoSuchElementException("value of empty map")
87+
private[immutable] def next: ListMap[K, V] = throw new NoSuchElementException("next of empty map")
9088

9189
override protected[this] def className = "ListMap"
9290

91+
}
92+
93+
/**
94+
* $factoryInfo
95+
*
96+
* Note that each element insertion takes O(n) time, which means that creating a list map with
97+
* n elements will take O(n^2^) time. This makes the builder suitable only for a small number of
98+
* elements.
99+
*
100+
* @see [[http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#list-maps "Scala's Collection Library overview"]]
101+
* section on `List Maps` for more information.
102+
* @since 1
103+
* @define Coll ListMap
104+
* @define coll list map
105+
*/
106+
@SerialVersionUID(3L)
107+
object ListMap extends MapFactory[ListMap] {
93108
/**
94109
* Represents an entry in the `ListMap`.
95110
*/
96-
protected class Node[V1 >: V](override protected val key: K,
97-
override protected val value: V1) extends ListMap[K, V1] {
111+
private[immutable] class Node[K, V](
112+
override private[immutable] val key: K,
113+
override private[immutable] val value: V,
114+
private[immutable] var _init: ListMap[K, V]
115+
) extends ListMap[K, V] {
116+
117+
releaseFence()
98118

99119
override def size: Int = sizeInternal(this, 0)
100120

101-
@tailrec private[this] def sizeInternal(cur: ListMap[K, V1], acc: Int): Int =
121+
@tailrec private[this] def sizeInternal(cur: ListMap[K, V], acc: Int): Int =
102122
if (cur.isEmpty) acc
103123
else sizeInternal(cur.next, acc + 1)
104124

105125
override def isEmpty: Boolean = false
126+
106127
override def knownSize: Int = -1
128+
107129
@throws[NoSuchElementException]
108-
override def apply(k: K): V1 = applyInternal(this, k)
130+
override def apply(k: K): V = applyInternal(this, k)
109131

110-
@tailrec private[this] def applyInternal(cur: ListMap[K, V1], k: K): V1 =
132+
@tailrec private[this] def applyInternal(cur: ListMap[K, V], k: K): V =
111133
if (cur.isEmpty) throw new NoSuchElementException("key not found: " + k)
112134
else if (k == cur.key) cur.value
113135
else applyInternal(cur.next, k)
114136

115-
override def get(k: K): Option[V1] = getInternal(this, k)
137+
override def get(k: K): Option[V] = getInternal(this, k)
116138

117-
@tailrec private[this] def getInternal(cur: ListMap[K, V1], k: K): Option[V1] =
139+
@tailrec private[this] def getInternal(cur: ListMap[K, V], k: K): Option[V] =
118140
if (cur.isEmpty) None
119141
else if (k == cur.key) Some(cur.value)
120142
else getInternal(cur.next, k)
121143

122144
override def contains(k: K): Boolean = containsInternal(this, k)
123145

124-
@tailrec private[this] def containsInternal(cur: ListMap[K, V1], k: K): Boolean =
125-
if(cur.isEmpty) false
146+
@tailrec private[this] def containsInternal(cur: ListMap[K, V], k: K): Boolean =
147+
if (cur.isEmpty) false
126148
else if (k == cur.key) true
127149
else containsInternal(cur.next, k)
128150

129-
override def updated[V2 >: V1](k: K, v: V2): ListMap[K, V2] = {
151+
override def updated[V1 >: V](k: K, v: V1): ListMap[K, V1] = {
130152
val (m, k0) = removeInternal(k, this, Nil)
131-
new m.Node[V2](k0, v)
153+
new Node(k0, v, m)
132154
}
133155

134-
override def removed(k: K): ListMap[K, V1] = removeInternal(k, this, Nil)._1
135-
136-
@tailrec private[this] def removeInternal(k: K, cur: ListMap[K, V1], acc: List[ListMap[K, V1]]): (ListMap[K, V1], K) =
156+
@tailrec private[this] def removeInternal(k: K, cur: ListMap[K, V], acc: List[ListMap[K, V]]): (ListMap[K, V], K) =
137157
if (cur.isEmpty) (acc.last, k)
138-
else if (k == cur.key) (acc.foldLeft(cur.next) { (t, h) => new t.Node(h.key, h.value) }, cur.key)
158+
else if (k == cur.key) (acc.foldLeft(cur.next) { (t, h) => new Node(h.key, h.value, t) }, cur.key)
139159
else removeInternal(k, cur.next, cur :: acc)
140160

141-
override protected def next: ListMap[K, V1] = ListMap.this
161+
override def removed(k: K): ListMap[K, V] = removeInternal(k, this, Nil)._1
142162

143-
override def last: (K, V1) = (key, value)
144-
override def init: ListMap[K, V1] = next
145-
}
146-
}
163+
override private[immutable] def next: ListMap[K, V] = _init
147164

148-
/**
149-
* $factoryInfo
150-
*
151-
* Note that each element insertion takes O(n) time, which means that creating a list map with
152-
* n elements will take O(n^2^) time. This makes the builder suitable only for a small number of
153-
* elements.
154-
*
155-
* @see [[http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#list-maps "Scala's Collection Library overview"]]
156-
* section on `List Maps` for more information.
157-
* @since 1
158-
* @define Coll ListMap
159-
* @define coll list map
160-
*/
161-
@SerialVersionUID(3L)
162-
object ListMap extends MapFactory[ListMap] {
165+
override def last: (K, V) = (key, value)
166+
override def init: ListMap[K, V] = next
167+
168+
}
163169

164170
def empty[K, V]: ListMap[K, V] = EmptyListMap.asInstanceOf[ListMap[K, V]]
165171

@@ -171,8 +177,54 @@ object ListMap extends MapFactory[ListMap] {
171177
case _ => (newBuilder[K, V] ++= it).result()
172178
}
173179

174-
def newBuilder[K, V]: Builder[(K, V), ListMap[K, V]] =
175-
new ImmutableBuilder[(K, V), ListMap[K, V]](empty) {
176-
def addOne(elem: (K, V)): this.type = { elems = elems + elem; this }
180+
/** Returns a new ListMap builder
181+
*
182+
* The implementation safely handles additions after `result()` without calling `clear()`
183+
*
184+
* @tparam K the map key type
185+
* @tparam V the map value type
186+
*/
187+
def newBuilder[K, V]: Builder[(K, V), ListMap[K, V]] = new ListMapBuilder[K, V]
188+
}
189+
190+
private[immutable] final class ListMapBuilder[K, V] extends mutable.Builder[(K, V), ListMap[K, V]] {
191+
private[this] var isAliased: Boolean = false
192+
private[this] var underlying: ListMap[K, V] = ListMap.empty
193+
194+
override def clear(): Unit = {
195+
underlying = ListMap.empty
196+
isAliased = false
197+
}
198+
199+
override def result(): ListMap[K, V] = {
200+
isAliased = true
201+
releaseFence()
202+
underlying
203+
}
204+
205+
override def addOne(elem: (K, V)): this.type = addOne(elem._1, elem._2)
206+
207+
def addOne(key: K, value: V): this.type = {
208+
if (isAliased) {
209+
underlying = underlying.updated(key, value)
210+
} else {
211+
var prev: ListMap.Node[K, V] = null
212+
var curr = underlying
213+
while (curr.nonEmpty) {
214+
if (key == curr.key) {
215+
if (prev eq null) {
216+
underlying = underlying.next
217+
} else {
218+
prev._init = curr.init
219+
}
220+
underlying = new ListMap.Node(curr.key, value, underlying)
221+
return this
222+
}
223+
prev = curr.asInstanceOf[ListMap.Node[K, V]]
224+
curr = curr.next
225+
}
226+
underlying = new ListMap.Node(key, value, underlying)
177227
}
228+
this
229+
}
178230
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package scala.collection.immutable
2+
3+
import org.openjdk.jmh.annotations._
4+
import org.openjdk.jmh.infra._
5+
import org.openjdk.jmh.runner.IterationType
6+
import benchmark._
7+
import java.util.concurrent.TimeUnit
8+
9+
@BenchmarkMode(Array(Mode.AverageTime))
10+
@Fork(2)
11+
@Threads(1)
12+
@Warmup(iterations = 10)
13+
@Measurement(iterations = 10)
14+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
15+
@State(Scope.Benchmark)
16+
class ListMapBenchmark {
17+
@Param(Array("1", "10", "100", "1000"))
18+
var size: Int = _
19+
20+
var kvs: Iterable[(Int, Int)] = _
21+
22+
@Setup(Level.Trial)
23+
def initKeys(): Unit = {
24+
val unique = (0 to size).map(i => i -> i)
25+
kvs = unique ++ unique
26+
}
27+
28+
@Benchmark
29+
def builder(bh: Blackhole): Unit = {
30+
val b = new ListMapBuilder[Int, Int]
31+
bh.consume(b.addAll(kvs).result())
32+
}
33+
}

0 commit comments

Comments
 (0)