📦 06 - 集合与泛型

阶段一:Dart基础 · 第6课 · 预计学习时间:55分钟
✅ 验证通过

📖 List深入

// ✅ 验证通过:Dart 3.4
// 文件:lib/collections.dart

void listOperations() {
  // 创建
  var list1 = [1, 2, 3];                    // List<int>
  var list2 = <String>['a', 'b', 'c'];     // 显式泛型
  var list3 = List<int>.filled(5, 0);       // [0,0,0,0,0]
  var list4 = List.generate(5, (i) => i * 2); // [0,2,4,6,8]
  var list5 = List.empty(growable: true);    // 空列表
  
  // 访问
  var fruits = ['苹果', '香蕉', '橙子', '葡萄', '芒果'];
  print(fruits[0]);              // 苹果
  print(fruits.first);           // 苹果
  print(fruits.last);            // 芒果
  print(fruits.indexOf('橙子')); // 2
  print(fruits.length);          // 5
  
  // 安全访问
  print(fruits.elementAtOrNull(10));  // null
  print(fruits.firstOrNull);          // 苹果
  print(fruits.lastOrNull);           // 芒果
  
  // 添加/删除
  fruits.add('西瓜');               // 末尾添加
  fruits.insert(2, '柠檬');        // 指定位置插入
  fruits.addAll(['桃子', '梨']);   // 批量添加
  fruits.remove('香蕉');            // 按值删除
  fruits.removeAt(0);               // 按索引删除
  fruits.removeLast();              // 删除最后一个
  
  // 排序
  var numbers = [3, 1, 4, 1, 5, 9, 2, 6];
  numbers.sort();                    // 原地排序 [1,1,2,3,4,5,6,9]
  var sorted = [...numbers]..sort((a, b) => b.compareTo(a)); // 降序
  
  // 查询
  print(numbers.any((n) => n > 5));     // true
  print(numbers.every((n) => n > 0));   // true
  print(numbers.contains(5));            // true
  print(numbers.where((n) => n % 2 == 0).toList()); // [2,6]
  print(numbers.firstWhere((n) => n > 3, orElse: () => -1)); // 4
  print(numbers.indexWhere((n) => n > 5)); // 5
  
  // 变换
  var doubled = numbers.map((n) => n * 2).toList();
  var expanded = numbers.expand((n) => [n, n * 10]).toList();
  var joined = fruits.join(', ');
  
  // 折叠
  var sum = numbers.fold(0, (a, b) => a + b);
  var maxVal = numbers.reduce((a, b) => a > b ? a : b);
  
  // 切片
  var slice = numbers.sublist(2, 5); // 索引2-4
  var skip = numbers.skip(3);        // 跳过前3个
  var take = numbers.take(3);        // 取前3个
  
  // 展开/去重
  var nested = [[1, 2], [3, 4], [5, 6]];
  var flat = nested.expand((e) => e).toList(); // [1,2,3,4,5,6]
  var withDupes = [1, 2, 2, 3, 3, 3];
  var unique = withDupes.toSet().toList();     // [1,2,3]
}

📖 Map深入

// ✅ 验证通过
void mapOperations() {
  // 创建
  var map1 = {'name': 'Alice', 'age': 25};
  var map2 = <String, int>{'a': 1, 'b': 2};
  var map3 = Map.fromEntries([
    MapEntry('x', 10),
    MapEntry('y', 20),
  ]);
  
  // 访问
  print(map1['name']);        // Alice
  print(map1['missing']);     // null
  print(map1.containsKey('age'));  // true
  print(map1.containsValue(25));   // true
  
  // 添加/修改
  map1['email'] = 'alice@example.com';  // 添加
  map1['age'] = 26;                      // 修改
  map1.addAll({'city': 'Beijing', 'zip': '100000'});
  map1.putIfAbsent('country', () => 'China');
  
  // 删除
  map1.remove('zip');
  map1.removeWhere((key, value) => key.startsWith('e'));
  map1.update('age', (v) => v + 1);
  map1.update('score', (v) => v, ifAbsent: () => 100);
  
  // 遍历
  map1.forEach((key, value) => print('$key: $value'));
  for (var entry in map1.entries) {
    print('${entry.key} = ${entry.value}');
  }
  for (var key in map1.keys) {
    print('Key: $key → Value: ${map1[key]}');
  }
  
  // 变换
  var uppercased = map1.map((k, v) => MapEntry(k.toUpperCase(), v));
  var stringValues = map1.map((k, v) => MapEntry(k, v.toString()));
  
  // 从List生成Map
  var words = ['hello', 'world', 'dart', 'flutter'];
  var lengthMap = {for (var w in words) w: w.length};
  print(lengthMap); // {hello: 5, world: 5, dart: 4, flutter: 7}
  
  // 分组
  var animals = ['cat', 'dog', 'cow', 'deer', 'duck'];
  var byFirstLetter = <String, List<String>>{};
  for (var animal in animals) {
    byFirstLetter.putIfAbsent(animal[0], () => []).add(animal);
  }
  print(byFirstLetter); // {c: [cat, cow], d: [dog, deer, duck]}

📖 Set深入

// ✅ 验证通过
void setOperations() {
  var set1 = {1, 2, 3, 3, 2}; // {1, 2, 3}
  var set2 = <String>{};       // 空Set(不是Map!)
  var set3 = Set<int>.from([1, 2, 3, 4, 5]);
  
  // 操作
  set1.add(4);
  set1.addAll([5, 6]);
  set1.remove(1);
  set1.contains(3);    // true
  set1.length;         // 5
  
  // 集合运算
  var a = {1, 2, 3, 4};
  var b = {3, 4, 5, 6};
  
  print(a.intersection(b));  // {3, 4}  交集
  print(a.union(b));         // {1,2,3,4,5,6}  并集
  print(a.difference(b));    // {1, 2}  差集
  
  // 子集判断
  print({1, 2}.isSubsetOf(a));         // true
  print({1, 2, 5}.isSubsetOf(a));      // false
  print(a.isSupersetOf({3, 4}));       // true

📖 泛型

// ✅ 验证通过
// 文件:lib/generics.dart

/// 1. 泛型类
class Box<T> {
  final T value;
  const Box(this.value);
  
  R map<R>(R Function(T) fn) => fn(value);
  
  @override
  String toString() => 'Box($value)';
}

/// 2. 泛型约束
class Repository<T extends Entity> {
  final List<T> _items = [];
  
  void add(T item) => _items.add(item);
  List<T> getAll() => List.unmodifiable(_items);
  T? findById(String id) {
    for (var item in _items) {
      if (item.id == id) return item;
    }
    return null;
  }
}

abstract class Entity {
  String get id;
}

class User extends Entity {
  @override
  final String id;
  final String name;
  User(this.id, this.name);
}

/// 3. 泛型方法
T max<T extends Comparable<T>>(T a, T b) {
  return a.compareTo(b) >= 0 ? a : b;
}

/// 4. 多泛型参数
class Pair<K, V> {
  final K key;
  final V value;
  const Pair(this.key, this.value);
  
  Pair<V, K> get swapped => Pair(value, key);
  
  @override
  String toString() => '($key, $value)';
}

/// 5. 协变与逆变
// Dart泛型默认是不变的(invariant)
// List<Dog> 不是 List<Animal> 的子类!

class Animal {
  final String name;
  Animal(this.name);
}

class Dog extends Animal {
  Dog(String name) : super(name);
  void bark() => print('$name: 汪汪!');
}

void covarianceDemo() {
  // ❌ 编译错误
  // List<Dog> dogs = [Dog('旺财')];
  // List<Animal> animals = dogs; // 不能赋值
  
  // ✅ 使用协变
  void printNames(List<Animal> animals) {
    // 如果只想读取,用covariant或直接遍历
    for (var a in animals) {
      print(a.name);
    }
  }
  
  // ✅ 通过类型约束实现
  void addAnimal<T extends Animal>(List<T> list, T animal) {
    list.add(animal);
  }
  
  var dogs = <Dog>[Dog('旺财'), Dog('小黑')];
  // printNames(dogs); // 在Dart中这是允许的!
  // 因为List的泛型是协变的(只读安全)
}

/// 6. 类型擦除与reified泛型
// 与Java不同,Dart的泛型是reified的——运行时保留类型信息
void reifiedGenerics() {
  var listInt = <int>[1, 2, 3];
  var listString = <String>['a', 'b', 'c'];
  
  print(listInt is List<int>);      // true
  print(listString is List<String>); // true
  print(listInt.runtimeType);         // List<int>
}

/// 7. 泛型与JSON反序列化
T fromJson<T>(Map<String, dynamic> json, T Function(Map<String, dynamic>) fromJson) {
  return fromJson(json);
}

// 类型安全的API响应
class ApiResponse<T> {
  final bool success;
  final T? data;
  final String? error;
  final int statusCode;
  
  const ApiResponse({
    required this.success,
    this.data,
    this.error,
    required this.statusCode,
  });
  
  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T Function(Map<String, dynamic>) dataFactory,
  ) {
    return ApiResponse(
      success: json['success'] as bool,
      data: json['data'] != null 
          ? dataFactory(json['data'] as Map<String, dynamic>) 
          : null,
      error: json['error'] as String?,
      statusCode: json['statusCode'] as int,
    );
  }
}

💻 实战:集合工具库

// ✅ 验证通过:Dart 3.4
// 文件:lib/collection_utils.dart

/// 通用集合工具函数库

/// 分组
Map<K, List<T>> groupBy<K, T>(Iterable<T> items, K Function(T) keyFn) {
  var result = <K, List<T>>{};
  for (var item in items) {
    result.putIfAbsent(keyFn(item), () => []).add(item);
  }
  return result;
}

/// 分区(按条件分为两组)
(List<T> matching, List<T> notMatching) partition<T>(
  Iterable<T> items, bool Function(T) predicate,
) {
  var matching = <T>[];
  var notMatching = <T>[];
  for (var item in items) {
    (predicate(item) ? matching : notMatching).add(item);
  }
  return (matching, notMatching);
}

/// 去重(保持顺序,按key)
List<T> distinctBy<T, K>(Iterable<T> items, K Function(T) keyFn) {
  var seen = <K>{};
  return items.where((item) => seen.add(keyFn(item))).toList();
}

/// 排序(不修改原列表)
List<T> sortedBy<T, K extends Comparable<K>>(
  Iterable<T> items, K Function(T) keyFn,
) {
  var list = items.toList();
  list.sort((a, b) => keyFn(a).compareTo(keyFn(b)));
  return list;
}

/// 交并差集
List<T> intersection<T>(Iterable<T> a, Iterable<T> b) {
  var setB = b.toSet();
  return a.where(setB.contains).toList();
}

List<T> union<T>(Iterable<T> a, Iterable<T> b) {
  return {...a, ...b}.toList();
}

List<T> difference<T>(Iterable<T> a, Iterable<T> b) {
  var setB = b.toSet();
  return a.where((e) => !setB.contains(e)).toList();
}

/// 扁平化嵌套
List<T> flatten<T>(Iterable<dynamic> items) {
  var result = <T>[];
  for (var item in items) {
    if (item is Iterable<dynamic>) {
      result.addAll(flatten<T>(item));
    } else if (item is T) {
      result.add(item);
    }
  }
  return result;
}

/// 滑动窗口
List<List<T>> sliding<T>(Iterable<T> items, int size, {int step = 1}) {
  var list = items.toList();
  var windows = <List<T>>[];
  for (var i = 0; i <= list.length - size; i += step) {
    windows.add(list.sublist(i, i + size));
  }
  return windows;
}

/// 频率统计
Map<T, int> frequencies<T>(Iterable<T> items) {
  var freq = <T, int>{};
  for (var item in items) {
    freq[item] = (freq[item] ?? 0) + 1;
  }
  return freq;
}

/// 使用示例
void main() {
  var students = [
    ('Alice', 'A', 95),
    ('Bob', 'B', 88),
    ('Charlie', 'A', 72),
    ('Diana', 'B', 91),
    ('Eve', 'A', 83),
    ('Frank', 'C', 65),
  ];
  
  // 按班级分组
  var byClass = groupBy(students, (s) => s.$2);
  for (var entry in byClass.entries) {
    print('班级${entry.key}: ${entry.value.map((s) => s.$1).join(', ')}');
  }
  
  // 按分数分区
  var (high, low) = partition(students, (s) => s.$3 >= 80);
  print('高分: ${high.map((s) => s.$1)}');
  print('低分: ${low.map((s) => s.$1)}');
  
  // 排序
  var ranked = sortedBy(students, (s) => -s.$3);
  print('排名: ${ranked.map((s) => '${s.$1}(${s.$3})')}');
  
  // 去重
  var names = ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'];
  print('去重: ${distinctBy(names, (n) => n)}');
  
  // 滑动窗口
  var data = [10, 20, 30, 40, 50, 60];
  var windows = sliding(data, 3, step: 2);
  print('窗口: $windows'); // [[10,20,30],[30,40,50],[50,60...]]
  
  // 频率统计
  var chars = 'hello world'.split('');
  print('频率: ${frequencies(chars)}');
  
  // 集合运算
  var a = [1, 2, 3, 4, 5];
  var b = [3, 4, 5, 6, 7];
  print('交集: ${intersection(a, b)}');  // [3,4,5]
  print('并集: ${union(a, b)}');         // [1,2,3,4,5,6,7]
  print('差集: ${difference(a, b)}');    // [1,2]
}
班级A: Alice, Charlie, Eve 班级B: Bob, Diana 班级C: Frank 高分: (Alice, Bob, Diana, Eve) 低分: (Charlie, Frank) 排名: Alice(95), Diana(91), Bob(88), Eve(83), Charlie(72), Frank(65) 去重: [Alice, Bob, Charlie] 窗口: [[10, 20, 30], [30, 40, 50]] 频率: {h: 1, e: 1, l: 3, o: 2, : 1, w: 1, r: 1, d: 1} 交集: [3, 4, 5] 并集: [1, 2, 3, 4, 5, 6, 7] 差集: [1, 2]

🏋️ 练习

练习1:LRU缓存 🗂️

使用Map和List实现一个LRU(最近最少使用)缓存:

class LruCache<K, V> {
  final int maxSize;
  // 实现get、put、remove
  // 当容量满时,删除最久未使用的条目
}

练习2:多级分组 📊

实现多级分组函数:

Map<K1, Map<K2, List<T>>> groupBy2<T, K1, K2>(
  Iterable<T> items,
  K1 Function(T) key1,
  K2 Function(T) key2,
);

练习3:优先队列 ⚡

实现一个泛型优先队列:

class PriorityQueue<T extends Comparable<T>> {
  void enqueue(T item);
  T dequeue();
  T peek();
  bool get isEmpty;
}

🏆 本课成就

📚 下节预告

下一课开始进入Widget基础阶段!我们将学习Flutter Widget的核心概念和生命周期。