Search code examples
swiftclosuresfunc

How do I find min and max elements in an array of Int using closures?


I have a task to make a func with an array and a closure that accepts Int and Int? and returns Bool. Then, I have to find min and max elements of the array using the closure.

Here's what I've done:

func task(array: [Int], closure: (Int, Int?) -> Bool) -> Int? {
    var a: Int?  // it's a part of the task - to make an optional variable
    
    for i in array {
        if closure(i, a) {
            a = i
        }
    }
    return a
}


var numbers = [1, 2, 3, 46, 6, 2, 5, 7]

But I don't understand how to find min and max using it, what should I put in the closure?

I saw someone did this:

let max = task(array: numbers) {
    $1 == nil || $1! > $0
}



let min = task(array: numbers) {
    $1 == nil || $1! < $0
}

But I still can't get why did they compare $0 to $1! and why do we need two elements to find one min/max? Can you please explain the logic?


Solution

  • The task is deliberately a bit strange to teach you about closures, I think. Before fully explaining what is happening, look at this version of the code with different variable names. Try and understand what is happening before reading further.

    func task(numbers: [Int], compare: (Int, Int?) -> Bool) -> Int? {
        var resultSoFar: Int?  // this is initially nil
        
        for number in numbers {
            if compare(number, resultSoFar) {
                resultSoFar = number
            }
        }
        return resultSoFar
    }
    
    let numbers = [1, 2, 3, 46, 6, 2, 5, 7]
    
    let max = task(numbers: numbers) { number, resultSoFar in
        resultSoFar == nil || number > resultSoFar!
    }
    
    let min = task(numbers: numbers) { number, resultSoFar in
        resultSoFar == nil || number < resultSoFar!
    }
    
    print("min: \(min), max: \(max)")
    // "min: Optional(1), max: Optional(46)"
    

    The task function iterates over each of the numbers and uses the closure to see if each number is a “better” solution than whatever number it had previously found. Since there is no number to start with, resultSoFar is initially nil. That is why the closure needs to be passed an integer and an optional integer. The first is the candidate number from the array, the second is the best result so far. The min and max functions need to return true if the candidate number is smaller or larger than the number so far so that the final result will be the smallest/largest of all the numbers. If the result so far is nil then this is the first candidate number and must be the smallest/largest so far.

    Your classmate got their min/max the wrong way round because they are comparing the result so far and the candidate number the wrong way round.

    Using force unwrapping (!) is best avoided so a slightly better way of using task would look like this:

    
    let max = task(numbers: numbers) { number, resultSoFar in
        guard let resultSoFar else { return true }
        return number > resultSoFar
    }
    
    let min = task(numbers: numbers) { number, resultSoFar in
        guard let resultSoFar else { return true }
        return number < resultSoFar
    }
    
    if let min = min, let max = max {
        print("min: \(min), max: \(max)")
    } else {
        print("there is no minimum or maximum number in an empty array")
    }
    

    To “see” the way the task works some debug prints can be added. This should make it really clear how resultSoFar is used:

    func task(numbers: [Int], compare: (Int, Int?) -> Bool) -> Int? {
        var resultSoFar: Int?  // this is initially nil
        
        for number in numbers {
            print("Before checking \(number), resultSoFar is \(resultSoFar)")
            if compare(number, resultSoFar) {
                print("Setting resultSoFar to \(number)")
                resultSoFar = number
            }
        }
    
        print("Returning \(resultSoFar)")
        return resultSoFar
    }
    

    When calculating the maximum the debug prints look like this:

    Before checking 1, resultSoFar is nil
    Setting resultSoFar to 1
    Before checking 2, resultSoFar is Optional(1)
    Setting resultSoFar to 2
    Before checking 3, resultSoFar is Optional(2)
    Setting resultSoFar to 3
    Before checking 46, resultSoFar is Optional(3)
    Setting resultSoFar to 46
    Before checking 6, resultSoFar is Optional(46)
    Before checking 2, resultSoFar is Optional(46)
    Before checking 5, resultSoFar is Optional(46)
    Before checking 7, resultSoFar is Optional(46)
    Returning Optional(46)