Skip to content

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

Merged
merged 2 commits into from
Feb 12, 2021

Conversation

timvermeulen
Copy link
Contributor

This PR adds implementations of index(_:offsetBy:) and index(_:offsetBy:limitedBy:) to the Product2 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

  • I've added at least one test that validates that my change is working, if appropriate
  • I've followed the code style of the rest of the project
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary

@timvermeulen timvermeulen force-pushed the product-random-access branch from e4989f2 to 143d766 Compare January 20, 2021 16:45
Copy link

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

/// `(i, base2.endIndex)` to `(base1.index(after: i), base2.startIndex)` if
/// necessary.
@usableFromInline
internal func convertIndex(_ i1: Base1.Index, _ i2: Base2.Index) -> Index {

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)

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

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

@kylemacomber kylemacomber merged commit 4f7cd34 into apple:main Feb 12, 2021
@timvermeulen timvermeulen deleted the product-random-access branch March 9, 2021 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants