Umer Hassam
Umer Hassam

Reputation: 1330

Swift Generic Variables fatal error: unexpectedly found nil while unwrapping an Optional value

I'm trying to implement a simple generic method:

func findMax<T: Comparable>(numbers_array : [T]) -> ( min_tuple : T , max_tuple : T )
{
    var max_number : T?
    var min_number : T?

    for i in numbers_array
    {
        if(max_number < i)
        {
            max_number = i
        }
        if(min_number > i)
        {
            min_number = i
        }
    }
    return (min_number! , max_number!)
}

I am trying to access the method like this:

let result = findMax([3.4, 5, -7, -7.8])

But whenever I run this code I get the following error:

fatal error: unexpectedly found nil while unwrapping an Optional value

I think thats because I haven't assigned a value to var max_number : T? and var min_number : T?

I can't assign a value to them because they are of generic type and I think the garbage value is messing with the logic of this function... I might be wrong but that is what I have been able to assess from my debugging session.

Thanks in advance, Any help is greatly appreciated.

Upvotes: 3

Views: 360

Answers (2)

Antonio
Antonio

Reputation: 72820

Given that all considerations made by @AirspeedVelocity are correct, and his solution works great, if you are a fan of more compact but a little more cryptic code you can replace his if branch with the following code:

return dropFirst(numbers_array).reduce((numbers_array[0], numbers_array[0])) {
    ($1 < $0.0 ? $1 : $0.0, $1 > $0.1 ? $1 : $0.1)
}

which uses the reduce method to iteratively transform the array into a tuple of 2 elements.

The dropFirst discards the first element of the array, and the reduce method is applied to the resulting array.

The reduce method takes 2 parameters:

  • the initial value, which in this case is a bi-dimensional tuple with both elements set to the array's first element
  • a closure, applied to each element of the array, which returns a new tuple, determined by verifying whether the array element is less than/greater than respective tuple elements.

Upvotes: 3

Airspeed Velocity
Airspeed Velocity

Reputation: 40973

Swift is different to some other C-based languages in that it will not let you use an uninitialized variable that might contain garbage. If you declare but don’t initialize a variable, then use it before it’s guaranteed to be initialized (based on the possible code paths), you get a compiler error.

But var optionals are implicitly initialized to nil. So the problem here is your forced unwraps at the end (e.g. min_number!). There are code paths that result in the nils not getting reassigned to a real value, and then when you unwrap the nil with ! you get an error.

The reason for this is this line here:

if(min_number > i)

There is a version of > that takes optionals. If the two values are both non-nil, then it compares them. But if one is nil, it is always less than a non-nil version. So in your loop, you start with min_number as nil, so your if statement will never evaluate to true.

Here’s an alternative way of writing it that accounts for the possibility of an empty array:

func findMax<T: Comparable>(numbers_array : [T]) -> ( min_tuple : T , max_tuple : T )
{
    if let first_number = numbers_array.first {
        var min_number = first_number, max_number = first_number
        for i in dropFirst(numbers_array) {
            max_number = max(max_number, i)
            min_number = min(min_number, i)
        }
        return (min_number,max_number)
    }
    else {
        // the user has passed in an empty array... 
        // so there is no minimum, you must take some 
        // action like:
        fatalError("empty array passed")
    }
}

Alternatively, instead of fatalError you could have your function return an optional, with nil if the sequence was empty. In Swift 2.0 the minElement and maxElement methods switched from doing the former to doing the latter.

Upvotes: 4

Related Questions