Aidan Gomez
Aidan Gomez

Reputation: 8627

Count elements of array matching condition in Swift

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

Answers (7)

maxwell
maxwell

Reputation: 4156

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

Upvotes: 13

Leo Dabus
Leo Dabus

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

Declan McKenna
Declan McKenna

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

Zyntx
Zyntx

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

JeremyP
JeremyP

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

Abizern
Abizern

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

Aderstedt
Aderstedt

Reputation: 6518

Like this:

 let a: [Int] = ...
 let count = a.filter({ $0 % 2 == 0 }).count

Upvotes: 36

Related Questions