Skip to content
This repository has been archived by the owner on Mar 24, 2021. It is now read-only.

Add TwoWayGeneratingIterable #10

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ iterable.
elements by calling a function. A common use-case is to traverse properties in
an object graph, like the parent relationship in a tree.

`TwoWayGeneratingIterable` is similar to `GeneratingIterable`, but can be used when both the first and last element
are known and elements can be traversed in both directions.

`InfiniteIterable` is a base class for Iterables that throws on operations that
require a finite length.
40 changes: 40 additions & 0 deletions lib/src/generating_iterable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,46 @@ class GeneratingIterable<T> extends IterableBase<T> {
Iterator<T> get iterator => new _GeneratingIterator(initial(), next);
}

/**
* An Iterable who's first value is [initial] and who's last value is [last]
*
* Subsequent values are generated by passing the current value to the [next]
* function and prior values are generated by passing the current value to the
* [previous] function.
*
* The class is useful for creating lazy iterables from two-way object
* hierarchies and graphs.
*
* It's important that for the given initial value and next function that the
* sequence of items eventually terminates. Otherwise calling methods that
* expect a finite sequence, like `length` or `last`, will cause an infinite
* loop.
*/
class TwoWayGeneratingIterable<T> extends IterableBase<T> {
final initial;
final terminating;
final next;
final previous;

TwoWayGeneratingIterable(T this.initial(), T this.next(T o),
T this.terminating(), T this.previous(T o));

@override
Iterator<T> get iterator => new _GeneratingIterator(initial(), next);

Iterator<T> get reverseIterator =>
new _GeneratingIterator(terminating(), previous);

@override
T get last {
Iterator it = reverseIterator;
if (!it.moveNext()) {
throw new StateError("No elements");
}
return it.current;
}
}

class _GeneratingIterator<T> implements Iterator<T> {
final next;
T object;
Expand Down
51 changes: 51 additions & 0 deletions test/generating_iterable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,59 @@ main() {
expect(iterable, [node, parent]);
});
});

group('TwoWayGeneratingIterable', () {
test("should create an empty iterable for a null start object", () {
var iterable = new TwoWayGeneratingIterable(
() => null, (n) => null, () => null, (n) => null);
expect(iterable, []);
});

test("should create one-item empty iterable when next returns null", () {
var iterable = new TwoWayGeneratingIterable(
() => "Hello", (n) => null, () => "Hello", (n) => null);
expect(iterable, ["Hello"]);
});

test("should add items until next returns null in both ways", () {
var first = new LinkedEntry();
var second = new LinkedEntry()..previous = first;
var third = new LinkedEntry()..previous = second;
first.next = second;
second.next = third;

var iterable = new TwoWayGeneratingIterable<LinkedEntry>(
() => first, (n) => n.next, () => third, (n) => n.previous);
expect(iterable, [first, second, third]);

List reverse = [];
Iterator reverseIterator = iterable.reverseIterator;
while (reverseIterator.moveNext()) {
reverse.add(reverseIterator.current);
}

expect(reverse, [third, second, first]);
});

test("last should give last element", () {
var first = new LinkedEntry();
var second = new LinkedEntry()..previous = first;
var third = new LinkedEntry()..previous = second;
first.next = second;
second.next = third;

var iterable = new TwoWayGeneratingIterable<LinkedEntry>(
() => first, (n) => n.next, () => third, (n) => n.previous);
expect(iterable.last, third);
});
});
}

class Node {
Node parent;
}

class LinkedEntry {
LinkedEntry previous;
LinkedEntry next;
}