Skip to content

Commit

Permalink
Add GroupSet and SetGroup classes.
Browse files Browse the repository at this point in the history
GroupSet provides a view of the union of a set of sets. SetGroup
provides an easy way to manage a GroupSet in a class context.

R=floitsch@google.com, lrn@google.com

Review URL: https://codereview.chromium.org//1873373002 .
  • Loading branch information
nex3 committed May 3, 2016
1 parent c8b5bd4 commit b77fe79
Show file tree
Hide file tree
Showing 7 changed files with 409 additions and 2 deletions.
7 changes: 6 additions & 1 deletion pkgs/collection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## 1.5.2
## 1.6.0

* Add a `UnionSet` class that provides a view of the union of a set of sets.

* Add a `UnionSetController` class that provides a convenient way to manage the
contents of a `UnionSet`.

* Fix another incorrectly-declared generic type.

Expand Down
2 changes: 2 additions & 0 deletions pkgs/collection/lib/collection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ export "src/equality.dart";
export "src/iterable_zip.dart";
export "src/priority_queue.dart";
export "src/queue_list.dart";
export "src/union_set.dart";
export "src/union_set_controller.dart";
export "src/unmodifiable_wrappers.dart";
export "src/wrappers.dart";
88 changes: 88 additions & 0 deletions pkgs/collection/lib/src/union_set.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2016, 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 'unmodifiable_wrappers.dart';

/// A single set that provides a view of the union over a set of sets.
///
/// Since this is just a view, it reflects all changes in the underlying sets.
///
/// If an element is in multiple sets and the outer set is ordered, the version
/// in the earliest inner set is preferred. Component sets are assumed to use
/// `==` and `hashCode` for equality.
class UnionSet<E> extends SetBase<E> with UnmodifiableSetMixin<E> {
/// The set of sets that this provides a view of.
final Set<Set<E>> _sets;

/// Whether the sets in [_sets] are guaranteed to be disjoint.
final bool _disjoint;

/// Creates a new set that's a view of the union of all sets in [sets].
///
/// If any sets in [sets] change, this [UnionSet] reflects that change. If a
/// new set is added to [sets], this [UnionSet] reflects that as well.
///
/// If [disjoint] is `true`, then all component sets must be disjoint. That
/// is, that they contain no elements in common. This makes many operations
/// including [length] more efficient. If the component sets turn out not to
/// be disjoint, some operations may behave inconsistently.
UnionSet(this._sets, {bool disjoint: false}) : _disjoint = disjoint;

/// Creates a new set that's a view of the union of all sets in [sets].
///
/// If any sets in [sets] change, this [UnionSet] reflects that change.
/// However, unlike [new UnionSet], this creates a copy of its parameter, so
/// changes in [sets] aren't reflected in this [UnionSet].
///
/// If [disjoint] is `true`, then all component sets must be disjoint. That
/// is, that they contain no elements in common. This makes many operations
/// including [length] more efficient. If the component sets turn out not to
/// be disjoint, some operations may behave inconsistently.
UnionSet.from(Iterable<Set<E>> sets, {bool disjoint: false})
: this(sets.toSet(), disjoint: disjoint);

int get length => _disjoint
? _sets.fold(0, (length, set) => length + set.length)
: _iterable.length;

Iterator<E> get iterator => _iterable.iterator;

/// Returns an iterable over the contents of all the sets in [this].
Iterable<E> get _iterable =>
_disjoint ? _sets.expand((set) => set) : _dedupIterable;

/// Returns an iterable over the contents of all the sets in [this] that
/// de-duplicates elements.
///
/// If the sets aren't guaranteed to be disjoint, this keeps track of the
/// elements we've already emitted so that we can de-duplicate them.
Iterable<E> get _dedupIterable {
var seen = new Set<E>();
return _sets.expand((set) => set).where((element) {
if (seen.contains(element)) return false;
seen.add(element);
return true;
});
}

bool contains(Object element) => _sets.any((set) => set.contains(element));

E lookup(Object element) {
if (element == null) return null;

return _sets
.map((set) => set.lookup(element))
.firstWhere((result) => result != null, orElse: () => null);
}

Set<E> toSet() {
var result = new Set<E>();
for (var set in _sets) {
result.addAll(set);
}
return result;
}
}
54 changes: 54 additions & 0 deletions pkgs/collection/lib/src/union_set_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2016, 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 'union_set.dart';

/// A controller that exposes a view of the union of a collection of sets.
///
/// This is a convenience class for creating a [UnionSet] whose contents change
/// over the lifetime of a class. For example:
///
/// ```dart
/// class Engine {
/// Set<Test> get activeTests => _activeTestsGroup.set;
/// final _activeTestsGroup = new UnionSetController<Test>();
///
/// void addSuite(Suite suite) {
/// _activeTestsGroup.add(suite.tests);
/// _runSuite(suite);
/// _activeTestsGroup.remove(suite.tests);
/// }
/// }
/// ```
class UnionSetController<E> {
/// The [UnionSet] that provides a view of the union of sets in [this].
UnionSet<E> get set => _set;
UnionSet<E> _set;

/// The sets whose union is exposed through [set].
final _sets = new Set<Set<E>>();

/// Creates a set of sets that provides a view of the union of those sets.
///
/// If [disjoint] is `true`, this assumes that all component sets are
/// disjoint—that is, that they contain no elements in common. This makes
/// many operations including [length] more efficient.
UnionSetController({bool disjoint: false}) {
_set = new UnionSet<E>(_sets, disjoint: disjoint);
}

/// Adds the contents of [component] to [set].
///
/// If the contents of [component] change over time, [set] will change
/// accordingly.
void add(Set<E> component) {
_sets.add(component);
}

/// Removes the contents of [component] to [set].
///
/// If another set in [this] has overlapping elements with [component], those
/// elements will remain in [set].
bool remove(Set<E> component) => _sets.remove(component);
}
2 changes: 1 addition & 1 deletion pkgs/collection/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: collection
version: 1.5.2-dev
version: 1.6.0
author: Dart Team <misc@dartlang.org>
description: Collections and utilities functions and classes related to collections.
homepage: https://www.github.com/dart-lang/collection
Expand Down
36 changes: 36 additions & 0 deletions pkgs/collection/test/union_set_controller_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2016, 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 "package:test/test.dart";

import "package:collection/collection.dart";

void main() {
var controller;
var innerSet;
setUp(() {
innerSet = new Set.from([1, 2, 3]);
controller = new UnionSetController<int>()..add(innerSet);
});

test("exposes a union set", () {
expect(controller.set, unorderedEquals([1, 2, 3]));

controller.add(new Set.from([3, 4, 5]));
expect(controller.set, unorderedEquals([1, 2, 3, 4, 5]));

controller.remove(innerSet);
expect(controller.set, unorderedEquals([3, 4, 5]));
});

test("exposes a disjoint union set", () {
expect(controller.set, unorderedEquals([1, 2, 3]));

controller.add(new Set.from([4, 5, 6]));
expect(controller.set, unorderedEquals([1, 2, 3, 4, 5, 6]));

controller.remove(innerSet);
expect(controller.set, unorderedEquals([4, 5, 6]));
});
}
Loading

0 comments on commit b77fe79

Please sign in to comment.