// 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({}, acyclic: acyclic), isEmpty); }); test('with only one node', () { expect( _transitiveClosure({1: []}, acyclic: acyclic), equals({1: {}}), ); }); test('with no edges', () { expect( _transitiveClosure( {1: [], 2: [], 3: [], 4: []}, acyclic: acyclic, ), equals(>{1: {}, 2: {}, 3: {}, 4: {}}), ); }); test('with single edges', () { expect( _transitiveClosure( { 1: [2], 2: [3], 3: [4], 4: [], }, acyclic: acyclic, ), equals({ 1: {2, 3, 4}, 2: {3, 4}, 3: {4}, 4: {}, }), ); }); test('with many edges from one node', () { expect( _transitiveClosure( { 1: [2, 3, 4], 2: [], 3: [], 4: [], }, acyclic: acyclic, ), equals(>{ 1: {2, 3, 4}, 2: {}, 3: {}, 4: {}, }), ); }); test('with transitive edges', () { expect( _transitiveClosure( { 1: [2, 4], 2: [], 3: [], 4: [3], }, acyclic: acyclic, ), equals(>{ 1: {2, 3, 4}, 2: {}, 3: {}, 4: {3}, }), ); }); test('with diamond edges', () { expect( _transitiveClosure( { 1: [2, 3], 2: [4], 3: [4], 4: [], }, acyclic: acyclic, ), equals(>{ 1: {2, 3, 4}, 2: {4}, 3: {4}, 4: {}, }), ); }); test('with disjoint subgraphs', () { expect( _transitiveClosure( { 1: [2], 2: [3], 3: [], 4: [5], 5: [6], 6: [], }, acyclic: acyclic, ), equals(>{ 1: {2, 3}, 2: {3}, 3: {}, 4: {5, 6}, 5: {6}, 6: {}, }), ); }); }); test('respects custom equality and hash functions', () { final result = _transitiveClosure( { 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( { 1: [1], }, acyclic: true, ), throwsCycleException([1]), ); }); test('a multi-node cycle', () { expect( () => _transitiveClosure( { 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({ 1: [1], }), equals({ 1: {1}, }), ); }); test('with a single multi-node component', () { expect( _transitiveClosure({ 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({ 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({ 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({ 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> _transitiveClosure( Map> 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, ); }