blob: 705167e83a64d97e8be3b79b8134c7c5d939fcb6 [file] [log] [blame]
// 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<T> {
/// 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<int> _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<T, int> _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<Iterable<T>> 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<T> elements)
: _equivalences = [],
_class = {} {
for (var element in elements) {
_add(element);
}
}
List<List<T>> get classes {
_optimize();
var result = [for (var i = 0; i < _equivalences.length; i++) <T>[]];
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;
}
}