凪瀬 Blog
Programming SHOT BAR

目次

Blog 利用状況
  • 投稿数 - 260
  • 記事 - 0
  • コメント - 47085
  • トラックバック - 192
ニュース
広告
  • Java開発者募集中
  • 経歴不問
  • 腕に自信のある方
  • 富山市内
  • (株)凪瀬アーキテクツ
アクセサリ
  • あわせて読みたい
凪瀬悠輝(なぎせ ゆうき)
  • Java技術者
  • お茶好き。カクテル好き。
  • 所属は(株)凪瀬アーキテクツ
  • Twitter:@nagise

書庫

日記カテゴリ

 

2008年7月15日

ゆの in Javaとかに嵌って放出が遅れましたが、ソート祭りの話題。

非破壊で順序を変更してとりだす

GoFデザインパターンのIteratorパターンを用いて元になるListには手を加えずに ソートと同じような効果を得るサンプルです。

public class SortIterator<T> implements Iterator<T>, Iterable<T> {
  /** ソート対象 */
  private Iterable<T> target;
  /** ソート用Comparator.この実装を変えることで並ばせ方を変えられる */
  private Comparator<T> comparator;
  private T preElement;
  private List<T> equalValues;

  /**
   * コンストラクタ
   @param target ソート対象。SetやListなどを渡すことができる
   @param comparator 並ばせ方を決めるComparatorの実装
   */
  public SortIterator(Iterable<T> target, Comparator<T> comparator) {
    this.target = target;
    this.comparator = comparator;
    this.equalValues = new ArrayList<T>();
  }

  @Override
  public boolean hasNext() {
    T min = null;
    targetLoop:
    for (T elem : this.target) {
      // 前回までに返した値より小さいものは飛ばす
      if (this.preElement != null &&
          this.comparator.compare(this.preElement, elem0) {
        continue;
      }
      // 前回と同じ値のものは、重複を確認してすでに返した値なら飛ばす
      if (this.preElement != null &&
          this.comparator.compare(this.preElement, elem== 0) {
        // 参照の同値性で判断するために敢えてcontains()は使わない
        // contains()だとequals()による比較になるため
        for (T value : this.equalValues) {
          if (value == elem) {
            continue targetLoop;
          }
        }
        this.preElement = elem;
        this.equalValues.add(elem);
        return true;
      }
      // 前回までに返した値より大きいもののうち、最小のものを探す
      if (min == null) {
        min = elem;
        continue;
      }
      if (this.comparator.compare(min, elem0) {
        min = elem;
      }
    }
    if (min == null) {
      return false;
    }
    this.equalValues.clear();
    this.equalValues.add(min);
    this.preElement = min;
    return true;
  }

  @Override
  public T next() {
    return this.preElement;
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException();
  }

  @Override
  public Iterator<T> iterator() {
    return this;
  }
}

これを以下のように呼び出します。

    List<Integer> list = new ArrayList<Integer>();
    list.add(new Integer(3));
    list.add(new Integer(1));
    list.add(new Integer(4));
    list.add(new Integer(2));
    list.add(new Integer(1));

    SortIterator<Integer> ite = new SortIterator<Integer>(list,
        new Comparator<Integer>(){
          @Override
          public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
          }
    });
    for (int i : ite) {
      System.out.println(i);
    }

出力結果は

1 1 2 3 4

ちゃんと整列していますね。

解説

このクラスではソート対象のIterableと比較に用いるComparatorを保持して hasNext()のたびに今までに返した値より大きいもののうち一番小さいもの、 つまり「次に小さい奴」を探してnext()でそのオブジェクトを返します。

最初に「データをちょうだい」と言われた時に「一番小さい奴」を探して返し、 「次のデータをちょうだい」と言われたら「次に小さい奴」を探して返し、 …。

でも、それで利用側から見ると並び変わったように見えるんですね。

設計に利用するヒント

Listの中身そのものを並び替えた方が効果的なシチュエーションの方が多いかもしれませんが、 一時的に他の並び方を使いたいという場合はIterator側に並び替えを仕込むのも一つの手です。

これは非破壊であることがメリットで、複数のスレッドからオブジェクトの持ついろんな属性での 並び替えを並列に取得することもできます(その間に元のListが変更されると動きがおかしくなりますが…)。

そういう意味ではImmutableパターン、つまりオブジェクトを変更不可能にすることで 並列時の安全性を確保するようなケースで有用です。

データを利用する人が多岐にわたる場合、自分の都合だけで並び替えるわけにいかないかもしれません。 そのような場合に、外付けでデータの順番が変わったように見せる手法があることを知っておくと うまい設計を起こすヒントになるかもしれません。

外から利用する分には中のデータの表現方法なんて隠蔽されていて関心の外ですから、 必要になった時に自転車操業的に「次に小さい奴」を探して渡す、でも大丈夫なんですよね。

posted @ 9:22 | Feedback (7)