プロローグ
今日は 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)")
}
えるたそ〜
えるたそ〜