Java 8でStreamのMerge処理

Java 8のStream API。Zip Withが標準ライブラリから落ちてしまいました。
使用頻度は高いので、用意してみました。

public class ZipWithHelper {
  private static class ZipWithIterator<A, B, C> implements Iterator<C> {
    private final Iterator<? extends A> aIterator;
    private final Iterator<? extends B> bIterator;
    private final BiFunction<? super A, ? super B, C> mapper;

    public ZipWithIterator(Iterator<? extends A> aIterator, Iterator<? extends B> bIterator,
        BiFunction<? super A, ? super B, C> mapper) {
      this.aIterator = aIterator;
      this.bIterator = bIterator;
      this.mapper = mapper;
    }

    @Override
    public boolean hasNext() {
      return aIterator.hasNext() && bIterator.hasNext();
    }

    @Override
    public C next() {
      return mapper.apply(aIterator.next(), bIterator.next());
    }
  }

  public static <A, B, C> Stream<C> zipWith(Stream<? extends A> as, Stream<? extends B> bs,
      BiFunction<? extends A, ? extends B, C> mapper) {
    ZipWithIterator iterator = new ZipWithIterator(as.iterator(), bs.iterator(), mapper);
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
  }
}

Zip Withもそうなんですが、もともとは、Stream<A>、Stream<B>の結合処理をしたいよね、という話で始まっています。

どちらか片方Stream(Aとします)のキーでMapを作って、そのMapに対してもう片方のStream(B)でループを回せばいいじゃないかという話なのですが、
Stream A、Bのサイズが共に大きくて、Mapをメモリ上に確保する余裕がない場合に、せっかくStream APIがあるんだから、Inner Join的な結合(歯抜けになっている可能性のあるZip With)をする処理を書けばいいじゃないかという話になったのです。
つまり、メモリ消費量は定数、計算量はO(N+M)ということですね。

その時は、「驚くべきコードを見つけたがそれを書くには余白が云々」的なノリでごまかしたのですが、えー書けないの?と思われるのも癪なので書いてみました。

public class MergeJoinHelper {
  private static class MergeJoinIterator<A, B, C> implements Iterator<C> {
    private final Iterator<? extends A> aIterator;
    private final Iterator<? extends B> bIterator;
    private final MergeJoinComparator<? super A, ? super B> comparator;
    private final BiFunction<? super A, ? super B, C> mapper;

    private A currentA = null;
    private B currentB = null;
    private A nextA = null;
    private B nextB = null;
    private boolean searched = false;

    public MergeJoinIterator(Iterator<? extends A> aIterator, Iterator<? extends B> bIterator,
        MergeJoinComparator<? super A, ? super B> comparator, BiFunction<? super A, ? super B, C> mapper) {
      this.aIterator = aIterator;
      this.bIterator = bIterator;
      this.comparator = comparator;
      this.mapper = mapper;
      this.nextA = aIterator.hasNext() ? aIterator.next() : null;
      this.nextB = bIterator.hasNext() ? bIterator.next() : null;
    }

    public boolean hasNext() {
      return searchForNextCandidate();
    }

    public C next() {
      if (!searchForNextCandidate()) {
        throw new NoSuchElementException();
      }
      searched = false;
      return mapper.apply(currentA, currentB);
    }

    private boolean searchForNextCandidate() {
      if (searched) {
        return comparator.compare(currentA, currentB) == 0;
      }
      searched = true;
      while (nextA != null && nextB != null) {
        while (comparator.compare(nextA, nextB) > 0) {
          if (!bIterator.hasNext()) {
            nextB = null;
            return false;
          }
          nextB = bIterator.next();
        }
        while (comparator.compare(nextA, nextB) < 0) {
          if (!aIterator.hasNext()) {
            nextA = null;
            return false;
          }
        }
        if (comparator.compare(nextA, nextB) == 0) {
          currentA = nextA;
          currentB = nextB;
          nextA = aIterator.hasNext() ? aIterator.next() : null;
          nextB = bIterator.hasNext() ? bIterator.next() : null;
          return true;
        }
      }
      return false;
    }
  }

  public static <A, B, C> Stream<C> mergeJoin(Stream<? extends A> as, Stream<? extends B> bs,
      MergeJoinComparator<? super A, ? super B> comparator, BiFunction<? super A, ? super B, C> mapper) {
    MergeJoinIterator<? extends A, ? extends B, C> iterator = new MergeJoinIterator<>(as.iterator(), bs.iterator(), comparator, mapper);
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator,
      Spliterator.ORDERED), false);
  }
}
public interface MergeJoinComparator<A, B> {
  public int compare(A a, B b);
}


[A_10, A_20, A_30]
[B_0, B_10, B_30]

から、

[C_10(A_10, B_10), C_30(A_30, B_30)]

を生成してくれます。

一応制約があって、Stream<A>、Stream<B>がMergeJoinComparator的に厳密な単調増加になっている必要があります。
要するに、

[A_10, A_20, A_20]

みたいにキーが重複する場合は動作が保証されない点です。
前提条件として、キーは歯抜けがありうるけど(該当するレコードが存在しない可能性があるけど)、キーはキーとして一意になっているということです。
(そうでないと、計算量もメモリ消費量もどのみちO(NM)になって、「メモリ消費量は定数、計算量はO(N+M)」が実現できません)

あとは、対応するキーが存在しない(相方がnull)の場合に、outer join的な結合をするフラグ的なものは欲しいところです。

Delphiで連想配列

古いDelphiってMapクラスって用意されていないんですよね。僕の所有する最新のDelphiも2006です。

とはいえ、それ以前のDelphiでも「KeyがString型に限定される」ので良ければMapを使う事が出来ます。お馴染み、TStringListクラスですね。

続きを読む

Lambdajって面白そうなライブラリを見つけた

Lambdaj
http://code.google.com/p/lambdaj/

コレクションの操作を宣言的に書くことが出来るライブラリ。
同期の皆さんには是非「ループを自分で回さない」書き方があるんだ、ということを知ってもらいたいです。

このライブラリは
(1) Apache 2.0ライセンスなので自由に使える
(2) Java1.4でも使える (最新のJavaで開発出来るとは限りませんね)
(3) Mavenリポジトリに登録されているので、セットアップが楽ちん

という絶大なるメリットがあります。

Java/CassandraのHello World!

よ う や く で き た

バージョンが変わる度に書き方が変わるようだ。ネット、特に日本語の情報は大体ver0.6とか0.7とかで古い情報で、最新版だと「コンパイルすら通らない」。
wikiの方も使わせる気無いんじゃないかと思えるくらい判りにくいんで、まるで宝探しの気分だった(苦笑)。

一応最終的に1.1.6(1.1.5)で動かす方法を見つけたので、忘れないうちにメモっておく。

続きを読む

リスト系コンポーネントにオブジェクトを関連づけて利用する

例えばTListBoxの項目とかはTStringsで表現されているから、表示項目にオブジェクトを関連づけて使うと結構便利だよ、という話。

続きを読む

リストに大量のアイテムを登録する

例えばTListBoxに大量のアイテムを登録する場合、TListBox.AddItem (あるいはTListBox.Items.Add)を使うと、
1件追加する度にTListBoxの描画ルーチンが動いてしまうのでもの凄く時間が掛かってしまう。

こんな時は、一度TStrings(TStringList)のバッファを作成してアイテム登録を登録してから、TStrings.Assignを使うと良い。

続きを読む