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

Swift

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

// Tupleでもいいかも
// typealias RankingItem = (rank: Int, title: String, total: Int)
struct RankingItem {
    var rank: Int
    let title: String
    let total: Int
}

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

// 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, val title: String, val total: Int)

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

(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の最大値が入っているのでグラフ化もしやすいです。