はじめに
APIレスポンスから受け取ったデータを、五十音順にソートしてリスト表示するというものを行いました。
実際には不要だったのでプロダクトコードには残していませんが、ちょっとややこしくて時間食った&やった過程を無駄にしたくないので備忘録。
やること
-
[{"name": "お店の名前", "kana": "フリガナ"}]
というリストを五十音順ソート - リスト表示するのは
name
のお店の名前のみ
内容
五十音順と言えども、以下のような順序の指定がありました。
アイウエオ順→長音→清音→濁音→半濁音→小書きの仮名→ナミ字
- 長音: ー (直前の母音)
- 静音: ア とか ハ とか
- 濁音: ガ とか ダ とか
- 半濁音: パ とか ペ とか
- 小文字: ョ とか ッ とか
- ナミ字: 〜 (直前の母音)
Swift
には.sorted()
メソッドが用意されているので、配列や辞書などはこれを用いることができます。
// array
let array: [String] = ["パン", "バ", "ハ", "ハ〜シ", "〜バ", "ハーシ", "ハバ", "ハハ", "ハパーシ", "ハョーニ", "ーバ"]
let sortedArray = array.sorted()
print("sortedArray \(sortedArray)")
// sortedArray ["〜バ", "ハ", "ハ〜シ", "ハハ", "ハバ", "ハパーシ", "ハョーニ", "ハーシ", "バ", "パン", "ーバ"]
// dictionary
let dict = ["1": "パン", "2": "バ", "3": "ハ", "4": "ハ〜シ", "5": "〜バ", "6": "ハーシ", "7": "ハバ", "8": "ハハ", "9": "ハパーシ", "10": "ハョーニ", "11": "ーバ"]
let sortedDict = dict.sorted { $0.value < $1.value }
print("sortedDict \(sortedDict)")
// sortedDict [(key: "5", value: "〜バ"), (key: "3", value: "ハ"), (key: "4", value: "ハ〜シ"), (key: "8", value: "ハハ"), (key: "7", value: "ハバ"), (key: "9", value: "ハパーシ"), (key: "10", value: "ハョーニ"), (key: "6", value: "ハーシ"), (key: "2", value: "バ"), (key: "1", value: "パン"), (key: "11", value: "ーバ")]
辞書型の戻り値は純粋な辞書型ではなく、
[(key: Data, value: Data)]
という、keyとvalueを持ったタプルの配列で返ってきます。
ちなみに、ひら・カナ混合だと、降順だとひら→カナ
の順です。
しかし、既存のメソッドでソートされる順序は
アイウエオ順→ナミ字→清音→濁音→半濁音→小書きの仮名→長音
のようにナミ字の判定が最前列、長音が最後列といった風に上記の期待とは一部逆になります。
(厳密にいうとナミ字がア
よりも前に来ます)
今回必要とされる条件は
-
アイウエオ順→長音→清音→濁音→半濁音→小書きの仮名→ナミ字
の順 - 規定に沿ってソート、お店の名前だけディスプレイ
なので
1. 静音 <-> ナミ字 をスワップしてソートする
2. kana
だけソートするわけにいかないので、辞書型リストにしてセットでソートする
で対応します。
コード
struct Shop {
let name: String
let kana: String
}
let shopList: [Shop] = [Shop(name: "1", kana: "パン"),
Shop(name: "2", kana: "バ"),
Shop(name: "3", kana: "ハ"),
...]
// 全部書くの大変なので、以下のデータを元にしてAPIレスポンス時にshopListを作成していると思ってください
// ["1": "パン", "2": "バ", "3": "ハ", "4": "ハ〜シ", "5": "〜バ", "6": "ハーシ", "7": "ハバ", "8": "ハハ", "9": "ハパーシ", "10": "ハョーニ", "11": "ーバ"]
private var shopNames: [String] {
// name と kana をセットにするため辞書型にする
let baseDict = shopList.flatMap { [$0.name: $0.kana] }
// ”〜” と ”ー” を入れ替えてソートする
let replaced = replaceWaveAndLong(dict: baseDict).sorted { $0.value < $1.value }
// ソートしたら元のデータに戻すために、再度”〜” と ”ー” を入れ替える
let dict = replaceWaveAndLong(dict: replaced)
print("dict \(dict)")
// dict [(key: "11", value: "ーバ"), (key: "3", value: "ハ"), (key: "6", value: "ハーシ"), (key: "8", value: "ハハ"), (key: "7", value: "ハバ"), (key: "9", value: "ハパーシ"), (key: "10", value: "ハョーニ"), (key: "4", value: "ハ〜シ"), (key: "2", value: "バ"), (key: "1", value: "パン"), (key: "5", value: "〜バ")]
// name の部分だけ取り出す
return dict.map { $0.key }
}
// 指定のソート順に合わせるための ”〜” と ”ー” を入れ替えるメソッド
func replaceWaveAndLong(dict: [(key: String, value: String)]) -> [(key: String, value: String)] {
let replaced = dict.map { elem -> (key: String, value: String) in
let wave = "〜", long = "ー"
if elem.value.contains(wave) {
return (key: elem.key,
value: elem.value.replacingOccurrences(of: wave, with: long))
}
if elem.value.contains(long) {
return (key: elem.key,
value: elem.value.replacingOccurrences(of: long, with: wave))
}
return elem
}
return replaced
}
といった感じです。
まとめ
長音は大体の場合、前の文字の母音になるので、大体順序は先頭に来るように指定されることが多そう。でも既存だと後ろに行ってしまうので、このように手を加える必要がありそう。
ちなみにここに辿り着くまで、長音が来たら前の文字をローマ字化して、その母音と同じ音に変換してとか
unicodeで文字をスイッチしたりとか、enum
で全部作ってやろうかなど思いましたが、上記の条件ならこれで大丈夫そうでした。
実装することはなかったけど少し勉強になりました、ちゃんちゃん。