リストの順序を保持したグルーピング

リストをグルーピング、しかしあくまでも順序付けされた並びの中でのグルーピングで同じキーのグループが複数あっても
まとめない。
という要求があった時、少し悩んだ。

サンプルとなるお題、、、要素のキーが以下のようにならんでいる場合、

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 要素からグルーピングするキー文字列を取得する 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 は、

groupingBy - Oboe吹きプログラマの黙示録


Java8 StreamでWicket ListViewセル結合 - Oboe吹きプログラマの黙示録


とメモを書いたことがあったけど、改めて考えると面白い。