2019年7月13日土曜日

電卓キューのバグFix

電卓の数字入力のキューを整理していたら、バグがあることがわかりました。

以下がキューのPush,PoPの結果です。
1
12
123
1,234
12,345
12,345
12,345.6
12,345.67
12,345.678
12,345.6789
12345.678
12345.67
12345.6
12345.
12345
1234
123
12
1

よくよく見ると小数点下1桁を消したところで整数とすべきところ、小数点だけが残ってしまっています。ここをどうにかしなくてはいけません。

以下がPush、Popの操作部です。ここをいじるようではだめですね。ここはまったくいじりません。
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        // キューの生成
        let numberQueue = NumberQueue()

        // 数字のPush
        for i in 0...5 {
            numberQueue.push(String(i))
        }
        
        // 小数点のPush
        numberQueue.push(".")
        
        // 数字のPush
        for i in 6...9 {
            numberQueue.push(String(i))
        }
        
        // キューのPop
        for _ in 0...10{
            numberQueue.pop()
        }

    }

}

以下がNumberQueueクラスの主要部です。まだ機能は全て実装していないですが、PushとPopは実装しています。
class NumberQueue{
/*  0..9 "." のString型を入力とする電卓数字用のキュー
     表示とのセットで汎用化はしない。
     特殊機能:キューの初期化ととテーブルへの格納のための初期化コマンドを備える*/
    
// property
    let formatter = NumberFormatter()
    // キューの生成と初期化
    let queue = Queue()
// end property

// init
    init(){
        // スタイルを指定
        formatter.numberStyle = .decimal
        // 数値の区切り文字を指定する
        formatter.groupingSeparator = ","
        // 何桁ごとに区切り文字を入れるか指定する
        formatter.groupingSize = 3
        // 言語・地域
        formatter.locale = Locale(identifier:"ja_JP")
    }
// end init
    
// functions
 
    func formattedNumberString(_ val:String)->String{
        if let range = val.range(of: "."){
            let underDicimal = val.count - 1
         val.distance(from: val.startIndex, to:range.lowerBound)
            formatter.minimumFractionDigits = underDicimal
        }
        let doubleValue = Double(val)
        let nsNumberValue = NSNumber(value: doubleValue!)
        let numberString = String(formatter.string(from: nsNumberValue)!)
        return numberString

    }

    func push(_ s:String){
        if let c = changeString2Character(s){
            if let val = queue.push(c){
                let numString = formattedNumberString(val)
                print(numString)
            }
        }
    }// push
    
    func pop(){
        let p = queue.pop()
        if let s = p.s {
            print(s)
        }
    }
    
// end functions
}// end NumberQeue

Popに小数点処理やフォーマッターを入れ忘れています。
フォーマッターを2つは作りたくないので、Pushと同じフォーマッターを入れてみます。

    func pop(){
        let p = queue.pop()
        if let s = p.s {
            let numberString = formattedNumberString(s)
            print(numberString)
        }
    }

結果はこんな感じです。
1
12
123
1,234
12,345
12,345
12,345.6
12,345.67
12,345.678
12,345.6789
12,345.678
12,345.67
12,345.6
12,345
12,345
1,234
123
12
1
Fatal error: Unexpectedly found nil while unwrapping an Optional value
2019-07-13 09:20:28.044385+0900 Prototype[6835:575272] Fatal error: Unexpectedly found nil while unwrapping an Optional value

6を消したあと12,345になるのはいいですが、裏で動いているキューの小数点が消えていないので、もう一回popしたときにも同じ数字がでている点が一つ、

もう一つは、最後にnil処理が実装されておらず、Fatal errorが出ているところですね。

これらを修正していきます。
まず前者からですが、これはフォーマッタ(表示)の問題ではないので、小数点下一桁でのPopで小数点も消す処理は、Pop側で実装します。

    func pop(){
        let p = queue.pop()
        if let s = p.s {
            var queueString = s
            if queueString.suffix(1) == "." {
                queueString = String(queueString.prefix(queueString.count - 1))
                formatter.minimumFractionDigits = 0
                let _ = queue.pop()
            }
            let numberString = formattedNumberString(queueString)
            print(numberString)
        }
    }

ここでは、キューをPopして取り出した文字列の最後が小数点の場合に、小数点を削除する処理を入れました。

ただし、単に取り出した文字列を削除するだけではだめで、表示用のフォーマッターの小数点以下の桁をゼロにして、さらにキュー本体からも小数点を削除してあげなければダメでしたので、その処理を組み込んでいます。

しかし、表示処理をキューの機能に持たせるのはおかしいですね。

そこでフォーマッターの処理をみてみました。
フォーマッターの処理としては、小数点があったときだけ小数点以下の数値を変えていました。そのため、以前に小数点以下1桁の数値が入ったあとに、小数点のない数値が入ると小数点1桁のままで表示してしまう、というフォーマッターのバグであることがわかりました。

あと、後者のエラーは、フォーマッターで強制アンラップ(!)している部分でnilの処理が走ったためとわかり、オプショナルバインディングでnil処理を加えました。

結果フォーマッターとpop処理を次のように変更しした。

formatter
    func formattedNumberString(_ val:String)->String?{
        formatter.minimumFractionDigits = 0
        if let range = val.range(of: "."){
            let underDicimal = val.count - 1
          val.distance(from: val.startIndex, to:range.lowerBound)
            formatter.minimumFractionDigits = underDicimal
        }
        if let doubleValue = Double(val){
            let nsNumberValue = NSNumber(value: doubleValue)
            if let formatterString = formatter.string(from: nsNumberValue){
                return String(formatterString)
            }
            else{
                return nil
            }
        }
        else{
            return nil
        }
    }//formattedNumberString

pop処理
    func pop(){
        let p = queue.pop()
        if let s = p.s {
            var queueString = s
            if queueString.suffix(1) == "." {
                queueString = String(queueString.prefix(queueString.count - 1))
                let _ = queue.pop()
            }
            if let numberString = formattedNumberString(queueString){
                print(numberString)
            }
        }
    }
    

実行結果
1
12
123
1,234
12,345
12,345
12,345.6
12,345.67
12,345.678
12,345.6789
12,345.678
12,345.67
12,345.6
12,345
1,234
123
12
1

いやぁ、バグがとれてスッキリです。しかしSwiftは予想以上に洗練された言語なのがわかりますね。非常に感心しました。










0 件のコメント: