Reputation: 8627
I'm basically looking for the swift equivalent of the follow c++ code:
std::count_if(list.begin(), list.end(), [](int a){ return a % 2 == 0; }); // counts instances of even numbers in list
My problem isn't actually searching for even numbers, of course; simply the general case of counting instances matching a criterion.
I haven't seen a builtin, but would love to hear that I simply missed it.
Upvotes: 15
Views: 10339
Reputation: 4156
Default array:
let array: [Int] = [10, 10, 2, 10, 1, 2, 3]
count(where:) method
let countOfTen = array.count(where: { $0 == 10 }) // 3
filter(_:) method
let countOfTen = array.filter({ $0 == 10 }).count // 3
Upvotes: 13
Reputation: 236340
Swift 5 or later:
public extension Sequence {
func occurrences(where predicate: (Element) throws -> Bool) rethrows -> Int {
try reduce(0) { try predicate($1) ? $0 + 1 : $0 }
}
}
public extension Sequence where Element: Equatable {
func occurrences(of element: Element) -> Int {
reduce(0) { element == $1 ? $0 + 1 : $0 }
}
}
let multiplesOf2 = [1,2,3,4,4,5,4,5].occurrences{$0.isMultiple(of: 2)} // 4
"abcdeabca".occurrences(of: "a") // 3
extension BinaryInteger {
var isOdd: Bool { !isMultiple(of: 2) }
var isEven: Bool { isMultiple(of: 2) }
}
(-4).isOdd // false
(-3).isOdd // true
(-2).isOdd // false
(-1).isOdd // true
0.isOdd // false
1.isOdd // true
2.isOdd // false
3.isOdd // true
4.isOdd // false
(-4).isEven // true
(-3).isEven // false
(-2).isEven // true
(-1).isEven // false
0.isEven // true
1.isEven // false
2.isEven // true
3.isEven // false
4.isEven // true
let odds = [1,2,3,4,4,5,5,11].occurrences(where: \.isOdd) // 5
let evens = [1,2,3,4,4,5,5,11].occurrences(where: \.isEven) // 3
Upvotes: 0
Reputation: 4870
You can use Collection.lazy to have the simplicity of Aderstedt's Answer but with O(1) space.
let array = [1, 2, 3]
let count = array.lazy.filter({ $0 % 2 == 0 }).count
Upvotes: 6
Reputation: 659
The most compact reduce statement that will do this is:
let a = Array(1 ... 20)
let evencount = a.reduce(0) { $0 + ($1 % 2 == 0 ? 1 : 0) }
Reduce takes two variables: starts with 0 (var $0) then for every element in Array a (var $1) if the value is divisible by 2 with no remainder then add one to your count.
This is also efficient as it does not create an additional array unlike using a.filter(){}.count .
Upvotes: 3
Reputation: 86651
An alternative to Aderstedt's version
let a = [ .... ]
let count = a.reduce(0){
(count, element) in
return count + 1 - element % 2
}
My intuition says my way will be faster because it doesn't require the creation of a second array. However, you'd need to profile both methods to be sure.
Edit
Following MartinR's comment about generalisation of the function, here it is
extension SequenceType
{
func countMatchingCondition(condition: (Self.Generator.Element) -> Bool) -> Int
{
return self.reduce(0, combine: { (count, e) in count + (condition(e) ? 1 : 0) })
}
}
let a = [1, 2, 3, 3, 4, 12].countMatchingCondition { $0 % 2 == 0 }
print("\(a)") // Prints 3
Upvotes: 11
Reputation: 150605
You can also do this with reduce()
let a = Array(1 ... 20)
let evenCount = a.reduce(0) { (accumulator, value) -> Int in
guard value % 2 == 0 else { return accumulator }
return accumulator + 1
}
Almost everything you want to do with the map()
and filter
functions can actually be done with a reduce
although it's not always the most readable.
Upvotes: 2
Reputation: 6518
Like this:
let a: [Int] = ...
let count = a.filter({ $0 % 2 == 0 }).count
Upvotes: 36