OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of dart.collection; | |
6 | |
7 /** | |
8 * This [Iterable] mixin implements all [Iterable] members except `iterator`. | |
9 * | |
10 * All other methods are implemented in terms of `iterator`. | |
11 */ | |
12 abstract class IterableMixin<E> implements Iterable<E> { | |
13 // This class has methods copied verbatim into: | |
14 // - IterableBase | |
15 // - SetMixin | |
16 // If changing a method here, also change the other copies. | |
17 | |
18 Iterable/*<T>*/ map/*<T>*/(/*=T*/ f(E element)) => | |
19 new MappedIterable<E, dynamic/*=T*/>(this, f); | |
20 | |
21 Iterable<E> where(bool f(E element)) => new WhereIterable<E>(this, f); | |
22 | |
23 Iterable/*<T>*/ expand/*<T>*/(Iterable/*<T>*/ f(E element)) => | |
24 new ExpandIterable<E, dynamic/*=T*/>(this, f); | |
25 | |
26 bool contains(Object element) { | |
27 for (E e in this) { | |
28 if (e == element) return true; | |
29 } | |
30 return false; | |
31 } | |
32 | |
33 void forEach(void f(E element)) { | |
34 for (E element in this) f(element); | |
35 } | |
36 | |
37 E reduce(E combine(E value, E element)) { | |
38 Iterator<E> iterator = this.iterator; | |
39 if (!iterator.moveNext()) { | |
40 throw IterableElementError.noElement(); | |
41 } | |
42 E value = iterator.current; | |
43 while (iterator.moveNext()) { | |
44 value = combine(value, iterator.current); | |
45 } | |
46 return value; | |
47 } | |
48 | |
49 dynamic/*=T*/ fold/*<T>*/(var/*=T*/ initialValue, | |
50 dynamic/*=T*/ combine(var/*=T*/ previousValue, E element)) { | |
51 var value = initialValue; | |
52 for (E element in this) value = combine(value, element); | |
53 return value; | |
54 } | |
55 | |
56 bool every(bool f(E element)) { | |
57 for (E element in this) { | |
58 if (!f(element)) return false; | |
59 } | |
60 return true; | |
61 } | |
62 | |
63 String join([String separator = ""]) { | |
64 Iterator<E> iterator = this.iterator; | |
65 if (!iterator.moveNext()) return ""; | |
66 StringBuffer buffer = new StringBuffer(); | |
67 if (separator == null || separator == "") { | |
68 do { | |
69 buffer.write("${iterator.current}"); | |
70 } while (iterator.moveNext()); | |
71 } else { | |
72 buffer.write("${iterator.current}"); | |
73 while (iterator.moveNext()) { | |
74 buffer.write(separator); | |
75 buffer.write("${iterator.current}"); | |
76 } | |
77 } | |
78 return buffer.toString(); | |
79 } | |
80 | |
81 bool any(bool f(E element)) { | |
82 for (E element in this) { | |
83 if (f(element)) return true; | |
84 } | |
85 return false; | |
86 } | |
87 | |
88 List<E> toList({ bool growable: true }) => | |
89 new List<E>.from(this, growable: growable); | |
90 | |
91 Set<E> toSet() => new Set<E>.from(this); | |
92 | |
93 int get length { | |
94 assert(this is! EfficientLength); | |
95 int count = 0; | |
96 Iterator it = iterator; | |
97 while (it.moveNext()) { | |
98 count++; | |
99 } | |
100 return count; | |
101 } | |
102 | |
103 bool get isEmpty => !iterator.moveNext(); | |
104 | |
105 bool get isNotEmpty => !isEmpty; | |
106 | |
107 Iterable<E> take(int count) { | |
108 return new TakeIterable<E>(this, count); | |
109 } | |
110 | |
111 Iterable<E> takeWhile(bool test(E value)) { | |
112 return new TakeWhileIterable<E>(this, test); | |
113 } | |
114 | |
115 Iterable<E> skip(int count) { | |
116 return new SkipIterable<E>(this, count); | |
117 } | |
118 | |
119 Iterable<E> skipWhile(bool test(E value)) { | |
120 return new SkipWhileIterable<E>(this, test); | |
121 } | |
122 | |
123 E get first { | |
124 Iterator<E> it = iterator; | |
125 if (!it.moveNext()) { | |
126 throw IterableElementError.noElement(); | |
127 } | |
128 return it.current; | |
129 } | |
130 | |
131 E get last { | |
132 Iterator<E> it = iterator; | |
133 if (!it.moveNext()) { | |
134 throw IterableElementError.noElement(); | |
135 } | |
136 E result; | |
137 do { | |
138 result = it.current; | |
139 } while(it.moveNext()); | |
140 return result; | |
141 } | |
142 | |
143 E get single { | |
144 Iterator<E> it = iterator; | |
145 if (!it.moveNext()) throw IterableElementError.noElement(); | |
146 E result = it.current; | |
147 if (it.moveNext()) throw IterableElementError.tooMany(); | |
148 return result; | |
149 } | |
150 | |
151 E firstWhere(bool test(E value), { E orElse() }) { | |
152 for (E element in this) { | |
153 if (test(element)) return element; | |
154 } | |
155 if (orElse != null) return orElse(); | |
156 throw IterableElementError.noElement(); | |
157 } | |
158 | |
159 E lastWhere(bool test(E value), { E orElse() }) { | |
160 E result = null; | |
161 bool foundMatching = false; | |
162 for (E element in this) { | |
163 if (test(element)) { | |
164 result = element; | |
165 foundMatching = true; | |
166 } | |
167 } | |
168 if (foundMatching) return result; | |
169 if (orElse != null) return orElse(); | |
170 throw IterableElementError.noElement(); | |
171 } | |
172 | |
173 E singleWhere(bool test(E value)) { | |
174 E result = null; | |
175 bool foundMatching = false; | |
176 for (E element in this) { | |
177 if (test(element)) { | |
178 if (foundMatching) { | |
179 throw IterableElementError.tooMany(); | |
180 } | |
181 result = element; | |
182 foundMatching = true; | |
183 } | |
184 } | |
185 if (foundMatching) return result; | |
186 throw IterableElementError.noElement(); | |
187 } | |
188 | |
189 E elementAt(int index) { | |
190 if (index is! int) throw new ArgumentError.notNull("index"); | |
191 RangeError.checkNotNegative(index, "index"); | |
192 int elementIndex = 0; | |
193 for (E element in this) { | |
194 if (index == elementIndex) return element; | |
195 elementIndex++; | |
196 } | |
197 throw new RangeError.index(index, this, "index", null, elementIndex); | |
198 } | |
199 | |
200 | |
201 String toString() => IterableBase.iterableToShortString(this, '(', ')'); | |
202 } | |
203 | |
204 /** | |
205 * Base class for implementing [Iterable]. | |
206 * | |
207 * This class implements all methods of [Iterable] except [Iterable.iterator] | |
208 * in terms of `iterator`. | |
209 */ | |
210 abstract class IterableBase<E> extends Iterable<E> { | |
211 const IterableBase(); | |
212 | |
213 /** | |
214 * Convert an `Iterable` to a string like [IterableBase.toString]. | |
215 * | |
216 * Allows using other delimiters than '(' and ')'. | |
217 * | |
218 * Handles circular references where converting one of the elements | |
219 * to a string ends up converting [iterable] to a string again. | |
220 */ | |
221 static String iterableToShortString(Iterable iterable, | |
222 [String leftDelimiter = '(', | |
223 String rightDelimiter = ')']) { | |
224 if (_isToStringVisiting(iterable)) { | |
225 if (leftDelimiter == "(" && rightDelimiter == ")") { | |
226 // Avoid creating a new string in the "common" case. | |
227 return "(...)"; | |
228 } | |
229 return "$leftDelimiter...$rightDelimiter"; | |
230 } | |
231 List parts = []; | |
232 _toStringVisiting.add(iterable); | |
233 try { | |
234 _iterablePartsToStrings(iterable, parts); | |
235 } finally { | |
236 assert(identical(_toStringVisiting.last, iterable)); | |
237 _toStringVisiting.removeLast(); | |
238 } | |
239 return (new StringBuffer(leftDelimiter) | |
240 ..writeAll(parts, ", ") | |
241 ..write(rightDelimiter)).toString(); | |
242 } | |
243 | |
244 /** | |
245 * Converts an `Iterable` to a string. | |
246 * | |
247 * Converts each elements to a string, and separates the results by ", ". | |
248 * Then wraps the result in [leftDelimiter] and [rightDelimiter]. | |
249 * | |
250 * Unlike [iterableToShortString], this conversion doesn't omit any | |
251 * elements or puts any limit on the size of the result. | |
252 * | |
253 * Handles circular references where converting one of the elements | |
254 * to a string ends up converting [iterable] to a string again. | |
255 */ | |
256 static String iterableToFullString(Iterable iterable, | |
257 [String leftDelimiter = '(', | |
258 String rightDelimiter = ')']) { | |
259 if (_isToStringVisiting(iterable)) { | |
260 return "$leftDelimiter...$rightDelimiter"; | |
261 } | |
262 StringBuffer buffer = new StringBuffer(leftDelimiter); | |
263 _toStringVisiting.add(iterable); | |
264 try { | |
265 buffer.writeAll(iterable, ", "); | |
266 } finally { | |
267 assert(identical(_toStringVisiting.last, iterable)); | |
268 _toStringVisiting.removeLast(); | |
269 } | |
270 buffer.write(rightDelimiter); | |
271 return buffer.toString(); | |
272 } | |
273 } | |
274 | |
275 /** A set used to identify cyclic lists during toString() calls. */ | |
276 final List _toStringVisiting = []; | |
277 | |
278 /** Check if we are currently visiting `o` in a toString call. */ | |
279 bool _isToStringVisiting(Object o) { | |
280 for (int i = 0; i < _toStringVisiting.length; i++) { | |
281 if (identical(o, _toStringVisiting[i])) return true; | |
282 } | |
283 return false; | |
284 } | |
285 | |
286 /** | |
287 * Convert elments of [iterable] to strings and store them in [parts]. | |
288 */ | |
289 void _iterablePartsToStrings(Iterable iterable, List parts) { | |
290 /* | |
291 * This is the complicated part of [iterableToShortString]. | |
292 * It is extracted as a separate function to avoid having too much code | |
293 * inside the try/finally. | |
294 */ | |
295 /// Try to stay below this many characters. | |
296 const int LENGTH_LIMIT = 80; | |
297 /// Always at least this many elements at the start. | |
298 const int HEAD_COUNT = 3; | |
299 /// Always at least this many elements at the end. | |
300 const int TAIL_COUNT = 2; | |
301 /// Stop iterating after this many elements. Iterables can be infinite. | |
302 const int MAX_COUNT = 100; | |
303 // Per entry length overhead. It's for ", " for all after the first entry, | |
304 // and for "(" and ")" for the initial entry. By pure luck, that's the same | |
305 // number. | |
306 const int OVERHEAD = 2; | |
307 const int ELLIPSIS_SIZE = 3; // "...".length. | |
308 | |
309 int length = 0; | |
310 int count = 0; | |
311 Iterator it = iterable.iterator; | |
312 // Initial run of elements, at least HEAD_COUNT, and then continue until | |
313 // passing at most LENGTH_LIMIT characters. | |
314 while (length < LENGTH_LIMIT || count < HEAD_COUNT) { | |
315 if (!it.moveNext()) return; | |
316 String next = "${it.current}"; | |
317 parts.add(next); | |
318 length += next.length + OVERHEAD; | |
319 count++; | |
320 } | |
321 | |
322 String penultimateString; | |
323 String ultimateString; | |
324 | |
325 // Find last two elements. One or more of them may already be in the | |
326 // parts array. Include their length in `length`. | |
327 var penultimate = null; | |
328 var ultimate = null; | |
329 if (!it.moveNext()) { | |
330 if (count <= HEAD_COUNT + TAIL_COUNT) return; | |
331 ultimateString = parts.removeLast(); | |
332 penultimateString = parts.removeLast(); | |
333 } else { | |
334 penultimate = it.current; | |
335 count++; | |
336 if (!it.moveNext()) { | |
337 if (count <= HEAD_COUNT + 1) { | |
338 parts.add("$penultimate"); | |
339 return; | |
340 } | |
341 ultimateString = "$penultimate"; | |
342 penultimateString = parts.removeLast(); | |
343 length += ultimateString.length + OVERHEAD; | |
344 } else { | |
345 ultimate = it.current; | |
346 count++; | |
347 // Then keep looping, keeping the last two elements in variables. | |
348 assert(count < MAX_COUNT); | |
349 while (it.moveNext()) { | |
350 penultimate = ultimate; | |
351 ultimate = it.current; | |
352 count++; | |
353 if (count > MAX_COUNT) { | |
354 // If we haven't found the end before MAX_COUNT, give up. | |
355 // This cannot happen in the code above because each entry | |
356 // increases length by at least two, so there is no way to | |
357 // visit more than ~40 elements before this loop. | |
358 | |
359 // Remove any surplus elements until length, including ", ...)", | |
360 // is at most LENGTH_LIMIT. | |
361 while (length > LENGTH_LIMIT - ELLIPSIS_SIZE - OVERHEAD && | |
362 count > HEAD_COUNT) { | |
363 length -= parts.removeLast().length + OVERHEAD; | |
364 count--; | |
365 } | |
366 parts.add("..."); | |
367 return; | |
368 } | |
369 } | |
370 penultimateString = "$penultimate"; | |
371 ultimateString = "$ultimate"; | |
372 length += | |
373 ultimateString.length + penultimateString.length + 2 * OVERHEAD; | |
374 } | |
375 } | |
376 | |
377 // If there is a gap between the initial run and the last two, | |
378 // prepare to add an ellipsis. | |
379 String elision = null; | |
380 if (count > parts.length + TAIL_COUNT) { | |
381 elision = "..."; | |
382 length += ELLIPSIS_SIZE + OVERHEAD; | |
383 } | |
384 | |
385 // If the last two elements were very long, and we have more than | |
386 // HEAD_COUNT elements in the initial run, drop some to make room for | |
387 // the last two. | |
388 while (length > LENGTH_LIMIT && parts.length > HEAD_COUNT) { | |
389 length -= parts.removeLast().length + OVERHEAD; | |
390 if (elision == null) { | |
391 elision = "..."; | |
392 length += ELLIPSIS_SIZE + OVERHEAD; | |
393 } | |
394 } | |
395 if (elision != null) { | |
396 parts.add(elision); | |
397 } | |
398 parts.add(penultimateString); | |
399 parts.add(ultimateString); | |
400 } | |
OLD | NEW |