凪瀬 Blog
Programming SHOT BAR

目次

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

書庫

日記カテゴリ

 

ゆの 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パターン、つまりオブジェクトを変更不可能にすることで 並列時の安全性を確保するようなケースで有用です。

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

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

投稿日時 : 2008年7月15日 9:22
コメント
  • # re: ソートをしないで並び替え
    かずくん
    Posted @ 2008/07/15 22:08
    元データに手を加えずにソートを行う場合の別解として、添字(インデックス)を保持したリストをソートってのもありましたね。

    C++で、例えば、vector<Record>にデータを突っ込んでソートする場合、入れ替えの際に要素のコピーがが発生してしまう。
    要素のデータサイズが大きい and/or データ数が多いと、コピーのコストも馬鹿にならなくなってくるので、よりコストの低いインデックスをソートしよう、ってのだった希ガス。

    javaや.NET系だとヒープメモリに要素を持つから、あまり効果は期待できないだるけど
    (ハンドルの置換によるコピーだろうから)


  • # re: ソートをしないで並び替え
    通りすがり
    Posted @ 2008/07/16 11:03
    私だったら、TreeSetにコピーして済ませてしまうと思います。

    あと細かい話ですが、equalValuesにはIdentityHashMapを使うという手もありかと。
  • # re: ソートをしないで並び替え
    凪瀬
    Posted @ 2008/07/16 19:51
    > 添字(インデックス)を保持したリストをソートってのもありましたね。
    そうですね。これは表形式でのUIでよく使われますね。項目のタイトルをクリックしてソートするようなタイプの。

    > あと細かい話ですが、equalValuesにはIdentityHashMapを使うという手もありかと。

    うはー。ちょうどいい実装があったのか!
    http://java.sun.com/j2se/1.5.0/ja/docs/ja/api
    /java/util/IdentityHashMap.html
    こんなのあるとは思ってなかったw

    > TreeSetにコピーして済ませてしまうと思います。
    次は赤黒木を予定しているのですが正にそれになりますね。
    データ構造を工夫する方向の設計もあるよ、という話題で。
  • # re: ソートをしないで並び替え
    通りすがり
    Posted @ 2008/08/04 14:31
    "next()の前には必ず hasNext()"がよばれて、かつ
    "hesNext()は続けて呼ばれない"という前提のもとに
    実装されているようですが、Iteratorの実装として
    そういった前提は普通なのでしょうか?
    インターフェースの定義[1]を見ると、そのような話は
    みうけられないのですが。。。

    [1] http://java.sun.com/javase/6/docs/api/java/util/Iterator.html
  • # 【20080920東京勉強会#24】準備エントリ
    はつね
    Posted @ 2008/08/12 16:10
    【20080920東京勉強会#24】準備エントリ
  • # ロレックス レプリカ ヤフオク
    aeglixp@softbank.jp
    Posted @ 2022/09/01 2:44
    ブランドスマホケース/カバー激安通販ショップ
    ご来店いただき誠にありがとうございます。
    当店では「信頼第一、サービス第一」をモットーに、お客様第一主義で営業しております。取扱商品としては、iPhoneスマホケース、iPadケース、SAMSUNG GALAXY スマホケース、バッテリー&充電器や、関係する物などです。皆様のニーズにお応えすべく各種製品を取り揃えております。
    ごゆっくりお買い物をお楽しみください。皆様のお求めになりたい商品がきっと見つかります。
    シャネルiphone6 plusケース積み木iphone6 iphone6 plusケース MCM iphone6カバー 手帳型シャネル革iphone6 保護ケース 4.7インチiPhone 6 Plus カバー5.5 インチ ブランド SAMSUNG GALAXY NOTE4ケースCHANEL SAMSUNG NOTE4カバーブランド iphone6ケース ルイヴィトンエルメス Hermes iphone6ケースGUCCI iphone6ケース
    休業日: 365天受付年中無休
    ロレックス レプリカ ヤフオク https://www.2bcopy.com/product/product.aspx-id=10622.htm
  • # ロレックスコピー
    coxAcquic
    Posted @ 2023/05/29 12:46
    ロレックス買取"弊社はロレックスの商品特に大人気のロレックスデイトナシリーズのロレックス時計の種類を豊富に取り揃えます。日本ロレックス時計とロレックスレプリカのロレックスコピー品の品質よくて、激安税込み価格でご提供します。 }}}}}}
    https://www.kopi66.com/proType/list.aspx-page=2&id=173.htm
タイトル
名前
Url
コメント