2016-07-17 13 views
4

Мне часто приходится вычислять среднее и стандартное отклонение для числовых массивов. Поэтому я написал небольшой протокол и расширения для числовых типов, которые, похоже, работают. Я просто хотел бы получить обратную связь, если что-то не так с тем, как я это сделал. В частности, мне интересно, есть ли лучший способ проверить, может ли тип быть отличным как Double, чтобы избежать необходимости переменной asDouble и конструктором init(_:Double).Расширение Swift Array для стандартного отклонения

Я знаю, что есть проблемы с протоколами, которые допускают арифметику, но это, похоже, работает нормально и избавляет меня от установки стандартной функции отклонения в классы, которые в ней нуждаются.

protocol Numeric { 
    var asDouble: Double { get } 
    init(_: Double) 
} 

extension Int: Numeric {var asDouble: Double { get {return Double(self)}}} 
extension Float: Numeric {var asDouble: Double { get {return Double(self)}}} 
extension Double: Numeric {var asDouble: Double { get {return Double(self)}}} 
extension CGFloat: Numeric {var asDouble: Double { get {return Double(self)}}} 

extension Array where Element: Numeric { 

    var mean : Element { get { return Element(self.reduce(0, combine: {$0.asDouble + $1.asDouble})/Double(self.count))}} 

    var sd : Element { get { 
     let mu = self.reduce(0, combine: {$0.asDouble + $1.asDouble})/Double(self.count) 
     let variances = self.map{pow(($0.asDouble - mu), 2)} 
     return Element(sqrt(variances.mean)) 
    }} 
} 

редактировать: Я знаю, что это своего рода бессмысленно получить [Int].mean и sd, но я мог бы использовать числовое в другом месте, так что это для последовательности ..

редактировать: в @Severin Pappadeux отметил, дисперсия может выражаться таким образом, чтобы избежать тройного прохода по массиву - значит, тогда значение карты означает среднее значение. Вот окончательное расширение стандартного отклонения

extension Array where Element: Numeric { 

    var sd : Element { get { 
     let sss = self.reduce((0.0, 0.0)){ return ($0.0 + $1.asDouble, $0.1 + ($1.asDouble * $1.asDouble))} 
     let n = Double(self.count) 
     return Element(sqrt(sss.1/n - (sss.0/n * sss.0/n))) 
    }} 
} 
+2

'Int', как правило, такой же размер, как' Int64' на новых устройствах ('> =' iPhone 5S, которая представила процессор 64-битный), поэтому, если вы не работаете с действительно большими номерами, это не должно быть проблемой: но просто знайте, что 'init (_: Double)' может привести к переполнению целых чисел (исключение во время выполнения) в тех случаях, когда 'Element = Int' не может хранить целочисленное представление заданного (огромного) значения 'Double'. Возможно, это не проблема, если вы просто используете свои приложения Swift самостоятельно, но если вы отправляете их клиентам, это может быть полезно иметь в виду. – dfri

+0

Хорошо, спасибо. Вряд ли я буду использовать его с целыми числами, а значения, с которыми я работаю, физиологически ограничены до <500 с этим приложением. Так должно быть хорошо. –

+0

@dfri очень полезный комментарий! Я полагаю, что нет возможности «поймать» этот переток? – matt

ответ

1

В Swift 3 вы можете (или не можешь) быть в состоянии сохранить себя некоторые дублирования протокола с плавающей точкой, но в противном случае, что вы делаете, это точно.

1

Не то, что я знаю Свифт, но от Числового POV вы делаете это немного нерационально

В принципе, вы делаете два проход (на самом деле, три) по массиву, чтобы вычислить два значения, где один проход должно быть достаточно. Vairance может быть выражено как E (X) - Е (Х) , так что в какой-то псевдо-код:

tuple<float,float> get_mean_sd(data) { 
    float s = 0.0f; 
    float s2 = 0.0f; 
    for(float v: data) { 
     s += v; 
     s2 += v*v; 
    } 
    s /= count; 
    s2 /= count; 

    s2 -= s*s; 
    return tuple(s, sqrt(s2 > 0.0 ? s2 : 0.0)); 
} 
+0

Вы правы. Спасибо, это исключает тройной проход. –

+0

@twiz_ приветствую вас, хотя мне интересно, может ли это быть выражено через 'reduce()' –

+0

Получил его: 'let s = self.reduce ((0.0, 0.0)) {return ($ 0.0 + $ 1.asDouble , $ 0.1 + ($ 1.asDouble * $ 1.asDouble))} 'then s.1/n - s.0/n * s.0/n. Извините за ужасное форматирование. Новое в этом. –

1

Там на самом деле класс, который обеспечивает эту функциональность уже - называется NSExpression. Вы можете уменьшить размер и сложность кода, используя это вместо этого. Для этого класса довольно много вещей, но простая реализация того, что вы хотите, заключается в следующем.

let expression = NSExpression(forFunction: "stddev:", arguments: [NSExpression(forConstantValue: [1,2,3,4,5])]) 
let standardDeviation = expression.expressionValueWithObject(nil, context: nil) 

Вы также можете вычислить среднее значение и многое другое. Информация здесь: http://nshipster.com/nsexpression/

1

Swift расширение 4 Массив элементов с плавающей запятой:

extension Array where Element: FloatingPoint { 

    func sum() -> Element { 
     return self.reduce(0, +) 
    } 

    func avg() -> Element { 
     return self.sum()/Element(self.count) 
    } 

    func std() -> Element { 
     let mean = self.avg() 
     let v = self.reduce(0, { $0 + ($1-mean)*($1-mean) }) 
     return sqrt(v/(Element(self.count) - 1)) 
    } 

}