リストを項目ごとに集計するときのデータオブジェクトの使い方

irofさんのブログを見て、自分ならどう書くかなーと思ったので、読み進める前に書いてみました。

その結果、本題のアルゴリズムとは全然関係ないところで疑問を持ったので、グデグデとメモします。

元エントリ

irofさんによる元エントリはこちら。

{code, 名前, 数値} というデータ構造のリストについて、code(idみたいなものです)をキーに数値を集計したい。このとき、Javaでどう書くか?というお話です。

書いてみた

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

class Data {
	String code;
	String name;
	int value;

	Data() {
	}

	Data(String initCode, String initName, int initValue) {
		code = initCode;
		name = initName;
		value = initValue;
	}
}

public class SummaryCount {

	protected List<Data> getTestData() {
		List<Data> returnList = new ArrayList<Data>();
		returnList.add(new Data("A01", "hoge", 100));
		returnList.add(new Data("A01", "piyo", 200));
		returnList.add(new Data("A02", "hoge", 300));
		returnList.add(new Data("A03", "hoge", 400));
		returnList.add(new Data("A03", "piyo", 500));
		return returnList;
	}

	public static void main(String[] args) {
		SummaryCount sc = new SummaryCount();
		List<Data> testDataList = sc.getTestData();
		Map<String, Integer> summaryMap = sc.getSummaryByCode(testDataList);

		sc.showResult(summaryMap);
	}

	/**
	 * 各Dataのcodeをキーとしてvalueを集計します。
	 * 
	 * @param dataList
	 * @return
	 */
	protected Map<String, Integer> getSummaryByCode(List<Data> dataList) {
		Map<String, Integer> map = new HashMap<String, Integer>();
		for (Data data : dataList) {
			if (map.containsKey(data.code)) {
				map.put(data.code, map.get(data.code) + data.value);
			} else {
				map.put(data.code, data.value);
			}
		}
		return map;
	}

	protected void showResult(Map<String, Integer> tempMap) {
		Set<String> set = tempMap.keySet();
		for (String key : set) {
			System.out.println(key + ", " + tempMap.get(key));
		}
	}
}

思ったこと

その1

コンパイラは、かならずしも式を左から右へ評価するとは限りません。そのため、=オペレータの右辺と左辺で同じ式を評価してはいけません。

しかし、argumentが評価されてから、メソッドの結果が評価されるという順序は、さすがに保証されているだろうと思ってこう書きました。

				map.put(data.code, map.get(data.code) + data.value);

これは、いいんですよね?? ちなみに、もしダメだったら、こう書かなければいけませんが・・・。

				int i = map.get(data.code);
				map.put(data.code, i + data.value)

さすがに大丈夫でしょう、はい。。。

その2

集計に使うデータ構造として、irofさんはMapを使われていました。

自分も最初同じように書いたのですが、データオブジェクトの使い方について疑問を持ったので、Mapに変更しました。(こうして日記を書くために)

集計中の関心の対象は、キーであるcodeフィールドと、valueフィールドです。では、valueフィールドにどんどんデータを足しこんでいく集計の間、関心外のフィールド、つまりnameの値は、どうなるでしょうか。ここでは特に設定していないため、不定です。そのことに違和感がありました。

こういった場合、集計用のデータオブジェクトを新たに定義するべきですか? そこまでするのは面倒くさいとき、Data型を使ってもよいものなのでしょうか? 自分が書いたように、適宜、集計用のCollectionを持つと、やっぱり分かりづらいでしょうか。

これは、人によって答えが違うかもしれません。

余談

Javaで書く限り、そんなにバリエーションはないだろうと高を括っていたのですが、コメント欄に貼られたxuwei-kさんの回答(https://gist.github.com/1429171)を見て愕然としました。Scalaを習得すればあんな世界観を獲得できるのか! 凄い!

また、ひしだまさんの記事も、勉強になりました。