-
Notifications
You must be signed in to change notification settings - Fork 86
Add Iterable.slice() and Iterable.subsequence() extensions #191
Conversation
This also adds more efficient implementations of these for List. Inspired by Ruby's Enumerable.each_slice and Enumerable.each_cons methods.
lib/src/iterable_extensions.dart
Outdated
|
||
var iterator = this.iterator; | ||
var subsequence = <T>[]; | ||
while (iterator.moveNext()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably split this into two loops, instead of branching in the main loop on something which is going to always be false up to some point, then always true after. It really is two loops.
Maybe:
var buffer = ListQueue<T>(length + 1);
while (buffer.length < length) {
if (iterator.moveNext()) {
buffer.add(iterator.current);
} else {
return;
}
}
while (true) {
yield List<T>.unmodifiable(buffer);
if (!iterator.moveNext()) return;
buffer..removeFirst()..add(iterator.current);
}
(Why don't we have a do { beforeTest } while ( test ) { afterTest }
construct?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably split this into two loops, instead of branching in the main loop on something which is going to always be false up to some point, then always true after. It really is two loops.
Done.
Maybe:
var buffer = ListQueue<T>(length + 1); while (buffer.length < length) { if (iterator.moveNext()) { buffer.add(iterator.current); } else { return; } } while (true) { yield List<T>.unmodifiable(buffer); if (!iterator.moveNext()) return; buffer..removeFirst()..add(iterator.current); }
I don't think the ListQueue
actually makes this more efficient. You're still copying length
elements on every iteration in the List.unmodifiable()
call.
lib/src/iterable_extensions.dart
Outdated
/// last one ends. | ||
/// | ||
/// For example, `(1, 2, 3, 4, 5).slices(2)` returns `((1, 2), (3, 4), (5))`. | ||
Iterable<Iterable<T>> slices(int length) sync* { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "slices" name is not particularly telling.
Not sure which short-ish verb would better describe "chop list into equal sized chunks" through.
Ideas welcome.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ruby calls it each_slice
, from which I got this name. RxJS calls it bufferWithCount
, but that seems too strongly tied to its streaming API to use here. A Haskell package I found calls it chunksOf
.
I suppose we could call this chunks
, but I'm not sure that's much clearer.
lib/src/iterable_extensions.dart
Outdated
@@ -549,6 +549,54 @@ extension IterableExtension<T> on Iterable<T> { | |||
} | |||
return true; | |||
} | |||
|
|||
/// Returns an iterable whose elements are contiguous slices of [this]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns -> Creates
Ditto below (I still don't think "Returns" is ever the best word to start a DartDoc with).
lib/src/iterable_extensions.dart
Outdated
} | ||
} | ||
|
||
/// Returns an iterable of each subsequence of [this] of the given [length]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns -> Creates
lib/src/list_extensions.dart
Outdated
@@ -252,6 +252,38 @@ extension ListExtensions<E> on List<E> { | |||
} | |||
return true; | |||
} | |||
|
|||
/// Returns an iterable whose elements are contiguous [slice]s of [this]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also Returns->Creates in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Returns" is very common.
cc @munificent for opinions on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would do "Returns" if it were my code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I'll agree to disagree then.
To summarize my reasoning: "Returns" is never the best word to start a DartDoc with.
It's redundant. (All non-void
methods return something, so it's like adding "when called" to the method. It's a given).
If it's the most important thing to say about the method, then either the method should be a getter, or it's a parameterized-getter-like method, and I'd document it with a noun phrase too (so remove the "Returns"). Own up to the fact that the method is described by a noun phrase, don't try to hide it by adding "Returns" in front!
If not, then it shouldn't be the first word of the DartDoc. Say something about what the method actually does, not just what it returns.
Finally, we recommend having a paragraph of the DartDoc starting with "Returns" to document the return value. Having the first (short, one-line) paragraph take over that makes it harder to do a satisfactory description of the returned value, and having two sections starting with "Returns" looks odd and can be confusion.
Tl;dr: Using "Returns noun-phrase" as the primary DartDoc of a method is technically following the style guide, but missing the point. It's always better to use something else.
But if people want it, they can have it. I'll just keep suggesting improvements!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a tricky one. In cases like this, I don't think a noun phrase reads right. Those work best when the member sounds like it describes a property or facet of the underlying object. But I don't think most readers would think the list has these subsequences as a fundamental property.
I believe they will think of it more like a calculation that is producing the result. That means a verb sounds better to me, but I wouldn't want a verb that stressed how the result is calculated since that's an implementation to detail. To me, "Returns" works well then. It stresses that the function is producing the value, but abstracts over how.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
carve? (Found by browsing Webster Dictionary)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
batched
or batches
, perhaps with something to indicate the argument is the size of the batch, not the number of batches.
batchedByLength(3)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think referring to "slices" makes the most sense, since (at least for lists) this is returning views of the sort provided by slice()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that slices
is a an appropriate name. It's a noun phrase, which does mean that it's defined in terms of the values it provides. And that's fair, but the returned value is an iterable, and I'd document that in terms of its elements (with a plural). So:
/// Contiguous [slice]s of [this] with the given [length].
Don't say "Returns", that's a given. Don't say "Iterable of", that's obvious from the return type too. Just describe the elements of that iterable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll still maintain that the documentation should use a noun phrase when the function is named with a noun phrase. Those two should match.
lib/src/iterable_extensions.dart
Outdated
|
||
yield subsequence; | ||
while (iterator.moveNext()) { | ||
subsequence = [...subsequence.skip(1), iterator.current]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider:
subsequence = [for (var i = 1; i < length; i++) subsequence[i], iterator.current];
Avoids allocating an extra Iterable
+Iterator
wrapper and might allow a smart compiler to predict and preallocate the length.
(A very good compiler could recognize "for ... in built-in-list.take(n) ..." and convert it to the above, but I don't think our compilers are very good.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, you could imagine ...value
inserting a runtime check for EfficientLengthIterable
! I think the internals of the compiler are enough of a black box that it mostly makes sense to make the code as readable and semantic as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Holding off on "returns"/"creates" changes until Bob has a chance to chime in.
lib/src/iterable_extensions.dart
Outdated
|
||
yield subsequence; | ||
while (iterator.moveNext()) { | ||
subsequence = [...subsequence.skip(1), iterator.current]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, you could imagine ...value
inserting a runtime check for EfficientLengthIterable
! I think the internals of the compiler are enough of a black box that it mostly makes sense to make the code as readable and semantic as possible.
Based on the doc comment on the unmerged Iterable.slices from dart-archive/collection#191 Drop extra newline in changelog, sort deps, and drop `-nullsafety` from constraints.
Based on the doc comment on the unmerged Iterable.slices from dart-archive/collection#191 Drop extra newline in changelog, sort deps, and drop `-nullsafety` from constraints.
lib/src/list_extensions.dart
Outdated
@@ -252,6 +252,38 @@ extension ListExtensions<E> on List<E> { | |||
} | |||
return true; | |||
} | |||
|
|||
/// Returns an iterable whose elements are contiguous [slice]s of [this]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll still maintain that the documentation should use a noun phrase when the function is named with a noun phrase. Those two should match.
Based on the doc comment on the unmerged Iterable.slices from dart-archive/collection#191 Drop extra newline in changelog, sort deps, and drop `-nullsafety` from constraints.
This also adds more efficient implementations of these for List. Inspired by Ruby's Enumerable.each_slice method.
This also adds more efficient implementations of these for List.
Inspired by Ruby's Enumerable.each_slice and Enumerable.each_cons
methods.