web-dev-qa-db-ja.com

Javaで並べ替えられた配列リスト

私はこれに対する簡単な答えを見つけることができないことに困惑しています。私は本質的にJavaのデータ構造を探しています。これは_Java.util.List_インターフェースを実装しますが、そのメンバーをソート順に格納します。通常のArrayListおよびCollections.sort()を使用しますが、リストからメンバーを時々追加したり、頻繁に取得したりする場合があります。新しいものが追加されました。JDKやサードパーティのライブラリに存在するようなものを誰かが私に指摘できますか?

[〜#〜] edit [〜#〜]:データ構造は重複を保持する必要があります。

ANSWER'S SUMMARY:このすべてが非常に興味深く、多くのことを学びました。特にAioobeは、上記の私の要件(主に、重複をサポートするソートされたJava.util.List実装)を達成しようとする彼の忍耐力に言及するに値します。私は彼の答えを私が尋ねたものに対して最も正確であり、私が尋ねたものが正確に必要なものではなかったとしても、私が探していたものの意味を引き起こす最も多くの考えとして受け入れました。

私が求めた問題は、Listインターフェイス自体と、インターフェイスのオプションメソッドの概念にあります。 javadocを引用するには:

このインターフェイスのユーザーは、リスト内の各要素が挿入される場所を正確に制御できます。

ソート済みリストへの挿入では、挿入ポイントを正確に制御できません。次に、いくつかのメソッドをどのように処理するかを考える必要があります。 addを例に取ります:

public boolean add(Object o)

_ Appends the specified element to the end of this list (optional operation).
_

1)契約を破り、addのソートされたバージョンを実装する2)addにリストの最後に要素を追加させ、ソートされた順序を破る3)add out(オプション)UnsupportedOperationExceptionをスローし、ソートされた順序でアイテムを追加する別のメソッドを実装します。

おそらくオプション3が最適ですが、使用できないaddメソッドと、インターフェイスにない別のsortAddメソッドがあることは好ましくありません。

その他の関連ソリューション(順不同):

  • Java.util.PriorityQueue これは、おそらく私が要求したものよりも必要なものに最も近いでしょう。私の場合、キューはオブジェクトのコレクションの最も正確な定義ではありませんが、機能的には必要なすべてを実行します。
  • net.sourceforge.nite.util.SortedList 。ただし、この実装はadd(Object obj)メソッドでソートを実装することによりListインターフェースの規約を破り、奇妙なことにadd(int index, Object obj)に対して効果のないメソッドを持ちます。一般的なコンセンサスは、throw new UnsupportedOperationException()がこのシナリオではより良い選択である可能性を示唆しています。
  • Guava's TreeMultiSet 重複をサポートするセット実装
  • ca.odell.glazedlists.SortedList このクラスには、javadocに次の警告があります:_Warning: This class breaks the contract required by List_
82
Chris Knight

最小限のソリューション

これが「最小限の」ソリューションです。

_class SortedArrayList<T> extends ArrayList<T> {

    @SuppressWarnings("unchecked")
    public void insertSorted(T value) {
        add(value);
        Comparable<T> cmp = (Comparable<T>) value;
        for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--)
            Collections.swap(this, i, i-1);
    }
}
_

挿入は線形時間で実行されますが、とにかくArrayListを使用すると取得できます(挿入された要素の右側にあるすべての要素を何らかの方法でシフトする必要があります)。

比較できないものを挿入すると、ClassCastExceptionが発生します。 (これは PriorityQueue でもとられたアプローチです:自然順序付けに依存する優先度キューは、比較できないオブジェクトの挿入も許可しません(そうすることClassCastExceptionが発生する可能性があります)

_List.add_のオーバーライド

_List.add_ (または _List.addAll_ をオーバーライドして要素をソートされた方法で挿入することはインターフェース仕様の直接違反。あなたがcouldすることは、このメソッドをオーバーライドしてUnsupportedOperationExceptionをスローすることです。

_List.add_のドキュメントから:

boolean add(E e)
指定された要素をこのリストの最後に追加します(オプションの操作)。

addの両方のバージョン、addAllsetの両方のバージョンに同じ理由が適用されます。 (これらはすべて、リストインターフェイスによるオプションの操作です。)


いくつかのテスト

_SortedArrayList<String> test = new SortedArrayList<String>();

test.insertSorted("ddd");    System.out.println(test);
test.insertSorted("aaa");    System.out.println(test);
test.insertSorted("ccc");    System.out.println(test);
test.insertSorted("bbb");    System.out.println(test);
test.insertSorted("eee");    System.out.println(test);
_

.... prints:

_[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]
_
62
aioobe

つかいます - Java.util.PriorityQueue

11
Gadolin

Guava'sTreeMultiSet を試すことができます。

 Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
 System.out.println(ms);
6
Emil

SortedList をご覧ください

このクラスは、ソートされたリストを実装します。 2つのオブジェクトを比較し、それに応じてオブジェクトをソートできるコンパレーターで構築されます。リストにオブジェクトを追加すると、正しい場所にオブジェクトが挿入されます。コンパレータに従って等しいオブジェクトは、このリストに追加された順序でリストに含まれます。コンパレータが比較できるオブジェクトのみを追加します。


リストに既にコンパレーターと等しいオブジェクトが含まれている場合、新しいオブジェクトはこれらの他のオブジェクトの直後に挿入されます。

6
Jigar Joshi

Aioobeのアプローチが道です。ただし、彼のソリューションに対する次の改善を提案したいと思います。

class SortedList<T> extends ArrayList<T> {

    public void insertSorted(T value) {
        int insertPoint = insertPoint(value);
        add(insertPoint, value);
    }

    /**
     * @return The insert point for a new value. If the value is found the insert point can be any
     * of the possible positions that keeps the collection sorted (.33 or 3.3 or 33.).
     */
    private int insertPoint(T key) {
        int low = 0;
        int high = size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = (Comparable<T>) get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else {
                return mid; // key found
            }
        }

        return low;  // key not found
    }
}

aioobeのソリューションは、大きなリストを使用すると非常に遅くなります。リストがソートされているという事実を使用すると、バイナリ検索を使用して新しい値の挿入ポイントを見つけることができます。

また、継承よりも合成を使用します。

SortedList<E> implements List<E>, RandomAccess, Cloneable, Java.io.Serializable
4

リストは通常​​、アイテムが追加される順序を保持します。間違いなくlistが必要ですか、それともソートされたset(例えば TreeSet<E> )大丈夫ですか?基本的に、重複を保存する必要がありますか?

4
Jon Skeet

それはあなたには少し重いかもしれませんが、 GlazedLists には SortedList があり、テーブルまたはJListのモデルとして使用するのに最適です

2
I82Much

ArrayListをサブクラス化し、要素を追加した後にCollections.sort(this)を呼び出すことができます。これを行うには、2つのバージョンのaddと2つのaddAllをオーバーライドする必要があります。

パフォーマンスは、適切な場所に要素を挿入するよりスマートな実装ほど良くはありませんが、仕事はします。リストへの追加がまれな場合、リスト上のすべての操作で償却されるコストは低くなります。

1
Tom Anderson

Collection APIを壊すことでソート済みリストを実装する現在提案されている実装は、ツリーまたは類似の独自の実装を持っているので、TreeMapに基づいた実装がどのように実行されるか好奇心was盛でした。 (特にTreeSetはTreeMapにも基づいているため)

誰かもそれに興味があるなら、彼または彼女は自由にそれを調べることができます:

TreeList

コアライブラリ のその部分は、もちろんMaven依存関係を介して追加できます。 (Apacheライセンス)

現在、実装はguava SortedMultiSetとApache CommonsライブラリのTreeListと同じレベルで非常によく比較されているようです。

しかし、重要なものを見逃していないことを確認するために、私だけでなく実装をテストしていただければ幸いです。

宜しくお願いします!

0
Omnaest

同じ問題がありました。そこで、Java.util.TreeMapのソースコードを取得して、IndexedTreeMapと記述しました。独自のIndexedNavigableMapを実装します:

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

実装は、赤黒ツリーが変更されたときにノードの重みを更新することに基づいています。重みは、指定されたノードの下の子ノードの数に1を加えたもの-自己です。たとえば、ツリーを左に回転させる場合:

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeightは、単にルートまでの重みを更新します。

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

そして、インデックスによって要素を見つける必要がある場合、重みを使用する実装があります。

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

キーのインデックスを見つけるのも非常に便利です:

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    index += getWeight(e.left);

    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

この作業の結果は http://code.google.com/p/indexed-tree-map/ で見つけることができます。

TreeSet/TreeMap(およびindexed-tree-mapプロジェクトの対応するインデックス付き)は、重複キーを許可しません。値の配列に1つのキーを使用できます。重複したSortedSetが必要な場合は、値としてTreeMapを配列として使用します。私はそうするでしょう。

0

SortedSets/Listsと「通常の」ソート可能なコレクションの選択は、プレゼンテーションの目的でのみソートする必要があるか、ランタイム中のほぼすべてのポイントでソートする必要があるかによって異なります。ソートされたコレクションを使用すると、エレメントを挿入するたびにソートが行われるため、はるかに費用がかかる場合があります。

JDKでコレクションを選択できない場合は、 Apache Commons Collections をご覧ください。

0
codeporn