Skip to content

Commit f986690

Browse files
authored
Merge pull request scala/scala#9441 from mkeskells/2.12.x_ListMap_Set_fix
2 parents 2cd0f6b + 7a60d02 commit f986690

File tree

1 file changed

+48
-11
lines changed

1 file changed

+48
-11
lines changed

library/src/scala/collection/immutable/ListSet.scala

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,47 +81,84 @@ sealed class ListSet[A] extends AbstractSet[A]
8181
def -(elem: A): ListSet[A] = this
8282

8383
override def ++(xs: GenTraversableOnce[A]): ListSet[A] =
84-
xs match {
85-
// we want to avoid to use of iterator as it causes allocations
86-
// during reverseList
84+
xs match {
85+
// we want to avoid to use of iterator as it causes allocations
86+
// during reverseList
8787
case ls: ListSet[A] =>
8888
if (ls eq this) this
8989
else {
9090
val lsSize = ls.size
91-
if (lsSize == 0) this else {
91+
if (lsSize == 0) this
92+
else if (isEmpty) ls
93+
else {
9294
@tailrec def skip(ls: ListSet[A], count: Int): ListSet[A] = {
9395
if (count == 0) ls else skip(ls.next, count - 1)
9496
}
9597

9698
@tailrec def containsLimited(n: ListSet[A], e: A, end: ListSet[A]): Boolean =
9799
(n ne end) && (e == n.elem || containsLimited(n.next, e, end))
98100

101+
@tailrec def distanceTo(n: ListSet[A], end: ListSet[A], soFar: Int): Int =
102+
if (n eq end) soFar else distanceTo(n.next, end, soFar + 1)
103+
99104
// We hope to get some structural sharing so find the tail of the
100105
// ListSet that are `eq` (or if there are not any then the ends of the lists),
101106
// and we optimise the add to only iterate until we reach the common end
102-
val thisSize = this.size
107+
val thisSize = this.size
103108
val remaining = Math.min(thisSize, lsSize)
104-
var thisTail = skip(this, thisSize - remaining)
105-
var lsTail = skip(ls, lsSize - remaining)
109+
var thisTail = skip(this, thisSize - remaining)
110+
var lsTail = skip(ls, lsSize - remaining)
111+
//find out what part of the the ListSet is sharable
112+
//as we can ignore the shared elements
106113
while ((thisTail ne lsTail) && !lsTail.isEmpty) {
107114
thisTail = thisTail.next
108115
lsTail = lsTail.next
109116
}
110-
var toAdd = ls
117+
var toAdd = ls
111118
var result: ListSet[A] = this
112119

120+
// Its quite a common case that we are just adding a few elements, so it there are less than 5 elements we
121+
// hold them in pending0..3
122+
// if there are more than these 4 we hold the rest in pending
123+
var pending : Array[A] = null
124+
var pending0, pending1, pending2, pending3: A = null.asInstanceOf[A]
125+
var pendingCount = 0
113126
while (toAdd ne lsTail) {
114127
val elem = toAdd.elem
115128
if (!containsLimited(result, elem, lsTail)) {
116-
val r = result
117-
result = new r.Node(elem)
129+
pendingCount match {
130+
case 0 => pending0 = elem
131+
case 1 => pending1 = elem
132+
case 2 => pending2 = elem
133+
case 3 => pending3 = elem
134+
case _ =>
135+
if (pending eq null)
136+
pending = new Array[AnyRef](distanceTo(toAdd, lsTail, 0)).asInstanceOf[Array[A]]
137+
pending(pendingCount - 4) = elem
138+
}
139+
pendingCount += 1
118140
}
119141
toAdd = toAdd.next
120142
}
143+
// add the extra values. They are added in reverse order so as to ensure that the iteration order is correct
144+
// remembering that the content is in the reverse order to the iteration order
145+
// i.e. this.next is really the previous value
146+
while (pendingCount > 0) {
147+
val elem: A = pendingCount match {
148+
case 1 => pending0
149+
case 2 => pending1
150+
case 3 => pending2
151+
case 4 => pending3
152+
case _ => pending(pendingCount - 5)
153+
}
154+
val r = result
155+
result = new r.Node(elem)
156+
pendingCount -= 1
157+
}
121158
result
122159
}
123160
}
124-
case _ =>
161+
case _ =>
125162
if (xs.isEmpty) this
126163
else (repr /: xs) (_ + _)
127164
}

0 commit comments

Comments
 (0)