-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Add a proposal for count(where:) #840
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
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# `count(where:)` | ||
|
||
* Proposal: [SE-NNNN](NNNN-filename.md) | ||
* Authors: [Soroush Khanlou](https://github.com/khanlou) | ||
* Review Manager: TBD | ||
* Status: **Awaiting implementation** | ||
|
||
## Introduction | ||
|
||
While Swift's `Sequence` models brings a lot of niceties that we didn't have access to in Objective-C, like `map` and `filter`, there are other useful operations on sequences that the standard library doesn't support yet. One current missing operation is `count(where:)`, which counts the number of elements in a `Sequence` that pass some test. | ||
|
||
Swift-evolution thread: [`count(where:)` on Sequence](https://forums.swift.org/t/count-where-on-sequence/11186) | ||
|
||
## Motivation | ||
|
||
Counting the number of objects that pass a test has a wide range of uses in many domains. However, Swift currently doesn't give its users a simple way to perform this operation. While the behavior can currently be approximated with a `filter` and a `count`, this approach creates an intermediate array which it immediately discards. This is a bit wasteful. | ||
|
||
[1, 2, 3, -1, -2].filter({ $0 > 0 }).count // => 3 | ||
|
||
To correctly avoid a potentially expensive intermediate array, you can use the Swift's `lazy` subsystem: | ||
|
||
[1, 2, 3, -1, -2].lazy.filter({ $0 > 0 }).count // => 3 | ||
|
||
However, using `lazy` comes with the downside of being forced to use an `@escaping` block. Lastly, you could rely on an eminently unreadable `reduce`: | ||
|
||
[1, 2, 3, -1, -2].reduce(0) { $1 > 0 ? $0 + 1 : $0 } | ||
|
||
These three solutions lie on a spectrum between "easy to write, but include performance traps" to "performant, but require Swift arcana to write". | ||
|
||
## Proposed solution | ||
|
||
The proposed solution would avoid a performance trap and provide a simple interface for users to both read and write. Autocomplete should present it to them handily as well. | ||
|
||
[1, 2, 3, -1, -2].count(where: { $0 > 0 }) // => 3 | ||
|
||
I use it as an extension in my code regularly, and I think it'd make a nice addition to the standard library. | ||
|
||
## Detailed design | ||
|
||
A reference implementation for the function is included here: | ||
|
||
extension Sequence { | ||
func count(where predicate: (Element) throws -> Bool) rethrows -> Int { | ||
var count = 0 | ||
for element in self { | ||
if try predicate(element) { | ||
count += 1 | ||
} | ||
} | ||
return count | ||
} | ||
} | ||
|
||
The recommended implementation can be found [in a pull request to `apple/swift`](https://github.com/apple/swift/pull/16099). | ||
|
||
## Source compatibility | ||
|
||
This change is additive only. | ||
|
||
## Effect on ABI stability | ||
|
||
This change is additive only. | ||
|
||
## Effect on API resilience | ||
|
||
This change is additive only. | ||
|
||
## Alternatives considered | ||
|
||
One alternative worth discussing is the addition of `count(of:)`, which can be implemented on sequences where `Element: Equatable`. This function returns the count of all objects that are equal to the parameter. I'm open to amending this proposal to include this function, but in practice I've never used or needed this function, so I've omitted it here. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
You can, instead of adding the four spaces, wrap these in backticks (```) and add syntax highlighting (```swift on the first line)