问题
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.
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).
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]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