15

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.

7 Answers 7

36

Like this:

 let a: [Int] = ...
 let count = a.filter({ $0 % 2 == 0 }).count
Sign up to request clarification or add additional context in comments.

6 Comments

Perfect, that's exactly what I was looking for. Will accept in ~9mins.
You can eliminate the parens around the closure to make it a trailing closure.
Also, this will have an O(n) space allocation, is there a one liner that maintains O(1) space?
@AidanGomez The author of this answer should put a lazy before filter which avoids O(n) space allocation
@AidanGomez if the answer is working, please accept the answer :)
|
13

Default array:

let array: [Int] = [10, 10, 2, 10, 1, 2, 3]

Swift 6

count(where:) method

let countOfTen = array.count(where: { $0 == 10 }) // 3

All Swift versions

filter(_:) method

let countOfTen = array.filter({ $0 == 10 }).count // 3

4 Comments

Is count(where:) a real thing? It's not working for me in Swift 5, and the link you provided is a 404 (page not found).
Okay, looks like count(where:) was removed from Swift 5 at the last minute. Hopefully it will reappear in a future version.
This Swift 5.0 feature was withdrawn in beta testing because it was causing performance issues for the type checker. Hopefully it will come back in time for Swift 5.1, perhaps with a new name to avoid problems. hackingwithswift.com/articles/126/whats-new-in-swift-5-0
11

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

3 Comments

@MartinR I originally wrote that line as return count + element % 2 == 0 ? 1 : 0 as it happens, which is the same but avoids casting booleans to ints.
I have deleted my previous comment because I noticed that Int(element % 2 == 0) actually creates an NSNumber object from the boolean. – What I wanted to say is that your solution wouldn't work with more general predicates.
This turned out to be the fastest solution in my simple test :)
6

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

Comments

3

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 .

Comments

2

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.

2 Comments

If the last line was return accumulator + 1 you wouldn't need to make it var
@JeremyP I'll need to read up on reduce but these seem like another nice option.
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.