@@ -81,47 +81,84 @@ sealed class ListSet[A] extends AbstractSet[A]
81
81
def - (elem : A ): ListSet [A ] = this
82
82
83
83
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
87
87
case ls : ListSet [A ] =>
88
88
if (ls eq this ) this
89
89
else {
90
90
val lsSize = ls.size
91
- if (lsSize == 0 ) this else {
91
+ if (lsSize == 0 ) this
92
+ else if (isEmpty) ls
93
+ else {
92
94
@ tailrec def skip (ls : ListSet [A ], count : Int ): ListSet [A ] = {
93
95
if (count == 0 ) ls else skip(ls.next, count - 1 )
94
96
}
95
97
96
98
@ tailrec def containsLimited (n : ListSet [A ], e : A , end : ListSet [A ]): Boolean =
97
99
(n ne end) && (e == n.elem || containsLimited(n.next, e, end))
98
100
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
+
99
104
// We hope to get some structural sharing so find the tail of the
100
105
// ListSet that are `eq` (or if there are not any then the ends of the lists),
101
106
// and we optimise the add to only iterate until we reach the common end
102
- val thisSize = this .size
107
+ val thisSize = this .size
103
108
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
106
113
while ((thisTail ne lsTail) && ! lsTail.isEmpty) {
107
114
thisTail = thisTail.next
108
115
lsTail = lsTail.next
109
116
}
110
- var toAdd = ls
117
+ var toAdd = ls
111
118
var result : ListSet [A ] = this
112
119
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
113
126
while (toAdd ne lsTail) {
114
127
val elem = toAdd.elem
115
128
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
118
140
}
119
141
toAdd = toAdd.next
120
142
}
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
+ }
121
158
result
122
159
}
123
160
}
124
- case _ =>
161
+ case _ =>
125
162
if (xs.isEmpty) this
126
163
else (repr /: xs) (_ + _)
127
164
}
0 commit comments