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

リストの比較、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 インスタンスを使い回せることができる。