読者です 読者をやめる 読者になる 読者になる

リスト順のままのグルーピングカウント

Java

リストをキーによってカウントする=グルーピングしてカウントするのに変な要求の題を突き付けられました。
例えば、以下のようなクラスのObjectのリスト、(内部にカウンタを持ちグルーピングカウントの結果を持つもの)とします。

public class Item{
   public int id;
   public String name;
   public int count;
   public Item(int id, String name){
      this.id = id;
      this.name = name;
   }
   @Override
   public String toString(){
      return "id=" + id + "  name=" + name + "  count="+count;
   }
}

これに対して、作成した List 以下のように作成したとして、、、

List<Item> list = new ArrayList<>();
list.add(new Item(1, "a"));
list.add(new Item(2, "a"));
list.add(new Item(3, "b"));
list.add(new Item(4, "b"));
list.add(new Item(5, "c"));
list.add(new Item(6, "a"));
list.add(new Item(7, "a"));

を グルーピングカウントして、Stream などで、

   list.stream().forEach(System.out::println);

の結果を、

id=1  name=a  count=4
id=2  name=a  count=0
id=3  name=b  count=2
id=4  name=b  count=0
id=5  name=c  count=1
id=6  name=a  count=0
id=7  name=a  count=0

ではなくて、、、リスト順にカウントして

id=1  name=a  count=2
id=2  name=a  count=0
id=3  name=b  count=2
id=4  name=b  count=0
id=5  name=c  count=1
id=6  name=a  count=2
id=7  name=a  count=0

のリストになるように、リストを作りなおしたいという変な結果を求める要求を突き付けられました。

困ったもので、悩んだあげく key と index と count を持つ次のようなクラスを用意して、、

public final class Meter implements Serializable{
   private String key;
   private int index;
   private int count;
   public Meter(){}
   // key, index, count 各々の setter, getter を用意
}

リストに対して、なんともクレイジーなコードを書くことになりました。

AtomicInteger index = new AtomicInteger(0);
Meter meter = new Meter();
list.stream().forEach(e->{
   int ix = index.getAndIncrement();
   if (meter.getCount()==0){
      meter.setKey(e.name);
      meter.setIndex(ix);
      meter.setCount(1);
   }else{
      if (meter.getKey().equals(e.name)){
         meter.setCount(meter.getCount()+1);
      }else{
         Item item = list.get(meter.getIndex());
         item.count = meter.getCount();
         list.set(meter.getIndex(), item);
         meter.setKey(e.name);
         meter.setIndex(ix);
         meter.setCount(1);
      }
   }
});
Item item = list.get(meter.getIndex());
item.count = meter.getCount();
list.set(meter.getIndex(), item);
list.stream().forEach(System.out::println);

の結果が、目的どおりにはなったけど、、こういう結果を求める仕様に釈然としません。

Arrays.asList が固定長のリストを返すことを忘れてはならない

Java

訳あって String を確実に作れるカンマ区切りの文字列と単独の宣言済の String 数個 から
単独の宣言済の String を先頭に、String
を作ることに少し悩みました。

例えば、、

String str = "a,b,c";
String h1 = "1";
String h2 = "2";
String h3 = "3";

String[] ary = Arrays.stream(str.split(","))
.collect(()->Arrays.asList(h1, h2, h3), (t, u)->t.add(u), (t,r)->r.addAll(t))
.toArray(new String[]{});

と思いつきで考えたのですが、これは、Arrays.asList が、固定長のリストを返してしまうので
その後の add で UnsupportedOperationException が発生してしまいます。

しかたなく、終端操作の Supplier の中で Stream で Listを生成してやることにしました。

String[] ary = Arrays.stream(str.split(","))
.collect(()->Arrays.stream(new String[]{h1, h2, h3}).collect(Collectors.toList())
        , (t, u)->t.add(u)
        , (t,r)->r.addAll(t)
).toArray(new String[]{});

GROUP_CONCAT を書く時の注意とメモ。

SQL

たまにしか書かない、GROUP_CONCAT の書式をよく忘れるのでメモ。

GROUP_CONCAT( 列名 [ ORDER  BY  順序つける列名 ] [ SEPARATOR 区切り文字] )

注意しなければならないのは CONCAT対象の列の値が NULL の場合、

SEPARATOR を指定しても区切り文字もつかず全てNULLだと空文字になってしまう。

 SEPARATOR ',' を指定しても全てNULLは期待する値 ",,,," のようにならない。

よって、

GROUP_CONCAT( COALESCE(列名 , 'NULL' SEPARATOR ',') 

のようにCOALESCE で逃げないと、正確な区切りを後で判定きない。

 

 

MySQL Error code 1418 , ストアドFUNCTION 作成で、

その他 SQL

MySQL で ストアドFUNCTION 作成で、

Error Code: 1418. This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

が出たら、

DETERMINISTICNO SQL、または READS SQL DATA のいずれかを CREATE FUNCTION ~BEGINの間に指定しないとならない。

詳しくは、、MySQLのマニュアルで。

 

CREATE FUNCTION  somefunc (a INT) RETURN  INT

DETERMINISTIC

BEGIN

 

 

リストの比較、ListDiff

Java

6年も前は、リストを比較して処理するのに、ブログ投稿したことを考えてた

http://oboe2uran.hatenablog.com/entry/2010/07/01/121003http://oboe2uran.hatenablog.com/entry/2010/07/01/121003


http://oboe2uran.hatenablog.com/entry/2010/06/26/132425http://oboe2uran.hatenablog.com/entry/2010/07/01/121003

でも、最近は、以下のように書くことが多くなった。

リスト、alist , blist がある時、

alist.stream().filter(e->!blist.contains(e)).forEach(e->{
   // alist だけに存在する処理
});

blist.stream().filter(e->!alist.contains(e)).forEach(e->{
   // blist だけに存在する処理
});

alist.stream().filter(e->blist.contains(e)).forEach(e->{
   // alist と blist 両方に存在する処理
});

でも、これは、filter(Predicate) を3回も注意して記述しなければならないし、contains で問い合わせて効率が
悪そう。

そこで考えたのが、
① リスト要素を等しいと判定する BiFunction
② 各々のリストだけに存在する要素に対する Consumer
③ 両方のリストに存在する要素に対する Consumer
を指定して比較処理を普遍的に実行すること。

import java.util.AbstractMap;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
 * リストの比較.
 *
 * ListDiff.of で、比較する2つのリスト要素を等しいと判定する BiFunction を指定してインスタンスを生成
 * 3つのメソッドの振る舞いを登録した後、diff メソッドを実行する。
 *
 * leftOnly  : 左側(1番目指定)リストだけに存在する要素に対する Consumer
 * rightonly : 右側(2番目指定)リストだけに存在する要素に対する Consumer
 * match     ; 両方のリストに存在する要素に対する BiConsumer
 *
 */
public final class ListDiff<T>{
	private BiFunction<T, T, Boolean> matchFunction;
	private Consumer<T> leftonly;
	private Consumer<T> rightonly;
	private BiConsumer<T, T> biConsumer;

	private ListDiff(BiFunction<T, T, Boolean> matchFunction){
		this.matchFunction = matchFunction;
	}
	
	public static <T> ListDiff<T> of(BiFunction<T, T, Boolean> matchFunction){
		 return new ListDiff<>(matchFunction);
	}
	public void leftOnly(Consumer<T> leftonly){
		this.leftonly = leftonly;
	}
	public void rightOnly(Consumer<T> rightonly){
		this.rightonly = rightonly;
	}
	public void match(BiConsumer<T, T> biConsumer){
		this.biConsumer = biConsumer;
	}
	public void diff(List<T> leftList, List<T> rightList){
		if (leftonly != null)
			leftList.stream().filter(e->rightList.stream()
			.noneMatch(t->matchFunction.apply(t, e))).forEach(leftonly);
		if (rightonly != null)
			rightList.stream().filter(e->leftList.stream()
			.noneMatch(t->matchFunction.apply(t, e))).forEach(rightonly);
		if (biConsumer != null)
			leftList.stream()
.map(e->new AbstractMap.SimpleEntry<T, T>(e
	, rightList.stream().filter(t->matchFunction.apply(t, e)).findFirst().orElse(null))
)
			.filter(e->e.getValue() != null).forEach(p->{
				biConsumer.accept(p.getKey(), p.getValue());
			});
	}
}

すると、、

ListDiff<String> listDiff = ListDiff.of((t1, t2)->t1.equals(t2));


listDiff.leftOnly(e->{
	// 後で処理する diff で、左(1番目のリスト)だけに存在する処理
});

listDiff.rightOnly(e->{
	// 後で処理する diff で、右(2番目のリスト)だけに存在する処理
});
listDiff.match((left, right)->{
	// 両方に存在する処理
});

// 比較の実行
listDiff.diff(alist, blist);

リスト要素を等しいと判定する記述が複雑で長くなるような要素のリストを比較する場合、
判定する記述は、1回の記述で済ませられるし、
この ListDiff インスタンスを使い回せることができる。

CSV書込み(2)

Java

CSV書込み、(1) - Oboe吹きプログラマの黙示録

のつづきです。

net.sf.csv4j.CSVWriter を使い、ヘッダ行もデータ行も呼び出し側の与えるデータの問題だと割り切って考えて

以下の関数型インターフェースを用意すれば、とても簡潔になる。

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.function.Supplier;
import net.sf.csv4j.CSVWriter;

/**
 * CSV生成インターフェース.
 */
@FunctionalInterface
public interface CsvCreator extends Serializable{

   public Supplier<Collection<String[]>> getLineElements();

   default public void create(OutputStream out, String charName){
      try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
         CSVWriter csvWriter = new CSVWriter(writer);
         for(String[] s:getLineElements().get()){
            csvWriter.writeLine(s);
         }
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
}

CSV出力データになる String[] のリスト

    List<String[]> list;

に対して

    CsvCreator c = ()->()->list;

で宣言して出力実行は、、、

try(OutputStream out = new FileOutputStream("sample.csv")){

   c.create(out, "MS932");

}catch(Exception e){
   e.printStackTrace();
}

で済んでしまう。




Java 8 が出た時にこういうことは思いつくべきで、今更である。

新しい技術が出たらできるだけ早く新技術を使用する仕事環境に身を置かないと腕が鈍る。。。

CSV書込み、(1)

Java

5年も前に CSVを生成する Javaプログラムコードを、csv4j を使って書いていた。


2011年 3月
http://oboe2uran.hatenablog.com/entry/2011/03/19/101643CSV書き込み(1) - Oboe吹きプログラマの黙示録

http://oboe2uran.hatenablog.com/entry/2011/03/22/210219CSV書き込み(2) - Oboe吹きプログラマの黙示録

http://oboe2uran.hatenablog.com/entry/2011/03/25/121714CSV書き込み(3) - Oboe吹きプログラマの黙示録

でも、1年前、これだと使い勝手が悪くて、 net.sf.csv4j.CSVWriter を使いたくなり、当時、環境としても Java7のままだったので、

次のインターフェースを用意して

public interface Csvoutable{
   /**
    * net.sf.csv4j.CSVWriter#writeLine(String[]) に渡す String[] を生成する。.
    * @return String[]
    */
   public String[] arrays();
}

このインターフェースで受け取った文字列配列をCSV出力する抽象クラスを用意して使った。

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.function.Supplier;
import net.sf.csv4j.CSVWriter;
/**
 * CSV作成抽象クラス.
 */
public abstract class CsvBinary{
   /**
    * ヘッダ行の提供.
    * @return CSVヘッダとなるString配列
    */
   protected abstract String[] getHeaders();

   /**
    * CSVコンテンツ行の提供.
    * @return CSVコンテンツとなる Csvoutable のコレクション
    */
   protected abstract Collection<? extends Csvoutable> getElements();

   /**
    * CSV生成.
    * @param bout 書込み先 OutputStream
    */
   public void createOut(OutputStream out){
      try{
         OutputStreamWriter writer = new OutputStreamWriter(out, "MS932");
         CSVWriter csvWriter = new CSVWriter(writer);
         csvWriter.writeLine(getHeaders());
         for(Csvoutable c:getElements()){
            csvWriter.writeLine(c.arrays());
         }
         writer.close();
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
}

使用例は、、

CsvBinary csvbinary = new CsvBinary(){
   @Override
   protected String[] getHeaders(){
      return new String[]{ "A", "B", "C" };
   }
   @Override
      protected Collection<? extends Csvoutable> getElements(){
      return getLineData();
   }
};

のように生成して、getElements で呼ぶgetLineDataは、、

rotected List<Csvoutable> getLineData(){
   List<Csvoutable> csvlist = new ArrayList<Csvoutable>();

   // DB読込み等ハンドラの中で以下を実行

   csvlist.add(new Csvoutable(){
      @Override
      public String[] arrays(){
         return new String[]{
            "11","12","テスト"
         };
      }
   });
   return csvlist;
}

CSVのヘッダ行とデータ行の書込みを明示的で見通しが良いと、1年前は思っていた。
でも、長くて複雑な出力要求がきたら、どうなんだ!?
→ そういう要件自体が問題なんですけど。

Csvoutable に、@FunctionalInterface() を付けたところで
1行書き出し文が、ラムダに代わるくらい、、、

Collection> を返すようメソッドを CsvBinary の中で、、
としても、、

たいして魅力的なコードにはならない。

→ つづきを、CSV書込み(2)で。。。