リストをグルーピング、しかしあくまでも順序付けされた並びの中でのグルーピングで同じキーのグループが複数あっても
まとめない。
という要求があった時、少し悩んだ。
サンプルとなるお題、、、要素のキーが以下のようにならんでいる場合、
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 を引数にした static メソッドを用意した。
リスト→グループ開始index:カウントのマップ生成.
@param list
@param getKey
@return
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 は、
groupingBy - Oboe吹きプログラマの黙示録
Java8 StreamでWicket ListViewセル結合 - Oboe吹きプログラマの黙示録
とメモを書いたことがあったけど、改めて考えると面白い。