38

I want my function to calculate the average of my Double type array. The array is called "votes". For now, I have 10 numbers.

When I call the average function to get the average of the array votes, it doesn't work.

Here's my code:

var votes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

func average(nums: Double...) -> Double {
    var total = 0.0
    for vote in votes {
        total += vote
    }
    let votesTotal = Double(votes.count)
    var average = total/votesTotal
    return average
}

average[votes]

How do I call the average here to get the average?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Lukesivi
  • 2,206
  • 4
  • 25
  • 43
  • average(votes). Declare it as average(nums: [Double]) and make sure that the votes array is [Double]. Right now it is an [Int] – MirekE Feb 02 '15 at 22:42

6 Answers6

134

You should use the reduce method to sum your sequence elements as follow:

Xcode Xcode 10.2+ • Swift 5 or later

extension Sequence where Element: AdditiveArithmetic {
    /// Returns the total sum of all elements in the sequence
    func sum() -> Element { reduce(.zero, +) }
}

extension Collection where Element: BinaryInteger {
    /// Returns the average of all elements in the array
    func average() -> Element { isEmpty ? .zero : sum() / Element(count) }
    /// Returns the average of all elements in the array as Floating Point type
    func average<T: FloatingPoint>() -> T { isEmpty ? .zero : T(sum()) / T(count) }
}

extension Collection where Element: BinaryFloatingPoint {
    /// Returns the average of all elements in the array
    func average() -> Element { isEmpty ? .zero : sum() / Element(count) }
}

let votes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let votesTotal = votes.sum()                       // 55
let votesAverage = votes.average()                 // 5
let votesDoubleAverage: Double = votes.average()   // 5.5

If you need to work with Decimal types its total sum it is already covered by the AdditiveArithmetic protocol extension method, so you only need to implement the average:

extension Collection where Element == Decimal {
    func average() -> Decimal { isEmpty ? .zero : sum() / Decimal(count) }
}


If you need to sum a certain property of a custom structure we can extend Sequence and create a method that takes a KeyPath as argument to calculate its sum:

extension Sequence  {
    func sum<T: AdditiveArithmetic>(_ predicate: (Element) -> T) -> T {
        reduce(.zero) { $0 + predicate($1) }
    }
}

Usage:

struct User {
    let name: String
    let age: Int
}

let users: [User] = [
    .init(name: "Steve", age: 45),
    .init(name: "Tim", age: 50)]

let ageSum = users.sum(\.age) // 95

And extend collection to calculate its average:

extension Collection {
    func average<T: BinaryInteger>(_ predicate: (Element) -> T) -> T {
        sum(predicate) / T(count)
    }
    func average<T: BinaryInteger, F: BinaryFloatingPoint>(_ predicate: (Element) -> T) -> F {
        F(sum(predicate)) / F(count)
    }
    func average<T: BinaryFloatingPoint>(_ predicate: (Element) -> T) -> T {
        sum(predicate) / T(count)
    }
    func average(_ predicate: (Element) -> Decimal) -> Decimal {
        sum(predicate) / Decimal(count)
    }
}

Usage:

let ageAvg = users.average(\.age)                 // 47
let ageAvgDouble: Double = users.average(\.age)   // 47.5
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    In Swift 2 I get Value of type '[Double]' has no member 'IntegerLiteralType' – Burf2000 Oct 14 '15 at 10:23
  • can this be done for arrays of `Decimal`? the code `extension Array where Element: Decimals` throws an error – highboi Apr 12 '18 at 18:12
  • Properties are expected to work in O(1), or an appropriate comment should note otherwise. I would use methods rather than computed properties in the cases of `total` and `average`. See the first rule in General Conventions here: https://swift.org/documentation/api-design-guidelines/ – Yoav Jan 31 '20 at 09:23
  • 1
    @Yoav You have a point, and 'reduce' does indeed operate at O(n) complexity, but I think most devs would expect such common operations to be exposed by properties. Having a mismatch between methods and properties would be a worse sin. And please remember, they are conventions, not rules. – Womble Mar 05 '20 at 02:55
8

You have some mistakes in your code:

//You have to set the array-type to Double. Because otherwise Swift thinks that you need an Int-array
var votes:[Double] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

func average(nums: [Double]) -> Double {

    var total = 0.0
    //use the parameter-array instead of the global variable votes
    for vote in nums{
        total += Double(vote)
    }

    let votesTotal = Double(nums.count)
    var average = total/votesTotal
    return average
}

var theAverage = average(votes)
Christian
  • 22,585
  • 9
  • 80
  • 106
4

A small one liner, using old fashioned Objective-C KVC translated in Swift:

let average = (votes as NSArray).value(forKeyPath: "@avg.floatValue")

You can also have the sum:

let sum = (votes as NSArray).value(forKeyPath: "@sum.floatValue")

More on this long forgotten gem : https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/CollectionOperators.html

Zaphod
  • 6,758
  • 3
  • 40
  • 60
2

Simple avarage with filter if needed (Swift 4.2):

let items: [Double] = [0,10,15]
func average(nums: [Double]) -> Double {
    let sum = nums.reduce((total: 0, elements: 0)) { (sum, item) -> (total: Double, elements: Double) in
        var result = sum
        if item > 0 { // example for filter
            result.total += item
            result.elements += 1
        }

        return result
    }

    return sum.elements > 0 ? sum.total / sum.elements : 0
}
let theAvarage = average(nums: items)
feca
  • 1,119
  • 16
  • 14
2

I have a set of signals that are created within the update function, to obtain the moving average I use this function that calculates the average within a window defined by the period of the moving average. Since my goal is to assemble a new set of signals containing the average, I will discard the signals from the original set. This is a good solution for those who want to have the moving average within an update function, in an SKScene for example.

func movingAvarage(_ period: Int) -> Double? {
  if signalSet.count >= period {
   let window = signalSet.suffix(period)
   let mean = (window.reduce(0, +)) / Double(period)
   signalSet = signalSet.dropLast(period)
   return mean
  }
  return nil
}
Maykon Meneghel
  • 335
  • 6
  • 8
1

Swift 4.2

For sheer elegant simplicity, I love:

// 1. Calls #3
func average <T> (_ values: T...) -> T where T: FloatingPoint
{
    return sum(values) / T(values.count)
}

While we're at it, other nice reduce-based operations:

// 2. Unnecessary, but I appreciate variadic params. Also calls #3.
func sum <T> (_ values: T...) -> T where T: FloatingPoint
{
    return sum(values)
}

// 3.
func sum <T> (_ values: [T]) -> T where T: FloatingPoint
{
    return values.reduce(0, +)
}

Credit: Adrian Houdart's MathKit, largely unchanged.


Cute Update:

I found the following in The Swift Programming Language:

The example below calculates the arithmetic mean (also known as the average) for a list of numbers of any length:

func arithmeticMean(_ numbers: Double...) -> Double {
   var total: Double = 0
   for number in numbers {
       total += number
   }
   return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
AmitaiB
  • 1,656
  • 1
  • 19
  • 19