groupingBy と toMap のメモ

Java Stream groupingBy と toMap のメモを今更だけど、テンプレートとしてメモの整理

List<Item> list

groupingBy

Map<String, List<Item>> map = list.stream().collect(Collectors.groupingBy(t->t.name));

ちゃんと書く

Map<String, List<Item>> map 
= list.stream().collect(Collectors.groupingBy(t->t.name , Collectors.mapping(u->u, Collectors.toList())));

TreeMap にしたい。。

Map<String, List<Item>> map = list.stream().collect(Collectors.groupingBy(t->t.name
   , TreeMap<String, List<Item>>::new, Collectors.mapping(u->u, Collectors.toList()) ) );

toMap

Map<String, List<Item>> map1 = list.stream().collect(Collectors.toMap(t->t.name
   , u->new ArrayList<Item>(Collections.singletonList(u))
   , (a, b)->Stream.concat(a.stream(), b.stream()).collect(Collectors.toList())
));

TreeMap にしたい。。

Map<String, List<Item>> map2 = list.stream().collect(Collectors.toMap(t->t.name
   , u->new ArrayList<Item>(Collections.singletonList(u))
   , (a, b)->Stream.concat(a.stream(), b.stream()).collect(Collectors.toList())
   , ()->new TreeMap<String, List<Item>>()
));

カウントするなら、

Map<String, Long> countmap = list.stream().collect(
   Collectors.groupingBy(t->t.name, Collectors.mapping(u->u, Collectors.counting()))
);

長さ制限しない重複順列

先日公開した要素の重複(繰り返しを許す)順列、RepeatablePermutation
yipuran-core/RepeatablePermutation.java at master · yipuran/yipuran-core · GitHub
これは、元のリストより大きい長さの順列を抽出しようとすると、
size over の IllegalArgumentException を発生させるようにしている。

RepeatablePermutation.of(Arrays.asList("A", "B", "C")).compute(4);

は、IllegalArgumentException としている。
元のリストより大きい長さを求められるようにこれ以上、ロジックを修正するのが面倒であり、
以下の手段で作成できる。
https://github.com/yipuran/yipuran-core/tree/master/src/main/java/org/yipuran/util/pch
の下に公開した Homogeneous (重複あり組み合わせ)を求めてから
RepeatablePermutation を求める方法である。
サンプル

Homogeneous.of(Arrays.asList("A", "B", "C")).compute(4).stream()
.map(e->RepeatablePermutation.of(e).compute(4))
.collect(ArrayList<List<String>>::new, (r, t)->r.addAll(t), (r, t)->{})
.stream().distinct()
.map(e->e.stream().collect(Collectors.joining(""))).forEach(System.out::println);


distinct() を実行させて、余計に抽出したものを取り除いている。
このようにサンプルは。文字列だからそのまま distinct() が有効であるが、
任意のクラスオブジェクトであれば、
正しく、equals メソッドと hashCode メソッドがoverride できていれば、
この distinct が使える。

また、このままでは並びが元のリスト並びからあまりにもかけ離れてしまうので、
以下のように、ソートすれば、すっきりするだろう。

Homogeneous.of(Arrays.asList("A", "B", "C")).compute(4).stream()
.map(e->RepeatablePermutation.of(e).compute(4))
.collect(ArrayList<List<String>>::new, (r, t)->r.addAll(t), (r, t)->{})
.stream().distinct()
.sorted((a, b)->{
      int r = 0;
     for(int i=0;i < a.size(); i++){
          if ((r = a.get(i).compareTo(b.get(i))) != 0) break;
     }
     return r;
})
.map(e->e.stream().collect(Collectors.joining(""))).forEach(System.out::println);

もっと短く書く

Homogeneous.of(Arrays.asList("A", "B", "C")).compute(4).stream()
.map(e->RepeatablePermutation.of(e).compute(4))
.collect(ArrayList<List<String>>::new, (r, t)->r.addAll(t), (r, t)->{})
.stream().distinct()
.sorted((a, b)->{
    int r=0,i=0;
    while((r = a.get(i).compareTo(b.get(i)))==0) i++;
    return r;
})
.map(e->e.stream().collect(Collectors.joining(""))).forEach(System.out::println);

Java マップの値からキーの参照

過去、何度も書いているかもしれない「マップの値からキーの参照」という命題
結果をStream で取得することにして、、、

public static <K , V> Stream<K> getKeysWithValue(Map<K, V> map, V v){
   return map.entrySet().stream()
             .filter(e->v.equals(e.getValue())).map(Map.Entry::getKey);
}

呼出し、、、

getKeysWithValue(map, "A").forEach(System.out::println);

equalsメソッドが使用できないなら、、

public static <K , V> Stream<K> getKeysWithCondition(Map<K, V> map, V v, BiPredicate<V, V> p){
   return map.entrySet().stream()
         .filter(e->p.test(v, e.getValue())).map(Map.Entry::getKey);
}

BiPredicate の第1引数=メソッドで指定するV
BiPredicate の第2引数=マップから取得したV

BiPredicateではなくて、Predicate で、、

public static <K , V> Stream<K> getKeysWithCondition(Map<K, V> map, Predicate<V> p){
   return map.entrySet().stream()
          .filter(e->p.test(e.getValue())).map(Map.Entry::getKey);
}
getKeysWithCondition(map, v->"A".equals(v)).forEach(System.out::println);

でも、こうなると、わざわざ、メソッドとして定義する必要なんて無いんじゃないか?
と思ってしまう。
でも、最初の

public static <K , V> Stream<K> getKeysWithValue(Map<K, V> map, V v){
   return map.entrySet().stream()
          .filter(e->v.equals(e.getValue())).map(Map.Entry::getKey);
}

は、結構良いかなあ??

重複順列

要素の重複有りの順列(Permutation)Java で、いろんなオブジェクトに対して作成できるようにする。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * RepeatablePermutation
 */
public class RepeatablePermutation<T>{
   private int r, u;
   private int[] c;
   private int[] p;
   private List<List<T>> result;
   private List<T> list;

   private RepeatablePermutation(List<T> list){
      this.list = list;
      c = new int[list.size()];
      p = new int[list.size()];
      for(int i=0; i < c.length; i++) c[i] = i;
   }

   public static <T> RepeatablePermutation<T> of(List<T> list){
      return new RepeatablePermutation<>(list);
   }

   public static <T> RepeatablePermutation<T> of(T[] ary){
      return new RepeatablePermutation<>(Arrays.asList(ary));
   }

   public List<List<T>> compute(int len){
      if (len > list.size()) throw new IllegalArgumentException("list size over");
      result = new ArrayList<>();
      r = len;
      u = c[c.length-1] - len;
      for(int i=0; i <= c.length; i++)
         calc(0, i);
      return result;
   }

   private void calc(int i, int j){
      p[i] = j;
      if (i == r - 1){
         List<T> t = new ArrayList<>();
         for(int k=0; k < r; k++){
            if (p[k] < c.length)
               t.add(list.get(c[p[k]]));
         }
         if (t.size()==r)
            result.add(t);
      }
      if (i < r - 1){
         int k=0;
         for(; k <= r;k++)
            calc(i+1, k);
         if (u > 0){
            for(int n=0; n < u; n++)
               calc(i+1, c[k + n] );
         }
      }
   }
}

サンプル
文字列 "A", "B", "C”, "D" から、3個の順列(重複あり)

RepeatablePermutation<String> r = RepeatablePermutation.of(Arrays.asList("A", "B", "C", "D"));
r.compute(3).stream()
.map(e->e.stream().collect(Collectors.joining("")))
.forEach(System.out::println);

整数の場合

RepeatablePermutation<Integer> r = RepeatablePermutation.of(Arrays.asList(0, 1, 2, 3, 4));
r.compute(3).stream()
.map(e->e.stream().map(i->i.toString()).collect(Collectors.joining("")))
.forEach(System.out::println);

hashCode と equals を簡単に実装

Java クラスの hashCode と equals メソッドを手書きするのは、結構面倒くさい。
IDE Eclipse で自動生成してもいいけど、毎回、各々これで書くのもなんだか。。。

Apache の commons-lang3 にある hashCode と equals のビルダーを使うと、
書く量も少なくて良いかも。
http://commons.apache.org/proper/commons-lang/
インポート

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

記述

@Override
public int hashCode(){
   return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj){
   return EqualsBuilder.reflectionEquals(this, obj);
}

これを使う利点は、DTO 等の基底クラスで、これを書いて基底クラスを
継承させると効果が引き継がれることだ。

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
/**
 * BaseDto
 */
public class BaseDto{
   @Override
   public int hashCode(){
      return HashCodeBuilder.reflectionHashCode(this);
   }
   @Override
   public boolean equals(Object obj){
      return EqualsBuilder.reflectionEquals(this, obj);
   }
}

これを、、、

public class Foo extends BaseDto {

とするだけで、equals と hashCode は機能する。HashSet の add を実行して試してみればわかる。

沢山 DTO クラスの記述が必要で equals と hashCode メソッド実装の必要性があるなら
便利な方法であろう。

東京オリンピック延期なら、Java祝日計算プログラムをまた、修正しなきゃならない。。。

3月24日のニュースを聞いて、東京オリンピックが1年延期なら、
またもや、Java祝日計算プログラムを修正しなきゃならない。のか!?

Java祝日計算 プロジェクト日本語トップページ - OSDN

プログラムは、2020年限定の処理部分を修正すれば良いのだが、
以下のURLのページが更新されるか、または、発表があってからにしようと思う。
2020年限定の祝日移動について | 首相官邸ホームページ

1つの Java ソースだけで完結させること。よけいなライブラリ、
リソースを読む形式にしないことをコンセプトにした
プログラムである。
10年も前から書いて修正してきたこともあり、
そのせいなのか、はっきり言って汚いコードでもあります。
書いた本人が綺麗なコードと思っていません。
Git-hub に引っ越しもしていない。。

当初は Java 版だけを書いていたが、途中から JavaScript 版も書いている。
Python はいろいろと出回ってるので今更、書くつもりはない。

配列、リストの要素が全て同じかチェックする

Java8 でも動くことを前提にする。(まだ Java8 を使う企業が多いからだ)

配列
equals で比較できる場合

public static <T> boolean matchElements(T[] ary) {
   if (ary.length < 2) return true;
   for(int i=0, j=1; j < ary.length; i++, j++) {
      if (!ary[i].equals(ary[j])) return false;
   }
   return true;
}

比較を BiPredicate にすることで equals と違う方法で判定する

public static <T> boolean matchElements(T[] ary, BiPredicate<T, T> m) {
   if (ary.length < 2) return true;
   for(int i=0, j=1; j < ary.length; i++, j++) {
      if (!m.test(ary[i], ary[j])) return false;
   }
   return true;
}

null がある場合、↑の BiPredicate を記述する中で対処すれば良いのだが、
あえて null の場合に BiPredicate に渡すものを定義するなら、

public static <T> boolean matchElements(T[] ary, BiPredicate<T, T> m, Supplier<T> nsup) {
   if (ary.length < 2) return true;
   for(int i=0, j=1; j < ary.length; i++, j++) {
      if (!m.test(Optional.ofNullable(ary[i]).orElse(nsup.get())
            , Optional.ofNullable(ary[j]).orElse(nsup.get()))) return false;
   }
   return true;
}

List で、イテレータを使う場合、

public static <T> boolean matchElements(List<T> list, BiPredicate<T, T> p) {
   for(ListIterator<T> it = list.listIterator(); it.hasNext();){
      T t = it.next();
      if (it.hasPrevious()){
         T u = it.previous();
         if (it.hasPrevious()){
            u = it.previous();
            if (!p.test(u, t)) return false;
            it.next();
         }
         it.next();
      }
   }
   return true;
}

null の場合に BiPredicate に渡すものを定義するなら、

public static <T> boolean matchElements(List<T> list, BiPredicate<T, T> p, Supplier<T> nsup) {
   for(ListIterator<T> it = list.listIterator(); it.hasNext();){
      T t = it.next();
      if (it.hasPrevious()){
         T u = it.previous();
         if (it.hasPrevious()){
            u = it.previous();
            if (!p.test(Optional.ofNullable(u).orElse(nsup.get())
                  , Optional.ofNullable(t).orElse(nsup.get()))) return false;
            it.next();
         }
         it.next();
      }
   }
   return true;
}


Stream で処理、
Collectors.groupingBy でカウントして調べる

public static <T, K> boolean matchElements(Collection<T> c, Function<T, K> m) {
   return c.stream().collect(Collectors.groupingBy(t->m.apply(t), Collectors.counting())).size()==1;
}

但し、これはFunction が null を返してはならない。

要素が null の場合を考慮していないので、null を除くなら

public static <T, K> boolean matchElements(Collection<T> c, Function<T, K> m) {
   return c.stream().filter(t->t != null)
      .collect(Collectors.groupingBy(t->m.apply(t), Collectors.counting())).size()==1;
}

そもそも、null の場合、別の値に置き換えて Function を実行させれば、Function の記述も楽になる。

public static <T, K> boolean matchElements(Collection<T> c, Function<T, K> m, Supplier<T> nullTemp) {
   return c.stream().map(t->Optional.ofNullable(t).orElse(nullTemp.get()))
      .collect(Collectors.groupingBy(t->m.apply(t), Collectors.counting())).size()==1;
}