Dart collections iterables
Skill Python51888/StudyDart-Skills/skills/dart-collections-iterables
Agent skills for Study Dart
npx -y skills add Python51888/StudyDart-Skills --skill dart-collections-iterablesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
使用 Dart 集合操作与可迭代工具链(List、Set、Map、Iterable),高效处理数据变换。
SKILL.md
10.4 KB, as published. Nobody here has run it
Working with Dart Collections and Iterables
Contents
- Collections Overview
- Collection Literals with Spread, If, and For
- Understanding Iterable and Lazy Evaluation
- Searching and Predicate Methods
- Termination and Conversion: toList() vs toSet()
- Building a Data Processing Pipeline
- Workflow: Implementing Collection Operations
- Examples
Collections Overview
Dart's core collections serve distinct roles. Choose the type that matches your data constraints, not habit.
Declaring Lists
Lists are ordered, indexable, and allow duplicates. Use square bracket literals.
var numbers = [1, 2, 3]; // List<int>
var constantList = const [1, 2, 3]; // Compile-time constant
Access elements with [] or elementAt. Lists implement Iterable.
Declaring Sets
Sets are unordered, unique-element collections. Use curly braces with an explicit type argument or assign to a Set variable.
var halogens = <String>{'fluorine', 'chlorine', 'fluorine'}; // Set<String> with one 'fluorine'
Set<String> names = {}; // empty set
Catch: {} alone creates a Map, not a Set.
Declaring Maps
Maps hold key-value pairs; keys are unique. Use map literals with key: value.
var gifts = {'first': 'partridge', 'second': 'turtledoves'};
Keys and values can be any type. Access via []; missing keys return null.
Collection Literals with Spread, If, and For
Dart allows control flow directly inside collection literals—no builder methods needed.
Spread Operator (...)
Expands an iterable in-place. Use ...? for null-aware expansion: if the expression is null, nothing is inserted.
var a = [1, 2, null];
var items = [0, ...a, 3]; // [0, 1, 2, null, 3]
List<int>? nullableList;
var safe = [0, ...?nullableList, 1]; // [0, 1]
Collection If
Include elements conditionally. Works with boolean expressions and patterns (if-case). Supports else.
bool include = true;
var list = [1, if (include) 2, 3]; // [1, 2, 3]
// Pattern matching
var data = 123;
var typeInfo = [
if (data case int i) 'Integer: $i',
if (data case String s) 'String: $s',
];
Collection For
Iterate and generate elements. Both for-in and traditional C-style loops are allowed.
var numbers = [1, 2, 3];
var squares = [for (var n in numbers) n * n]; // [1, 4, 9]
var descending = [for (var i = 5; i > 0; i--) i]; // [5, 4, 3, 2, 1]
Nest control flow elements freely inside literals.
var items = [
for (var i in [1, 2, 3])
if (i.isOdd) ...[i, i * 10]
]; // [1, 10, 3, 30]
Understanding Iterable and Lazy Evaluation
List and Set both implement Iterable. Methods like map, where, expand, take, skip, takeWhile, skipWhile are lazy — they return an Iterable that defers all work until a terminal operation triggers iteration.
Key Lazy Methods
map: Transforms each element.where: Filters elements by predicate.expand: Maps each element to an iterable and flattens.take/skip: Limit or skip the firstnelements.takeWhile/skipWhile: Control based on a condition.fold/reduce: Eager terminal operations that aggregate to a single value.forEach: Terminal iteration — use only for side effects, not to build new collections.
Lazy chains avoid intermediate allocations and early work.
final chain = numbers.where((n) => n > 2).map((n) => n * 10).take(3);
// No elements processed yet.
The chain executes only when you iterate (e.g., toList() or fold).
Searching and Predicate Methods
These methods traverse the iterable but stop early when possible. Use them for efficient existence checks and lookups.
firstWhere: Returns first element matching predicate. Throws if none, unlessorElseis provided.singleWhere: Returns the only matching element; throws if zero or multiple matches.any: Returnstrueif at least one element satisfies the predicate (short-circuits).every: Returnstrueif all elements satisfy (short-circuits on firstfalse).first/last: Direct access; throw on empty iterable.contains: Checks for equality-based presence.
var users = [User('Alice', 21), User('Bob', 17)];
// Safe lookup with default
var adult = users.firstWhere((u) => u.age >= 18, orElse: () => User('None', 0));
bool hasMinor = users.any((u) => u.age < 18); // true
bool allAdults = users.every((u) => u.age >= 18); // false
Prefer any over where(...).isNotEmpty — any stops after the first match, where would check all.
Termination and Conversion: toList() vs toSet()
When you need a concrete collection, call a terminal method:
toList(): Creates a newList. Preserves order and duplicates. O(n) time.toSet(): Creates a newSet. Removes duplicates (first occurrence kept, butSetis unordered). O(n) time.
Crucial: The lazy chain executes only once at this point. After consumption, the Iterable is “spent.” If you call toList() again on the same lazy iterable, it re‑evaluates from the source again, doubling work. Always store the result:
final processed = numbers.where((n) => n.isEven).map((n) => n * 2);
final list = processed.toList(); // eagerly evaluated
final set = list.toSet(); // reuses the already‑materialized list
If you need both derivatives, materialize once.
Building a Data Processing Pipeline
Combine lazy operations and terminal methods into a fluent pipeline. This example filters even numbers, doubles them, skips the first, takes the next three, and sums them.
void main() {
final numbers = [1, 2, 3, 4, 5, 6, 7, 8];
final sum = numbers
.where((n) => n.isEven) // [2, 4, 6, 8] (lazy)
.map((n) => n * 2) // [4, 8, 12, 16]
.skip(1) // [8, 12, 16]
.take(3) // [8, 12, 16]
.fold(0, (prev, n) => prev + n); // terminal: sum = 36
print(sum); // 36
}
Real‑world example: Extract valid email addresses from raw strings.
class EmailAddress {
final String address;
EmailAddress(this.address);
}
bool isValidEmail(EmailAddress email) => email.address.contains('@');
Iterable<EmailAddress> validEmails(Iterable<String> addresses) =>
addresses.map((a) => EmailAddress(a)).where(isValidEmail).toList();
void main() {
final input = ['[email protected]', 'invalid', '[email protected]'];
final result = validEmails(input);
print(result.map((e) => e.address).join(', ')); // [email protected], [email protected]
}
Workflow: Implementing Collection Operations
Follow this process when writing Dart collection manipulation code.
Task Progress
- Identify the target collection type (List, Set, Map) based on order, uniqueness, and key access needs.
- Choose literal syntax or constructor; add
constfor compile‑time constants. - Use spread (
.../...?),if, andforelements to inline building logic. - Design a lazy transformation chain with
where,map,expand,take,skip. - Insert search/predicate calls (
firstWhere,any,every) to short‑circuit when needed. - Apply a terminal operation (
toList,toSet,fold,reduce) only at the end. - Test with representative data; verify expected output.
- Refactor to avoid repeated materialization or redundant iterations.
Conditional Branching
- Duplicate removal needed? Use
toSet()or aSetliteral. If order must be preserved, useSetiteration order (insertion‑order from Dart 2.0+) or manually track seen items. - Need indexed access? Convert to
ListviatoList(). Avoid repeatedelementAt(i)on a lazy iterable—it re‑walks from the start each time. - Checking existence? Use
anyinstead ofwhere(...).isNotEmpty. - Handling missing elements? Always supply
orElsetofirstWhere/singleWhereif absence is expected; otherwise catchStateError. - Infinite iterables? Never call
last,length, ortoList()on them; usetakeortakeWhileto limit.
Feedback Loop
- Write the pipeline as a single expression chaining lazy methods and one terminal call.
- Run the file with
dart runor in DartPad. - If the result is wrong, temporarily break the chain:
- Insert
.toList()after each step and print to inspect intermediate state. - Replace the step with a dummy
map((e) => print(e))to see elements.
- Insert
- Adjust predicate or transformation logic.
- Re‑run until correct.
- Remove debug code; ensure final chain is clean and single‑terminal.
Examples
Example 1: Partitioning users by age
class User {
final String name;
final int age;
User(this.name, this.age);
}
void main() {
final users = [User('Alice', 21), User('Bob', 17), User('Charlie', 30)];
final (minors, adults) = (
users.where((u) => u.age < 18).toList(),
users.where((u) => u.age >= 18).toList(),
);
print('Minors: ${minors.map((u) => u.name).join(', ')}'); // Bob
print('Adults: ${adults.map((u) => u.name).join(', ')}'); // Alice, Charlie
}
Example 2: Building a report from transaction logs
class Transaction {
final double amount;
final String category;
Transaction(this.amount, this.category);
}
void main() {
final transactions = [
Transaction(12.0, 'food'),
Transaction(50.0, 'transport'),
Transaction(8.5, 'food'),
Transaction(30.0, 'utilities'),
];
final foodTotal = transactions
.where((t) => t.category == 'food')
.map((t) => t.amount)
.fold(0.0, (sum, a) => sum + a);
final summary = transactions
.where((t) => t.amount > 10.0)
.map((t) => '${t.category}: \$${t.amount.toStringAsFixed(2)}')
.toList();
print('Food total: \$${foodTotal.toStringAsFixed(2)}'); // $20.50
print('Large transactions: $summary'); // [transport: $50.00, utilities: $30.00]
}
Use these patterns to write concise, performant Dart collection code. Always let laziness do the heavy lifting until the moment you truly need the result.