-
Notifications
You must be signed in to change notification settings - Fork 440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Stride for Collection #24
Conversation
6819ce7
to
18bd60d
Compare
Sources/Algorithms/Stride.swift
Outdated
/// - Parameter step: The amount to step with each iteration. | ||
/// - Returns: Returns a collection stepping through the elements by the | ||
/// specified amount. | ||
public func stride(by step: Int) -> Stride<Self> { |
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 wonder if this would best be striding
to fit the ed/ing rule for naming nonmutating members. That said, I also worry about ambiguity with the existing StrideTo
/StrideThrough
types: are there other terms that are often used for this operation?
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 am in agreement that striding
is probably a better fit for this name for the reasons you've mentioned. I'm a little unsure that renaming the type to something else would be a good idea as the notion of a stride is widely known and something which is obvious as soon as you see it. Other terms used for striding include increment
, pitch
or step
- whilst they offer a similar meaning, they do not have the same understanding as striding
. In one of my earlier implementations I had named the type StridingCollection
which I would not be against being explicit about, I removed the suffix Collection
to keep in step with the rest of the algorithms in this repository. It would be good to see what other feedback I get regarding the naming of this before I change it. Thanks for your thoughts @xwu
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.
This is looking good, @ollieatkinson — thanks for this addition!
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.
@ollieatkinson A few more notes for you!
Sources/Algorithms/Stride.swift
Outdated
|
||
public struct Stride<Base: Collection> { | ||
|
||
public let base: Base |
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.
To help simplify the issue, do we need to expose base
at all?
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 was also unsure of the value of being able to refer to base
in this instance, but since all of the other collection algorithms do I thought it would be worth keeping consistent. Was there a specific issue you think that it might simplify?
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.
Sorry, the specific issue is the question of equality, which @natecook1000 mentioned above.
Two values are equal when they behave the same way for all salient operations. For this purpose, we say that iteration order isn’t salient for sets but is salient for arrays, that the sign of zero isn’t salient for floating-point numbers, etc.
It’s a judgment call about what is salient here, and reasonable people can disagree whether differing base
elements would matter; by contrast, if base
isn’t recoverable, then there is no ambiguity at all.
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 like what you're saying here and I'd be up for dropping the access modifier from the property - I cannot fathom a situation where you would want to use it outside of the scope of the collection itself.
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.
Agreed on this — base
can be internal, which removes any controversy here. (That should probably be the case for the rest of the collection wrappers as well.)
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.
Thanks both for your thoughts, I've removed public from base here and we can follow up in another P/R for the rest of the collections
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.
by contrast, if
base
isn’t recoverable, then there is no ambiguity at all.
I don't see how this matters in case the user knows what base
is, even if Stride
doesn't expose it. Couldn't there still be controversy about whether (0...10).striding(by: 4)
should equal (0...11).striding(by: 4)
?
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 just these last couple of notes, then this will be ready 👍
Sources/Algorithms/Stride.swift
Outdated
} | ||
|
||
public func index(_ i: Index, offsetBy distance: Int) -> Index { | ||
precondition(i.base < base.endIndex, "Advancing past end index") |
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.
This isn't a valid precondition, since c.index(c.endIndex, offsetBy: -1)
is valid for bidirectional collections.
afbb684
to
74a25e8
Compare
…lities for validateIndexTraversals
74a25e8
to
6df6133
Compare
I've rebased against the latest |
|
That makes sense, I've added a commit to address this behaviour here |
f8461f5
to
3669ff0
Compare
@natecook1000 Is there any more work required on this for it to be merged? |
limitedBy limit: Index | ||
) -> Index? { | ||
let distance = i == endIndex | ||
? -((base.count - 1) % stride + 1) + (n - 1) * -stride |
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.
FYI, Xcode 12.2 fails to compile this line due to an "expression too complex" error. Making the 1
values Int(1)
seems to resolve the issue.
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.
Thanks for raising this - I'm surprised that it's too complex given these are all integers, I could also annotate the distance
property : Int
to have the same effect.
I have just tested this on Big Sur 11.0.1 and Xcode 12.2 and I was able to compile fine - do you have anything else in your environment which might cause this, so I can track it down?
Thanks, @ollieatkinson — this is ready to go! |
Many thanks to @timvermeulen for continued help!
Stride
A type that steps over a collection’s elements by the specified amount.
This is available through the
striding(by:)
method on anyCollection
.If the stride is larger than the collection count, the resulting wrapper only contains the
first element.
The stride amount must be a positive value.
Detailed Design
The
striding(by:)
method is declared as aCollection
extension, and returns aStride
type:A custom
Index
type is defined so that it's not possible to get confused when tryingto access an index of the stride collection.
A careful thought was given to the composition of these strides by giving a custom
implementation to
index(_:offsetBy:limitedBy)
which multiplies the offset by thestride amount.
The following two lines of code are equivalent, including performance:
Complexity
The call to
striding(by: k)
is always O(1) and access to the next value in the strideis O(1) if the collection conforms to
RandomAccessCollection
, otherwise O(k).Comparison with other languages
rust has
Strided
available in a crate.c++ has std::slice::stride
The semantics of
striding
described in this documentation are equivalent.Checklist