Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit the results of a Swift array filter to X for performance

I have about 2000 elements in my array, and when it is filtered, I would like to end the filtering as soon as I have 5 elements in my filtered array.

Currently it is:

providerArray.filter({($0.lowercased().range(of:((row.value as? String)?.lowercased())!) != nil)})

which can return up to 2000 results which is a waste of processing, and time.

To be more clear, I need a solution that is equivalent to limiting the filter results like I can with coreData fetches [request setFetchLimit:5];

like image 867
NSF Avatar asked Jan 26 '17 10:01

NSF


People also ask

What is array filter() method in Swift?

This shorthand is the Array.filter () method. The Array.filter () method in Swift takes a filtering function as its argument and produces a new array. Here is how it works: It applies the filtering function for each element in the array. If an element passes the check, the element is added to a result array.

How to store the result of a filter in Swift?

Remembering that a filter in Swift returns an Arraythrough the filter(_:) function we need to store the result of the filter. Take the following example: let arr = [1,2,3,4]let evens = arr.filter{$0 % 2 == 0} // [2,4]

What is Arraya filter in Java?

A filteracts on any collection type (that is, an Array, Dictionaryor Set), and returns an Arraytype. A theoretical filter for an Array Let us take an example array[1,2,3,4] and we are going to call it arr.

What are the primary collections in Swift?

Swift offers three primary collections which are quite straightforward: Each collection comes in handy in different situations, but the most used one is probably Array. Swift implements its Array as a modern collection and it's used widely in this language.


2 Answers

The fastest solution in terms of execution time seems to be an explicit loop which adds matching elements until the limit is reached:

extension Sequence {
    public func filter(where isIncluded: (Iterator.Element) -> Bool, limit: Int) -> [Iterator.Element] {
        var result : [Iterator.Element] = []
        result.reserveCapacity(limit)
        var count = 0
        var it = makeIterator()

        // While limit not reached and there are more elements ...
        while count < limit, let element = it.next() {
            if isIncluded(element) {
                result.append(element)
                count += 1
            }
        }
        return result
    }
}

Example usage:

let numbers = Array(0 ..< 2000)
let result = numbers.filter(where: { $0 % 3 == 0 }, limit: 5)
print(result) // [0, 3, 6, 9, 12]
like image 163
Martin R Avatar answered Nov 15 '22 05:11

Martin R


You can use .lazy too boost the performance a bit:

let numbers: [Int] = Array(0 ..< 2000)

let result: AnySequence = numbers
    .lazy
    .filter {
        print("Calling filter for: \($0)")
        return ($0 % 3) == 0
    }
    .prefix(5)

print(Array(result))

This will call the filter function only for the first 15 values (until it finds 5 that pass the filter).

Now you can concentrate on boosting the performance of the filter itself. E.g. by caching values. You don't have to do this but if some values keep repeating, it can boost the performance a lot.

let numbers: [Int] = Array(0 ..< 2000)
var filterCache: [Int: Bool] = [:]

let result: AnySequence = numbers
    .lazy
    .filter {
        if let cachedResult = filterCache[$0] {
            return cachedResult
        }

        print("Calling filter for: \($0)")
        let result = (($0 % 3) == 0)

        filterCache[$0] = result

        return result
    }
    .prefix(5)

print(Array(result))

You can apply this method directly to your function.

Also note that to boost performance, you should either:

  • save ((row.value as? String)?.lowercased())! into a local variable because it is executed multiple times

  • simplify the expression using options:

 let result: AnySequence = providerArray
     .lazy
     .filter {
       $0.range(of: row.value as! String, options: [.caseInsensitive]) != nil
     }
     .prefix(5)
like image 20
Sulthan Avatar answered Nov 15 '22 06:11

Sulthan