2016-07-17 29 views
4

Ich benötige häufig die Berechnung von Mittelwert und Standardabweichung für numerische Arrays. Also habe ich ein kleines Protokoll und Erweiterungen für numerische Typen geschrieben, die zu funktionieren scheinen. Ich hätte gerne Feedback, wenn etwas nicht stimmt mit dem, was ich getan habe. Insbesondere frage ich mich, ob es eine bessere Möglichkeit gibt zu überprüfen, ob der Typ als Double umgewandelt werden kann, um die asDouble Variable und den init(_:Double) Konstruktor zu vermeiden.Swift Array Erweiterung für Standardabweichung

Ich weiß, es gibt Probleme mit Protokollen, die für Arithmetik zulassen, aber das scheint in Ordnung zu funktionieren und erspart mir, die Standardabweichungsfunktion in Klassen zu setzen, die es brauchen.

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)) 
    }} 
} 

edit: Ich weiß, es irgendwie sinnlos ist [Int].mean und sd zu bekommen, aber ich könnte numerisch an anderer Stelle verwenden, so dass es für die Konsistenz ist ..

edit: wie @Severin Pappadeux darauf hingewiesen, Varianz in einer Weise ausgedrückt werden, die den Triple-Pass auf dem Array vermeidet - bedeuten dann Karte dann bedeuten. Hier ist die letzte Standardabweichung Erweiterung

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' ist in der Regel die gleiche Größe wie' Int64' auf neueren Geräten ('> =' iPhone 5S, die die 64-Bit-Prozessor eingeführt), es sei denn, Sie arbeiten mit wirklich großen Zahlen, sollte dies kein Problem sein, aber nur wissen, dass 'init (_: Double)' kann zu einem Integer-Überlauf (Laufzeit Ausnahme) in Fällen, in denen das Element = Int 'type kann die Integer-Darstellung eines gegebenen (riesigen) 'Double'-Wertes nicht speichern. Möglicherweise kein Problem, wenn Sie Ihre Swift-Apps nur selbst verwenden, aber für den Fall, dass Sie an Kunden versenden, sollte dies im Hinterkopf behalten werden. – dfri

+0

Ok interessant danke. Es ist unwahrscheinlich, dass ich es mit Ganzzahlen verwenden werde, und die Werte, mit denen ich arbeite, sind mit dieser App physiologisch auf <500 beschränkt. Also sollte in Ordnung sein. –

+0

@dfri sehr nützlicher Kommentar! Ich nehme an, dass es keine Möglichkeit gibt, diese Art von Überlauf zu "fangen"? – matt

Antwort

1

In Swift 3 Sie können (oder auch nicht) in der Lage sein, sich mit dem Floatingpoint-Protokoll einige Überschneidungen zu retten, aber sonst, was Sie tun, ist hier genau richtig.

1

Nicht, dass ich weiß, Swift, aber von Numerik POV bist du es ein wenig ineffektiv

Im Grunde tun, sind Sie zwei Pässe zu tun (eigentlich drei) über die Anordnung zwei Werte zu berechnen, wobei ein Durchgang sollte genug sein. Vairance könnte als E (X) ausgedrückt werden - E (X) , so in einigen Pseudo-Code:

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

Sie haben Recht. Danke, das vermeidet den Dreifachpass. –

+0

@ twiz_ Sie sind willkommen, obwohl ich bin neugierig, ob es über 'reduce()' –

+0

ausgedrückt werden konnte Es: 's = self.reduce ((0.0, 0.0)) {return ($ 0.0 + $ 1.asDouble , $ 0.1 + ($ 1.asDoppel * $ 1.asDoppel))} 'dann s.1/n - s.0/n * s.0/n. Entschuldigung für die schreckliche Formatierung. Neu dazu. –

1

Es gibt tatsächlich eine Klasse, die diese Funktionalität bereits bietet - genannt NSExpression. Sie können Ihre Code-Größe und Komplexität reduzieren, indem Sie stattdessen verwenden. Es gibt eine Menge Zeug in dieser Klasse, aber eine einfache Implementierung von dem, was Sie wollen, ist wie folgt.

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

Sie können auch Mittelwert berechnen, und vieles mehr. Info hier: http://nshipster.com/nsexpression/

1

Swift 4 Array-Erweiterung mit Floatingpoint-Elemente:

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)) 
    } 

}