JavaにSortedListがないのはなぜですか?


503

JavaにはSortedSetおよびSortedMapインターフェースがあります。どちらもJavaコレクションフレームワークに属し、要素にアクセスするためのソートされた方法を提供します。

しかし、私の理解ではSortedList、Java にはありません。を使用java.util.Collections.sort()してリストをソートできます。

それがそのように設計されている理由は何ですか?



6
では、リストの中央に要素を挿入したときに期待される結果は何ですか?
bestss

4
@bestsss java.util.Listインターフェースを実装しないSortedListクラスを持つことは完全に可能です。要求された機能をサポートするデータ構造がない理由を問い合わせとして質問を読んでください。名前などの重要ではない詳細に気を取られないでください。
Alderath、2012年

@Alderath、これの通常の構造はツリー(赤/黒、avlまたはbtree)で、順序付けをサポートするための追加のprev / nextリンクがあります。私は同様の構造の赤/黒、前/次のリンクを使用しています。ただし、これはかなりニッチな用途です。ツリーは順序付けと挿入順序の両方をたどることができ、O(logn)の検索/包含がありますが、get(int)はO(n)です。そのニッチな適用性という事実を考えると、必要に応じて実装するのは開発者に任されていたと思います。
bestss

3
「なぜ」という質問には答えませんが、TreeSet の最も簡単な回避策は、ゼロを返さないコンパレータを使用することです。たとえば、int diff = this.score - that.score; return (diff == 0) ? 1 : diff; これは臭いハックなので、Comparableを実装するのではなく、匿名のコンストラクタ引数として提供します。
2015年

回答:


682

リスト反復子は、何よりもまず、リストの要素をリストの内部順序(別名挿入順序)で取得することを保証します。より具体的には、要素を挿入した順序、またはリストの操作方法です。並べ替えはデータ構造の操作と見なすことができ、リストを並べ替える方法はいくつかあります。

私は個人的にそれを見て、有用性の順に方法を並べます。

1. 代わりにSetまたはBagコレクションの使用を検討してください

注:これは通常、とにかくやりたいことなので、このオプションを一番上に置きます。

ソートされたセットは、挿入時にコレクションを自動的にソートします。つまり、コレクションに要素を追加するときにソートを実行します。また、手動で並べ替える必要もありません。

さらに、重複する要素について心配する必要がない(または持つ必要がない)場合は、TreeSet<T>代わりに使用できます。それは実装SortedSetしてNavigableSetインターフェースし、おそらくリストから期待するように動作します:

TreeSet<String> set = new TreeSet<String>();
set.add("lol");
set.add("cat");
// automatically sorts natural order when adding

for (String s : set) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

自然な順序付けが必要ない場合は、をとるコンストラクタパラメータを使用できますComparator<T>

別の方法としては、使用することができます複数セット(としても知られているバッグで、Set代わりに、要素の重複を可能にし、それらのサードパーティの実装があります。特にグアバのライブラリには、TreeMultisetとよく似たがありTreeSetます。

2.リストを並べ替えます Collections.sort()

上記のように、Listsのソートはデータ構造の操作です。したがって、さまざまな方法で並べ替えられる「1つの信頼できる情報源」が必要な状況では、手動で並べ替えるのがよいでしょう。

java.util.Collections.sort()メソッドでリストを並べ替えることができます。方法のコードサンプルは次のとおりです。

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

Collections.sort(strings);
for (String s : strings) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

コンパレーターの使用

1つの明確な利点はComparatorsortメソッドで使用できることです。Java は、ロケール依存のソート文字列に役立つComparatorなどの実装もいくつか提供してCollatorいます。以下はその一例です。

Collator usCollator = Collator.getInstance(Locale.US);
usCollator.setStrength(Collator.PRIMARY); // ignores casing

Collections.sort(strings, usCollator);

並行環境でのソート

ただしsort、コレクションのインスタンスは操作されるため、メソッドの使用は並行環境では使いにくいため、代わりに不変コレクションの使用を検討する必要があります。これは、グアバがOrderingクラスで提供するものであり、単純なワンライナーです:

List<string> sorted = Ordering.natural().sortedCopy(strings);

3.リストをラップして java.util.PriorityQueue

Javaにはソートされたリストはありませんが、ソートされたキューがあり、おそらく同じように機能します。これは、あるjava.util.PriorityQueueクラス。

ニコ・ハーセは、これにも答える関連質問へのコメントにリンクしました。

並べ替えられたコレクションでは、内部データ構造を操作たくない場合がほとんどです。そのため、PriorityQueueはListインターフェースを実装していません(その要素に直接アクセスできるためです)。

PriorityQueueイテレータに関する注意

PriorityQueueクラスの実装Iterable<E>Collection<E>、それはいつものように繰り返すことができるようにインターフェイス。ただし、イテレータは、ソートされた順序で要素を返すことが保証されていません。代わりに(Alderathがコメントで指摘しているように)poll()、空になるまでキューに入れる必要があります。

任意のコレクションを取得するコンストラクタを介して、リストを優先度キューに変換できることに注意してください。

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

PriorityQueue<String> sortedStrings = new PriorityQueue(strings);
while(!sortedStrings.isEmpty()) {
    System.out.println(sortedStrings.poll());
}
// Prints out "cat" and "lol"

4.独自のSortedListクラスを作成する

注:これを行う必要はありません。

新しい要素を追加するたびにソートする独自のListクラスを作成できます。これは、実装によってはかなり重い計算になる可能性があり、2つの主な理由により、演習として実行したくない場合を除いて無意味です。

  1. メソッドは、ユーザーが指定したインデックスに要素が存在することを保証する必要List<E>があるため、インターフェイスのコントラクトにadd違反します。
  2. なぜ車輪を再発明するのですか?上記の最初のポイントで指摘したように、代わりにツリーセットまたはマルチセットを使用する必要があります。

ただし、ここで演習としてそれを実行したい場合は、開始するためのコードサンプルであり、AbstractList抽象クラスを使用します。

public class SortedList<E> extends AbstractList<E> {

    private ArrayList<E> internalList = new ArrayList<E>();

    // Note that add(E e) in AbstractList is calling this one
    @Override 
    public void add(int position, E e) {
        internalList.add(e);
        Collections.sort(internalList, null);
    }

    @Override
    public E get(int i) {
        return internalList.get(i);
    }

    @Override
    public int size() {
        return internalList.size();
    }

}

必要なメソッドをオーバーライドしていない場合、デフォルトの実装からAbstractListがスローされることに注意してくださいUnsupportedOperationException


12
+1。イモ、これはトップ投票の回答よりも建設的です。ただし、2つの小さな欠点があります。PriorityQueueはランダムアクセスをサポートしていません。peek(elementIndex)はできません。したがって、たとえば、行うことはできませんInteger maxVal = prioQueue.peek(prioQueue.size() - 1);。2つ目は、PriorityQueueを単に並べ替えられたリストとして使用する場合、そのようなデータ構造が存在する場合PriorityQueue、コードで見るよりも直感的に聞こえにくくなりSortedListます。
Alderath、2012年

12
そして、誰かがコメントでリンクした他の質問を見た後、別の大きな欠点は、PriorityQueueの反復子が特定の順序で要素を返すことが保証されていないことです。したがって、何かを見落とさない限り、たとえばPriorityQueueのすべてのオブジェクトを出力するための唯一の方法は、キューが空になるまで繰り返しpoll()することです。私にとって、これは境界線が遅れているように感じます。PriorityQueue内のオブジェクトを2回印刷するには、最初にPriorityQueueをコピーしてから、元のPriorityQueueからすべてのオブジェクトをpoll()し、次にコピーからすべてのオブジェクトをpol()lする必要があります。
Alderath、2012年

1
うーん...あなたは正しいAlderathのようです。PriorityQueueのイテレータを使用して、要素を期待どおりの順序で取得することはできません。回答を編集する必要があるようです。
スポイケ2012年

7
プライオリティキューは単なるヒープであり、質問の回答に属していないimoだけがトップにアクセスできます。
bestss

1
またCollections.sort()Comparatorオブジェクトを使用して、並べ替えに使用する比較関数を定義することもできます。
マイク 'Pomax'カマーマンズ2013年

71

リストの概念は、自動的にソートされるコレクションの概念と互換性がないためです。リストのポイントは、を呼び出した後list.add(7, elem)、への呼び出しlist.get(7)が返されることelemです。自動ソートされたリストでは、要素が任意の位置になってしまう可能性があります。


26

すべてのリストは、アイテムが追加された順序(FIFO順序付け)ですでに「ソート」されているため、を使用して、要素の自然順序付けを含む別の順序でリストを「並べ替え」ることができますjava.util.Collections.sort()

編集:

データ構造としてのリストは、興味深いことに、挿入されたアイテムの順序に基づいています。

セットにはその情報はありません。

時間を追加して注文したい場合は、を使用してくださいList。他の基準で注文する場合は、を使用してくださいSortedSet


22
セットは重複を許可していません
bestsss

10
これは良い答えではないと思います。もちろん、Java APIのリストには、アイテムがいつ、どのように挿入されたかによって決定される特定の順序があります。しかしながら、挿入方法/時間に依存する方法で順序付けられたリストの存在は、順序が別の方法で(例えば、コンパレータによって)決定される他のデータ構造を有することを妨げない。基本的に、OPは、SortedSetに相当するデータ構造が存在しない理由を尋ねていますが、例外として、データ構造は等しい要素の複数の出現を許可する必要があります。
Alderath 2012年

5
質問まで私のフォローは以下のようになりますので、「なぜにSortedSetのような働きはデータ構造が存在しないが、複数の等しい要素を含めることができた?」(「と答えないでくださいセットが一つだけの要素を含めることができるので、」)
Alderath

@Alderath:stackoverflow.com/questions/416266/sorted-collection-in-javaを参照してください。つまり、GuavaのTreeMultisetを使用します
Janus Troelsen、

4
「ソート済み」を二重引用符で囲んでも、リストは「ソート済み」ではありません。彼らは注文されました。
Koray Tugay 2017

21

セットとマップは非線形データ構造です。リストは線形データ構造です。

ここに画像の説明を入力してください


ツリーデータ構造SortedSetSortedMapインターフェイスは、実装され TreeSetTreeMap使用されているRed-Blackツリー実装アルゴリズムをそれぞれ使用します。したがって、重複するアイテム(またはの場合はキーMap)がないことを確認します。

  • List はすでに順序付けられたコレクションとインデックスベースのデータ構造を維持していますが、ツリーはインデックスベースのデータ構造ではありません。
  • Tree 定義上、重複を含めることはできません。
  • ではList、我々は重複を持つことができるので、何もありませんTreeList(なしつまりSortedList)。
  • リストは、挿入順に要素を維持します。したがって、リストをソートする場合は、使用する必要がありますjava.util.Collections.sort()。要素の自然な順序に従って、指定されたリストを昇順でソートします。

14

JavaFX SortedList

少し時間がかかりましたが、Java 8にはソートがありましたListhttp://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

javadocsでわかるように、これはJavaFXコレクションの一部であり、ObservableListでソートされたビューを提供することを目的としています。

更新:Java 11では、JavaFXツールキットがJDKの外に移動し、独立したライブラリになっていることに注意してください。JavaFX 11は、ダウンロード可能なSDKとして、またはMavenCentralから入手できます。https://openjfx.ioを参照してください


2
残念ながら、このSortedListは通常のリストのように機能しません。たとえば、デフォルトのコンストラクターがありません(つまり、ObservableListを使用して構成する必要があります...)
Erel Segal-Halevi

JavaFXのSortedListは明らかにGUIコンポーネントを使用するためのものであり、オーバーヘッドが原因で、ソートされたオブジェクトのリストを作成するだけでは適していません。また、プロジェクトでGUIが使用されていない場合でも、FXモジュール全体を参照することになります。
COBRA.cH 2018年

1
@ COBRA.cHはい、そうです。よりパフォーマンスの高いSorted Listは、おそらくTreeMapの薄いラッパーであり、整数はキーの重複をカウントするために使用されます。また、0を返すことはありませんコンパレータとTreeSetのを使用することができます
swpalmer

なぜ反対票か。質問は、JDKライブラリにソートされたリストがないという前提から始まります。この回答はその仮定を修正します。この特定のソートされたリストの実装が好きかどうかにかかわらず、反対票を投じる理由にはなりません。この回答はクラスを推奨せず、クラスの存在を指摘するだけです。
バジルブルク2018

10

初心者のために、2015年4月の時点で、Android ではサポートライブラリにSortedListクラスが追加されましたRecyclerView。これは、で動作するように特別に設計されています。これについてのブログ投稿です。


1
このコメントの時点で、Androids SortedListはRecyclerViewでのonItemMoved()機能のサポートが不足していることに注目する価値があります。独自のSortedListを書くのは効率が悪く、制限を回避するために私がしなければならなかったことです。
マシュー

4

もう1つのポイントは、挿入操作の時間の複雑さです。リスト挿入の場合、O(1)の複雑さが予想されます。しかし、これはソートされたリストでは保証できませんでした。

そして最も重要な点は、リストはその要素について何も想定していないということです。たとえば、equalsまたはを実装しないもののリストを作成できますcompare


O(logn)insert / delete / find / containsを含むリストを持つことはできますが、get(int)を含まないリストを持つことができます。
bestss

3
最後のポイントは本当に良い説明ではありません。SortedSet実装しないものを作ることができますComparableこのTreeSetコンストラクタを参照してください。
Alderath、2012

1
@Alderath-多分私の言い回しが弱すぎました。しかし、観測では、セットの要素とツリーのキーは少なくとも同等である必要がありますが、リスト要素はそうである必要はありません。セットとツリーの順序付け/等価関係がコンパレータで実装されているかどうかは重要ではありませんが、必要です。
Ingo

リスト O(1)の挿入を保証せず、O(1)のアクセスを保証しますbigocheatsheet.com
Matt Quigley

1
@Betlistaあなたは正しいです!コメントを更新できませんが、Java Listインターフェースはそのメソッドのパフォーマンス仕様を保証しません。
マットQuigley

3

次のように考えてください。Listインターフェースには、などのメソッドがadd(int index, E element)ありset(int index, E element)ます。契約では、Xの位置に要素を追加すると、その前に要素を追加または削除しない限り、Xの要素が見つかることになります。

リストの実装が要素をインデックスに基づく以外の順序で格納する場合、上記のリストメソッドは意味がありません。


2

リストAPIの最初の行は、それが順序付けられたコレクション(シーケンスとも呼ばれる)であることを示しています。リストをソートすると、順序を維持できないため、JavaにはTreeListがありません。
APIが言うように、Javaリストはシーケンスからインスピレーションを得て、シーケンスプロパティを確認しますhttp://en.wikipedia.org/wiki/Sequence_(mathematics

リストをソートできないという意味ではありませんが、Javaは彼の定義に厳密であり、デフォルトではリストのソートされたバージョンを提供していません。



2

要素をソートする方法を探しているが、効率的な方法でインデックスを使用して要素にアクセスできる場合は、以下を実行できます。

  1. ストレージにランダムアクセスリストを使用します(例ArrayList
  2. 常にソートされていることを確認してください

次にCollections.binarySearch、挿入/削除インデックスを取得するために使用できる要素を追加または削除します。リストはランダムアクセスを実装しているため、決定されたインデックスを使用してリストを効率的に変更できます。

例:

/**
 * @deprecated
 *      Only for demonstration purposes. Implementation is incomplete and does not 
 *      handle invalid arguments.
 */
@Deprecated
public class SortingList<E extends Comparable<E>> {
    private ArrayList<E> delegate;

    public SortingList() {
        delegate = new ArrayList<>();
    }

    public void add(E e) {
        int insertionIndex = Collections.binarySearch(delegate, e);

        // < 0 if element is not in the list, see Collections.binarySearch
        if (insertionIndex < 0) {
            insertionIndex = -(insertionIndex + 1);
        }
        else {
            // Insertion index is index of existing element, to add new element 
            // behind it increase index
            insertionIndex++;
        }

        delegate.add(insertionIndex, e);
    }

    public void remove(E e) {
        int index = Collections.binarySearch(delegate, e);
        delegate.remove(index);
    }

    public E get(int index) {
        return delegate.get(index);
    }
}

1

indexed-tree-mapの使用を検討してください。これは、JDKの拡張されたTreeSetであり、インデックスを使用して要素にアクセスし、反復なしで、またはツリーをバックアップする非表示の基になるリストなしで要素のインデックスを検索できます。このアルゴリズムは、変更があるたびに変更するノードの重みを更新することに基づいています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.