diff --git a/README.md b/README.md index d9c4dc4..c484a54 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/lib/src/generating_iterable.dart b/lib/src/generating_iterable.dart index 13041a6..f83dc01 100644 --- a/lib/src/generating_iterable.dart +++ b/lib/src/generating_iterable.dart @@ -51,6 +51,46 @@ class GeneratingIterable extends IterableBase { Iterator 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 extends IterableBase { + 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 get iterator => new _GeneratingIterator(initial(), next); + + Iterator 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 implements Iterator { final next; T object; diff --git a/test/generating_iterable_test.dart b/test/generating_iterable_test.dart index 34b8f69..9990cb6 100644 --- a/test/generating_iterable_test.dart +++ b/test/generating_iterable_test.dart @@ -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( + () => 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( + () => first, (n) => n.next, () => third, (n) => n.previous); + expect(iterable.last, third); + }); + }); } class Node { Node parent; } + +class LinkedEntry { + LinkedEntry previous; + LinkedEntry next; +}