私はこれに対する簡単な答えを見つけることができないことに困惑しています。私は本質的に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メソッドがあることは好ましくありません。
その他の関連ソリューション(順不同):
add(Object obj)
メソッドでソートを実装することによりListインターフェースの規約を破り、奇妙なことにadd(int index, Object obj)
に対して効果のないメソッドを持ちます。一般的なコンセンサスは、throw new UnsupportedOperationException()
がこのシナリオではより良い選択である可能性を示唆しています。Warning: This class breaks the contract required by List
_これが「最小限の」ソリューションです。
_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
の両方のバージョン、addAll
とset
の両方のバージョンに同じ理由が適用されます。 (これらはすべて、リストインターフェイスによるオプションの操作です。)
_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]
_
つかいます - Java.util.PriorityQueue
。
Guava'sTreeMultiSet を試すことができます。
Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
System.out.println(ms);
SortedList をご覧ください
このクラスは、ソートされたリストを実装します。 2つのオブジェクトを比較し、それに応じてオブジェクトをソートできるコンパレーターで構築されます。リストにオブジェクトを追加すると、正しい場所にオブジェクトが挿入されます。コンパレータに従って等しいオブジェクトは、このリストに追加された順序でリストに含まれます。コンパレータが比較できるオブジェクトのみを追加します。
リストに既にコンパレーターと等しいオブジェクトが含まれている場合、新しいオブジェクトはこれらの他のオブジェクトの直後に挿入されます。
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
リストは通常、アイテムが追加される順序を保持します。間違いなくlistが必要ですか、それともソートされたset(例えば TreeSet<E>
)大丈夫ですか?基本的に、重複を保存する必要がありますか?
それはあなたには少し重いかもしれませんが、 GlazedLists には SortedList があり、テーブルまたはJListのモデルとして使用するのに最適です
ArrayListをサブクラス化し、要素を追加した後にCollections.sort(this)を呼び出すことができます。これを行うには、2つのバージョンのaddと2つのaddAllをオーバーライドする必要があります。
パフォーマンスは、適切な場所に要素を挿入するよりスマートな実装ほど良くはありませんが、仕事はします。リストへの追加がまれな場合、リスト上のすべての操作で償却されるコストは低くなります。
Collection APIを壊すことでソート済みリストを実装する現在提案されている実装は、ツリーまたは類似の独自の実装を持っているので、TreeMapに基づいた実装がどのように実行されるか好奇心was盛でした。 (特にTreeSetはTreeMapにも基づいているため)
誰かもそれに興味があるなら、彼または彼女は自由にそれを調べることができます:
コアライブラリ のその部分は、もちろんMaven依存関係を介して追加できます。 (Apacheライセンス)
現在、実装はguava SortedMultiSetとApache CommonsライブラリのTreeListと同じレベルで非常によく比較されているようです。
しかし、重要なものを見逃していないことを確認するために、私だけでなく実装をテストしていただければ幸いです。
宜しくお願いします!
同じ問題がありました。そこで、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を配列として使用します。私はそうするでしょう。
SortedSets/Listsと「通常の」ソート可能なコレクションの選択は、プレゼンテーションの目的でのみソートする必要があるか、ランタイム中のほぼすべてのポイントでソートする必要があるかによって異なります。ソートされたコレクションを使用すると、エレメントを挿入するたびにソートが行われるため、はるかに費用がかかる場合があります。
JDKでコレクションを選択できない場合は、 Apache Commons Collections をご覧ください。