OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async' show | 5 import 'dart:async' show EventSink; |
6 EventSink; | |
7 import 'dart:collection'; | 6 import 'dart:collection'; |
8 import 'dart:convert'; | 7 import 'dart:convert'; |
9 | 8 |
10 import '../../compiler.dart'; | 9 import '../../compiler.dart'; |
11 import '../common.dart'; | 10 import '../common.dart'; |
12 import '../compiler.dart' show | 11 import '../compiler.dart' show Compiler; |
13 Compiler; | |
14 import '../util/util.dart'; | 12 import '../util/util.dart'; |
15 | 13 |
16 | |
17 // Helper methods for statistics. | 14 // Helper methods for statistics. |
18 | 15 |
19 /// Current stats collector. Use [enableStatsOutput] to enable recording of | 16 /// Current stats collector. Use [enableStatsOutput] to enable recording of |
20 /// stats. | 17 /// stats. |
21 Stats get stats { | 18 Stats get stats { |
22 enableDebugMode(); | 19 enableDebugMode(); |
23 if (_stats == null) { | 20 if (_stats == null) { |
24 _stats = const Stats(); | 21 _stats = const Stats(); |
25 } | 22 } |
26 return _stats; | 23 return _stats; |
27 } | 24 } |
28 | 25 |
29 Stats _stats; | 26 Stats _stats; |
30 | 27 |
31 /// Enable recording of stats. Use [Stats.dumpStats] to output the record stats. | 28 /// Enable recording of stats. Use [Stats.dumpStats] to output the record stats. |
32 /// | 29 /// |
33 /// Pass the [outputProvider] of [Compiler] to generate stats into a separate | 30 /// Pass the [outputProvider] of [Compiler] to generate stats into a separate |
34 /// file using [name] and [extension] for the filename. If omitted, stats are | 31 /// file using [name] and [extension] for the filename. If omitted, stats are |
35 /// printed on standard out. | 32 /// printed on standard out. |
36 /// | 33 /// |
37 /// If [xml] is `true`, stats output is formatted as XML with a default | 34 /// If [xml] is `true`, stats output is formatted as XML with a default |
38 /// extension of 'xml', otherwise the output is indented text with a default | 35 /// extension of 'xml', otherwise the output is indented text with a default |
39 /// extension of 'log'. | 36 /// extension of 'log'. |
40 void enableStatsOutput({CompilerOutputProvider outputProvider, | 37 void enableStatsOutput( |
41 bool xml: true, | 38 {CompilerOutputProvider outputProvider, |
42 String name: 'stats', | 39 bool xml: true, |
43 String extension, | 40 String name: 'stats', |
44 int examples: 10}) { | 41 String extension, |
| 42 int examples: 10}) { |
45 if (_stats != null) { | 43 if (_stats != null) { |
46 throw new StateError('Stats have already been initialized.'); | 44 throw new StateError('Stats have already been initialized.'); |
47 } | 45 } |
48 enableDebugMode(); | 46 enableDebugMode(); |
49 | 47 |
50 StatsOutput output; | 48 StatsOutput output; |
51 if (outputProvider != null) { | 49 if (outputProvider != null) { |
52 if (extension == null) { | 50 if (extension == null) { |
53 extension = xml ? 'xml' : 'log'; | 51 extension = xml ? 'xml' : 'log'; |
54 } | 52 } |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 /// [a] and [b]: | 188 /// [a] and [b]: |
191 /// | 189 /// |
192 /// 'idA && idB' lists the elements both in [a] and [b], | 190 /// 'idA && idB' lists the elements both in [a] and [b], |
193 /// '!idA && idB' lists the elements not in [a] but in [b], and | 191 /// '!idA && idB' lists the elements not in [a] but in [b], and |
194 /// 'idA && !idB' lists the elements in [a] but not in [b]. | 192 /// 'idA && !idB' lists the elements in [a] but not in [b]. |
195 /// | 193 /// |
196 /// If [dataA] and/or [dataB] are provided, additional information on the | 194 /// If [dataA] and/or [dataB] are provided, additional information on the |
197 /// elements are looked up in [dataA] or [dataB] using [dataA] as the primary | 195 /// elements are looked up in [dataA] or [dataB] using [dataA] as the primary |
198 /// source. | 196 /// source. |
199 void dumpCorrelation(idA, Iterable a, idB, Iterable b, | 197 void dumpCorrelation(idA, Iterable a, idB, Iterable b, |
200 {Map dataA, Map dataB}) {} | 198 {Map dataA, Map dataB}) {} |
201 } | 199 } |
202 | 200 |
203 /// Interface for printing output data. | 201 /// Interface for printing output data. |
204 /// | 202 /// |
205 /// This class serves as the disabled output. | 203 /// This class serves as the disabled output. |
206 class StatsOutput { | 204 class StatsOutput { |
207 const StatsOutput(); | 205 const StatsOutput(); |
208 | 206 |
209 /// Print [text] as on a separate line. | 207 /// Print [text] as on a separate line. |
210 void println(String text) {} | 208 void println(String text) {} |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
243 start(id); | 241 start(id); |
244 createGroupContent(); | 242 createGroupContent(); |
245 end(id); | 243 end(id); |
246 } | 244 } |
247 | 245 |
248 /// End a group [id]. | 246 /// End a group [id]. |
249 void end(String id) {} | 247 void end(String id) {} |
250 | 248 |
251 /// Start a stat entry for [id] with additional [data]. | 249 /// Start a stat entry for [id] with additional [data]. |
252 void open(String id, | 250 void open(String id, |
253 [Map<String, dynamic> data = const <String, dynamic>{}]) {} | 251 [Map<String, dynamic> data = const <String, dynamic>{}]) {} |
254 | 252 |
255 /// Create a stat entry for [id] with additional [data] and content created by | 253 /// Create a stat entry for [id] with additional [data] and content created by |
256 /// [createChildContent]. | 254 /// [createChildContent]. |
257 void child(String id, | 255 void child(String id, |
258 [Map<String, dynamic> data = const <String, dynamic>{}, | 256 [Map<String, dynamic> data = const <String, dynamic>{}, |
259 void createChildContent()]) { | 257 void createChildContent()]) { |
260 open(id, data); | 258 open(id, data); |
261 if (createChildContent != null) createChildContent(); | 259 if (createChildContent != null) createChildContent(); |
262 close(id); | 260 close(id); |
263 } | 261 } |
264 | 262 |
265 /// End a stat entry for [id]. | 263 /// End a stat entry for [id]. |
266 void close(String id) {} | 264 void close(String id) {} |
267 | 265 |
268 /// Starts a group of additional information. | 266 /// Starts a group of additional information. |
269 void beginExtra() {} | 267 void beginExtra() {} |
270 | 268 |
271 /// Starts a group of additional information. | 269 /// Starts a group of additional information. |
272 void endExtra() {} | 270 void endExtra() {} |
273 } | 271 } |
274 | 272 |
275 /// Abstract base class for [ConsolePrinter] and [XMLPrinter]. | 273 /// Abstract base class for [ConsolePrinter] and [XMLPrinter]. |
276 abstract class BasePrinter extends StatsPrinter with Indentation { | 274 abstract class BasePrinter extends StatsPrinter with Indentation { |
277 final int examples; | 275 final int examples; |
278 final StatsOutput output; | 276 final StatsOutput output; |
279 | 277 |
280 BasePrinter({this.output: const DebugOutput(), | 278 BasePrinter({this.output: const DebugOutput(), this.examples: 10}) { |
281 this.examples: 10}) { | |
282 indentationUnit = " "; | 279 indentationUnit = " "; |
283 } | 280 } |
284 } | 281 } |
285 | 282 |
286 /// [StatsPrinter] that displays stats in console lines. | 283 /// [StatsPrinter] that displays stats in console lines. |
287 class ConsolePrinter extends BasePrinter { | 284 class ConsolePrinter extends BasePrinter { |
288 int extraLevel = 0; | 285 int extraLevel = 0; |
289 | 286 |
290 ConsolePrinter({StatsOutput output: const DebugOutput(), | 287 ConsolePrinter({StatsOutput output: const DebugOutput(), int examples: 10}) |
291 int examples: 10}) | |
292 : super(output: output, examples: examples); | 288 : super(output: output, examples: examples); |
293 | 289 |
294 void open(String id, | 290 void open(String id, |
295 [Map<String, dynamic> data = const <String, dynamic>{}]) { | 291 [Map<String, dynamic> data = const <String, dynamic>{}]) { |
296 if (extraLevel > 0) return; | 292 if (extraLevel > 0) return; |
297 | 293 |
298 StringBuffer sb = new StringBuffer(); | 294 StringBuffer sb = new StringBuffer(); |
299 sb.write(indentation); | 295 sb.write(indentation); |
300 String space = ''; | 296 String space = ''; |
301 if (data['title'] != null) { | 297 if (data['title'] != null) { |
302 sb.write('${data['title']}:'); | 298 sb.write('${data['title']}:'); |
303 space = ' '; | 299 space = ' '; |
304 data.remove('title'); | 300 data.remove('title'); |
305 } else if (data['name'] != null) { | 301 } else if (data['name'] != null) { |
(...skipping 28 matching lines...) Expand all Loading... |
334 void endExtra() { | 330 void endExtra() { |
335 extraLevel--; | 331 extraLevel--; |
336 } | 332 } |
337 } | 333 } |
338 | 334 |
339 /// [StatsPrinter] that displays stats in XML format. | 335 /// [StatsPrinter] that displays stats in XML format. |
340 class XMLPrinter extends BasePrinter { | 336 class XMLPrinter extends BasePrinter { |
341 static const HtmlEscape escape = const HtmlEscape(); | 337 static const HtmlEscape escape = const HtmlEscape(); |
342 bool opened = false; | 338 bool opened = false; |
343 | 339 |
344 XMLPrinter({output: const DebugOutput(), | 340 XMLPrinter({output: const DebugOutput(), int examples: 10}) |
345 int examples: 10}) | |
346 : super(output: output, examples: examples); | 341 : super(output: output, examples: examples); |
347 | 342 |
348 void start(String id) { | 343 void start(String id) { |
349 if (!opened) { | 344 if (!opened) { |
350 output.println('<?xml version="1.0" encoding="UTF-8"?>'); | 345 output.println('<?xml version="1.0" encoding="UTF-8"?>'); |
351 opened = true; | 346 opened = true; |
352 } | 347 } |
353 open(id); | 348 open(id); |
354 } | 349 } |
355 | 350 |
356 void end(String id) { | 351 void end(String id) { |
357 close(id); | 352 close(id); |
358 } | 353 } |
359 | 354 |
360 void open(String id, | 355 void open(String id, |
361 [Map<String, dynamic> data = const <String, dynamic>{}]) { | 356 [Map<String, dynamic> data = const <String, dynamic>{}]) { |
362 StringBuffer sb = new StringBuffer(); | 357 StringBuffer sb = new StringBuffer(); |
363 sb.write(indentation); | 358 sb.write(indentation); |
364 sb.write('<$id'); | 359 sb.write('<$id'); |
365 data.forEach((key, value) { | 360 data.forEach((key, value) { |
366 if (value != null) { | 361 if (value != null) { |
367 sb.write(' $key="${escape.convert('$value')}"'); | 362 sb.write(' $key="${escape.convert('$value')}"'); |
368 } | 363 } |
369 }); | 364 }); |
370 sb.write('>'); | 365 sb.write('>'); |
371 output.println(sb.toString()); | 366 output.println(sb.toString()); |
(...skipping 21 matching lines...) Expand all Loading... |
393 List<StackTraceLine> commonPrefix; | 388 List<StackTraceLine> commonPrefix; |
394 List<_StackTraceNode> subtraces; | 389 List<_StackTraceNode> subtraces; |
395 | 390 |
396 _StackTraceNode(this.commonPrefix, this.count, this.subtraces); | 391 _StackTraceNode(this.commonPrefix, this.count, this.subtraces); |
397 | 392 |
398 _StackTraceNode.root() : this([], 0, []); | 393 _StackTraceNode.root() : this([], 0, []); |
399 | 394 |
400 _StackTraceNode.leaf(StackTraceLines stackTrace) | 395 _StackTraceNode.leaf(StackTraceLines stackTrace) |
401 : this(stackTrace.lines, 1, const []); | 396 : this(stackTrace.lines, 1, const []); |
402 | 397 |
403 _StackTraceNode.node(List<StackTraceLine> commonPrefix, | 398 _StackTraceNode.node(List<StackTraceLine> commonPrefix, _StackTraceNode first, |
404 _StackTraceNode first, | 399 _StackTraceNode second) |
405 _StackTraceNode second) | |
406 : this(commonPrefix, first.count + second.count, [first, second]); | 400 : this(commonPrefix, first.count + second.count, [first, second]); |
407 | 401 |
408 void add(StackTraceLines stackTrace) { | 402 void add(StackTraceLines stackTrace) { |
409 count++; | 403 count++; |
410 if (!stackTrace.lines.isEmpty) { | 404 if (!stackTrace.lines.isEmpty) { |
411 addSubtrace(stackTrace); | 405 addSubtrace(stackTrace); |
412 } | 406 } |
413 } | 407 } |
414 | 408 |
415 void addSubtrace(StackTraceLines stackTrace) { | 409 void addSubtrace(StackTraceLines stackTrace) { |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
496 final id; | 490 final id; |
497 int totalCount = 0; | 491 int totalCount = 0; |
498 final int sampleFrequency; | 492 final int sampleFrequency; |
499 | 493 |
500 _StackTraceTree(this.id, this.sampleFrequency) : super.root(); | 494 _StackTraceTree(this.id, this.sampleFrequency) : super.root(); |
501 | 495 |
502 void dumpTraces(StatsPrinter printer) { | 496 void dumpTraces(StatsPrinter printer) { |
503 printer.open('trace', { | 497 printer.open('trace', { |
504 'id': id, | 498 'id': id, |
505 'totalCount': totalCount, | 499 'totalCount': totalCount, |
506 'sampleFrequency': sampleFrequency}); | 500 'sampleFrequency': sampleFrequency |
| 501 }); |
507 dumpSubtraces(printer); | 502 dumpSubtraces(printer); |
508 printer.close('trace'); | 503 printer.close('trace'); |
509 } | 504 } |
510 | 505 |
511 void sample() { | 506 void sample() { |
512 if (totalCount++ % sampleFrequency == 0) { | 507 if (totalCount++ % sampleFrequency == 0) { |
513 add(stackTrace(offset: 3)); | 508 add(stackTrace(offset: 3)); |
514 } | 509 } |
515 } | 510 } |
516 } | 511 } |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
569 } | 564 } |
570 | 565 |
571 void recordElement(key, element, {data}) { | 566 void recordElement(key, element, {data}) { |
572 setsMap.putIfAbsent(key, () => new Map())[element] = data; | 567 setsMap.putIfAbsent(key, () => new Map())[element] = data; |
573 } | 568 } |
574 | 569 |
575 void recordTrace(key, {int sampleFrequency}) { | 570 void recordTrace(key, {int sampleFrequency}) { |
576 if (sampleFrequency == null) { | 571 if (sampleFrequency == null) { |
577 sampleFrequency = stackTraceSampleFrequency; | 572 sampleFrequency = stackTraceSampleFrequency; |
578 } | 573 } |
579 traceMap.putIfAbsent(key, | 574 traceMap |
580 () => new _StackTraceTree(key, sampleFrequency)).sample(); | 575 .putIfAbsent(key, () => new _StackTraceTree(key, sampleFrequency)) |
581 | 576 .sample(); |
582 } | 577 } |
583 | 578 |
584 Iterable getList(String key) { | 579 Iterable getList(String key) { |
585 Map map = setsMap[key]; | 580 Map map = setsMap[key]; |
586 if (map == null) return const []; | 581 if (map == null) return const []; |
587 return map.keys; | 582 return map.keys; |
588 } | 583 } |
589 | 584 |
590 void dumpStats({void beforeClose()}) { | 585 void dumpStats({void beforeClose()}) { |
591 printer.start('stats'); | 586 printer.start('stats'); |
592 dumpFrequencies(); | 587 dumpFrequencies(); |
593 dumpSets(); | 588 dumpSets(); |
594 dumpCounters(); | 589 dumpCounters(); |
595 dumpTraces(); | 590 dumpTraces(); |
596 if (beforeClose != null) { | 591 if (beforeClose != null) { |
597 beforeClose(); | 592 beforeClose(); |
598 } | 593 } |
599 printer.end('stats'); | 594 printer.end('stats'); |
600 } | 595 } |
601 | 596 |
602 void dumpSets() { | 597 void dumpSets() { |
603 printer.group('sets', () { | 598 printer.group('sets', () { |
604 setsMap.forEach((k, set) { | 599 setsMap.forEach((k, set) { |
605 dumpIterable('examples', '$k', set.keys, | 600 dumpIterable('examples', '$k', set.keys, |
606 limit: printer.examples, dataMap: set); | 601 limit: printer.examples, dataMap: set); |
607 }); | 602 }); |
608 }); | 603 }); |
609 | |
610 } | 604 } |
611 | 605 |
612 void dumpFrequencies() { | 606 void dumpFrequencies() { |
613 printer.group('frequencies', () { | 607 printer.group('frequencies', () { |
614 frequencyMaps.forEach((key, Map<dynamic, List> map) { | 608 frequencyMaps.forEach((key, Map<dynamic, List> map) { |
615 printer.child('frequency', {'title': '$key'}, () { | 609 printer.child('frequency', {'title': '$key'}, () { |
616 dumpFrequency(map); | 610 dumpFrequency(map); |
617 }); | 611 }); |
618 }); | 612 }); |
619 }); | 613 }); |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
660 printer.open('counter', {'title': '$id', 'count': count}); | 654 printer.open('counter', {'title': '$id', 'count': count}); |
661 result.forEach((int count, Set examples) { | 655 result.forEach((int count, Set examples) { |
662 if (counter == printer.examples) { | 656 if (counter == printer.examples) { |
663 printer.beginExtra(); | 657 printer.beginExtra(); |
664 hasMore = true; | 658 hasMore = true; |
665 } | 659 } |
666 if (examples.length == 1 && | 660 if (examples.length == 1 && |
667 (examplesLimit == 0 || !hasData(examples.first))) { | 661 (examplesLimit == 0 || !hasData(examples.first))) { |
668 printer.child('examples', {'count': count, 'example': examples.first}); | 662 printer.child('examples', {'count': count, 'example': examples.first}); |
669 } else { | 663 } else { |
670 printer.child('examples', | 664 printer.child('examples', {'count': count, 'examples': examples.length}, |
671 {'count': count, 'examples': examples.length}, | |
672 () { | 665 () { |
673 examples.forEach((example) { | 666 examples.forEach((example) { |
674 dumpIterable( | 667 dumpIterable('examples', '$example', map[example], |
675 'examples', '$example', map[example], | 668 limit: examplesLimit, includeCount: false); |
676 limit: examplesLimit, | |
677 includeCount: false); | |
678 }); | 669 }); |
679 }); | 670 }); |
680 } | 671 } |
681 counter++; | 672 counter++; |
682 }); | 673 }); |
683 if (hasMore) { | 674 if (hasMore) { |
684 printer.endExtra(); | 675 printer.endExtra(); |
685 } | 676 } |
686 printer.close('counter'); | 677 printer.close('counter'); |
687 } | 678 } |
688 | 679 |
689 void dumpTraces() { | 680 void dumpTraces() { |
690 printer.group('traces', () { | 681 printer.group('traces', () { |
691 traceMap.keys.forEach(dumpTrace); | 682 traceMap.keys.forEach(dumpTrace); |
692 }); | 683 }); |
693 } | 684 } |
694 | 685 |
695 void dumpTrace(key) { | 686 void dumpTrace(key) { |
696 _StackTraceTree tree = traceMap[key]; | 687 _StackTraceTree tree = traceMap[key]; |
697 tree.dumpTraces(printer); | 688 tree.dumpTraces(printer); |
698 } | 689 } |
699 | 690 |
700 void dumpCorrelation(keyA, Iterable a, keyB, Iterable b, | 691 void dumpCorrelation(keyA, Iterable a, keyB, Iterable b, |
701 {Map dataA, Map dataB}) { | 692 {Map dataA, Map dataB}) { |
702 printer.child('correlations', {'title': '$keyA vs $keyB'}, () { | 693 printer.child('correlations', {'title': '$keyA vs $keyB'}, () { |
703 List aAndB = a.where((e) => e != null && b.contains(e)).toList(); | 694 List aAndB = a.where((e) => e != null && b.contains(e)).toList(); |
704 List aAndNotB = a.where((e) => e != null && !b.contains(e)).toList(); | 695 List aAndNotB = a.where((e) => e != null && !b.contains(e)).toList(); |
705 List notAandB = b.where((e) => e != null && !a.contains(e)).toList(); | 696 List notAandB = b.where((e) => e != null && !a.contains(e)).toList(); |
706 dumpIterable('correlation', '$keyA && $keyB', aAndB, dataMap: dataA, | 697 dumpIterable('correlation', '$keyA && $keyB', aAndB, |
707 limit: printer.examples); | 698 dataMap: dataA, limit: printer.examples); |
708 dumpIterable('correlation', '$keyA && !$keyB', aAndNotB, dataMap: dataA, | 699 dumpIterable('correlation', '$keyA && !$keyB', aAndNotB, |
709 limit: printer.examples); | 700 dataMap: dataA, limit: printer.examples); |
710 dumpIterable('correlation', '!$keyA && $keyB', notAandB, dataMap: dataB, | 701 dumpIterable('correlation', '!$keyA && $keyB', notAandB, |
711 limit: printer.examples); | 702 dataMap: dataB, limit: printer.examples); |
712 }); | 703 }); |
713 } | 704 } |
714 | 705 |
715 void dumpIterable(String tag, String title, Iterable iterable, | 706 void dumpIterable(String tag, String title, Iterable iterable, |
716 {int limit, Map dataMap, bool includeCount: true}) { | 707 {int limit, Map dataMap, bool includeCount: true}) { |
717 if (limit == 0) return; | 708 if (limit == 0) return; |
718 | 709 |
719 Map childData = {}; | 710 Map childData = {}; |
720 Iterable nonNullIterable = iterable.where((e) => e != null); | 711 Iterable nonNullIterable = iterable.where((e) => e != null); |
721 if (nonNullIterable.isEmpty && !includeCount) { | 712 if (nonNullIterable.isEmpty && !includeCount) { |
722 childData['name'] = title; | 713 childData['name'] = title; |
723 } else { | 714 } else { |
724 childData['title'] = title; | 715 childData['title'] = title; |
725 } | 716 } |
726 if (includeCount) { | 717 if (includeCount) { |
(...skipping 24 matching lines...) Expand all Loading... |
751 | 742 |
752 /// Returns a map that is an inversion of [map], where the keys are the values | 743 /// Returns a map that is an inversion of [map], where the keys are the values |
753 /// of [map] and the values are the set of keys in [map] that share values. | 744 /// of [map] and the values are the set of keys in [map] that share values. |
754 /// | 745 /// |
755 /// If [equals] and [hashCode] are provided, these are used to determine | 746 /// If [equals] and [hashCode] are provided, these are used to determine |
756 /// equality among the values of [map]. | 747 /// equality among the values of [map]. |
757 /// | 748 /// |
758 /// If [isValidKey] is provided, this is used to determine with a value of [map] | 749 /// If [isValidKey] is provided, this is used to determine with a value of [map] |
759 /// is a potential key of the inversion map. | 750 /// is a potential key of the inversion map. |
760 Map<dynamic, Set> inverseMap(Map map, | 751 Map<dynamic, Set> inverseMap(Map map, |
761 {bool equals(key1, key2), | 752 {bool equals(key1, key2), |
762 int hashCode(key), | 753 int hashCode(key), |
763 bool isValidKey(potentialKey)}) { | 754 bool isValidKey(potentialKey)}) { |
764 Map<dynamic, Set> result = new LinkedHashMap<dynamic, Set>( | 755 Map<dynamic, Set> result = new LinkedHashMap<dynamic, Set>( |
765 equals: equals, hashCode: hashCode, isValidKey: isValidKey); | 756 equals: equals, hashCode: hashCode, isValidKey: isValidKey); |
766 map.forEach((k, v) { | 757 map.forEach((k, v) { |
767 if (isValidKey == null || isValidKey(v)) { | 758 if (isValidKey == null || isValidKey(v)) { |
768 result.putIfAbsent(v, () => new Set()).add(k); | 759 result.putIfAbsent(v, () => new Set()).add(k); |
769 } | 760 } |
770 }); | 761 }); |
771 return result; | 762 return result; |
772 } | 763 } |
773 | 764 |
774 /// Return a new map heuristically sorted by the keys of [map]. If the first | 765 /// Return a new map heuristically sorted by the keys of [map]. If the first |
775 /// key of [map] is [Comparable], the keys are sorted using [sortMap] under | 766 /// key of [map] is [Comparable], the keys are sorted using [sortMap] under |
776 /// the assumption that all keys are [Comparable]. | 767 /// the assumption that all keys are [Comparable]. |
777 /// Otherwise, the keys are sorted as string using their `toString` | 768 /// Otherwise, the keys are sorted as string using their `toString` |
778 /// representation. | 769 /// representation. |
779 Map trySortMap(Map map) { | 770 Map trySortMap(Map map) { |
780 Iterable iterable = map.keys.where((k) => k != null); | 771 Iterable iterable = map.keys.where((k) => k != null); |
781 if (iterable.isEmpty) return map; | 772 if (iterable.isEmpty) return map; |
782 var key = iterable.first; | 773 var key = iterable.first; |
783 if (key is Comparable) { | 774 if (key is Comparable) { |
784 return sortMap(map); | 775 return sortMap(map); |
785 } | 776 } |
786 return sortMap(map, (a, b) => '$a'.compareTo('$b')); | 777 return sortMap(map, (a, b) => '$a'.compareTo('$b')); |
787 } | 778 } |
788 | 779 |
789 /// Returns a new map in which the keys of [map] are sorted using [compare]. | 780 /// Returns a new map in which the keys of [map] are sorted using [compare]. |
790 /// If [compare] is null, the keys must be [Comparable]. | 781 /// If [compare] is null, the keys must be [Comparable]. |
791 Map sortMap(Map map, [int compare(a,b)]) { | 782 Map sortMap(Map map, [int compare(a, b)]) { |
792 List keys = map.keys.toList(); | 783 List keys = map.keys.toList(); |
793 keys.sort(compare); | 784 keys.sort(compare); |
794 Map sortedMap = new Map(); | 785 Map sortedMap = new Map(); |
795 keys.forEach((k) => sortedMap[k] = map[k]); | 786 keys.forEach((k) => sortedMap[k] = map[k]); |
796 return sortedMap; | 787 return sortedMap; |
797 } | 788 } |
798 | |
OLD | NEW |