-
Notifications
You must be signed in to change notification settings - Fork 448
Index offsetting for Product2 #66
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
Conversation
e4989f2
to
143d766
Compare
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 got part way through this patch, but wasn't able to finish. I'll pick back up reviewing offsetForward(_:by:limitedBy:)
and offsetBackward(_:by:limitedBy:)
tomorrow.
Sources/Algorithms/Product.swift
Outdated
/// `(i, base2.endIndex)` to `(base1.index(after: i), base2.startIndex)` if | ||
/// necessary. | ||
@usableFromInline | ||
internal func convertIndex(_ i1: Base1.Index, _ i2: Base2.Index) -> 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.
is this a "conversion" or is it more of a "normalization" or "canonicalization"?
if distance >= 0 { | ||
return limit >= i | ||
? offsetForward(i, by: distance, limitedBy: limit) | ||
: offsetForward(i, by: distance) |
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 found these ternaries confusing until I went and read the documentation for index(_:offsetBy:limitedBy:)
, in particular:
/// - limit: A valid index of the collection to use as a limit. If
/// `distance > 0`, a limit that is less than `i` has no effect.
/// Likewise, if `distance < 0`, a limit that is greater than `i` has no
/// effect.
I don't know if we should add a comment inline to this effect, or if it's reasonable to assume that if you're reading an implementation of this method you should be familiar with the expected behavior.
let base2Limit = limit.i1 == i1 ? limit.i2 : base2.endIndex | ||
return base2.index(base2.startIndex, offsetBy: base2Distance, limitedBy: base2Limit) | ||
.map { i2 in Index(i1: i1, i2: i2) } | ||
} |
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 my main question with this patch is how much do these special cases in offsetForward
and offsetBackward
buy us? It's a lot to hold in your head.
Can we get away with just the main "loop"?
internal func offsetForward(
_ i: Index, by distance: Int, limitedBy limit: Index
) -> Index? {
assert(distance >= 0)
assert(limit >= i)
let suffixCount = base2[i.i2...].count
let remaining = distance - suffixCount
let nextI1 = base1.index(after: i.i1)
let prefixCount = base2[..<i.i2].count
let base2Count = prefixCount + suffixCount
let base1Distance = remaining / base2Count
guard let i1 = base1.index(nextI1, offsetBy: base1Distance, limitedBy: limit.i1)
else { return nil }
// The distance from `base2.startIndex` to the target.
let base2Distance = remaining % base2Count
let base2Limit = limit.i1 == i1 ? limit.i2 : base2.endIndex
return base2.index(base2.startIndex, offsetBy: base2Distance, limitedBy: base2Limit)
.map { i2 in Index(i1: i1, i2: i2) }
}
This PR adds implementations of
index(_:offsetBy:)
andindex(_:offsetBy:limitedBy:)
to theProduct2
collection. There are ways to further reduce the amount of indices traversed, but this should perform well enough.LIke
Chain2
,Product2
's index offsetting behavior will now unfortunately perform slightly worse for non-random-access collections in specific scenarios, but there's not much we can do about that.Checklist