| // Copyright (c) 2023, 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. | |
| import 'dart:collection'; | |
| import 'package:graphs/graphs.dart'; | |
| import 'package:test/test.dart'; | |
| import 'utils/utils.dart'; | |
| void main() { | |
| group('for an acyclic graph', () { | |
| for (final acyclic in [true, false]) { | |
| group('with acyclic: $acyclic', () { | |
| group('returns the transitive closure for a graph', () { | |
| test('with no nodes', () { | |
| expect(_transitiveClosure<int>({}, acyclic: acyclic), isEmpty); | |
| }); | |
| test('with only one node', () { | |
| expect( | |
| _transitiveClosure<int>({1: []}, acyclic: acyclic), | |
| equals({1: <int>{}}), | |
| ); | |
| }); | |
| test('with no edges', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [], | |
| 2: [], | |
| 3: [], | |
| 4: [], | |
| }, acyclic: acyclic), | |
| equals(<int, Set<int>>{1: {}, 2: {}, 3: {}, 4: {}}), | |
| ); | |
| }); | |
| test('with single edges', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [3], | |
| 3: [4], | |
| 4: [], | |
| }, acyclic: acyclic), | |
| equals({ | |
| 1: {2, 3, 4}, | |
| 2: {3, 4}, | |
| 3: {4}, | |
| 4: <int>{}, | |
| }), | |
| ); | |
| }); | |
| test('with many edges from one node', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2, 3, 4], | |
| 2: [], | |
| 3: [], | |
| 4: [], | |
| }, acyclic: acyclic), | |
| equals(<int, Set<int>>{ | |
| 1: {2, 3, 4}, | |
| 2: {}, | |
| 3: {}, | |
| 4: {}, | |
| }), | |
| ); | |
| }); | |
| test('with transitive edges', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2, 4], | |
| 2: [], | |
| 3: [], | |
| 4: [3], | |
| }, acyclic: acyclic), | |
| equals(<int, Set<int>>{ | |
| 1: {2, 3, 4}, | |
| 2: {}, | |
| 3: {}, | |
| 4: {3}, | |
| }), | |
| ); | |
| }); | |
| test('with diamond edges', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2, 3], | |
| 2: [4], | |
| 3: [4], | |
| 4: [], | |
| }, acyclic: acyclic), | |
| equals(<int, Set<int>>{ | |
| 1: {2, 3, 4}, | |
| 2: {4}, | |
| 3: {4}, | |
| 4: {}, | |
| }), | |
| ); | |
| }); | |
| test('with disjoint subgraphs', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [3], | |
| 3: [], | |
| 4: [5], | |
| 5: [6], | |
| 6: [], | |
| }, acyclic: acyclic), | |
| equals(<int, Set<int>>{ | |
| 1: {2, 3}, | |
| 2: {3}, | |
| 3: {}, | |
| 4: {5, 6}, | |
| 5: {6}, | |
| 6: {}, | |
| }), | |
| ); | |
| }); | |
| }); | |
| test('respects custom equality and hash functions', () { | |
| final result = _transitiveClosure<int>( | |
| { | |
| 0: [2], | |
| 3: [4], | |
| 5: [6], | |
| 7: [], | |
| }, | |
| equals: (i, j) => (i ~/ 2) == (j ~/ 2), | |
| hashCode: (i) => (i ~/ 2).hashCode, | |
| ); | |
| expect( | |
| result.keys, | |
| unorderedMatches([ | |
| 0, | |
| anyOf([2, 3]), | |
| anyOf([4, 5]), | |
| anyOf([6, 7]), | |
| ]), | |
| ); | |
| expect( | |
| result[0], | |
| equals({ | |
| anyOf([2, 3]), | |
| anyOf([4, 5]), | |
| anyOf([6, 7]), | |
| }), | |
| ); | |
| expect( | |
| result[2], | |
| equals({ | |
| anyOf([4, 5]), | |
| anyOf([6, 7]), | |
| }), | |
| ); | |
| expect( | |
| result[4], | |
| equals({ | |
| anyOf([6, 7]), | |
| }), | |
| ); | |
| expect(result[6], isEmpty); | |
| }); | |
| }); | |
| } | |
| }); | |
| group('for a cyclic graph', () { | |
| group('with acyclic: true throws a CycleException for a graph with', () { | |
| test('a one-node cycle', () { | |
| expect( | |
| () => _transitiveClosure<int>({ | |
| 1: [1], | |
| }, acyclic: true), | |
| throwsCycleException([1]), | |
| ); | |
| }); | |
| test('a multi-node cycle', () { | |
| expect( | |
| () => _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [3], | |
| 3: [4], | |
| 4: [1], | |
| }, acyclic: true), | |
| throwsCycleException([4, 1, 2, 3]), | |
| ); | |
| }); | |
| }); | |
| group('returns the transitive closure for a graph', () { | |
| test('with a single one-node component', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [1], | |
| }), | |
| equals({ | |
| 1: {1}, | |
| }), | |
| ); | |
| }); | |
| test('with a single multi-node component', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [3], | |
| 3: [4], | |
| 4: [1], | |
| }), | |
| equals({ | |
| 1: {1, 2, 3, 4}, | |
| 2: {1, 2, 3, 4}, | |
| 3: {1, 2, 3, 4}, | |
| 4: {1, 2, 3, 4}, | |
| }), | |
| ); | |
| }); | |
| test('with a series of multi-node components', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [1, 3], | |
| 3: [4], | |
| 4: [3, 5], | |
| 5: [6], | |
| 6: [5, 7], | |
| 7: [8], | |
| 8: [7], | |
| }), | |
| equals({ | |
| 1: {1, 2, 3, 4, 5, 6, 7, 8}, | |
| 2: {1, 2, 3, 4, 5, 6, 7, 8}, | |
| 3: {3, 4, 5, 6, 7, 8}, | |
| 4: {3, 4, 5, 6, 7, 8}, | |
| 5: {5, 6, 7, 8}, | |
| 6: {5, 6, 7, 8}, | |
| 7: {7, 8}, | |
| 8: {7, 8}, | |
| }), | |
| ); | |
| }); | |
| test('with a diamond of multi-node components', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [1, 3, 5], | |
| 3: [4], | |
| 4: [3, 7], | |
| 5: [6], | |
| 6: [5, 7], | |
| 7: [8], | |
| 8: [7], | |
| }), | |
| equals({ | |
| 1: {1, 2, 3, 4, 5, 6, 7, 8}, | |
| 2: {1, 2, 3, 4, 5, 6, 7, 8}, | |
| 3: {3, 4, 7, 8}, | |
| 4: {3, 4, 7, 8}, | |
| 5: {5, 6, 7, 8}, | |
| 6: {5, 6, 7, 8}, | |
| 7: {7, 8}, | |
| 8: {7, 8}, | |
| }), | |
| ); | |
| }); | |
| test('mixed single- and multi-node components', () { | |
| expect( | |
| _transitiveClosure<int>({ | |
| 1: [2], | |
| 2: [1, 3], | |
| 3: [4], | |
| 4: [5], | |
| 5: [4, 6], | |
| 6: [7], | |
| 7: [8], | |
| 8: [7], | |
| }), | |
| equals({ | |
| 1: {1, 2, 3, 4, 5, 6, 7, 8}, | |
| 2: {1, 2, 3, 4, 5, 6, 7, 8}, | |
| 3: {4, 5, 6, 7, 8}, | |
| 4: {4, 5, 6, 7, 8}, | |
| 5: {4, 5, 6, 7, 8}, | |
| 6: {7, 8}, | |
| 7: {7, 8}, | |
| 8: {7, 8}, | |
| }), | |
| ); | |
| }); | |
| }); | |
| }); | |
| } | |
| /// Returns the transitive closure of a graph represented a map from keys to | |
| /// edges. | |
| Map<T, Set<T>> _transitiveClosure<T extends Object>( | |
| Map<T, List<T>> graph, { | |
| bool Function(T, T)? equals, | |
| int Function(T)? hashCode, | |
| bool acyclic = false, | |
| }) { | |
| assert((equals == null) == (hashCode == null)); | |
| if (equals != null) { | |
| graph = LinkedHashMap(equals: equals, hashCode: hashCode)..addAll(graph); | |
| } | |
| return transitiveClosure( | |
| graph.keys, | |
| (node) { | |
| expect(graph, contains(node)); | |
| return graph[node]!; | |
| }, | |
| equals: equals, | |
| hashCode: hashCode, | |
| acyclic: acyclic, | |
| ); | |
| } |