Какие способы работы с Map в Dart вы знаете?

«Какие способы работы с Map в Dart вы знаете?» — вопрос из категории Алгоритмы и структуры данных, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Dart я активно использую Map для различных сценариев. Вот основные подходы:

1. Создание и инициализация Map:

// Литерал Map
final Map<String, int> scores = {
  'Alice': 100,
  'Bob': 85,
  'Charlie': 92,
};

// Конструктор
final emptyMap = Map<String, dynamic>();
final fromConstructor = Map.from(scores); // Копия

// Для неизменяемых данных
const constantMap = const {
  'apiUrl': 'https://api.example.com',
  'timeout': 30,
};

2. Преобразование коллекций в Map:

// Из List в Map
final users = ['Alice', 'Bob', 'Charlie'];
final userMap = Map.fromIterable(
  users,
  key: (user) => user.toLowerCase(),
  value: (user) => User(name: user),
);
// {'alice': User('Alice'), 'bob': User('Bob'), ...}

// Из двух List (ключи и значения)
final keys = ['id', 'name', 'email'];
final values = [1, 'Alice', 'alice@example.com'];
final map = Map.fromIterables(keys, values);
// {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}

// Из List<MapEntry>
final entries = [
  MapEntry('createdAt', DateTime.now()),
  MapEntry('updatedAt', DateTime.now()),
];
final fromEntries = Map.fromEntries(entries);

3. Работа с MapEntry:

// Создание Map через entries
final config = Map.fromEntries([
  MapEntry('theme', 'dark'),
  MapEntry('notifications', true),
  MapEntry('language', 'ru'),
]);

// Итерация по entries
config.entries.forEach((entry) {
  print('${entry.key}: ${entry.value}');
});

// Фильтрация через entries
final filtered = Map.fromEntries(
  config.entries.where((entry) => entry.value != null),
);

4. Полезные методы Map:

final map = {'a': 1, 'b': 2, 'c': 3};

// Проверка и доступ
if (map.containsKey('a')) {
  final value = map['a'];
}

// Безопасное получение с значением по умолчанию
final value = map['d'] ?? 0;
final value2 = map.putIfAbsent('d', () => 0);

// Обновление значений
map.update('a', (v) => v + 1);
map.update('d', (v) => v + 1, ifAbsent: () => 1);

// Удаление
map.remove('b');
map.removeWhere((key, value) => value < 2);

// Преобразование
final doubled = map.map((key, value) => MapEntry(key, value * 2));

5. Кастомные Map для специфичных нужд:

// Case-insensitive Map
class CaseInsensitiveMap<K extends String, V> implements Map<K, V> {
  final Map<String, V> _map = {};

  @override
  V? operator [](Object? key) {
    return _map[(key as String).toLowerCase()];
  }

  @override
  void operator []=(K key, V value) {
    _map[key.toLowerCase()] = value;
  }

  // ... остальная реализация Map
}

// Использование
final ciMap = CaseInsensitiveMap<String, int>();
ciMap['Hello'] = 1;
print(ciMap['HELLO']); // 1

6. JSON-сериализация (частая задача в Flutter):

// Map ↔ JSON
final map = {'name': 'Alice', 'age': 30};
final jsonString = jsonEncode(map);
final decodedMap = jsonDecode(jsonString) as Map<String, dynamic>;

// Для сложных объектов с json_serializable
@JsonSerializable()
class User {
  final String name;
  final int age;

  User(this.name, this.age);

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

7. LinkedHashMap (дефолтная реализация) vs SplayTreeMap vs HashMap:

// Сохраняет порядок вставки (по умолчанию)
final ordered = LinkedHashMap<String, int>();

// Автоматическая сортировка по ключам
final sorted = SplayTreeMap<String, int>();
sorted['z'] = 1;
sorted['a'] = 2;
print(sorted.keys); // (a, z)

// Не гарантирует порядок, но может быть быстрее
final hashMap = HashMap<String, int>();

В Flutter-разработке Map особенно полезны для конфигурации, кэширования данных, передачи параметров между экранами и работы с JSON API. Я часто использую Map.fromIterable для преобразования списков из API в более удобные для работы структуры.