Множества Set в Dart

Что такое множества

Коллекция Set (множество) в программировании — это структура данных, которая хранит только уникальные элементы. Это означает, что если попытаться добавить один и тот же элемент несколько раз, Set сохранит его только один раз. Это делает Set очень полезным в ситуациях, когда необходимо исключить дубликаты и работать только с уникальными значениями.

Проблема дублирования данных

Система голосования: Каждый человек должен иметь только один голос. Если бы дублирование было разрешено, результаты голосования были бы искажены.

Уникальные идентификаторы (ID): В любой системе (паспортные данные, номера счетов в банке) каждому объекту присваивается уникальный ID. Дублирование ID приведет к путанице и потере данных.

Билеты на концерт: Каждый билет должен быть уникальным и использоваться только один раз. Дублирование билетов приведет к тому, что на одно место могут претендовать несколько человек.

Как решали проблему дублирования до Set? Писали велосипеды ...

Dart - Решение без Set

Светлая тема Темная тема
void main() {
  List uniqueVotes = [];

  void addVote(String email) {
    bool exists = false;
    for (var vote in uniqueVotes) {
      // O(n) — медленно!
      if (vote == email) {
        exists = true;
        break;
      }
    }
    if (!exists) {
      uniqueVotes.add(email);
    }
  }

  addVote('artem@ya.ru');
  addVote('artem@ya.ru');
  addVote('anna@ya.ru');
  addVote('natasha@ya.ru');

  print(uniqueVotes); 
  // [artem@ya.ru, anna@ya.ru, natasha@ya.ru]
}

Проблемы такого подхода:

То же решение с использованием множества:

Dart - Решение с Set

Светлая тема Темная тема
void main() {
  Set uniqueVotes = {}; // Создание пустого множества

  void addVote(String email) {
    uniqueVotes.add(email);
  }

  addVote('artem@ya.ru');
  addVote('artem@ya.ru');
  addVote('anna@ya.ru');
  addVote('natasha@ya.ru');

  print(uniqueVotes); 
  // [artem@ya.ru, anna@ya.ru, natasha@ya.ru]
}

Достоинства:

Количество элементов List (мс) Set (мс)
1 000 15 0.2
10 000 1 500 0.3
100 000 150 000 0.5

Множества особенно эффективны в сценариях, когда:

Пример простого множества

Dart - Простое множество

Светлая тема Темная тема
Set games = {
  'Witcher', 
  'MTGA', 
  'GTA6', 
  'HSR', 
  'LostArk',
  'LostArk',
  'LostArk',
};

print(games); // {'Witcher', 'MTGA', 'GTA6', 'HSR', 'LostArk'}

Типы реализаций Set в Dart

В Dart существуют различные реализации множеств, каждая со своими особенностями:

Выбор конкретной реализации зависит от требований вашего приложения: если важен порядок добавления — используйте LinkedHashSet, если необходима сортировка — SplayTreeSet, если важна только скорость — HashSet

Создание множеств

Dart - Создание с помощью литералов

Светлая тема Темная тема
// Создание с помощью литералов
Set games = {
  'Witcher', 
  'MTGA', 
  'GTA6', 
  'HSR', 
  'LostArk',
  'LostArk',
  'LostArk',
};

// !!! ВНИМАНИЕ: var names = {}; создаст Map, а не Set!

// Создание пустого множества (важно указать тип!)
var names = {};

Специальные конструкторы

Dart - Создание из списка

Светлая тема Темная тема
void main() {

  List gameList = [
    'Witcher',
    'MTGA',
    'GTA6',
    'HSR',
    'LostArk',
    'LostArk',
    'LostArk',
  ];

  print(gameList); // [Witcher, MTGA, GTA6, HSR, LostArk, LostArk, LostArk]

  // Создание Множества из Списка
  // Все дубликаты в списке удаляются! Очень удобно!
  Set gameSet = Set.from(gameList);

  print(gameSet); // {Witcher, MTGA, GTA6, HSR, LostArk}
}

Dart - Константное множество

Светлая тема Темная тема
// Создание константного множества
final constantGames = const {'Witcher', 'Gwint', 'DMC', 'FF', 'LostArk'};

// constantGames.add('Gothic2'); 
// Ошибка: Cannot modify unmodifiable set

Основные свойства и методы Set

Dart - Основные свойства

Светлая тема Темная тема
var games = {'Witcher', 'Gwint', 'DMC', 'FF', 'LostArk'};
 
print(games.length);     // 5 - количество элементов
print(games.first);      // Первый элемент
print(games.last);       // Последний элемент
print(games.isEmpty);    // false - проверка на пустоту
print(games.isNotEmpty); // true - проверка на непустоту

Dart - Методы доступа

Светлая тема Темная тема
var games = {'Witcher', 'Gwint', 'DMC', 'FF', 'LostArk'};

// Получение элемента по индексу
print(games.elementAt(0)); // Элемент с индексом 0

// Проверка наличия элемента
print(games.contains('Witcher')); // true

// Поиск элемента (возвращает сам элемент или null)
print(games.lookup('Witcher')); // Witcher
print(games.lookup('Skyrim')); // null

Методы для модификации множества

Dart - Добавление и удаление

Светлая тема Темная тема
var games = {'Witcher', 'Gwint'};

// Добавление одного элемента
games.add('TES');

// Добавление нескольких элементов
games.addAll({'Langrisser', 'Gothic2', 'Gothic2'});
print(games); // {Witcher, Gwint, TES, Langrisser, Gothic2}

// Обратите внимание: 'Gothic2' добавится только один раз!

// Удаление элемента
games.remove('Gwint');

// Удаление элементов по условию
games.removeWhere((game) => game.length > 5);

// Очистка множества
games.clear();

Операции над множествами

Set предоставляет мощные методы для выполнения математических операций над множествами

Dart - Операции над множествами

Светлая тема Темная тема
var setA = {'Witcher', 'Gwint', 'DMC', 'FF', 'LostArk'};
var setB = {'Warcraft', 'Witcher', 'Gwint', 'Diablo'};

// Разность множеств (A - B): элементы из A, которых нет в B
print(setA.difference(setB)); // {DMC, FF, LostArk}

// Пересечение множеств (A ∩ B): элементы, общие для A и B
print(setA.intersection(setB)); // {Witcher, Gwint}

// Объединение множеств (A ∪ B): все элементы из A и B
print(setA.union(setB)); // {Witcher, Gwint, DMC, FF, LostArk, Warcraft, Diablo}

Отношения между множествами

Dart - Проверка отношений

Светлая тема Темная тема
var setA = {'Witcher', 'Gwint', 'DMC'};
var setB = {'Witcher', 'Gwint'};
var setC = {'Warcraft', 'Diablo'};


// Проверка, является ли setB подмножеством setA
print(setB.every((element) => setA.contains(element))); // true

// или

print(setA.containsAll(setB)); // true
  
// Проверка, являются ли множества непересекающимися
print(setA.intersection(setC).isEmpty); // true - множества не пересекаются

Spread-операторы

Dart - Spread-операторы

Светлая тема Темная тема
var basicGames = {'Witcher', 'Gwint'};
var additionalGames = {'DMC', 'FF'};

// Использование spread-оператора для объединения множеств
var allGames = {...basicGames, ...additionalGames};
print(allGames); // {Witcher, Gwint, DMC, FF}

// Null-aware spread оператор
Set? nullableGames;
var safeGames = {...basicGames, ...?nullableGames};
print(safeGames); // {Witcher, Gwint}

Условные конструкции и циклы

Dart - Условные конструкции и циклы

Светлая тема Темная тема
var isRPGFan = true;
var adventureGames = {'Tomb Raider', 'Uncharted'};

// Условное добавление элементов с помощью if
var games = {
  'Witcher',
  'DMC',
  if (isRPGFan) 'Final Fantasy',
};  

// Добавление элементов с помощью for
var moreGames = {
  'Witcher',
  'DMC',
  for (var game in adventureGames) 'Adventure: $game',
};

Особенности и ограничения

При работе с множествами в Dart следует помнить о следующих особенностях

Практическое применение

Set особенно полезен в следующих сценариях:

1. Удаление дубликатов из списка:

Dart - Удаление дубликатов

Светлая тема Темная тема
var listWithDuplicates = ['A', 'B', 'A', 'C', 'B'];
var uniqueItems = Set.from(listWithDuplicates).toList();
print(uniqueItems); // [A, B, C]

2. Проверка уникальности элементов:

Dart - Проверка уникальности

Светлая тема Темная тема
bool hasUniqueElements(List list) {
  return list.length == Set.from(list).length;
}

3. Фильтрация данных:

Dart - Фильтрация данных

Светлая тема Темная тема
var allUsers = {'Анна', 'Артём', 'Настя', 'Антон'};
var activeUsers = {'Анна', 'Настя'};

var inactiveUsers = allUsers.difference(activeUsers);
print(inactiveUsers); // {Артём, Антон}

4. Нахождение общих элементов:

Dart - Общие элементы

Светлая тема Темная тема
var teamA = {'Анна', 'Юля', 'Наташа'};
var teamB = {'Юля', 'Антон', 'Артём'};

var inBothTeams = teamA.intersection(teamB);
print(inBothTeams); // {Юля}