LoginSignup
10

More than 5 years have passed since last update.

Swift で「千反田える」

Last updated at Posted at 2016-08-20

プロローグ

今日は iOSDC の日でしたが、気付いたらもうチケット完売してて行けなかったorz
というわけで iOSDC の場外トーク(嘘)で、「一反田えー」「二反田びー」…「二十六反田ぜっど」「二十七反田えー」…のように定義した場合、果たして「千反田」は「える」になるのだろうか、Swift で検証してみた。

最低限の検証

まずは一番簡単なソースとして、1反田a 2反田b 3反田c…の出力を作ってみる:

let alphabets = Array("abcdefghijklmnopqrstuvwxyz".characters)

for i in 0 ..< 1000 {
    let alphabet = alphabets[i % alphabets.count]
    print("\(i + 1)反田\(alphabet)")
}

// 最後の行:1000反田l

おお、ちゃんと 1000 番目の反田に対応してるアルファベットは l で間違いないようです!
ところがこれはただの 1000反田l であり、千反田えるまでにはまだほど遠い。
というわけでプログラムを改造しよう。

アルファベットの日本語出力

まずは a b c などのアルファベットの日本語出力機能をつけてあげなければ。というわけで Character の拡張を作ります:

extension Character {

    var japanese: String {

        switch self {
        case "a": return "えー"
        case "b": return "びー"
        case "c": return "しー"
        case "d": return "でぃー"
        case "e": return "いー"
        case "f": return "えふ"
        case "g": return "じー"

        case "h": return "えいち"
        case "i": return "あい"
        case "j": return "じぇい"
        case "k": return "けい"
        case "l": return "える"
        case "m": return "えむ"
        case "n": return "えぬ"

        case "o": return "おー"
        case "p": return "ぴー"
        case "q": return "きゅー"
        case "r": return "あーる"
        case "s": return "えす"
        case "t": return "てぃー"

        case "u": return "ゆー"
        case "v": return "ゔぃー"
        case "w": return "だぶりゅー"
        case "x": return "えっくす"
        case "y": return "わい"
        case "z": return "ぜっど"

        default: return ""
        }

    }

}

これでもう一度ループ回してみます

let alphabets = Array("abcdefghijklmnopqrstuvwxyz".characters)

for i in 0 ..< 1000 {
    let alphabet = alphabets[i % alphabets.count]
    print("\(i + 1)反田\(alphabet.japanese)")
}

// 最後の行:1000反田える

よし少しだけだけ千反田えるに近づきました!

数字の日本語出力

一桁しかないアルファベットより、何桁もある数字の方が難しいね。何より桁やその桁の数字によって書き方も違ってくるし。
というわけでアプローチを考えないといけないわけですが、とりあえずまずは渡された数字を桁毎の日本語を決めなければなりません。ところが普通に数学的にこの作業をやると割と面倒だし結局出力してるのは String 型だし、ここでちょっと変わったアプローチとして、一回数字を文字列に変換して文字列の各文字を取る、というやり方ができます。こうすることで、先ほどの a b c と同じように Character 型で日本語を定義することができます。というわけで先ほどの Character の拡張をちょっと手を加えてみます

extension Character {

    var japanese: String {

        switch self {
        case "0": return "零"
        case "1": return "一"
        case "2": return "二"
        case "3": return "三"
        case "4": return "四"
        case "5": return "五"
        case "6": return "六"
        case "7": return "七"
        case "8": return "八"
        case "9": return "九"

        // ...

        default: return ""
        }

    }

}

ところがこれはまだ「桁」という概念を考慮していない。このままだと 1000 が来ても 一零零零 の出力が出るだけです。

「桁」という概念は通常のテキストと違い、「右から左に」数えていくものです。つまり適当な数字を与えて、この数字の左から3番目の桁はどの桁なのか、というのは左から3つ数えても何もわかりません。右からこの桁まで数えて初めてわかるものです。なのでここの処理は逆順で一回処理しなければなりません。そして右から順番には「一の桁」「十の桁」「百の桁」「千の桁」となりますが、「一の桁」というのは特に「桁」を言わない(つまり例えば「30」は「三十」のように「十の桁」を指す「十」を言いますが、「3」は別に「一の桁」を指す「一」は言わない)ので、桁の言い方としては「十」「百」「千」の言い方だけ定義する必要があります。というわけでそろそろ Int の拡張を作ります:

extension Int {

    var japanese: String {
        let digits = Array("\(self)".characters).reverse()
        let pronunciations = digits.enumerate().map { (i, digit) -> String in
            let iPronunciation: String
            switch i {
            case 1:
                iPronunciation = "十"

            case 2:
                iPronunciation = "百"

            case 3:
                iPronunciation = "千"

            default:
                iPronunciation = ""
            }

            return digit.japanese + iPronunciation
        }
        return pronunciations.reverse().reduce("", combine: { (pronunciation, digit) -> String in
            return pronunciation + digit
        })
    }

}

必要なのは千の桁までなのでとりあえず「千」までを定義しておきます

ところがこの定義の場合まだ十分ではありません、例えば 100 の数字が出た場合、この定義では 一百零十零 の出力になります。我々が望んでいるのは の出力です。なのでさらに追加定義が必要です。

まず最初に、桁の数字が「0」の場合、そもそもその桁を言わないように処理を入れてあげなければなりません。

そして次に、桁の数字が「1」の場合、最下位、つまり「一の桁」の場合以外は習慣的に「一」なんて言いません。

というわけで上記の処理をさらに取り入れた修正がこんな風になります:

extension Int {

    var japanese: String {
        let digits = Array("\(self)".characters).reverse()
        let pronunciations = digits.enumerate().map { (i, digit) -> String in
            switch digit {
            case "0":
                return ""

            default:
                let iPronunciation: String
                switch i {
                case 1:
                    iPronunciation = "十"

                case 2:
                    iPronunciation = "百"

                case 3:
                    iPronunciation = "千"

                default:
                    iPronunciation = ""
                }

                let digitPronunciation: String
                if digit == "1" && i > 0 {
                    digitPronunciation = ""
                } else {
                    digitPronunciation = digit.japanese
                }

                return digitPronunciation + iPronunciation

            }

        }
        return pronunciations.reverse().reduce("", combine: { (pronunciation, digit) -> String in
            return pronunciation + digit
        })
    }

}

そしてここまできたら Character 型も Int 型も var japanese: String の算出プロパティーがあるので、せっかくだから protocol を作りましょう:

protocol JapaneseConvertible {
    var japanese: String { get }
}

extension Character: JapaneseConvertible {

    var japanese: String {
        // ...
    }

}

extension Int: JapaneseConvertible {

    var japanese: String {
        // ...
    }

}

これで最後もう一度ループ回しましょう:

let alphabets = Array("abcdefghijklmnopqrstuvwxyz".characters)

for i in 0 ..< 1000 {
    let alphabet = alphabets[i % alphabets.count]
    print("\((i + 1).japanese)反田\(alphabet.japanese)")
}

// 最後の行:千反田える

やった!千反田えるができた!

全ソースコード

import Foundation

protocol JapaneseConvertible {
    var japanese: String { get }
}

extension Character: JapaneseConvertible {

    var japanese: String {

        switch self {
        case "0": return "零"
        case "1": return "一"
        case "2": return "二"
        case "3": return "三"
        case "4": return "四"
        case "5": return "五"
        case "6": return "六"
        case "7": return "七"
        case "8": return "八"
        case "9": return "九"

        case "a": return "えー"
        case "b": return "びー"
        case "c": return "しー"
        case "d": return "でぃー"
        case "e": return "いー"
        case "f": return "えふ"
        case "g": return "じー"

        case "h": return "えいち"
        case "i": return "あい"
        case "j": return "じぇい"
        case "k": return "けい"
        case "l": return "える"
        case "m": return "えむ"
        case "n": return "えぬ"

        case "o": return "おー"
        case "p": return "ぴー"
        case "q": return "きゅー"
        case "r": return "あーる"
        case "s": return "えす"
        case "t": return "てぃー"

        case "u": return "ゆー"
        case "v": return "ゔぃー"
        case "w": return "だぶりゅー"
        case "x": return "えっくす"
        case "y": return "わい"
        case "z": return "ぜっど"

        default: return ""
        }

    }

}

extension Int: JapaneseConvertible {

    var japanese: String {
        let digits = Array("\(self)".characters).reverse()
        let pronunciations = digits.enumerate().map { (i, digit) -> String in
            switch digit {
            case "0":
                return ""

            default:
                let iPronunciation: String
                switch i {
                case 1:
                    iPronunciation = "十"

                case 2:
                    iPronunciation = "百"

                case 3:
                    iPronunciation = "千"

                default:
                    iPronunciation = ""
                }

                let digitPronunciation: String
                if digit == "1" && i > 0 {
                    digitPronunciation = ""
                } else {
                    digitPronunciation = digit.japanese
                }

                return digitPronunciation + iPronunciation

            }

        }
        return pronunciations.reverse().reduce("", combine: { (pronunciation, digit) -> String in
            return pronunciation + digit
        })
    }

}

let alphabets = Array("abcdefghijklmnopqrstuvwxyz".characters)

for i in 0 ..< 1000 {
    let alphabet = alphabets[i % alphabets.count]
    print("\((i + 1).japanese)反田\(alphabet.japanese)")
}

えるたそ〜

えるたそ〜

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10