// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. /// Equivalence class and equivalence builder. /// /// Allows incrementally building equivalence classes by equating elements. /// /// One way to use this class is to start with an empty `Equivalence` class Equivalence { /// Equivalence class IDs. /// /// Each entry is an index into this list. /// If the entry is the same as its index, it is the canonical /// ID for an equivalence class. /// If not, the entry points to an earlier entry which is in the same /// equivalence class. /// Every entry can be followed iteratively to a canonical equivalence /// class ID, and any two entries represent the same equivalence class /// if they point to the same canonical ID. final List _equivalences; /// Mapping from element to equivalence class ID. /// /// Two elements are considered equivalent (by [eq]) if their /// IDs point to the same canonical equivalence class ID. /// /// When doing lookups, the map is updated with the canonical /// ID if it's not currently pointing directly to that. final Map _class; /// Each element of [equivalenceClasses] is an equivalence by itself. /// /// If any element is in more than one equivalence class, /// if [allowCollapse] is `false` an error is raised, and /// If [allowCollapse] is `true` the equivalence classes are /// collapsed. Equivalence( Iterable> equivalenceClasses, { bool allowCollapse = false, }) : _equivalences = [], _class = {} { for (var eqClass in equivalenceClasses) { var newClass = _equivalences.length; _equivalences.add(newClass); for (var element in eqClass) { var existing = _class[element]; if (existing == null) { _class[element] = newClass; } else if (existing != newClass) { if (!allowCollapse) { // Wasn't in the *same* iterable. throw ArgumentError.value( equivalenceClasses, 'equivalenceClasses', "Contains element '$element' more than once", ); } var c1 = _canonicalizeId(existing); var c2 = _canonicalizeId(newClass); if (c1 != c2) { c1 = _equate(c1, c2); } _class[element] = c1; newClass = c1; } } } } /// All [elements] as distinct equivalence classes. /// /// A starting point for building an equivalence from scratch. Equivalence.distinct(Iterable elements) : _equivalences = [], _class = {} { for (var element in elements) { _add(element); } } List> get classes { _optimize(); var result = [for (var i = 0; i < _equivalences.length; i++) []]; for (var MapEntry(:key, :value) in _class.entries) { result[value].add(key); } return result; } /// Makes all elements point directly to their canonical equivalence class /// ID, makes all canonical IDs be consecutive small integers, and /// truncates the `_equivalences` list to only those values that are needed. void _optimize() { // Ensure all elements point to canonical class. _canonicalize(); var head = 0; var tail = _equivalences.length - 1; // Reuse unreachable early entries in `_equivalences` for later equivalence. // Invariant: 0..head-1 are canonical, tail=1..length are not. outer: while (head <= tail) { if (_equivalences[head] == head) { head++; } else { // _equivalences[head] is known not canonical. while (head < tail) { if (_equivalences[tail] != tail) { tail--; } else { _equivalences[head] = _equivalences[tail] = head; head++; tail--; continue outer; } } break; } } _canonicalize(); _equivalences.length = head; } void _canonicalize() { _class.updateAll((_, id) => _canonicalizeId(id)); } /// Adds element. /// /// If the element is already in the equivalence, nothing changes. /// If not, it is added in a new singleton equivalence class. void add(T element) { if (!_class.containsKey(element)) _add(element); } /// Adds new element to equivalence class, in an equivalence class of its own. int _add(T element) { assert(_class[element] == null); var newClass = _equivalences.length; _equivalences.add(newClass); _class[element] = newClass; return newClass; } /// The canonical equivalence class ID of the element. /// /// Is `null` if the element has not been added to the equivalence. int? _classOf(T element) { var c = _class[element]; if (c != null) { var e = _equivalences[c]; if (e == c) return e; return _equivalences[c] = _canonicalizeId(e); } return null; } int _canonicalizeId(int id) { while (true) { var nextId = _equivalences[id]; if (nextId == id) return id; _equivalences[id] = nextId; id = nextId; } } bool eq(T v1, T v2) { var c1 = _classOf(v1); return c1 != null && c1 == _classOf(v2); } /// Make two elements equivalent. /// /// This merges the equivalence classes of the two elements. /// /// If either element is not already in the equivalence, /// it is added, as by [add]. void equate(T v1, T v2) { var c1 = _classOf(v1); var c2 = _classOf(v2); if (c1 == null) { _class[v1] = c2 ?? _add(v2); } else if (c2 == null) { _class[v2] = c1; } else { var newId = _equate(c1, c2); if (c1 != newId) _class[v1] = newId; if (c2 != newId) _class[v2] = newId; } } /// Merge two equivalence classes. int _equate(int c1, int c2) { // Both must be canonical. assert(_equivalences[c1] == c1); assert(_equivalences[c2] == c2); if (c1 == c2) return c1; if (c1 < c2) { _equivalences[c2] = c1; return c1; } _equivalences[c1] = c2; return c2; } }