From ef2da0ca701d01731360a98c00d8c5e43d05669a Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Tue, 5 Mar 2024 04:40:03 +0000 Subject: [PATCH] feat: Implement more of iter --- lib/iter.dart | 2 + lib/src/iter/iterator.dart | 260 +++++++++++++++++-- lib/src/iter/iterator_extensions.dart | 47 +++- lib/src/iter/peekable.dart | 45 ++++ lib/src/iter/zip.dart | 32 +++ lib/src/option/future_option.dart | 4 +- lib/src/option/future_option_extensions.dart | 4 +- lib/src/slice/slice.dart | 7 +- test/array/array_test.dart | 10 + test/iter/iter_test.dart | 141 ++++++++++ 10 files changed, 520 insertions(+), 32 deletions(-) create mode 100644 lib/src/iter/peekable.dart create mode 100644 lib/src/iter/zip.dart diff --git a/lib/iter.dart b/lib/iter.dart index 05d86e1..eb2d9e0 100644 --- a/lib/iter.dart +++ b/lib/iter.dart @@ -1,3 +1,5 @@ library iter; export "src/iter/iterator.dart"; +export "src/iter/peekable.dart"; +export "src/iter/zip.dart"; diff --git a/lib/src/iter/iterator.dart b/lib/src/iter/iterator.dart index 1f9a180..8e0a4a3 100644 --- a/lib/src/iter/iterator.dart +++ b/lib/src/iter/iterator.dart @@ -1,3 +1,4 @@ +import 'package:rust_core/iter.dart'; import 'package:rust_core/result.dart'; import 'package:rust_core/slice.dart'; import 'package:rust_core/option.dart'; @@ -14,7 +15,17 @@ extension type RIterator(Iterable iterable) implements Iterable { // any: Implemented by Iterable.any // array_chunks // by_ref -// chain + + /// Takes two iterators and creates a new iterator over both in sequence. + RIterator chain(Iterable other) { + return RIterator(_chainHelper(other)); + } + + Iterable _chainHelper(Iterable other) sync* { + yield* iterable; + yield* other; + } + // cloned // cmp // cmp_by @@ -89,10 +100,10 @@ extension type RIterator(Iterable iterable) implements Iterable { return RIterator(iterable.expand(f)); } -// flatten: //todo, in extension +// flatten: Implemented in an extension // fold: Implemented by Iterable.fold // for_each: Implemented by Iterable.forEach -// fuse +// fuse: Implemented in an extension // ge // gt @@ -108,7 +119,40 @@ extension type RIterator(Iterable iterable) implements Iterable { } } -// intersperse + /// Creates a new iterator which places a separator between adjacent items of the original iterator. + /// Similar to join with strings. + RIterator intersperse(T element) { + return RIterator(_intersperseHelper(element)); + } + + Iterable _intersperseHelper(T element) sync* { + final iterator = iterable.iterator; + if (iterator.moveNext()) { + var current = iterator.current; + if (iterator.moveNext()) { + yield current; + current = iterator.current; + if (iterator.moveNext()) { + var next = iterator.current; + while (iterator.moveNext()) { + yield element; + yield current; + current = next; + next = iterator.current; + } + yield element; + yield current; + yield element; + yield next; + } else { + yield element; + } + } else { + yield current; + } + } + } + // intersperse_with // is_partitioned // is_sorted @@ -127,32 +171,197 @@ extension type RIterator(Iterable iterable) implements Iterable { // le // lt // map: Implemented by Iterable.map -// map_while + + /// Creates an iterator that both yields elements based on a predicate and maps. + /// It will call this closure on each element of the iterator, and yield elements while it returns Some(_). + RIterator mapWhile(Option Function(T) f) { + return RIterator(_mapWhileHelper(f)); + } + + Iterable _mapWhileHelper(Option Function(T) f) sync* { + for (final element in iterable) { + final result = f(element); + if (result.isSome()) { + yield result.v!; + } else { + break; + } + } + } + + // map_windows -// max -// max_by -// max_by_key -// min -// min_by -// min_by_key +// max: // todo in an extesion + + /// Returns the element that gives the maximum value with respect to the specified comparison function. + Option maxBy(int Function(T,T) f){ + if (iterable.isEmpty) { + return None; + } + var max = iterable.first; + for (final element in iterable) { + if (f(element, max) > 0) { + max = element; + } + } + return Some(max); + } + + /// Returns the element that gives the maximum value from the specified function. + Option maxByKey>(U Function(T) f) { + if (iterable.isEmpty) { + return None; + } + var max = iterable.first; + var maxVal = f(max); + for (final element in iterable) { + final val = f(element); + if (val.compareTo(maxVal) > 0) { + max = element; + maxVal = val; + } + } + return Some(max); + } + +// min: // todo in an extesion + + /// Returns the element that gives the minimum value with respect to the specified comparison function. + Option minBy(int Function(T,T) f){ + if (iterable.isEmpty) { + return None; + } + var min = iterable.first; + for (final element in iterable) { + if (f(element, min) < 0) { + min = element; + } + } + return Some(min); + } + + /// Returns the element that gives the minimum value from the specified function. + Option minByKey>(U Function(T) f) { + if (iterable.isEmpty) { + return None; + } + var min = iterable.first; + var minVal = f(min); + for (final element in iterable) { + final val = f(element); + if (val.compareTo(minVal) < 0) { + min = element; + minVal = val; + } + } + return Some(min); + } + // ne // next_chunk -// nth + + /// Returns the nth element of the iterator. + /// Like most indexing operations, the count starts from zero, so nth(0) returns the first value, nth(1) the second, and so on. + /// nth() will return None if n is greater than or equal to the length of the iterator. + Option nth(int n) { + if (n < 0) { + return None; + } + var index = 0; + for (final element in iterable) { + if (index == n) { + return Some(element); + } + index++; + } + return None; + } + // partial_cmp // partial_cmp_by -// partition -// partition_in_place -// peekable -// position + + /// Consumes an iterator, creating two collections from it. + /// partition() returns a pair, all of the elements for which it returned true, and all of the elements for which it returned false. + (List, List) partition(bool Function(T) f) { + final first = []; + final second = []; + for (final element in iterable) { + if (f(element)) { + first.add(element); + } else { + second.add(element); + } + } + return (first, second); + } + +// partition_in_place: Will not implement, not possible in Dart + + /// Creates an iterator which can use the "peek" to look at the next element of the iterator without consuming it. + Peekable peekable() => Peekable(iterable); + + + /// Searches for an element in an iterator, returning its index. + Option position(bool Function(T) f) { + var index = 0; + for (final element in iterable) { + if (f(element)) { + return Some(index); + } + index++; + } + return None; + } + // product // reduce: Implemented by Iterable.reduce -// rev -// rposition -// scan + + /// Reverses the iterable + RIterator rev() => RIterator(iterable.toList(growable: false).reversed); + + /// Searches for an element in an iterator from the right, returning its index. + /// Recommended to use with a list, as it is more efficient, otherwise use [position]. + Option rposition(bool Function(T) f) { + var index = iterable.length - 1; + final self = this.iterable; + if(self is List){ + for (var i = self.length - 1; i >= 0; i--) { + if (f(self[i])) { + return Some(i); + } + } + return None; + } + for (final element in iterable.toList(growable: false).reversed) { + if (f(element)) { + return Some(index); + } + index--; + } + return None; + } + +// scan: //todo // size_hint // skip: Implemented by Iterable.skip // skip_while: Implemented by Iterable.skipWhile -// step_by + + /// Creates an iterator starting at the same point, but stepping by the given amount at each iteration. + RIterator stepBy(int step) { + assert(step > 0, 'Step must be greater than 0'); + return RIterator(_stepByHelper(step)); + } + + Iterable _stepByHelper(int step) sync* { + var index = 0; + for (final element in iterable) { + if (index % step == 0) { + yield element; + } + index++; + } + } + // sum // take: Implemented by Iterable.take // take_while: Implemented by Iterable.takeWhile @@ -161,6 +370,13 @@ extension type RIterator(Iterable iterable) implements Iterable { // try_fold // try_for_each // try_reduce -// unzip -// zip +// unzip: Implemented in extension + + /// Zips this iterator with another and yields pairs of elements. + /// The first element comes from the first iterator, and the second element comes from the second iterator. + /// If either iterator does not have another element, the iterator stops. + RIterator<(T, U)> zip(Iterable other) => RIterator(Zip(this.iterable, other)); + + @override + Iterator get iterator => iterable.iterator; } diff --git a/lib/src/iter/iterator_extensions.dart b/lib/src/iter/iterator_extensions.dart index 721d741..a556a91 100644 --- a/lib/src/iter/iterator_extensions.dart +++ b/lib/src/iter/iterator_extensions.dart @@ -2,7 +2,7 @@ part of 'iterator.dart'; extension IteratorExtension on Iterator { Option next() { - if(moveNext()){ + if (moveNext()) { return Some(current); } return None; @@ -13,6 +13,21 @@ extension IterableExtension on Iterable { RIterator iter() => RIterator(this); } +extension IteratorOnIteratorIterabel on RIterator> { + /// Flatten an iterator of iterators into a single iterator. + RIterator flatten() { + return RIterator(_flattenHelper()); + } + + Iterable _flattenHelper() sync* { + for (final iterator in iterable) { + for (final value in iterator) { + yield value; + } + } + } +} + extension IteratorSliceExtension on RIterator> { //todo } @@ -22,10 +37,34 @@ extension IteratorIterableExtension on RIterator> { } extension IteratorOptionExtension on RIterator> { - //todo + /// Creates an iterator which ends after the first None. + RIterator fuse() { + return RIterator(_fuseHelper()); + } + + Iterable _fuseHelper() sync* { + for (final option in iterable) { + if (option.isNone()) { + break; + } + yield option.unwrap(); + } + } } -extension IteratorResultExtension - on RIterator> { +extension IteratorResultExtension on RIterator> { //todo } + +extension IteratorOnIteratorTUExtension on RIterator<(T, U)> { + /// Converts an iterator of pairs into a pair of containers. + (List, List) unzip() { + final first = []; + final second = []; + for (final (t, u) in iterable) { + first.add(t); + second.add(u); + } + return (first, second); + } +} diff --git a/lib/src/iter/peekable.dart b/lib/src/iter/peekable.dart new file mode 100644 index 0000000..8e3c4ef --- /dev/null +++ b/lib/src/iter/peekable.dart @@ -0,0 +1,45 @@ + + +import 'package:rust_core/option.dart'; + +/// An iterator which can use the "peek" to look at the next element of the iterator without consuming it. +class PeekableIterator implements Iterator { + final Iterator _iterator; + Option _peeked = None; + + PeekableIterator(this._iterator); + + /// Returns the next element of the iterator without consuming it. + Option peek() { + if (_peeked.isNone()) { + if (_iterator.moveNext()) { + _peeked = Some(_iterator.current); + } + } + return _peeked; + } + + @override + bool moveNext() { + if (_peeked.isSome()) { + _peeked = None; + return true; + } + return _iterator.moveNext(); + } + + @override + T get current => _peeked.unwrapOr(_iterator.current); +} + +/// An iterable which can use the "peek" to look at the next element of the iterator without consuming it. +class Peekable extends Iterable { + final Iterable _iterable; + + Peekable(this._iterable); + + @override + PeekableIterator get iterator { + return PeekableIterator(_iterable.iterator); + } +} \ No newline at end of file diff --git a/lib/src/iter/zip.dart b/lib/src/iter/zip.dart new file mode 100644 index 0000000..8de6ae4 --- /dev/null +++ b/lib/src/iter/zip.dart @@ -0,0 +1,32 @@ +/// Zips to iterators into a single iterator of pairs. +class Zip extends Iterable<(T,U)> { + final Iterable _iterableT; + final Iterable _iterableU; + + Zip(this._iterableT, this._iterableU); + + @override + IteratorZip get iterator { + return IteratorZip(_iterableT.iterator,_iterableU.iterator); + } +} + +class IteratorZip implements Iterator<(T,U)> { + final Iterator _iteratorT; + final Iterator _iteratorU; + late (T,U) _current; + + IteratorZip(this._iteratorT,this._iteratorU); + + @override + bool moveNext() { + if (!_iteratorT.moveNext() || !_iteratorU.moveNext()) { + return false; + } + _current = (_iteratorT.current,_iteratorU.current); + return true; + } + + @override + (T,U) get current => _current; +} \ No newline at end of file diff --git a/lib/src/option/future_option.dart b/lib/src/option/future_option.dart index 6d46d3d..9650acf 100644 --- a/lib/src/option/future_option.dart +++ b/lib/src/option/future_option.dart @@ -13,7 +13,7 @@ extension FutureOptionExtension on FutureOption { Future> andThen(FutureOr> Function(T) f) { return then((option) => - option.isSome() ? f(option.unwrap()) : Future.value(const _None())); + option.isSome() ? f(option.unwrap()) : Future.value(None)); } Future expect(String msg) { @@ -24,7 +24,7 @@ extension FutureOptionExtension on FutureOption { return then((option) async => option.isSome() && (await predicate(option.unwrap())) ? option - : const _None()); + : None); } Future> inspect(FutureOr Function(T) f) { diff --git a/lib/src/option/future_option_extensions.dart b/lib/src/option/future_option_extensions.dart index d6454cb..613f8de 100644 --- a/lib/src/option/future_option_extensions.dart +++ b/lib/src/option/future_option_extensions.dart @@ -18,7 +18,7 @@ extension FutureOptionOptionExtension on FutureOption> { /// Converts from FutureOption> to FutureOption. Future> flatten() async { var optionOption = await this; - return optionOption.isSome() ? optionOption.unwrap() : const _None(); + return optionOption.isSome() ? optionOption.unwrap() : None; } } @@ -45,7 +45,7 @@ extension OptionResultExtension on Option> { return Err(val.unwrapErr()); } } - return Ok(const _None()); + return Ok(None); } } diff --git a/lib/src/slice/slice.dart b/lib/src/slice/slice.dart index 13d4227..a464395 100644 --- a/lib/src/slice/slice.dart +++ b/lib/src/slice/slice.dart @@ -15,8 +15,11 @@ final class SliceIterator implements Iterator { @override bool moveNext() { - _index++; - return _index < _slice._end; + if(_index + 1 < _slice._end){ + _index++; + return true; + } + return false; } @override diff --git a/test/array/array_test.dart b/test/array/array_test.dart index fd1e5f5..5148eea 100644 --- a/test/array/array_test.dart +++ b/test/array/array_test.dart @@ -1,3 +1,4 @@ +import 'package:rust_core/iter.dart'; import 'package:rust_core/result.dart'; import 'package:test/test.dart'; import 'package:rust_core/array.dart'; @@ -62,4 +63,13 @@ void main() { }); expect(tryMap, Err("Error")); }); + + //************************************************************************// + test("Array and List composability",(){ + Array arr = Array(1, 3); + List list = ["1", "1", "1"]; + final other = arr.iter().zip(list); + RIterator other2 = arr.iter(); + + }); } diff --git a/test/iter/iter_test.dart b/test/iter/iter_test.dart index ba9b666..6927773 100644 --- a/test/iter/iter_test.dart +++ b/test/iter/iter_test.dart @@ -1,4 +1,5 @@ import 'package:rust_core/iter.dart'; +import 'package:rust_core/slice.dart'; import 'package:test/test.dart'; import 'package:rust_core/option.dart'; @@ -20,6 +21,12 @@ main() { expect(filtered, [4, 8]); }); + test("fuse", () { + var list = >[Some(1), Some(2), None, Some(4), Some(5)]; + var fused = list.iter().fuse(); + expect(fused, [1, 2]); + }); + test("find", () { var list = [1, 2, 3, 4, 5]; var found = list.iter().find((e) => e == 3); @@ -36,4 +43,138 @@ main() { }); expect(found, Some(4)); }); + + test("intersperse", () { + var list = [1, 2, 3, 4, 5]; + var interspersed = list.iter().intersperse(0); + expect(interspersed, [1, 0, 2, 0, 3, 0, 4, 0, 5]); + + var list2 = [1]; + var interspersed2 = list2.iter().intersperse(0); + expect(interspersed2, [1]); + + var list3 = []; + var interspersed3 = list3.iter().intersperse(0); + expect(interspersed3, []); + }); + + + test("maxBy",(){ + var list = [1, 2, 3, 4, 5]; + var max = list.iter().maxBy((int a, int b) => a.compareTo(b)); + expect(max, Some(5)); + }); + + test("maxByKey", (){ + var list = [1, 2, 3, 4, 5]; + var max = list.iter().maxByKey((int a) => a); + expect(max, Some(5)); + }); + + test("minBy",(){ + var list = [1, 2, 3, 4, 5]; + var min = list.iter().minBy((int a, int b) => a.compareTo(b)); + expect(min, Some(1)); + }); + + test("minByKey", (){ + var list = [1, 2, 3, 4, 5]; + var min = list.iter().minByKey((int a) => a); + expect(min, Some(1)); + }); + + test("mapWhile", () { + var list = [1, 2, 3, 4, 5]; + var mapped = list.iter().mapWhile((e) { + if (e < 4) { + return Some(e); + } + return None; + }); + expect(mapped, [1, 2, 3]); + }); + + test("peekable",(){ + var list = [1, 2, 3, 4, 5]; + PeekableIterator peekable = list.iter().peekable().iterator; + expect(peekable.peek(), Some(1)); + expect(peekable.next(), Some(1)); + expect(peekable.peek(), Some(2)); + expect(peekable.peek(), Some(2)); + expect(peekable.next(), Some(2)); + expect(peekable.peek(), Some(3)); + expect(peekable.next(), Some(3)); + expect(peekable.peek(), Some(4)); + expect(peekable.next(), Some(4)); + expect(peekable.peek(), Some(5)); + expect(peekable.next(), Some(5)); + expect(peekable.peek(), None); + expect(peekable.next(), None); + }); + + test("position", () { + var list = [1, 2, 3, 4, 5]; + var pos = list.iter().position((e) => e == 2); + expect(pos, Some(1)); + }); + + test("rposition", () { + var list = [1, 2, 3, 4, 5]; + var rpos = list.iter().rposition((e) => e == 2); + expect(rpos, Some(1)); + }); + + test("unzip", () { + var list = [(1, 2), (3, 4), (5, 6)]; + var unzipped = list.iter().unzip(); + expect(unzipped.$1, [1, 3, 5]); + expect(unzipped.$2, [2, 4, 6]); + + var list2 = <(int, double)>[]; + var unzipped2 = list2.iter().unzip(); + expect(unzipped2.$1, []); + expect(unzipped2.$2, []); + }); + + test("zip", () { + var list = [1, 2, 3, 4, 5]; + var zipped = list.iter().zip([6, 7, 8, 9, 10]); + expect(zipped, [ + (1, 6), + (2, 7), + (3, 8), + (4, 9), + (5, 10), + ]); + var zipped2 = list.iter().zip([6, 7, 8, 9]); + expect(zipped2, [ + (1, 6), + (2, 7), + (3, 8), + (4, 9), + ]); + var zipped3 = list.iter().zip([6, 7, 8, 9, 10, 11]); + expect(zipped3, [ + (1, 6), + (2, 7), + (3, 8), + (4, 9), + (5, 10), + ]); + var zipped4 = list.iter().zip([]); + expect(zipped4, []); + var zipped5 = [].iter().zip([6, 7, 8, 9, 10]); + expect(zipped5, []); + }); + + //************************************************************************// + + test("Can take slice", () { + var list = [1, 2, 3, 4, 5]; + var slice = Slice(list, 1, 3); + var iter = RIterator(slice); + iter = RIterator(slice); + RIterator iter2 = RIterator(slice); + expect(slice, [2, 3]); + }); }