Use flattenedToList/flattenedToSet instead of generic expand().

It is faster.

[expand: 102]
[flattened: 131]
[flattened2: 37]

import 'package:collection/collection.dart';

main() {
  const outerLength = 100;
  const innerLength = 10;
  final listOfLists = List.generate(outerLength, (i) {
    return List.generate(innerLength, (j) => i * innerLength + j);
  });

  for (var i = 0; i < 10; i++) {
    f(listOfLists);
  }
}

void f(List<List<int>> listOfLists) {
  const repeatCount = 10000;

  {
    final timer = Stopwatch()..start();
    for (var i = 0; i < repeatCount; i++) {
      listOfLists.expand((e) => e).toList();
    }
    print('[expand: ${timer.elapsedMilliseconds}]');
  }

  {
    final timer = Stopwatch()..start();
    for (var i = 0; i < repeatCount; i++) {
      listOfLists.flattened.toList();
    }
    print('[flattened: ${timer.elapsedMilliseconds}]');
  }

  {
    final timer = Stopwatch()..start();
    for (var i = 0; i < repeatCount; i++) {
      listOfLists.flattened2.toList();
    }
    print('[flattened2: ${timer.elapsedMilliseconds}]');
  }
}

extension ListListExtensions<T> on List<List<T>> {
  Iterable<T> get flattened2 {
    return [
      for (final elements in this) ...elements,
    ];
  }
}

Change-Id: I0bf9dc0c8735fe62aab69cbce276254e2db110f9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345202
Reviewed-by: Jacob Richman <jacobr@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov
2024-01-08 18:55:48 +00:00
committed by Commit Queue
parent 882e883be2
commit 8d3b6ce54c
15 changed files with 72 additions and 21 deletions
@@ -8,6 +8,7 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin;
@@ -60,8 +61,7 @@ class PluginCodeActionsProducer extends AbstractCodeActionsProducer {
.map((response) => plugin.EditGetFixesResult.fromResponse(response))
.expand((response) => response.fixes)
.map(_convertFixes)
.expand((fix) => fix)
.toList();
.flattenedToList;
}
@override
@@ -13,6 +13,7 @@ import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/lsp/semantic_tokens/encoder.dart';
import 'package:analysis_server/src/lsp/semantic_tokens/legend.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
typedef StaticOptions
@@ -65,8 +66,7 @@ abstract class AbstractSemanticTokensHandler<T>
return toSourceRangeNullable(lineInfo, range).mapResult((range) async {
final serverTokens = await getServerResult(path, range);
final pluginHighlightRegions =
getPluginResults(path).expand((results) => results).toList();
final pluginHighlightRegions = getPluginResults(path).flattenedToList;
if (token.isCancellationRequested) {
return cancelled();
+2 -2
View File
@@ -27,6 +27,7 @@ import 'package:analyzer/source/source_range.dart' as server;
import 'package:analyzer/src/dart/analysis/search.dart' as server
show DeclarationKind;
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
import 'package:collection/collection.dart';
import 'package:path/path.dart' as path;
@@ -1165,8 +1166,7 @@ List<lsp.DocumentHighlight> toHighlights(
.map((occurrence) => occurrence.offsets.map((offset) =>
lsp.DocumentHighlight(
range: toRange(lineInfo, offset, occurrence.length))))
.expand((occurrences) => occurrences)
.toSet()
.flattenedToSet
.toList();
}
@@ -7,6 +7,7 @@ import 'dart:math' as math;
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:meta/meta.dart';
final semanticTokenLegend = SemanticTokenLegendLookup();
@@ -33,8 +34,8 @@ class SemanticTokenLegendLookup {
_usedTokenTypes = Set.of(highlightRegionTokenTypes.values
.followedBy(CustomSemanticTokenTypes.values))
.toList();
_usedTokenModifiers = Set.of(highlightRegionTokenModifiers.values
.expand((v) => v)
_usedTokenModifiers = Set.of(highlightRegionTokenModifiers
.values.flattenedToList
.followedBy(CustomSemanticTokenModifiers.values))
.toList();
@@ -14,6 +14,7 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
Future<void> scheduleImplementedNotification(
LegacyAnalysisServer server, Iterable<String> files) async {
@@ -42,8 +43,7 @@ void sendAnalysisNotificationAnalyzedFiles(LegacyAnalysisServer server) {
_sendNotification(server, () {
var analyzedFiles = server.driverMap.values
.map((driver) => driver.knownFiles)
.expand((files) => files)
.toSet();
.flattenedToSet;
// Exclude *.yaml files because IDEA Dart plugin attempts to index
// all the files in folders which contain analyzed files.
@@ -22,6 +22,7 @@ import 'package:analysis_server/src/services/correction/fix/data_driven/variable
import 'package:analysis_server/src/services/refactoring/framework/formal_parameter.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/util/yaml.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/string.dart';
import 'package:collection/collection.dart';
import 'package:yaml/yaml.dart';
@@ -350,9 +351,8 @@ class TransformSetParser {
var firstEntry = entries.firstOrNull;
if (firstEntry == null) {
if (required) {
var validKeysList = translators.keys
.expand((keys) => keys)
.quotedAndCommaSeparatedWithOr;
var validKeysList =
translators.keys.flattenedToList.quotedAndCommaSeparatedWithOr;
_reportError(TransformSetErrorCode.missingOneOfMultipleKeys, errorNode,
[validKeysList]);
}
@@ -8,6 +8,7 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
/// A utility class used to analyze a library from which some set of
/// declarations are being moved in order to compute the set of changes needed
@@ -298,8 +299,7 @@ class _ReferenceFinder extends RecursiveAstVisitor<void> {
// Extensions can be used without a prefix, so we can use any import that
// brings in the extension.
if (import == null && prefix == null && element is ExtensionElement) {
import = _importsByPrefix.values
.expand((imports) => imports)
import = _importsByPrefix.values.flattenedToList
.where((import) =>
// Because we don't know what prefix we're looking for (any is
// allowed), use the imports own prefix when checking for the
@@ -51,6 +51,7 @@ import 'package:analyzer/src/summary2/macro.dart';
import 'package:analyzer/src/summary2/package_bundle_format.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/uri_cache.dart';
/// This class computes [AnalysisResult]s for Dart files.
@@ -2558,7 +2559,7 @@ class _DiscoverAvailableFilesTask {
// Discover files in package/lib folders.
var packageMap = driver._sourceFactory.packageMap;
if (packageMap != null) {
folderIterator = packageMap.values.expand((f) => f).iterator;
folderIterator = packageMap.values.flattenedToList.iterator;
} else {
folderIterator = <Folder>[].iterator;
}
@@ -149,7 +149,7 @@ class _LibraryNode extends graph.Node<_LibraryNode> {
.whereType<LibraryExportWithFile>()
.map((export) => export.exportedLibrary),
])
.expand((libraries) => libraries)
.flattenedToList
.whereNotNull()
.toSet();
@@ -27,6 +27,7 @@ import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/error/codes.dart'
show CompileTimeErrorCode, WarningCode;
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:meta/meta.dart';
/// Tracks upper and lower type bounds for a set of type parameters.
@@ -491,7 +492,7 @@ class GenericInferrer {
.values
.where((l) =>
l.every((c) => c.isSatisfiedBy(_typeSystem, inferred)) == expected)
.expand((i) => i);
.flattenedToList;
String unsatisfied = _formatConstraints(isSatisfied(false));
String satisfied = _formatConstraints(isSatisfied(true));
+2 -1
View File
@@ -68,6 +68,7 @@ import 'package:analyzer/src/fasta/doc_comment_builder.dart';
import 'package:analyzer/src/fasta/error_converter.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:pub_semver/pub_semver.dart';
@@ -3071,7 +3072,7 @@ class AstBuilder extends StackListener {
debugEvent("SwitchBlock");
var membersList = popTypedList2<List<SwitchMemberImpl>>(caseCount);
var members = membersList.expand((members) => members).toList();
var members = membersList.flattenedToList;
Set<String> labels = <String>{};
for (var member in members) {
@@ -460,7 +460,7 @@ class LibraryBuilder {
}
final augmentationCode = macroApplier.buildAugmentationLibraryCode(
_macroResults.expand((e) => e).toList(),
_macroResults.flattenedToList,
);
if (augmentationCode == null) {
return;
@@ -19,6 +19,26 @@ extension IterableExtension<E> on Iterable<E> {
}
}
extension IterableIterableExtension<T> on Iterable<Iterable<T>> {
/// Elements of each iterable in this iterable.
///
/// At the moment of writing, this method is `2.75` times faster than
/// `expand((e) => e)`, and `3.5` faster than `flattened` from
/// `package:collection`.
List<T> get flattenedToList {
return [
for (final elements in this) ...elements,
];
}
/// Elements of each iterable in this iterable.
Set<T> get flattenedToSet {
return {
for (final elements in this) ...elements,
};
}
}
extension IterableMapEntryExtension<K, V> on Iterable<MapEntry<K, V>> {
Map<K, V> get mapFromEntries => Map.fromEntries(this);
}
@@ -8,6 +8,7 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -3022,7 +3023,7 @@ class _InterfacePrinter {
String name,
Map<Name, List<ExecutableElement>> map,
) {
final isEmpty = map.values.expand((elements) => elements).where((element) {
final isEmpty = map.values.flattenedToList.where((element) {
if (_configuration.withObjectMembers) return true;
return !element.isObjectMember;
}).isEmpty;
@@ -9,6 +9,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(IterableExtensionTest);
defineReflectiveTests(IterableIterableExtensionTest);
defineReflectiveTests(IterableMapEntryExtensionTest);
defineReflectiveTests(ListExtensionTest);
});
@@ -21,6 +22,31 @@ class IterableExtensionTest {
}
}
@reflectiveTest
class IterableIterableExtensionTest {
test_flattenedToList() {
expect(
[
[0],
[1, 2],
[3, 3]
].flattenedToList,
[0, 1, 2, 3, 3],
);
}
test_flattenedToSet() {
expect(
[
[0, 0],
[1, 2, 1],
[3, 3]
].flattenedToSet,
{0, 1, 2, 3},
);
}
}
@reflectiveTest
class IterableMapEntryExtensionTest {
test_mapFromEntries() {