Edited at

Swiftで一次元配列を評価して二次元配列を生成する方法

More than 3 years have passed since last update.

let sampleArray = ([

CellViewModel(y: 2015, m: 1, d: 1),
CellViewModel(y: 2015, m: 1, d: 2),
CellViewModel(y: 2015, m: 1, d: 3),
CellViewModel(y: 2015, m: 2, d: 1),
CellViewModel(y: 2015, m: 3, d: 1),
CellViewModel(y: 2015, m: 3, d: 10),
CellViewModel(y: 2015, m: 4, d: 1),
CellViewModel(y: 2015, m: 4, d: 1),
CellViewModel(y: 2015, m: 8, d: 1),
CellViewModel(y: 2015, m: 10, d: 1),
CellViewModel(y: 2015, m: 12, d: 1),
CellViewModel(y: 2016, m: 1, d: 1),
CellViewModel(y: 2016, m: 2, d: 1),
CellViewModel(y: 2016, m: 3, d: 1),
])


やりたいこと

以上のようなsampleArrayの各要素の日付を評価して同じ年月のものをgroupingして新しく二次元配列を生成する


なぜ困っているか

・ググってみてもrubyのgroup_byのようなメソッドがswiftにはない

・評価ロジックを自分で考えなければいけない


思考過程

まずは出来上がりの形を思い浮かべてみた。

let result = [

[2015-1-1, 2015-1-2, 2015-1-3],
[2015-2-1],
[2015-3-1, 2015-3-10],
[2015-4-1, 2015-4-1],
[2015-8-1],
[2015-10-1],
[2015-12-1],
[2016-1-1],
[2016-2-1],
[2016-3-1]
]


ロジック

各要素(modelとする)を直前のもの(previousModelとする)と比較して

(1)日付が同じであるとき

previousModelを含んでいる配列の末尾に追加

[..., previousModel, model]

(2)日付が異なるとき

previousModelを含んでいる配列はresultに追加し、これとは別に、新たにmodelのみを格納している配列を初期化し、追加する

[..., previousModel], [model]

というのを各要素についてloopでしてあげればよさそう。

previousModelとpreviousModelを含む配列が必要そうであるので、この配列をgroupとして定義してあげる。

そして(2)のように日付が異なるときにはgroupresultに追加して、また新しくgroupを宣言して同様の評価を繰り返していく。


実装

resultとgroupを宣言して、for-loopを回して上記のロジックで評価をしてresultを生成する。

とりあえず以下のように書いてみた。

var result: [[CellViewModel]] = []

var group: [CellViewModel] = []
var previousModel: CellViewModel

for cellViewModel in source {
if cellViewModel.date.isSameMonthOf(previousModel.date){
// 日付が同じとき
group.append(cellViewModel)
}else{
//日付が異なるとき
result.append(group)
group = [cellViewModel]
}
}
result.append(group)
return result

しかし、(2)のgroup = [cellViewModel]

group = []

group.append[cellViewModel]

と書いてあげれば二行目は(1)の処理と同じであるため、条件分岐の外に書くことができます。

このように条件分岐させてあげたいときに各条件内の処理で重複があるときはなるべく外に出してあげましょう。

var result: [[CellViewModel]] = []

var group: [CellViewModel] = []
var previousModel: CellViewModel

for cellViewModel in source {
if !cellViewModel.date.isSameMonthOf(previousModel.date){
// 日付が異なるとき
result.append(group)
group = []
}
group.append(model)
}
result.append(group)
return result

また、次の問題としては1回目のloopのときにはpreviousModelがnilであることです。

これはpreviousModelをOptionalで定義してあげれば解消できます。

forced unwrap(previousModel!)はcrashする危険性があるのでif letを用いて表記をするとcrashせず、かつ、可読性もよいです。


結論

というわけで以下のように書くとうまく行きました。

何を持って評価するかによって書き方は多少変わりますが、それ以外はさほど変えずに書けるのではないでしょうか。


sample.swift

 func groupViewModels(source: [CellViewModel]) -> [[CellViewModel]]{ 

var result: [[CellViewModel]] = []
var group: [CellViewModel] = []
var previousModel: CellViewModel? = nil

for cellViewModel in source {
if let previousModel = previousModel {
if !cellViewModel.date.isSameMonthOf(previousModel.date) {
result.append(group)
group = []
}
}
previousModel = cellViewModel
group.append(cellViewModel)
}
result.append(group)
return result
}