[Swift Kotlin]配列の要素を種類別にカウントしてランキング表を作る

目次

Swift

let animals = ["イヌ", "ネコ", "ヤギ", "イヌ", "ネコ", "ヤギ", "イヌ", "ネコ", "イヌ", "ハムスター", "ヤギ", "モルモット", "ウサギ", "ウサギ"]

struct RankingItem {
    var rank: Int = 1
    var title: String = ""
    var total: Int = 0
}

let dict = animals.reduce(into: [:]) { $0[$1, default: 0] += 1 }
var items = dict.map { RankingItem(title: $0.0, total: $0.1) }.sorted { $0.total > $1.total }

// itemsが空でもレンジの右辺が0にならないようにしておく
// Fatal error: Can't form Range with upperBound < lowerBound
(1 ..< max(1, items.count)).forEach { i in
    items[i].rank = (items[i].total == items[i - 1].total) ? items[i - 1].rank : i + 1
}
print(items)

reduceの初期値を辞書にできるのと中身のdefault値を設定できるのでgroupingしてからカウントし直さなくて済みます。

集計結果

RankingItem(rank: 1, title: "イヌ", total: 4)
RankingItem(rank: 2, title: "ネコ", total: 3)
RankingItem(rank: 2, title: "ヤギ", total: 3)
RankingItem(rank: 4, title: "ウサギ", total: 2)
RankingItem(rank: 5, title: "ハムスター", total: 1)
RankingItem(rank: 5, title: "モルモット", total: 1)

Kotlin

val animals = listOf("イヌ", "ネコ", "ヤギ", "イヌ", "ネコ", "ヤギ", "イヌ", "ネコ", "イヌ", "ハムスター", "ヤギ", "モルモット", "ウサギ", "ウサギ")

// struct相当
data class RankingItem(
    var rank: Int = 1,
    val title: String = "",
    var total: Int = 0
)

val dict = animals.groupingBy { it }.eachCount()
val items = dict.map { RankingItem(1, it.key, it.value) }.sortedByDescending(RankingItem::total).toMutableList()

(1 until items.size).forEach { i ->
    items[i].rank = if (items[i].total == items[i - 1].total) items[i - 1].rank else i + 1
}
println(items)

配列を書き換えられるようにmutable形式にしておきます。

集計結果

RankingItem(rank=1, title=イヌ, total=4)
RankingItem(rank=2, title=ネコ, total=3)
RankingItem(rank=2, title=ヤギ, total=3)
RankingItem(rank=4, title=ウサギ, total=2)
RankingItem(rank=5, title=ハムスター, total=1)
RankingItem(rank=5, title=モルモット, total=1)

結果を出力する

ランキングの順位まで計算しておくとUITableViewのcellForRowAtのような場面でも値を渡すだけで表示させられます。また、最初の要素にtotalの最大値が入っているのでグラフ化もしやすいです。