Skip to content
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 suffix:while method #65

Merged
merged 8 commits into from
Feb 9, 2021
Merged

add suffix:while method #65

merged 8 commits into from
Feb 9, 2021

Conversation

michiboo
Copy link
Contributor

@michiboo michiboo commented Jan 20, 2021

Thanks for contributing to Swift Algorithms!



- [x] I've completed this task
- [ ] This task isn't completed

Add a suffix(while:) method #62

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

Copy link
Contributor

@mdznr mdznr left a comment

Choose a reason for hiding this comment

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

Excited to see this function added, bringing more parity between the suffix and prefix family of functions!

If changes are still being worked on, I’d recommend making use of a new feature on GitHub: Drafts. By marking this PR as a draft, it helps to convey the author’s intended state of the changes.[1]

I left a few small comments inline, but want to emphasize the primary high-level piece of feedback here, which may make several of the comments inapplicable. I think suffix(while:) should return SubSequence instead of [Element] for consistency and performance reasons.


  1. https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request


extension BidirectionalCollection {
@inlinable
public __consuming func Suffix(while predicate: (Element) throws -> Bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public __consuming func Suffix(while predicate: (Element) throws -> Bool
public __consuming func suffix(
while predicate: (Element) throws -> Bool

extension BidirectionalCollection {
@inlinable
public __consuming func Suffix(while predicate: (Element) throws -> Bool
) rethrows -> [Element] {
Copy link
Contributor

@mdznr mdznr Jan 20, 2021

Choose a reason for hiding this comment

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

Can this return SubSequence instead?

prefix(while:) on Collection returns a SubSequence.[1]


  1. https://github.com/apple/swift/blob/main/stdlib/public/core/Collection.swift#L1328

//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// Suffix(while:)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Suffix(while:)
// suffix(while:)

Comment on lines 16 to 17
extension BidirectionalCollection {
@inlinable
Copy link
Contributor

Choose a reason for hiding this comment

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

Documentation should be added. Mirroring the documentation for prefix(while:) would be a good start.

Comment on lines 28 to 29
result.reverse()
return Array(result)
Copy link
Contributor

@mdznr mdznr Jan 20, 2021

Choose a reason for hiding this comment

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

This double-reverse operation and converting to Array shouldn’t be necessary. The last index of the collection can be retrieved with endIndex, then walked backwards using index(before:).

This would be similar to the implementation of prefix(while:)[1], but a little bit trickier. Since endIndex is beyond the subscript-able range of a collection, the index has to be moved backwards before using it to subscript the collection, and ensuring it doesn’t go before the first element (start index). In SuffixTests.swift, I made some comments about test cases that will be important for ensuring that these cases are accounted for.


  1. https://github.com/apple/swift/blob/main/stdlib/public/core/Collection.swift#L1330

Tests/SwiftAlgorithmsTests/SuffixTests.swift Show resolved Hide resolved
//
// This source file is part of the Swift Algorithms open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Copyright (c) 2021 Apple Inc. and the Swift project authors

//
// This source file is part of the Swift Algorithms open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Copyright (c) 2021 Apple Inc. and the Swift project authors

Comment on lines 20 to 21
var result = ContiguousArray<Element>()
self.reverse()
Copy link
Contributor

Choose a reason for hiding this comment

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

Consistent indentation

Suggested change
var result = ContiguousArray<Element>()
self.reverse()
var result = ContiguousArray<Element>()
self.reverse()

Comment on lines 20 to 21
var result = ContiguousArray<Element>()
self.reverse()
Copy link
Contributor

@mdznr mdznr Jan 20, 2021

Choose a reason for hiding this comment

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

I don’t believe reverse() can be used here (won’t compile), because it is a mutating function that reverses the elements of the collection in-place. This function, suffix(while:), isn’t mutating (and should remain that way), so it can’t use self.reverse() in its implementation.

It seems that you might have meant to use the non-mutating version of that function, reversed(), which returns a reversed collection:

    for element in self.reversed() {

However, this comment likely won’t be applicable when changing the function to return SubSequence instead.

@CTMacUser
Copy link
Contributor

What about something like (neither compiled nor tested):

extension BidirectionalCollection {
  public func suffix(
    while predicate: (Element) throws -> Bool
  ) rethrows -> SubSequence {
    let start = startIndex
    var result = endIndex
    while result > start {
      let previous = index(before: result)
      guard try predicate(self[previous]) else { break }

      result = previous
    }
    return self[result...]
  }
}

?

@michiboo michiboo marked this pull request as draft January 21, 2021 01:27
@mdznr
Copy link
Contributor

mdznr commented Jan 21, 2021

@CTMacUser I think that’s pretty much exactly what I imagined the implementation would look like.

Although, I think we might want to change

while result > start {

to

while result != start {

A lot of similar algorithms in the standard library use != instead of > or < for comparing indexes. I’ll have to do more research as to why that is. My initial guess is perhaps because implementations of custom indexes have slightly faster implementations of != than <. But either way, it would be good to have consistency here.

michiboo and others added 2 commits January 23, 2021 08:38
Co-authored-by: Xiaodi Wu <13952+xwu@users.noreply.github.com>
@kylemacomber
Copy link
Contributor

A lot of similar algorithms in the standard library use != instead of > or < for comparing indexes. I’ll have to do more research as to why that is. My initial guess is perhaps because implementations of custom indexes have slightly faster implementations of != than <. But either way, it would be good to have consistency here.

I agree that we should use != to be consistent with the stdlib.

One of the reasons stdlib uses != is historical. Originally Collection.Index in the stdlib was only Equatable, not Comparable. From SE-0065 which added the Comparable requirement:

It's worth noting that this requirement isn't strictly necessary. Without it, though, indices would have no requirements beyond Equatable, and creation of a Range<T> would have to be allowed for any T conforming to Equatable. As a consequence, most interesting range operations, such as containment checks, would be unavailable unless T were also Comparable, and we'd be unable to provide bounds-checking in the general case.

That said, the requirement has real benefits. For example, it allows us to support distance measurement between arbitrary indices, even in collections without random access traversal. In the old model, x.distance(to: y) for these collections had the undetectable precondition that x precede y, with unpredictable consequences for violation in the general case.

Co-authored-by: Xiaodi Wu <13952+xwu@users.noreply.github.com>
@michiboo michiboo marked this pull request as ready for review January 25, 2021 12:38
Copy link
Member

@natecook1000 natecook1000 left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution, @michiboo! Could you update your tests to compile and pass? It looks like this is otherwise ready to go.

Co-authored-by: Xiaodi Wu <13952+xwu@users.noreply.github.com>
@michiboo michiboo requested a review from natecook1000 February 7, 2021 11:35
@natecook1000
Copy link
Member

This is all set now — thanks for this implementation, @michiboo!

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.

6 participants