Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Add Iterable.slice() and Iterable.subsequence() extensions #191

Merged
merged 9 commits into from
Dec 21, 2021

Conversation

nex3
Copy link
Contributor

@nex3 nex3 commented May 1, 2021

This also adds more efficient implementations of these for List.

Inspired by Ruby's Enumerable.each_slice and Enumerable.each_cons
methods.

This also adds more efficient implementations of these for List.

Inspired by Ruby's Enumerable.each_slice and Enumerable.each_cons
methods.
@nex3 nex3 requested a review from lrhn May 1, 2021 01:01
@google-cla google-cla bot added the cla: yes label May 1, 2021

var iterator = this.iterator;
var subsequence = <T>[];
while (iterator.moveNext()) {
Copy link
Contributor

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?)

Copy link
Contributor Author

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.

/// 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* {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@nex3 nex3 requested a review from lrhn May 4, 2021 22:22
@@ -549,6 +549,54 @@ extension IterableExtension<T> on Iterable<T> {
}
return true;
}

/// Returns an iterable whose elements are contiguous slices of [this].
Copy link
Contributor

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).

}
}

/// Returns an iterable of each subsequence of [this] of the given [length].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns -> Creates

@@ -252,6 +252,38 @@ extension ListExtensions<E> on List<E> {
}
return true;
}

/// Returns an iterable whose elements are contiguous [slice]s of [this].
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

@lrhn lrhn May 6, 2021

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!

Copy link
Contributor

@munificent munificent May 11, 2021

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.

Copy link

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)

Copy link

@rakudrama rakudrama May 11, 2021

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)

Copy link
Contributor Author

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().

Copy link
Contributor

@lrhn lrhn Sep 13, 2021

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.

Copy link
Contributor

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.


yield subsequence;
while (iterator.moveNext()) {
subsequence = [...subsequence.skip(1), iterator.current];
Copy link
Contributor

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.)

Copy link
Contributor Author

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.

Copy link
Contributor Author

@nex3 nex3 left a 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.


yield subsequence;
while (iterator.moveNext()) {
subsequence = [...subsequence.skip(1), iterator.current];
Copy link
Contributor Author

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.

@nex3 nex3 requested a review from lrhn May 10, 2021 20:18
natebosch added a commit to dart-archive/async that referenced this pull request May 20, 2021
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.
natebosch added a commit to dart-archive/async that referenced this pull request May 20, 2021
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.
@nex3 nex3 requested a review from lrhn October 26, 2021 20:45
@@ -252,6 +252,38 @@ extension ListExtensions<E> on List<E> {
}
return true;
}

/// Returns an iterable whose elements are contiguous [slice]s of [this].
Copy link
Contributor

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.

@nex3 nex3 merged commit c8b249a into master Dec 21, 2021
@nex3 nex3 deleted the slice-and-subseq branch December 21, 2021 21:33
mosuem pushed a commit to dart-lang/core that referenced this pull request Oct 14, 2024
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.
mosuem pushed a commit to dart-lang/core that referenced this pull request Oct 18, 2024
This also adds more efficient implementations of these for List.

Inspired by Ruby's Enumerable.each_slice method.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy