Skip to content

Commit 59635bf

Browse files
khanloulattner
authored andcommitted
add a proposal for count(where:) (#840)
1 parent 170b79f commit 59635bf

File tree

1 file changed

+70
-0
lines changed

1 file changed

+70
-0
lines changed

proposals/XXXX-count-where.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# `count(where:)`
2+
3+
* Proposal: [SE-NNNN](NNNN-filename.md)
4+
* Authors: [Soroush Khanlou](https://github.com/khanlou)
5+
* Review Manager: TBD
6+
* Status: **Awaiting implementation**
7+
8+
## Introduction
9+
10+
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.
11+
12+
Swift-evolution thread: [`count(where:)` on Sequence](https://forums.swift.org/t/count-where-on-sequence/11186)
13+
14+
## Motivation
15+
16+
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.
17+
18+
[1, 2, 3, -1, -2].filter({ $0 > 0 }).count // => 3
19+
20+
To correctly avoid a potentially expensive intermediate array, you can use the Swift's `lazy` subsystem:
21+
22+
[1, 2, 3, -1, -2].lazy.filter({ $0 > 0 }).count // => 3
23+
24+
However, using `lazy` comes with the downside of being forced to use an `@escaping` block. Lastly, you could rely on an eminently unreadable `reduce`:
25+
26+
[1, 2, 3, -1, -2].reduce(0) { $1 > 0 ? $0 + 1 : $0 }
27+
28+
These three solutions lie on a spectrum between "easy to write, but include performance traps" to "performant, but require Swift arcana to write".
29+
30+
## Proposed solution
31+
32+
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.
33+
34+
[1, 2, 3, -1, -2].count(where: { $0 > 0 }) // => 3
35+
36+
I use it as an extension in my code regularly, and I think it'd make a nice addition to the standard library.
37+
38+
## Detailed design
39+
40+
A reference implementation for the function is included here:
41+
42+
extension Sequence {
43+
func count(where predicate: (Element) throws -> Bool) rethrows -> Int {
44+
var count = 0
45+
for element in self {
46+
if try predicate(element) {
47+
count += 1
48+
}
49+
}
50+
return count
51+
}
52+
}
53+
54+
The recommended implementation can be found [in a pull request to `apple/swift`](https://github.com/apple/swift/pull/16099).
55+
56+
## Source compatibility
57+
58+
This change is additive only.
59+
60+
## Effect on ABI stability
61+
62+
This change is additive only.
63+
64+
## Effect on API resilience
65+
66+
This change is additive only.
67+
68+
## Alternatives considered
69+
70+
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.

0 commit comments

Comments
 (0)