ZetCode

Dart SetMixin

last modified April 4, 2025

In Dart, SetMixin is an abstract class that provides base implementation for Set collections. It helps create custom Set implementations with unique elements.

SetMixin implements core Set operations while allowing customization. It ensures all elements are unique based on their equality implementation.

Basic SetMixin Implementation

Here's how to create a basic Set implementation using SetMixin.

main.dart
import 'dart:collection';

class MySet<E> extends SetBase<E> with SetMixin<E> {
  final _set = <E>{};

  @override
  Iterator<E> get iterator => _set.iterator;

  @override
  int get length => _set.length;

  @override
  bool contains(Object? element) => _set.contains(element);

  @override
  E? lookup(Object? element) => _set.lookup(element);

  @override
  bool add(E element) => _set.add(element);

  @override
  bool remove(Object? element) => _set.remove(element);

  @override
  void clear() => _set.clear();
}

void main() {
  var mySet = MySet<int>();
  mySet.addAll([1, 2, 3, 3, 4]);
  
  print(mySet); // {1, 2, 3, 4}
}

We create MySet by extending SetBase and mixing in SetMixin. The implementation delegates to an internal Set. Duplicates are automatically handled.

$ dart main.dart
{1, 2, 3, 4}

Custom Equality Set

We can create a Set with custom equality by overriding contains and lookup.

main.dart
import 'dart:collection';

class CaseInsensitiveSet extends SetBase<String> with SetMixin<String> {
  final _set = <String>{};

  @override
  Iterator<String> get iterator => _set.iterator;

  @override
  int get length => _set.length;

  @override
  bool contains(Object? element) {
    if (element is! String) return false;
    return _set.any((e) => e.toLowerCase() == element.toLowerCase());
  }

  @override
  String? lookup(Object? element) {
    if (element is! String) return null;
    return _set.firstWhere(
      (e) => e.toLowerCase() == element.toLowerCase(),
      orElse: () => null,
    );
  }

  @override
  bool add(String element) {
    if (contains(element)) return false;
    return _set.add(element);
  }

  @override
  bool remove(Object? element) {
    if (element is! String) return false;
    final existing = lookup(element);
    return existing != null ? _set.remove(existing) : false;
  }

  @override
  void clear() => _set.clear();
}

void main() {
  var ciSet = CaseInsensitiveSet();
  ciSet.add('Apple');
  ciSet.add('apple'); // Won't add
  
  print(ciSet); // {Apple}
}

This Set treats strings case-insensitively. The contains and lookup methods are overridden to implement custom equality logic.

$ dart main.dart
{Apple}

Set Operations with SetMixin

SetMixin provides implementations for common set operations like union and intersection.

main.dart
import 'dart:collection';

class MySet<E> extends SetBase<E> with SetMixin<E> {
  final _set = <E>{};

  @override
  Iterator<E> get iterator => _set.iterator;

  @override
  int get length => _set.length;

  @override
  bool contains(Object? element) => _set.contains(element);

  @override
  E? lookup(Object? element) => _set.lookup(element);

  @override
  bool add(E element) => _set.add(element);

  @override
  bool remove(Object? element) => _set.remove(element);

  @override
  void clear() => _set.clear();
}

void main() {
  var setA = MySet<int>()..addAll([1, 2, 3]);
  var setB = MySet<int>()..addAll([3, 4, 5]);
  
  print('Union: ${setA.union(setB)}');
  print('Intersection: ${setA.intersection(setB)}');
  print('Difference: ${setA.difference(setB)}');
}

We demonstrate union, intersection, and difference operations. These methods are provided by SetMixin and work with any Set implementation.

$ dart main.dart
Union: {1, 2, 3, 4, 5}
Intersection: {3}
Difference: {1, 2}

Filtering Elements

SetMixin provides where method to filter elements based on a condition.

main.dart
import 'dart:collection';

class MySet<E> extends SetBase<E> with SetMixin<E> {
  final _set = <E>{};

  @override
  Iterator<E> get iterator => _set.iterator;

  @override
  int get length => _set.length;

  @override
  bool contains(Object? element) => _set.contains(element);

  @override
  E? lookup(Object? element) => _set.lookup(element);

  @override
  bool add(E element) => _set.add(element);

  @override
  bool remove(Object? element) => _set.remove(element);

  @override
  void clear() => _set.clear();
}

void main() {
  var numbers = MySet<int>()..addAll([1, 2, 3, 4, 5, 6]);
  var evens = numbers.where((n) => n % 2 == 0).toSet();
  
  print('Original: $numbers');
  print('Evens: $evens');
}

The where method returns an iterable of elements that satisfy the predicate. We convert it back to a Set using toSet().

$ dart main.dart
Original: {1, 2, 3, 4, 5, 6}
Evens: {2, 4, 6}

SetMixin with Custom Objects

When storing custom objects, proper hashCode and == implementations are crucial.

main.dart
import 'dart:collection';

class Person {
  final String name;
  final int age;

  Person(this.name, this.age);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Person && name == other.name && age == other.age;

  @override
  int get hashCode => name.hashCode ^ age.hashCode;

  @override
  String toString() => 'Person($name, $age)';
}

class PersonSet extends SetBase<Person> with SetMixin<Person> {
  final _set = <Person>{};

  @override
  Iterator<Person> get iterator => _set.iterator;

  @override
  int get length => _set.length;

  @override
  bool contains(Object? element) => _set.contains(element);

  @override
  Person? lookup(Object? element) => _set.lookup(element);

  @override
  bool add(Person element) => _set.add(element);

  @override
  bool remove(Object? element) => _set.remove(element);

  @override
  void clear() => _set.clear();
}

void main() {
  var people = PersonSet();
  people.add(Person('Alice', 30));
  people.add(Person('Bob', 25));
  people.add(Person('Alice', 30)); // Duplicate
  
  print(people); // {Person(Alice, 30), Person(Bob, 25)}
}

The Person class implements proper equality checks. The Set correctly identifies duplicates based on the custom == implementation.

$ dart main.dart
{Person(Alice, 30), Person(Bob, 25)}

Best Practices

Source

Dart SetMixin Documentation

This tutorial covered Dart's SetMixin with practical examples demonstrating its key features and usage patterns for creating custom Set implementations.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.