リスト要素の重複チェック

先日、リスト要素の重複チェックに、なるほど唸ってしまったものを方法を見つけました。
Java8 Streamの重複チェック (Collectorを使ってきれいに書く) - Qiita

static<T> Collector<T,?,Boolean> uniqueElements(){
    Set<T> set = new HashSet<>();
    return Collectors.reducing(true, set::add, Boolean::logicalAnd);
}

でも、これって、Java の Set の add が、オブジェクトの equals() メソッドでチェックするのだから、
equals 実装が正しくないクラスオブジェクトでこのまま、総称型のこのメソッドを使用するのは
危険だと思う。

そもそも、重複するもの抽出する例は、次のサンプルが書けます。。

List<String> list =
Arrays.asList("A", "B", "A", "C", "F", "D", "C", "C", "E", "F")
.stream()
.collect(Collectors.groupingBy(x->x, Collectors.counting()))
.entrySet().stream()
.filter(e->e.getValue() > 1)
.<String>map(e->e.getKey())
.collect(Collectors.toList());

重複カウントは次のように取得できます。

long lc =
Arrays.asList("A", "B", "A", "C", "F", "D", "C", "C", "E", "F")
.stream()
.collect(Collectors.groupingBy(x->x, Collectors.counting()))
.values().stream()
.filter(e->e > 1)
.collect(Collectors.counting());

だから、boolean にだってできます。

boolean b = Arrays.asList("A", "B", "A", "C", "F", "D", "C", "C", "E", "F")
.stream()
.collect(Collectors.groupingBy(x->x, Collectors.counting()))
.values().stream()
.filter(e->e > 1)
.collect(Collectors.counting()) > 0;

メソッドを定義するのが嫌なら、このようになるのかな。

重複存在すれば true を返すCollectors - Oboe吹きプログラマの黙示録

ツール daria の重複チェックを考える(2)

GitHub - yipuran/daria: Database data set Tool

を更新しました。詳細は Wiki に書きましたが抜粋すると、、
https://github.com/yipuran/daria/wiki

  • Excel 1行目のDBテーブル列名の書式を、太字にすることで対象の列データ一意制約チェックを実行して、一意制約に違反すれば該当シートのINSERT SQL文は一切実行されない。
  • INSERT文をテキストファイルに出力するだけの場合も一意制約チェックは、この1行目列名を太字にすることで既存のDBに格納されているレコードとのチェックも行う。
  • 一意制約チェックをするキーは複合キーにも対応しており、その場合は対象列1行目をすべて太字にする。
  • 一意制約チェックをすることで実行終了まで遅くなるが、TRUNCATEを実行しないINSERT文をテキストファイルに出力するだけの場合は、対象1行が一意になるか既存DBへのチェックがあるため 大量であれば遅くなりやすい。


バージョンは、1.2 になった。

Combinations のインスタンス生成を修正することにした

申し訳ないが、yipuran-core の Combinations のインスタンス生成方法を、ver 4.10 から
変えることにした。

使い回すようにしたいからだ。

yipuran-core/Combinations.java at master · yipuran/yipuran-core · GitHub

要素の重複を許す組み合わせ

yipuran-core で書いた Combinations
yipuran-core/Combinations.java at master · yipuran/yipuran-core · GitHub
は、要素の重複を許さない、組み合わせだ。
つまり、"A", "B", "C" の組み合わせで、"AAA" や、"AAB" という解を許さない Combination である。
この要素の重複、"AAA" や、"AAB" を組み合わせの解に持つ、nHr

homogeneous
Java で求めることを検討した。

まず、参考にさせていただいたのだが、https://ideone.com/gRQfPt であるが、これをちょっと加工して以下のように数字パターンで求めるもの

int n = 4;
int r = 3;
int[] d = new int[n > r ? n+1 : r+1];
for(int i=1; i <= n; i++) d[i] = 1;
while(d[0] <= 0){
   if (d[d.length-1] != 0){
      for(int i=1; i <= r ; i++)
         System.out.print(d[i]);
      System.out.println("");
   }
   for(int j=r; 0 <= j; j--){
      d[j]++;
      for(int k=j+1; k <=r; k++) {
         d[k] = d[k-1];
      }
      if (d[j] <= n) break;
   }
}

上のこれには、大きな欠点がある。n には、10以上を指定すると数字が要素に並んでしまうので区切りない。
n < r の場合、n=3 , r=5 では、想定以外の '11101' が結果として出てしまう。
また、数字ではなく文字やオブジェクト要素の組み合わせを求めるものではない。
現実的にはこのままでは使えない

そこで、以下のように Stream を使う。

public static <T> List<List<T>> homogeneous(List<T> list, int len){
   List<List<T>> rtn = new ArrayList<>();
   int n = list.size();
   int[] d = new int[n > len ? n+1 : len+1];
   for(int i=1; i <= n; i++) d[i] = 1;
   while(d[0] <= 0){
      if (d[d.length-1] != 0){
         List<T> lt = Arrays.stream(d).boxed()
            .filter(e->e > 0)
            .map(e->list.get(e-1))
            .limit(len)
            .collect(Collectors.toList());
         if (lt.size()==len){
            rtn.add(lt);
         }
      }
      for(int j=len; 0 <= j; j--){
         d[j]++;
         for(int k=j+1; k <= len; k++) {
            d[k] = d[k-1];
         }
         if (d[j] <= n) break;
      }
   }
   return rtn;
}

filter と、limit で組み合わせる数を指定するところが、ミソである。

ツール daria の重複チェックを考える(1)

Excel で記述したデータを Database テーブルにセットするツール daria
GitHub - yipuran/daria: Database data set Tool
で、実装していない機能がある。
それは、格納するデータの重複制限チェックである。
daria は、DBの対象テーブル構造をチェックするのだから、ついでに Primary Key もチェックすれば
いいじゃないかと検討していたが、daria は、1種類のDBだけをサポートするのではなく、
 Oracle
 MySQL
 PostgreSQL
 H2
等をサポートするコンセプトであるので、そこまでやると、UNIQUE Key のことも考えると
スキーマのテーブル情報を読んでチェックするのが正しいと解っていても躊躇してしまう。
それに、daria の実行オプションとして、TRUNCATE して書込み実行するのではなく、
単に、INSERT文をテキストファイル出力するだけの機能を使う場合は、既存に格納されたレコードと
一意制約チェックするとなると、面倒で躊躇していた。

指定する データ Excel の1行目ヘッダ行のカラム名記述を、Bold (太字)にすると一意のキーである。
という約束にすれば、複数列でのキーによる一意制約のチェックも簡単であるし、
何より、データ値を記述する際にマーカーとしてもすぐに判別できる。

Excel でセルが太字で記述されたかどうかは、Apache-POI では以下の戻り値が true である。

XSSFCell から、getCellStyle() で取得する org.apache.poi.xssf.usermodel.XSSFCellStyle から、
org.apache.poi.xssf.usermodel.XSSFFont を取得して、
更に、getBold() の結果が true なら 太字

近日中に実装しようかと考えてるが、
TRUNCATEしない実行では、既存のレコードとの重複チェックまで実行すると遅くなり、
Excelデータだけをチェックすることを標準にして、遅くなってもDB内との突合せチェックする為に、
完全チェックオプションを追加するしかないかもしれない。

オブジェクトのコピーで除外フィールドを指定可能とするかどうか

以前作成した yipuran-core の中のField ユーティリティ
yipuran-core/FieldUtil.java at master · yipuran/yipuran-core · GitHub

これに、コピー元のフィールドで、コピー除外対象を指定できるようにした方が良いか迷っている。
→ FieldUtil に新しい static メソッドを追加することになる。
コピーメソッドが、FieldUtil インスタンスメソッドの形だったら、チェインメソッドで除外したいフィールド指定メソッドを
連結させるのだが、コピーメソッドは、static メソッドで設計したものだ。

そこで、新たに引数を追加して、ポリモフィズムとして、

public static <R, T> R copy(T t, Supplier<R> s, Predicate<String> p)

のメソッドを追加することだ。
Predicate<String> p は、negate() が実行されて処理することが前提になる。
これは、コピー元 T のフィールド名文字列が、指定する Predicate<String> p で不一致条件になることで
コピー対象から除外させようという考えだ。

除外フィールド名を書きならべても良いのだが、それよりも正規表現による除外を指定できたり、いろいろできる方が
良いと思った。

でも、メソッドを呼び出し側が一見、誤解をしないだろうか?

yipuran-core の FieldUtil にメソッド追加すべきかどうか迷っている。