この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
モバイルアプリサービス部の中安です。
swift4になってDictonaryに grouping
という初期化の方法が加わったそうです。
ある配列(Sequence)をルールに基づいて辞書でグルーブ化するなどができるというものです。
たとえば
let strings = """
Improved dictionary functionality
One of the most intriguing proposals for Swift 4 was to add some new functionality to dictionaries to make them more powerful, and also to make them behave more like you would expect in certain situations.
Let's start with a simple example: filtering dictionaries in Swift 3 does not return a new dictionary. Instead, it returns an array of tuples with key/value labels.
""".split(separator: " ")
let grouped = Dictionary(grouping: strings) { string in string.first ?? "#" }
print(grouped["w", default:[]])
長い英文をスペース区切りで文字列の配列にし、"w"で始まる単語だけを出力する・・・というサンプルです。
// 出力結果
["was", "would", "with", "with"]
やってることは
Dictionary(grouping: strings) { string in string.first ?? "#" }
だけですので、実装はシンプルですが、これだけで簡単に [String : [String]]
の辞書ができあがるというわけです。
どういうことができそうか
こういう型であれば、Groupedなテーブルビューやインデックス付きのテーブルビューなどで力を発揮しそうです。
今回は試しに某RPGの道具の一覧を出す・・・というようなサンプルでやってみたいと思います。
構造体
struct Item {
let name: String
let price: Int
}
名前と値段だけの構造体です。データとしては以下のようなデータがあるものとします。
static let items = [
Item(name: "ひのきの棒", price: 5),
Item(name: "はやぶさの剣", price: 25000),
Item(name: "こん棒", price: 30),
Item(name: "毒針", price: 10),
Item(name: "銅の剣", price: 100),
:
(略)
:
Item(name: "スタミナの種", price: 90),
Item(name: "賢さの種", price: 120),
Item(name: "ラックの種", price: 45),
]
ビューコントローラ
画面はシンプルな UITableViewController です。 やってることは特別なことは何もないので説明は割愛します。
class ViewController: UITableViewController {
var items = [Int : [Item]]()
override func viewDidLoad() {
super.viewDidLoad()
prepareData()
}
private func prepareData() {
// (後述)
}
// (これ以外の実装は後述)
}
データを準備するところ
安いアイテムは「レベル1」、そこから4段階評価でアイテムのレベルをグループ分けするものとします。
上記ビューコントローラの実装のprepareData()
で実際にデータを用意してみます。
private func prepareData() {
items = Dictionary(grouping: Item.items) { item -> Int in
switch item.price {
case 0..<100: return 0
case 100..<1000: return 1
case 1000..<5000: return 2
default: return 3
}
}
}
あまり綺麗ではないですが、値段によって4つのグループに分けると処理はこういう感じで書くことができます。 swift3だともうすこし複雑に書くことになりますね。
この結果を画面で確認してみます。
どうでしょうか。
あくまで先頭から順番に走査していった結果なので、どうにも値段がバラバラに並んでます。
これを安い順に並ぶようにするには reduce
をしてやって再定義する感じになると思います。
reduceの際は要素が [Int : [Item]]
という辞書ではなく、 [(key: Int, value: [Item])]
というタプルの配列で来るので注意が必要です。
(下記ソースの変数tuple
)
private func prepareData() {
items = Dictionary(grouping: Item.items) { item -> Int in
switch item.price {
case 0..<100: return 0
case 100..<1000: return 1
case 1000..<5000: return 2
default: return 3
}
}
.reduce([Int: [Item]]()) { dic, tuple in
var dic = dic
dic[tuple.key] = tuple.value.sorted { $0.price < $1.price }
return dic
}
}
うまく値段順に揃いました。
最後に
このサンプルを作る際に Playground で LiveView というのを個人的に初めて使ってみました。
参考: [iOS 10] PlaygroundでUIKitの描画を行う
今回のサンプルを LiveView@Playground でパッと貼り付けて動くようにソースを載せておきますので、swift4 の動 くPlayground 環境で触ってみてください。
import UIKit
import PlaygroundSupport
struct Item {
let name: String
let price: Int
static let items = [
Item(name: "ひのきの棒", price: 5),
Item(name: "はやぶさの剣", price: 25000),
Item(name: "こん棒", price: 30),
Item(name: "毒針", price: 10),
Item(name: "銅の剣", price: 100),
Item(name: "聖なるナイフ", price: 200),
Item(name: "魔道士の杖", price: 1500),
Item(name: "刺の鞭", price: 320),
Item(name: "くさり鎌", price: 550),
Item(name: "鉄の槍", price: 750),
Item(name: "鉄の爪", price: 770),
Item(name: "鋼鉄の剣", price: 1500),
Item(name: "裁きの杖", price: 2700),
Item(name: "鉄の斧", price: 2500),
Item(name: "大鋏", price: 3700),
Item(name: "理力の杖", price: 2500),
Item(name: "大金槌", price: 6500),
Item(name: "ゾンビキラー", price: 9800),
Item(name: "ドラゴンキラー", price: 15000),
Item(name: "破壊の剣", price: 45000),
Item(name: "王者の剣", price: 35000),
Item(name: "アブない水着", price: 78000),
Item(name: "布の服", price: 10),
Item(name: "旅人の服", price: 70),
Item(name: "稽古着", price: 80),
Item(name: "皮の鎧", price: 150),
Item(name: "甲羅の鎧", price: 300),
Item(name: "身かわしの服", price: 2900),
Item(name: "くさりかたびら", price: 480),
Item(name: "鉄のまえかけ", price: 700),
Item(name: "武闘着", price: 800),
Item(name: "鉄の鎧", price: 1100),
Item(name: "ハデな服", price: 1300),
Item(name: "魔法の法衣", price: 4400),
Item(name: "鋼鉄の鎧", price: 2400),
Item(name: "天使のローブ", price: 3000),
Item(name: "魔法の鎧", price: 5800),
Item(name: "水の羽衣", price: 12500),
Item(name: "ドラゴンメイル", price: 9800),
Item(name: "皮の盾", price: 90),
Item(name: "青銅の盾", price: 180),
Item(name: "鉄の盾", price: 700),
Item(name: "水鏡の盾", price: 8800),
Item(name: "力の盾", price: 15000),
Item(name: "皮の帽子", price: 80),
Item(name: "ターバン", price: 160),
Item(name: "鉄兜", price: 2000),
Item(name: "鉄仮面", price: 3500),
Item(name: "薬草", price: 8),
Item(name: "毒消し草", price: 10),
Item(name: "キメラの翼", price: 25),
Item(name: "聖水", price: 20),
Item(name: "満月草", price: 30),
Item(name: "まだらクモ糸", price: 35),
Item(name: "毒蛾の粉", price: 500),
Item(name: "消え去り草", price: 300),
Item(name: "祈りの指輪", price: 2500),
Item(name: "水鉄砲", price: 15),
Item(name: "世界樹の葉", price: 3),
Item(name: "幸せの靴", price: 75),
Item(name: "銀の竪琴", price: 7),
Item(name: "命の木の実", price: 150),
Item(name: "力の種", price: 180),
Item(name: "すばやさの種", price: 60),
Item(name: "スタミナの種", price: 90),
Item(name: "賢さの種", price: 120),
Item(name: "ラックの種", price: 45),
]
}
class ViewController: UITableViewController {
var items = [Int : [Item]]()
override func viewDidLoad() {
super.viewDidLoad()
prepareData()
}
private func prepareData() {
items = Dictionary(grouping: Item.items) { item -> Int in
switch item.price {
case 0..<100: return 0
case 100..<1000: return 1
case 1000..<5000: return 2
default: return 3
}
}
.reduce([Int: [Item]]()) { dic, tuple in
var dic = dic
dic[tuple.key] = tuple.value.sorted { $0.price < $1.price }
return dic
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items[section]?.count ?? 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "レベル\(section + 1)"
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let reuseIdentifier = "cell"
var cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier)
if cell == nil {
cell = UITableViewCell(style: .value1, reuseIdentifier: reuseIdentifier)
}
let item = self.items[indexPath.section]![indexPath.row]
cell!.textLabel?.text = item.name
cell!.detailTextLabel?.text = "\(item.price)G"
return cell!
}
}
PlaygroundPage.current.liveView = ViewController(style: .grouped)