リストをグルーピング、しかしあくまでも順序付けされた並びの中でのグルーピングで同じキーのグループが複数あっても
まとめない。
という要求があった時、少し悩んだ。
サンプルとなるお題、、、要素のキーが以下のようにならんでいる場合、
A , A , A , B , B , A , A , D , C , C , E , E
求めたい結果は、順序どおりのグルーピングで、リストの INDEX とカウントで以下のような結果が欲しいのが
今回のお題。
index = 0 count = 3 → A , A , A の3個をカウント index = 3 count = 2 → B , B の2個をカウント index = 5 count = 2 → A , A の2個をカウント index = 7 count = 1 → D の1個をカウント index = 2 count = 2 → C , C の2個をカウント index = 2 count = 2 → E , E の2個をカウント
このように、A のキーをグルーピングといっても隣接だけのグルーピングでカウントする、つまりリストの並びに沿う。
何の目的で、このような結果を求めたいかというと、Excel や HTMLで表現するテーブル表にこのカウント方法に従った結果が
欲しいことがあるからだ。
Javaストリームの java.util.stream.Collectors#groupingBy を使ってしまうと、この上のサンプルで、A は 5個の
グルーピングになってしまう。
さらに、リストの 開始INDEXを求めたい。
このような課題が出現する箇所でループ文をいつもコーディングするのは、毎回コーディングに気をつけなくてはならない。
のでバグを書きやすい。
リストの要素からキーを求める java.util.function.Function
/** * リスト→グループ開始index:カウントのマップ生成. * @param list リスト * @param getKey 要素からグルーピングするキー文字列を取得する Function * @return key=グループ開始index , value=グルーピングしたカウント */ public static <E> Map<Integer, Integer> groupingCountIndexMap(List<E> list, Function<E, String> getKey){ Map<Integer, Integer> map = new HashMap<>(); if (list.size()==0) return map; int index = 0; int count = 1; int row = 0; ListIterator<E> it = list.listIterator(); while(it.hasNext()){ String s = getKey.apply(it.next()); if (it.hasNext()){ if (s.equals(getKey.apply(it.next()))){ count++; }else{ map.put(row, count); count = 1; row = index + 1; } it.previous(); } index++; } map.put(row, count); return map; }
テスト例、、、
文字列リストでも試せるが、それではあまりにも芸がないので、Item というクラスが、public属性文字列 name を
持っているとする。
public class Item{ public String name; }
A , A , A , B , B , A , A , D , C , C , E , E の並びで、List
List<Item> list; /* 省略 */ Map<Integer, Integer> map = groupingCountIndexMap(list, e->e.name); System.out.println(map);
標準出力として次の結果が得られる。
{0=3, 3=2, 5=2, 7=1, 8=2, 10=2}
以前、groupingBy は、
Java8 StreamでWicket ListViewセル結合 - Oboe吹きプログラマの黙示録
とメモを書いたことがあったけど、改めて考えると面白い。