optional chaining in Swift 3: why does one example work and not the other?

↘锁芯ラ 提交于 2019-12-23 05:15:09

问题


Here is a detailed account of a problem following my previous post on Swift optionals.

Thanks to leads given here, here and here, I am able to read fractions (for harmonic ratios) or decimals (for cents) from a string array to calculate the frequencies of notes in musical scales.

Each element in the string array is first tested to see if it contains a / or a . One of two functions then identifies input errors using optional chaining so both fractional and decimal numbers conform to rules outlined in this tuning file format.

Example 1 and 1a shows what happens with correctly entered data in both formats.

  1. Scale with a mixture of fractions and decimals

                    C      D            E      F            G            Ab       B             C’      
    let tuning = [ "1/1", "193.15686", "5/4", "503.42157", "696.57843", "25/16", "1082.89214", "2/1"]
    

the column in the debug area shows input data (top down), row shows output frequencies (l-to-r).

    Optional("1/1")
    Optional("193.15686")
    Optional("5/4")
    Optional("503.42157")
    Optional("696.57843")
    Optional("25/16")
    Optional("1082.89214")
    Optional("2/1")
    [261.62599999999998, 292.50676085897425, 327.03249999999997, 349.91970174951047, 391.22212058238728, 408.79062499999998, 489.02764963627084, 523.25199999999995]

Examples 2 & 3 show how both functions react to bad input (i.e. wrongly entered data).

  1. bad fractions are reported (e.g. missing denominator prints a message)

    Optional("1/1")
    Optional("5/")
    User input error - invalid fraction: frequency now being set to 0.0 Hertz 
    Optional("500.0")
    Optional("700.0")
    Optional("2/1")
    [261.62599999999998, 0.0, 349.22881168708938, 391.99608729493866, 523.25199999999995]
    
  2. bad decimals are not reported (e.g. after 700 there is no .0 - this should produce a message)

    Optional("1/1")
    Optional("5/4")
    Optional("500.0")
    Optional("700")
    Optional("2/1")
    [261.62599999999998, 327.03249999999997, 349.22881168708938, 0.0, 523.25199999999995]
    

NOTE: In addition to the report 0.0 (Hz) appears in the row when an optional is nil. This was inserted elsewhere in the code (where it is explained in context with a comment.)

The problem in a nutshell ? the function for fractions reports a fault whereas the function for decimal numbers fails to detect bad input.

Both functions use optional chaining with a guard statement. This works for faulty fractions but nothing I do will make the function report a faulty input condition for decimals. After checking the code thoroughly I’m convinced the problem lies in the conditions I’ve set for the guard statement. But I just can’t get this right. Can anyone please explain what I did wrong ?

Tuner.swift

import UIKit

class Tuner {

    var tuning                      = [String]()
    let tonic: Double               = 261.626   // frequency of middle C
    var index                       = -1
    let centsPerOctave: Double      = 1200.0    // mandated by Scala tuning file format
    let formalOctave: Double        = 2.0       // Double for stretched-octave tunings

init(tuning: [String]) {
    self.tuning                     = tuning

    let frequency                   = tuning.flatMap(doubleFromDecimalOrFraction)
    print(frequency)

}


func doubleFromDecimalOrFraction(s: String?) -> Double {

    index                           += 1
    let whichNumericStringType      = s
    print(whichNumericStringType as Any)        // eavesdrop on String?

    var possibleFrequency: Double?

    //  first process decimal.
    if (whichNumericStringType?.contains("."))!             {
        possibleFrequency           = processDecimal(s: s)
    }

    //  then process fractional.
    if (whichNumericStringType?.contains("/"))!             {
        possibleFrequency           = processFractional(s: s)
    }

    // Insert "0.0" marker. Remove when processDecimal works
    let noteFrequency               = possibleFrequency
    let zeroFrequency               = 0.0
    // when noteFrequency? is nil, possibleFrequency is set to zeroFrequency
    let frequency                   = noteFrequency ?? zeroFrequency

    return frequency    // TO DO let note: (index: Int, frequency: Double)

    }


func processFractional(s: String?) -> Double?   {

    var fractionArray               = s?.components(separatedBy: "/")

    guard let numerator             = Double((fractionArray?[0])!.digits),
        let denominator             = Double((fractionArray?[1])!.digits),
        numerator                   > 0,
        denominator                 != 0,
        fractionArray?.count        == 2
        else
    {
        let possibleFrequency       = 0.0
        print("User input error - invalid fraction: frequency now being set to \(possibleFrequency) Hertz ")
        return possibleFrequency
        }
    let possibleFrequency           = tonic * (numerator / denominator)
    return possibleFrequency
        }


func processDecimal(s: String?) -> Double?      {

    let decimalArray                = s?.components(separatedBy: ".")
    guard let _                     = s,
        decimalArray?.count         == 2
        else
    {
        let denominator             = 1
        let possibleFrequency       = 0.0
        print("User input error (value read as \(s!.digits)/\(denominator) - see SCL format, http://www.huygens-fokker.org/scala/scl_format.html): frequency now being forced to \(possibleFrequency) Hertz ")
        return possibleFrequency
        }
    let power                       = Double(s!)!/centsPerOctave
    let possibleFrequency           = tonic * (formalOctave**power)
    return possibleFrequency
        }
    }


extension String {

    var digits: String {
    return components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
        }
    }


precedencegroup Exponentiative {

    associativity: left
    higherThan: MultiplicationPrecedence

    }


infix operator ** : Exponentiative

func ** (num: Double, power: Double) -> Double{
    return pow(num, power)
    }

ViewController.swift

import UIKit

class ViewController: UIViewController {

    // test pitches: rational fractions and decimal numbers (currently 'good')
    let tuning = ["1/1", "5/4", "500.0", "700.0", "2/1"]

    // Diatonic scale: rational fractions
    //       let tuning = [ "1/1", "9/8", "5/4", "4/3", "3/2", "27/16", "15/8", "2/1"]

    // Mohajira: rational fractions
    //    let tuning = [ "21/20", "9/8", "6/5", "49/40", "4/3", "7/5", "3/2", "8/5", "49/30", "9/5", "11/6", "2/1"]

    // Diatonic scale: 12-tET
    //    let tuning = [ "0.0", "200.0", "400.0", "500", "700.0", "900.0", "1100.0", "1200.0"]

    // Diatonic scale: mixed 12-tET and rational fractions
    //    let tuning = [ "0.0", "9/8", "400.0", "4/3", "700.0", "27/16", "1100.0", "2/1"]

    // Diatonic scale: 19-tET
    //     let tuning = [ "0.0", "189.48", "315.8", "505.28", "694.76", "884.24", "1073.72", "1200.0"]

    // Diatonic 1/4-comma meantone scale. Pietro Aaron's temperament (1523) : mixed cents and rational fractions
    //    let tuning = [ "1/1", "193.15686", "5/4", "503.42157", "696.57843", "25/16", "1082.89214", "2/1"]

override func viewDidLoad() {
    super.viewDidLoad()

    _ = Tuner(tuning: tuning)

    }
}

回答1:


The problem in a nutshell ? the function for fractions reports a fault whereas the function for decimal numbers fails to detect bad input.

The function for decimal numbers does detect “bad” input. However, "700" does not contain ".", and you only call processDecimal(s:) if the string does contain ".". If the string doesn't contain "." and also doesn't contain "/", doubleFromDecimalOrFraction(s:) doesn't call any function to parse the string.



来源:https://stackoverflow.com/questions/41406935/optional-chaining-in-swift-3-why-does-one-example-work-and-not-the-other

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!