日々常々

ふつうのプログラマがあたりまえにしたいこと。

リストを項目ごとに集計する

2015/5/3 に続きっぽいのを書きました → リストを項目ごとに集計する - Java8ばーじょん - 日々常々

データをコードごとに集計することってのは結構よくあります。

例えばこんなデータを…

code name value
A01 hoge 100
A01 piyo 200
A02 hoge 300
A03 hoge 400
A03 piyo 500

codeごとに集計してこうしたい。

code value
A01 300
A02 300
A03 900

現場でよく見るのはこんな感じになってます。

// データはこれのリストに入ってるてことで。
class Data {
	String code;
	String name;
	int value;
}

public List<Data> summary(List<Data> list) {
	List<Data> result = new ArrayList<>();
	Data data = null;
	for (Data d : list) {
		if (data == null || !data.code.equals(d.code)) {
			if (data != null) {
				result.add(data);
			}
			data = new Data();
			data.code = d.code;
		}
		data.value += d.value;
	}
	if (data != null) {
		result.add(data);
	}
	return result;
}

一般的にどういうのかは知らないですが、私の現場では「ブレイク処理」とか言われてます。コードが変わるタイミングでブレイクするとかそんな意味らしいです。
前のDataを保持しながらcodeが変わっているかを比較し、変わっていたら結果リストに前のDataを追加して新しいDataを作り、codeが変わっていなかったら前のDataに、valueを加算する。ループが終わったら結果に最後まで残ってたDataを追加する。言葉で書くとわけわかんない…。
データは元々RDBに入ってたりするので、「集計はSQLでやれば良いのにー」と思わなくはないのですが、なんかあんまりやらないですね。取得したデータをさらに加工しながら集計するとかもあるので、この辺は一概には言えないところかも。まぁそれはとりあえず置いておきます。
この処理はだいたい定形的に書かれるのですけど、正直意味が分かりにくいし、よくバグってるのを見ます。こんな面倒なことやってたらバグっても仕方ないと思うのです。でも、こういうのの典型的なのってどう書くものなんだろう。


私はこんなふうに書いたりします。

public static List<Data> summary(List<Data> list) {
	Map<String, Data> map = new LinkedHashMap<>();
	for (Data d : list) {
		if (!map.containsKey(d.code)) {
			Data data = new Data();
			data.code = d.code;
			map.put(d.code, data);
		}
		map.get(d.code).value += d.value;
	}
	return new ArrayList<Data>(map.values());
}

入力値がコードでソートされていない場合の挙動が違いますが、そこは同じコードを集計するのが目的だとかなんとか言ってごまかす感じで。なんかこう、スマートな書き方あったら、教えて下さると、とても嬉しい…。