1

Building on this question, I am trying to calculate the variance of an array of Int.

My extension looks like this so far:

extension Array where Element: Integer {
    /// Returns the sum of all elements in the array
    var total: Element {
        return reduce(0, combine: +)
    }
    /// Returns the average of all elements in the array
    var average: Double {
        return isEmpty ? 0 : Double(total.hashValue) / Double(count)
    }

    /// Returns an array of the squared deviations from the mean
    var squaredDeviations: [Double] {
        let mean = average
        return isEmpty ? 0 : map( { number in
            let difference = Double(number) - mean
            return pow(distance, 2)
        })
    }
}

Total and average work fine, but for squaredDifferences it appears that you must return the same type as the array when using map. Is there a way to get around this?

Update I was receiving the compiler error:

Result values in '? :" expression have mismatching types 'Int' and '[ _ ]'

The problem was that I was returning 0 which is not an array of Doubles. Also I was not using number.hashValue, and therefor couldn't initialize the double.

Community
  • 1
  • 1
jjatie
  • 5,152
  • 5
  • 34
  • 56
  • `t appears that you must return the same type as the array when using map` Not true, map exists mainly to facilitate just that, conversion between types. – Alexander Jul 06 '16 at 16:27
  • "it appears that you must return the same type as the array when using map" No, there's no such rule. You can return any type from your `map` function. And indeed, you are doing so: an integer comes in and a double comes out. Is this your real code??? What, exactly, is the problem? – matt Jul 06 '16 at 16:27
  • 3
    The problem is that, if the array is empty, you try to return `0`, which is not an array of Double. – Eduardo Jul 06 '16 at 16:31
  • 1
    the answer is there (ref. AMomchilov), I have another note. Be careful and don't mix value and hashValue in the average calculation! there is no guarantee, that Int value is the same as hashValue ... – user3441734 Jul 06 '16 at 16:46
  • Would you recommend I use toIntMax() instead? – jjatie Jul 06 '16 at 16:55

1 Answers1

3

The issue here is that you have two possible values that can be returned:

return isEmpty ? 0 : map( { number in
    let difference = Double(number) - mean
    return pow(distance, 2)
})

Lets break these conditional operator down into if/else:

if isEmpty {
    return 0 //inferred return type: Int
}
else {
    return map( { number in
        let difference = Double(number) - mean
        return pow(distance, 2)
    }) // inferred return type: [Double]
}

The two possible returns have different types.

Variance is the the average of the squared differences from the Mean. You forgot to do the averaging step. Try:

var squaredDeviations: Double {
    let mean = average
    return isEmpty ? 0.0 : self.map{ pow(Double($0) - mean, 2) }.average
}

On a side note: I would recommend against using computed properties to do expensive computations. It's presents a misleading API that doesn't make it clear that it's a slow, linear time, procedure. Here's how I would do this:

extension Array where Element: Integer {
    /// Returns the sum of all elements in the array
    func summed() -> Element {
        return self.reduce(0, combine: +)
    }

    /// Returns the average of all elements in the array
    func averaged() -> Double {
        return Double(self.summed()) / Double(count)
    }

    /// Returns an array of the squared deviations from the mean
    func squaredDeviations() -> [Double] {
        let average = self.averaged()
        return self.map { pow(Double($0) - average, 2) }
    }

    /// Returns the variance of the Array
    func variance() -> Double {
        return self.squaredDeviations().averaged()
    }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Thanks. I was going to break out variance as a separate variable, as I want to be able to access just the squared deviations from the mean as well. – jjatie Jul 06 '16 at 16:38
  • Oh I see. I'll whip that up real quick – Alexander Jul 06 '16 at 16:39
  • 1
    See my edit. I would avoid computed properties for expensive computations – Alexander Jul 06 '16 at 16:43
  • I see. Is this just to be clear that an operation is being performed and may not return instantly? – jjatie Jul 06 '16 at 16:45
  • 1
    Yep. Suppose `averaged()` was a computed property rather than a function. As a consumer to your API, it wouldn't be obvious that it's linear time. Suppose I wrote `map{ pow(Double($0) - self.average, 2)}` rather than using the `let average = self.averaged()` caching variable. Without knowing it, the `squaredDeviations()` function just went from linear to quadratic time complexity. – Alexander Jul 06 '16 at 16:48
  • @AMomchilov I agree that it can be misleading to use computed properties, but on the other hand, `.average` is a lot nicer to write than `.averaged()`, in my opinion. Besides, it's pretty silly to think that computing the average can be done in constant time. – Tim Vermeulen Jul 06 '16 at 17:40
  • @TimVermeulen Actually, average could be implemented to be computed on the fly as elements are added and removed, so that computing it is a matter of just accessing the cached variable. Furthermore, it's a matter of conceptual consistency. In Swift 3, the standard library has been making a large push to move computed properties into functions where appropriate – Alexander Jul 06 '16 at 17:44
  • 1
    @TimVermeulen For example, `String.lowercaseString` has been changed to `Swift.lowercased()` – Alexander Jul 06 '16 at 17:48
  • isn't the variance just the square of the standard-deviation, which is already implemented in swift, and in Cocoa/CocoaTouch ? there are plenty of ways to use the built-in implementation (via NSExpression, or even KVC function accessors, and Swift Algorithms are supporting lots of basic algorithms) – Motti Shneor Feb 19 '19 at 07:58
  • @MottiShneor I don't understand the point you're trying to make. Cocoa, CocoaTouch, NSExpression, and KVC are not native Swift, and they haven't been ported to Linux. – Alexander Feb 19 '19 at 16:09
  • @Alexander indeed I never thought of swift outside of the Cocoa/Cocoa-touch realm. – Motti Shneor Feb 20 '19 at 10:09