disjoint_set 1.0.0 copy "disjoint_set: ^1.0.0" to clipboard
disjoint_set: ^1.0.0 copied to clipboard

A highly efficient implementation of the Disjoint Set (Union-Find) data structure with union by rank and path compression optimizations.

example/disjoint_set_example.dart

// Copyright (c) 2025, the disjoint_set project authors.
// Please see the AUTHORS file for details. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file.

import 'dart:convert';
import 'package:disjoint_set/disjoint_set.dart';

/// A Point class to represent 2D coordinates.
/// Used to demonstrate DisjointSet with custom objects.
class Point {
  final int x;
  final int y;

  Point(this.x, this.y);

  // Distance calculation
  double distanceTo(Point other) {
    final dx = x - other.x;
    final dy = y - other.y;
    return (dx * dx + dy * dy)
        .toDouble(); // Using squared distance for efficiency
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) || other is Point && x == other.x && y == other.y;

  @override
  int get hashCode => x.hashCode ^ y.hashCode;

  @override
  String toString() => '($x,$y)';

  // Conversion to/from JSON
  Map<String, dynamic> toJson() => {'x': x, 'y': y};
  static Point fromJson(dynamic json) =>
      Point(json['x'] as int, json['y'] as int);
}

/// Main entry point showing different aspects of the DisjointSet data structure
void main() {
  print('Disjoint Set (Union-Find) Examples\n');
  print(
      'This example demonstrates the key features of the DisjointSet package:');
  print('  * Basic operations (makeSet, find, union)');
  print('  * Set queries (connected, getSet, getAllSets)');
  print('  * Advanced features (mergeIf, remove)');
  print('  * Serialization with toJson and fromJson');
  print('  * Performance characteristics with different dataset sizes');
  print('  * Working with custom objects\n');

  // Basic operations
  basicOperations();

  // Set queries
  setQueries();

  // Advanced features
  advancedFeatures();

  // Serialization
  serializationExample();

  // Performance characteristics
  performanceExample();

  // Custom object example
  customObjectExample();
}

/// Demonstrates basic DisjointSet operations: makeSet, find, union
///
/// These are the core operations that form the foundation of the data structure:
/// - makeSet: Creates a new singleton set containing one element
/// - find: Returns the representative element of a set
/// - union: Merges two sets together
void basicOperations() {
  print('\n=== Basic Operations ===\n');

  // Create a new DisjointSet for strings
  // DisjointSet is a generic class that can work with any type
  final ds = DisjointSet<String>();

  // Add elements to the structure
  print('Creating singleton sets for A, B, C, D, E');
  ds.makeSet('A');
  ds.makeSet('B');
  ds.makeSet('C');
  ds.makeSet('D');
  ds.makeSet('E');

  // Initial state
  print('\nInitial state:');
  print('Number of elements: ${ds.size}');
  print('Number of sets: ${ds.setCount}');

  // Find representatives
  print('\nRepresentatives:');
  print('Representative of A: ${ds.find('A')}');
  print('Representative of B: ${ds.find('B')}');

  // Union operations
  print('\nPerforming unions:');
  print('Union A and B');
  ds.union('A', 'B');

  print('Union C and D');
  ds.union('C', 'D');

  print('Union A and C');
  ds.union('A', 'C');

  // After unions
  print('\nAfter unions:');
  print('Number of sets: ${ds.setCount}');

  // Find representatives after unions
  print('\nRepresentatives after unions:');
  print('Representative of A: ${ds.find('A')}');
  print('Representative of B: ${ds.find('B')}');
  print('Representative of C: ${ds.find('C')}');
  print('Representative of D: ${ds.find('D')}');
  print('Representative of E: ${ds.find('E')}');
}

/// Demonstrates set query operations: connected, getSet, getAllSets
void setQueries() {
  print('\n=== Set Queries ===\n');

  final ds = DisjointSet<int>();

  // Create sets for numbers 0-9
  for (int i = 0; i < 10; i++) {
    ds.makeSet(i);
  }

  // Create some connections
  ds.union(0, 1);
  ds.union(1, 2);
  ds.union(3, 4);
  ds.union(4, 5);
  ds.union(6, 7);
  ds.union(7, 8);

  // Test connectivity
  print('Connectivity tests:');
  print('0 and 2 connected: ${ds.connected(0, 2)}'); // true
  print('0 and 3 connected: ${ds.connected(0, 3)}'); // false
  print('3 and 5 connected: ${ds.connected(3, 5)}'); // true
  print('6 and 8 connected: ${ds.connected(6, 8)}'); // true
  print('0 and 9 connected: ${ds.connected(0, 9)}'); // false

  // Get all elements in a specific set
  print('\nElements in set containing 0:');
  print(ds.getSet(0)); // {0, 1, 2}

  print('\nElements in set containing 4:');
  print(ds.getSet(4)); // {3, 4, 5}

  // Get all sets
  print('\nAll disjoint sets:');
  final allSets = ds.getAllSets();
  for (int i = 0; i < allSets.length; i++) {
    print('Set ${i + 1}: ${allSets[i]}');
  }
}

/// Demonstrates advanced features: mergeIf, remove
///
/// - mergeIf: Combines elements that satisfy a custom equivalence predicate
/// - remove: Removes an element from its set while maintaining structure
void advancedFeatures() {
  print('\n=== Advanced Features ===\n');

  // mergeIf example - grouping numbers by their parity (odd/even)
  // This is a perfect use case for mergeIf - a simple equivalence relation
  final ds1 = DisjointSet<int>();

  // Add numbers 1-10
  for (int i = 1; i <= 10; i++) {
    ds1.makeSet(i);
  }

  print('Initial sets (10 singleton sets):');
  print('Number of sets: ${ds1.setCount}');

  // Merge numbers with the same parity (odd/even)
  print('\nMerging numbers with the same parity (odd/even):');
  // The predicate function returns true for numbers that should be in the same set
  ds1.mergeIf((a, b) => a % 2 == b % 2);

  print('After mergeIf:');
  print('Number of sets: ${ds1.setCount}'); // Should be 2 (odd and even)

  final oddSet = ds1.getSet(1);
  final evenSet = ds1.getSet(2);

  print('Odd numbers set: $oddSet');
  print('Even numbers set: $evenSet');

  // Remove example
  print('\nRemove example:');
  final ds2 = DisjointSet<String>();

  // Create sets
  for (final c in ['A', 'B', 'C', 'D', 'E']) {
    ds2.makeSet(c);
  }

  // Join some elements
  ds2.union('A', 'B');
  ds2.union('B', 'C');

  print('Before removal:');
  print('Set A: ${ds2.getSet('A')}'); // {A, B, C}
  print('Connected A-B: ${ds2.connected('A', 'B')}'); // true

  // Remove B - this demonstrates how the structure maintains integrity
  // even when removing elements that might be representatives
  print('\nRemoving B:');
  ds2.remove('B');

  print('After removal:');
  print('Set A: ${ds2.getSet('A')}'); // {A, C}
  print('Connected A-C: ${ds2.connected('A', 'C')}'); // true
  print('B exists: ${ds2.find('B') != null}'); // false
}

/// Demonstrates serialization with toJson and fromJson
///
/// This feature allows saving the disjoint set structure to JSON format
/// and restoring it later, preserving all set relationships.
void serializationExample() {
  print('\n=== Serialization ===\n');

  final ds = DisjointSet<String>();

  // Create some sets
  ds.makeSet('A');
  ds.makeSet('B');
  ds.makeSet('C');
  ds.makeSet('D');
  ds.makeSet('E');

  // Join some elements
  ds.union('A', 'B');
  ds.union('C', 'D');

  print('Original disjoint set:');
  print('Sets: ${ds.getAllSets()}');

  // Convert to JSON
  final jsonMap = ds.toJson();
  final jsonString = jsonEncode(jsonMap);

  print('\nJSON representation:');
  print(jsonString);

  // Create a new DisjointSet from the JSON
  final decodedMap = jsonDecode(jsonString) as Map<String, dynamic>;
  final newDs = DisjointSet<String>.fromJson(
    decodedMap,
    fromJsonT: (dynamic json) => json as String,
  );

  print('\nDeserialized disjoint set:');
  print('Sets: ${newDs.getAllSets()}');

  // Verify connections are preserved
  print('\nVerifying connections:');
  print('A-B connected: ${newDs.connected('A', 'B')}'); // true
  print('C-D connected: ${newDs.connected('C', 'D')}'); // true
  print('A-C connected: ${newDs.connected('A', 'C')}'); // false
}

/// Demonstrates performance characteristics with different sizes
///
/// The DisjointSet implementation uses two key optimizations:
/// - Union by rank: Keeps trees balanced by attaching smaller trees under larger ones
/// - Path compression: Flattens the structure during find operations
///
/// These optimizations together provide a near-constant time complexity for operations.
void performanceExample() {
  print('\n=== Performance Characteristics ===\n');
  print('The DisjointSet implementation uses two optimizations:');
  print('  * Union by rank: Keeps trees balanced');
  print(
      '  * Path compression: Flattens the structure during find operations\n');

  // Performance with different sizes
  for (final size in [100, 1000, 10000]) {
    print('Testing with $size elements:');
    final ds = DisjointSet<int>();

    // Measure makeSet
    final stopwatch = Stopwatch()..start();
    for (int i = 0; i < size; i++) {
      ds.makeSet(i);
    }
    final makeSetTime = stopwatch.elapsedMilliseconds;

    // Measure union (create a long chain)
    stopwatch.reset();
    for (int i = 0; i < size - 1; i++) {
      ds.union(i, i + 1);
    }
    final unionTime = stopwatch.elapsedMilliseconds;

    // Measure find (first and last element)
    stopwatch.reset();
    ds.find(0);
    ds.find(size - 1);
    final findTime = stopwatch.elapsedMicroseconds;

    // Report times
    print('  makeSet for $size elements: $makeSetTime ms');
    print('  union for ${size - 1} pairs: $unionTime ms');
    print('  find after compression: $findTime µs');
    print('  final set count: ${ds.setCount}');
    print('');
  }
}

/// Demonstrates using DisjointSet with custom objects
///
/// This example shows how to use DisjointSet with custom classes,
/// including handling serialization for custom objects.
void customObjectExample() {
  print('\n=== Custom Object Example ===\n');

  // Create some points
  final points = [
    Point(1, 2),
    Point(2, 3),
    Point(10, 10),
    Point(11, 12),
    Point(20, 20),
    Point(100, 100),
  ];

  // Create a DisjointSet for Points
  final ds = DisjointSet<Point>();

  // Add all points
  for (final point in points) {
    ds.makeSet(point);
  }

  print('Initial points:');
  for (final point in points) {
    print('  $point');
  }

  // Group points that are close to each other (squared distance < 25)
  print('\nGrouping points with squared distance < 25:');
  print('Note: mergeIf creates transitive relationships, which may not be');
  print('ideal for distance-based clustering. For real applications, consider');
  print('using a graph-based approach for non-transitive relationships.');
  ds.mergeIf((a, b) => a.distanceTo(b) < 25);

  // Print the resulting clusters
  print('\nResulting clusters:');
  final clusters = ds.getAllSets();
  for (int i = 0; i < clusters.length; i++) {
    print('Cluster ${i + 1}: ${clusters[i]}');
  }

  // Serialization with custom objects
  print('\nSerializing custom objects:');
  final jsonMap = ds.toJson();

  // For custom objects, we need to handle the serialization manually
  final Map<String, dynamic> customJson = {
    'elements': (jsonMap['elements'] as List<dynamic>)
        .map((p) => (p as Point).toJson())
        .toList(),
    'sets': jsonMap['sets'],
  };

  final jsonString = jsonEncode(customJson);
  print('JSON: $jsonString');

  // Deserializing
  print('\nDeserializing custom objects:');
  final decodedMap = jsonDecode(jsonString) as Map<String, dynamic>;

  final customDecodedJson = {
    'elements':
        (decodedMap['elements'] as List<dynamic>).map(Point.fromJson).toList(),
    'sets': decodedMap['sets'],
  };

  final newDs = DisjointSet<Point>.fromJson(
    customDecodedJson,
    fromJsonT: (dynamic json) => json as Point,
  );

  print('Deserialized clusters:');
  final newClusters = newDs.getAllSets();
  for (int i = 0; i < newClusters.length; i++) {
    print('Cluster ${i + 1}: ${newClusters[i]}');
  }
}
2
likes
160
points
13
downloads

Publisher

verified publisherkator.dev

Weekly Downloads

A highly efficient implementation of the Disjoint Set (Union-Find) data structure with union by rank and path compression optimizations.

Repository (GitHub)
View/report issues
Contributing

Documentation

API reference

License

MIT (license)

Dependencies

meta

More

Packages that depend on disjoint_set