| // 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, |
| ); |
| } |