問い合わせマトリクスの実装

複数の条件、条件の数にもよるが表にまとめないと管理が辛いことは、よくあるケースです。
条件数も少なめで、求めた結果も偏りがあるなら、if 文のネストを書いてもなんとか人の思考も
時間的に追いつくでしょう。
案外、条件1つ増えただけでも、数学的に結構な組み合わせ結果は増えていくもので、
if 文を連続して書いていくのって賢くないのでは?と思うことが多くあります。
f:id:posturan:20191230140004j:plain
↑のような表の条件表、条件A~Cの Y or N に対する、結果 Result TRUE/FALSE の表が存在する時、
if 文を書かないで、結果 Result を求める方法

結果の方向が少ない方だけを、A→B→C 順=縦順で Y/N を連結して最後に、Result TRUE/FALSEを
連結した String[] を用する。

private String[] patterns = {
   "Y_Y_N_true", "Y_N_N_true", "N_Y_Y_true"
};

これをクラスのコンストラクタ内で、A→B→Cの Y/Nの連結をキーに、Result TRUE/FALSEを値の
Map にする。

private Map<String, Boolean> map;
map = Arrays.stream(patterns).collect(()->new HashMap<String, Boolean>()
, (r, t)->r.put(t.substring(0, t.lastIndexOf("_")), Boolean.valueOf(t.substring(t.lastIndexOf("_") + 1)))
, (r, u)->{});

これで準備が整い、A,B.Cの条件の問い合わせは、

public boolean query(String a, String b, String c) {
   return Optional.ofNullable(map.get(a+"_"+b+"_"+c)).orElse(false);
}

と、完全にタイプセーフではないが、複雑なif 文を書かなくてすむ。

でも、条件数が多くて上の String[] patterns など、コードに大量に書いてられないという場合も
あるだろう。

では、条件表ファイルをプログラムに読込ませるという考え方を採用する。
Excel のまま読むこともできるが、プロジェクトによっては、Apache-POI で読込む処理の導入を禁止
することもあるだろう。管理上、そのまま Excel読込んだ方が良いとは思うが。。。
せめても、Excel からCSVにしたファイルで実装する。
yipuran-csvCsvprocess を使わせてもらう)
クラスと同じ場所に、pattern.csv というファイル名でCSVを用意した実装
まだ、Java8 使用のプロジェクトが世の中には多いので仕方なく Java8 で書く。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.yipuran.csv.Csvprocess;
import org.yipuran.csv4j.ParseException;
import org.yipuran.csv4j.ProcessingException;
/**
 * Matrix
 */
public class Matrix{
   private Map<String, Boolean> map;

   public Matrix(){
      Map<Integer, String> cmap = new HashMap<>();
      String csvpath = Matrix.class.getPackage().getName().replaceAll("\\.", "/") + "/pattern.csv";
      try(InputStream in = new FileInputStream(
             new File(ClassLoader.getSystemClassLoader().getResource(csvpath).toURI()))
      ){
         Csvprocess csvproces = new Csvprocess();
         csvproces.read(new InputStreamReader(in, "MS932"), h->{}, (n, p)->{
            IntStream.range(1, p.size()).boxed().forEach(i->{
               Optional.ofNullable(cmap.get(i)).<Runnable>map(e->()->{
                  cmap.put(i, e + "_" + p.get(i));
               }).orElse(()->{
                  cmap.put(i, p.get(i));
               }).run();
            });
         });
         map = cmap.values().stream().collect(()->new HashMap<String, Boolean>()
               , (r, t)->r.put(t.substring(0, t.lastIndexOf("_"))
                               , Boolean.valueOf(t.substring(t.lastIndexOf("_") + 1)))
               , (r, u)->{});
      }catch(IOException ex){
         ex.printStackTrace();
      }catch(URISyntaxException ex){
         ex.printStackTrace();
      }catch(ParseException ex){
         ex.printStackTrace();
      }catch(ProcessingException ex){
         ex.printStackTrace();
      }
   }
   public Optional<Boolean> query(List<String> list){
      return Optional.ofNullable(map.get(list.stream().collect(Collectors.joining("_"))));
   }
   public Optional<Boolean> query(String...ary){
      return Optional.ofNullable(map.get(Arrays.stream(ary).collect(Collectors.joining("_"))));
   }
}

query メソッドで、条件を順序を考慮してリストで呼び出す。

でも、↑は結果をBoolean に限定していて良くない!

結果Object を、総称型とし、CSVファイルは File で指定させる。
関数型インターフェース Function で、最終行の結果文字列 String から
結果Object を求めるラムダを指定させるようにする。

これも、まだ Java8 使用のプロジェクトが世の中には多いので仕方なく Java8 で書く。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.yipuran.csv.Csvprocess;
import org.yipuran.csv4j.ParseException;
import org.yipuran.csv4j.ProcessingException;

/**
 * MatrixModel
 */
public class MatrixModel<T>{
   private Map<String, T> map;

   public MatrixModel(File file, Function<String, T> resultFunc) {
      Map<Integer, String> cmap = new HashMap<>();
      try(InputStream in = new FileInputStream(file)){
         Csvprocess csvproces = new Csvprocess();
         csvproces.read(new InputStreamReader(in, "MS932"), h->{},(n, p)->{
            int cmax = p.size();
            IntStream.rangeClosed(1, cmax).boxed().forEach(i->{
               Optional.ofNullable(cmap.get(i)).<Runnable>map(e->()->{
                  cmap.put(i, e + "_" + p.get(i));
               }).orElse(()->{
                  cmap.put(i, p.get(i));
               }).run();
            });
         });
         map = cmap.values().stream().collect(()->new HashMap<String, T>()
               , (r, t)->r.put(t.substring(0, t.lastIndexOf("_"))
                           , resultFunc.apply(t.substring(t.lastIndexOf("_") + 1)))
               , (r, u)->{});
      }catch(IOException ex){
         ex.printStackTrace();
      }catch(ParseException ex){
         ex.printStackTrace();
      }catch(ProcessingException ex){
         ex.printStackTrace();
      }
   }
   public Optional<T> query(List<String> list){
      return Optional.ofNullable(map.get(list.stream().collect(Collectors.joining("_"))));
   }
   public Optional<T> query(String...ary){
      return Optional.ofNullable(map.get(Arrays.stream(ary).collect(Collectors.joining("_"))));
   }
}

インスタンス生成の都度、CSV読込みでそれが嫌ならシングルトン化でもすれば良いであろう。

呼出し、、、

MatrixModel<Boolean> m = new MatrixModel<>(file, e->Boolean.valueOf(e));

if (m.query("Y", "N", "N").orElse(false)){
      //  TODO  Result : True の処理、、、
}