[iOS] swift | 数値の文字列を文字列のまま3桁区切りの文字列で返したい

2015/10/14

こんにちは。きんくまです。

電卓アプリのZippyCalcなんですが、桁区切りをやっていないのでアップデートで対応したいと思いました。

桁区切りっていう用語が正しいのかわからないのですが数値をカンマで区切る

12345 -> 12,345

というやつです。

どうやら調べてみると、数値からはデフォルトでNSNumberFormmterがあるみたいで簡単に変換できるみたいですね。すばらしい!

>> [iOS] 3桁区切りの数字文字列を生成する方法
>> [Swift]数字を三桁ごとにカンマ区切りにする – Qiita
>> iOS Tips #3 NSNumberFormatterで数値を文字列に変換する | Developers.IO

で、ZippyCalcの場合は文字列を編集した後に最後に数値や式として評価したいので、文字列は文字列のまま扱って桁区切りできればいいなと思いました。

文字列のままn桁区切りしたい

それで作ってみたのがこれです。

import Foundation

public class NumberStringDigitSplitter {
    private class func checkFirstCharIsMinus(str:String)->(hasMinus:Bool, trimmedStr:String){
        var hasMinus:Bool = false
        if str.characters.count == 0 {
            return (false, str)
        }
        let firstCharStartIndex = str.startIndex
        let firstCharEndIndex = firstCharStartIndex.advancedBy(1)
        let firstCharRange:Range = Range(start: firstCharStartIndex, end: firstCharEndIndex)
        let firstChar:String = str.substringWithRange(firstCharRange)
        var trimmedStr:String = str
        if firstChar == "-" {
            hasMinus = true
            trimmedStr = trimmedStr.substringFromIndex(firstCharEndIndex)
        }
        return (hasMinus, trimmedStr)
    }
    
    private class func splitByPoint(str:String)->(beforePointStr:String, afterPointStr:String?){
        var beforePointStr:String!
        var afterPointStr:String?
        var pointRange:Range! = str.rangeOfString(".")
        if pointRange != nil {
            let afterRange:Range = Range(start: pointRange.endIndex.advancedBy(-1), end: str.endIndex)
            afterPointStr = str.substringWithRange(afterRange)
            pointRange.startIndex = str.startIndex
            pointRange.endIndex = pointRange.endIndex.advancedBy(-1)
            beforePointStr = str.substringWithRange(pointRange)
        }else{
            beforePointStr = str
        }
        return (beforePointStr, afterPointStr)
    }
    
    private class func splitByDigit(str:String, digit:Int = 3)->String{
        let strCount:Int = str.characters.count
        var strArr:[String] = [String]()
        var token:String!
        for i in 0 ..< strCount {
            let tokenRange = Range(start: str.startIndex.advancedBy(i),
                end: str.startIndex.advancedBy(i + 1))
            token = str.substringWithRange(tokenRange)
            strArr.append(token)
        }
        strArr = strArr.reverse()
        
        var resultArr:[String] = [String]()
        for i in 0 ..< strCount {
            resultArr.append(strArr[i])
            if i == 0 || i == strCount - 1{
                continue
            }
            if i % digit == digit - 1 {
                resultArr.append(",")
            }
        }
        return resultArr.reverse().joinWithSeparator("")
    }
    
    public class func split(str:String, digit:Int = 3)->String{
        var replacedStr:String = str.stringByReplacingOccurrencesOfString(",", withString: "")
        let checkMinusResult:(hasMinus:Bool, trimmedStr:String) = self.checkFirstCharIsMinus(replacedStr)
        replacedStr = checkMinusResult.trimmedStr
        
        let splitByPointResult:(beforePointStr:String, afterPointStr:String?) = self.splitByPoint(replacedStr)
        
        var beforePointStr:String = splitByPointResult.beforePointStr
        beforePointStr = self.splitByDigit(beforePointStr, digit: digit)
        
        var resultStr:String = beforePointStr
        if splitByPointResult.afterPointStr != nil {
            resultStr = "\(resultStr)\(splitByPointResult.afterPointStr!)"
        }
        if checkMinusResult.hasMinus {
            resultStr = "-\(resultStr)"
        }
        return resultStr
    }
}

桁区切りだけなのに長くてスミマセン。
やっていることとしては、

1. マイナスがあるかどうかチェック。あればマイナスを取り除く
2. 小数点の前後で文字列を区切る
3. 整数部(小数点の前)をn桁区切りにする
4. 1,2,3でできたものをつなぎ合わせて完成

です。

使い方。digitという引数がn桁区切りでして、3を指定すると3桁区切りになります。4なら4桁区切り。

let s:String = "12345"
let result:String = NumberStringDigitSplitter.split(s, digit:3)
print("\(result)") //-> "12,345"

テストコード

import XCTest

class NumberStringDegitSplitterTests: XCTestCase {

    override func setUp() {
        super.setUp()
    }
    
    override func tearDown() {
        super.tearDown()
    }

    func test_split_digit3(){
        var testDatas:[(String, String)] = [
            ("", ""),
            
            ("1", "1"),
            ("12", "12"),
            ("123", "123"),
            ("1234", "1,234"),
            ("123456", "123,456"),
            ("123456789012345", "123,456,789,012,345"),
            
            (",1,", "1"),
            ("1,2", "12"),
            (",12,3", "123"),
            ("1,234,", "1,234"),
            (",12,345,6", "123,456"),
            ("123,456,789,012,345", "123,456,789,012,345"),
            
            ("1.2.3", "1.2.3"),
            ("14.2.3", "14.2.3"),
            ("145.2.3", "145.2.3"),
            ("2145.2.3", "2,145.2.3"),
            ("123456.789", "123,456.789"),
            ("123456.7890.12345", "123,456.7890.12345"),
            ("123,45,6.78,90.12345", "123,456.7890.12345"),
            
            ("123.", "123."),
            (".", "."),
            (".345", ".345"),
            
            ("-2", "-2"),
            ("-23", "-23"),
            ("-234", "-234"),
            ("-2345", "-2,345"),
            ("-23456", "-23,456"),
            ("-234567", "-234,567"),
            ("-2345678", "-2,345,678")
        ]
        for i in 0 ..< testDatas.count {
            let testData:(testStr:String, answer:String) = testDatas[i]
            let result:String = NumberStringDigitSplitter.split(testData.testStr, digit:3)
            XCTAssertEqual(result, testData.answer)
        }
    }
    
    func test_split_digit4(){
        var testDatas:[(String, String)] = [
            ("", ""),
            
            ("1", "1"),
            ("12", "12"),
            ("123", "123"),
            ("1234", "1234"),
            ("123456", "12,3456"),
            ("123456789012345", "123,4567,8901,2345"),
            
            (",1,", "1"),
            ("1,2", "12"),
            (",12,3", "123"),
            ("1,234,", "1234"),
            (",12,345,6", "12,3456"),
            ("123,456,789,012,345", "123,4567,8901,2345"),
            
            ("1.2.3", "1.2.3"),
            ("14.2.3", "14.2.3"),
            ("145.2.3", "145.2.3"),
            ("2145.2.3", "2145.2.3"),
            ("123456.789", "12,3456.789"),
            ("123456.7890.12345", "12,3456.7890.12345"),
            ("123,45,6.78,90.12345", "12,3456.7890.12345"),
            
            ("123.", "123."),
            (".", "."),
            (".345", ".345"),
            
            ("-2", "-2"),
            ("-23", "-23"),
            ("-234", "-234"),
            ("-2345", "-2345"),
            ("-23456", "-2,3456"),
            ("-234567", "-23,4567"),
            ("-2345678", "-234,5678")
        ]
        for i in 0 ..< testDatas.count {
            let testData:(testStr:String, answer:String) = testDatas[i]
            let result:String = NumberStringDigitSplitter.split(testData.testStr, digit:4)
            XCTAssertEqual(result, testData.answer)
        }
    }
}
LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

LINEスタンプ作りました!
毎日使える。とぼけたウサギ。LINEスタンプ販売中! 毎日使える。とぼけたウサギ

ページトップへ戻る