Javaでは、アイテムでArrayList
を作成してから呼び出します:
Collections.sort(list, comparator);
TreeMap
でできるように、リストの作成時にコンパレータに渡す方法はありますか?
目標は、リストに要素を追加できるようにすることです。リストの最後に自動的に追加する代わりに、リストはComparator
に基づいてソートされ、決定されたインデックスに新しい要素を挿入します。 Comparator
によって。そのため、基本的には、リストは新しい要素が追加されるたびに並べ替える必要があります。
とにかくComparator
を使用して、または他の同様の手段でこれを実現する方法はありますか?
ArrayListの動作を変更できます
List<MyType> list = new ArrayList<MyType>() {
public boolean add(MyType mt) {
super.add(mt);
Collections.sort(list, comparator);
return true;
}
};
注:PriorityQueueはリストではありません。どのタイプのコレクションであるかを気にしなければ、最も簡単なのはTreeSetを使用することです。これはTreeMapに似ていますが、コレクションです。 PriorityQueueの唯一の利点は、重複を許可することです。
注:大規模なコレクションの場合、再分類はあまり効率的ではありません。バイナリ検索を使用してエントリを挿入する方が高速です。 (しかしより複雑)
編集:「リスト」が何をする必要があるかに大きく依存します。 ArrayList、LinkedList、PriorityQueue、TreeSet、または他のソートされたコレクションのリストラッパーを作成し、実際に使用されるメソッドを実装することをお勧めします。そうすれば、コレクションの要件を十分に理解でき、コレクションが正しく機能することを確認できます。
EDIT(2):代わりにbinarySearchを使用することに非常に関心があったため。 ;)
List<MyType> list = new ArrayList<MyType>() {
public boolean add(MyType mt) {
int index = Collections.binarySearch(this, mt);
if (index < 0) index = ~index;
super.add(index, mt);
return true;
}
};
誰もがPriorityQueue
を提案しています。ただし、PriorityQueue
の内容に対して iterate を使用すると、要素はnotソートされた順序である。メソッドpeek()
、poll()
などからのみ「最小」要素を取得することが保証されます。
TreeSet
の方が適しているようです。警告は、Set
として、重複する要素を含めることはできず、インデックスを使用したランダムアクセスをサポートしないことです。
JDKにSortedList
実装がないという十分な理由があると思われます。個人的には、JDKで自動ソートを1つ行う理由を考えることはできません。
時期尚早の最適化がうまくいかなかった。リストが挿入されるたびにリストが読み取られない場合、理由もなく繰り返しソートを繰り返し無駄にしています。読み取りの直前にソートすることはより多くreactiveであり、読み取り前にリストをソートする必要があるかどうかを示すboolean
がどこかにあるより良い。
問題は、リストをIterator
ループまたは_for each
_ループで走査する場合にのみ順序に注意することです。したがって、反復するコードの前にCollections.sort()
を呼び出す方が、試行するよりもパフォーマンスが高くなります。挿入ごとにリストを常にソートしたままにします。
重複のためList
にはあいまいさがありますが、重複を決定論的にどのように順序付けますか? SortedSet
がありますが、それは一意性のために理にかなっています。ただし、List
を並べ替えると、重複の副作用や、すべてのオブジェクトをComparable
にするなどの制約や、コードで示すようにComparator
代わりに作業を行うことができます。
.add()
でのソート自動ソートList
が便利な非常に特殊な状況がある場合は、List
実装をサブクラス化し、.add()
をオーバーライドすることができます。カスタムコンストラクターに渡すCollections.sort(this, comparator)
を実行します。理由として、LinkedList
の代わりにArrayList
を使用しました。ArrayList
は、List
から始まる自然な挿入ソート順です。また、インデックスで.add()
する機能もあります。これは、常に並べ替えられたList
が必要な場合、ほとんど役に立たないので、何らかの方法で処理する必要があり、おそらく理想的ではありません。 Javadocによると、
_void add(int index, Object element)
_
このリストの指定された位置に指定された要素を挿入します(optional operation)。
したがって、UnSupportedOperationException
をスローするだけで問題ありません。または、メソッドのJavaDocでドキュメント化する場合は、index
を無視して.add(Object element);
に委任することもできます。
通常、多くの挿入/削除およびソートを行う場合は、「リスト」の使用によりパフォーマンス特性が向上するため、LinkedList
を使用します。
以下に簡単な例を示します。
_import Java.util.Collections;
import Java.util.Comparator;
import Java.util.LinkedList;
public class SortedList<E> extends LinkedList<E>
{
private Comparator<E> comparator;
public SortedList(final Comparator<E> comparator)
{
this.comparator = comparator;
}
/**
* this ignores the index and delegates to .add()
* so it will be sorted into the correct place immediately.
*/
@Override
public void add(int index, Object element)
{
this.add(element);
}
@Override
public boolean add(final E e)
{
final boolean result = super.add(e);
Collections.sort(this, this.comparator);
return result;
}
}
_
または、Iterator
を取得するときにのみソートでき、List
を反復処理するときにソート順が本当に重要な場合にのみ、これがパフォーマンス指向になります。これは、すべての反復の前にCollections.sort()
を呼び出す必要のないクライアントコードのユースケースをカバーし、その動作をクラスにカプセル化します。
_import Java.util.Collections;
import Java.util.Comparator;
import Java.util.Iterator;
import Java.util.LinkedList;
public class SortedList<E> extends LinkedList<E>
{
private Comparator<E> comparator;
public SortedList(final Comparator<E> comparator)
{
this.comparator = comparator;
}
@Override
public Iterator<E> iterator()
{
Collections.sort(this, this.comparator);
return super.iterator();
}
}
_
もちろん、Comparator
がnull
であるかどうかを確認するためのエラーチェックと処理が必要であり、その場合はどうすればよいのでしょうか。重複を処理する決定論的な方法はまだありません。
グアバを使用している場合は、使用する必要があります
Ordering.immutableSortedCopy()
繰り返し処理を行う必要がある場合のみ。
TreeSet(または重複が必要な場合はTreeMultiset)のようなもので、より効率的なランダムアクセスが可能ですが、Javaで実装されたとは思えません。ツリーの各ノードにその左側のサブツリーのサイズを記憶させることで、時間内のインデックスO(log(size))
で要素にアクセスできるようになります。これは悪くありません。
それを実装するには、基になるTreeMapのかなりの部分を書き換える必要があります。
GuavaTreeMultiset を使用します。要素が重複している可能性があるため、List
が必要だと仮定します。それはあなたが望むすべてをします。持っていないものの1つは、インデックスベースのアクセスです。とにかく、選択したインデックスに要素を配置しないので、あまり意味がありません。もう1つ注意すべきことは、equal
オブジェクトの重複を実際には保存しないということです...それらの総数のカウントだけです。
commons-collectionsには TreeBag
があります
最初にPriorityQueue
を提案しましたが、その反復順序は未定義なので、空になるまでキューのクローンの先頭を取得して反復しない限り、役に立ちません。
おそらく反復順序に関心があるので、iterator()
メソッドをオーバーライドできると思います。
public class OrderedIterationList<E> extends ArrayList<E> {
@Override
public Iterator<E> iterator() {
Object[] array = this.toArray(); // O(1)
Arrays.sort(array);
return Arrays.asList(array).iterator(); // asList - O(1)
}
}
ソートされたコレクションのスナップショットを保存してこれを改善し、modCount
を使用してコレクションが変更されていないかどうかを確認できます。
ユースケースによっては、これはPeterの提案よりも効率が劣るかもしれません。たとえば、複数のアイテムを追加して反復する場合。 (繰り返しの間にアイテムを追加しない)、これはより効率的かもしれません。
同様の問題に直面して作成した indexed-tree-map を検討すると、インデックスによって要素にアクセスし、ソート順を維持しながら要素のインデックスを取得できます。重複は、同じキーの下の値として配列に入れることができます。
ソートされた構造をO(n)要素の追加/ indexOf/remove/getの時間より短い)にする唯一の方法は、ツリーを使用することです。その場合、操作は通常O(log2n)そして、トラバースはO(1)のようなものです。
O(n)は単なるリンクリストです。
編集:リンクリストにバイナリ検索を挿入します。挿入操作では、バイナリ構造を使用せず、小さいサイズではないため、最適です。
@Peter:O(log2n)比較(遅い)の挿入とO(n)の動き)のアルゴリズムがあります。 LinkedListをオーバーライドしますので、それを実現しますが、それはできる限りすっきりしています。アルゴリズムをできる限りクリーンに保ち、理解しやすくし、少し最適化することができます。
package t1;
import Java.util.LinkedList;
import Java.util.List;
import Java.util.ListIterator;
import Java.util.Random;
public class SortedList {
private static <T> int binarySearch(ListIterator<? extends Comparable<? super T>> i, T key){
int low = 0;
int high= i.previousIndex();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid;
}
return -(low + 1); // key not found
}
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null;
int pos = i.nextIndex();
if (pos <= index) {
do {
obj = i.next();
} while (pos++ < index);
} else {
do {
obj = i.previous();
} while (--pos > index);
}
return obj;
}
private static void move(ListIterator<?> i, int index) {
int pos = i.nextIndex();
if (pos==index)
return;
if (pos < index) {
do {
i.next();
} while (++pos < index);
}
else {
do {
i.previous();
} while (--pos > index);
}
}
@SuppressWarnings("unchecked")
static <T> int insert(List<? extends Comparable<? super T>> list, T key){
ListIterator<? extends Comparable<? super T>> i= list.listIterator(list.size());
int idx = binarySearch(i, key);
if (idx<0){
idx=~idx;
}
move(i, idx);
((ListIterator<T>)i).add(key);
return i.nextIndex()-1;
}
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<Integer>();
LinkedList<Integer> unsorted = new LinkedList<Integer>();
Random r =new Random(11);
for (int i=0;i<33;i++){
Integer n = r.nextInt(17);
insert(list, n);
unsorted.add(n);
}
System.out.println(" sorted: "+list);
System.out.println("unsorted: "+unsorted);
}
JavaFX TransformationList階層には、SortedListと呼ばれるものがあります。リストは完全に監視可能であるため、追加/削除はリストを監視している他のリスナーに通知します。
これを行うための基本的なアプローチは、他のObservableListの変更を監視し、戦略的にCollections.binarySearch()を使用することです。またはOlog(n)時間で削除します。
ここで言及していない問題が1つあります。それは、同じcompareTo署名、つまりT1.compareTo( T2)==0。この場合、ソートされたリスト(以下に独自のソースコードを投稿します)にはラッパー要素タイプが必要です。これはElement。これは、JavaFXの作成者がSortedListで行ったことに似ています。この理由は完全に削除操作によるものであり、compareToの重複がある場合、元の要素を見つけることは不可能です。通常、TreeSetのようなNavigableSet実装では、これらの重複は決してSetに入りません。リストは異なります。
チェーン内の前のソースが更新されると、結果をダウンストリームに完全に伝播する(Java Streams)に非常によく似た)連鎖可能な観察可能なリストのライブラリがあります。
クラス階層
インターフェース
/**
* Binds the elements of this list to the function application of each element of a
* source observable list.
* <p>
* While a {@code IListContentBinding} is bound, any attempt to modify its contents
* will result in an {@code UnsupportedOperationException}. To unbind the list, call
* {@link #unbind() unbind}.
*
* @param <S> The element type of the input source list that will generate change
* events.
* @param <T> The element type of this output list.
*/
public interface IListContentBinding<S, T> extends ObservableList<T>, ObservableListValue<T>, IContentBinding {... details not shown ....}
すべてのバインディングタイプ(ソート、個別、マップ、フラットマップなど)の抽象基本クラス
/**
* Binds the elements of this list to the function application of each element of a
* source observable list.
* <p>
* While a {@code ListContentBinding} is bound, any attempt to modify its contents
* will result in an {@code UnsupportedOperationException}. To unbind the list, call
* {@link #unbind() unbind}.
*
* @param <S> The element type of the source list that will generate change events.
* @param <T> The element type of this binding.
*/
public abstract class ListContentBinding<S, T> extends ObservableListWrapper<T>
implements IListContentBinding<S, T> {.... details not shown ....}
バインドクラスの並べ替え
/**
* A {@code ListContentBinding} implementation that generates sorted elements from a
* source list. The comparator can be set to another {@code Comparator} function at
* any time through the {@link #comparatorProperty() comparator} property.
* <p>
* Unlike the Collections {@link Collections#sort(List) list sort} or Arrays
* {@link Arrays#sort(Object[]) array sort}, once this binding has been added to the
* order of duplicate elements cannot be guaranteed to match the original order of
* the source list. That is the insertion and removal mechanism do not guarantee that
* the original order of duplicates (those items where T1.compareTo(T2) == 0) is
* preserved. However, any removal from the source list is <i>guaranteed</i> to
* remove the exact object from this sorted list. This is because an int <i>ID</i> field
* is added to the wrapped item through the {@link Element} class to ensure that
* matching duplicates can be further compared.
* <p>
* Added/Removed objects from the source list are placed inside this sorted list
* through the {@link Arrays#binarySearch(Object[], Object, Comparator) array binary
* search} algorithm. For any duplicate item in the sorted list, a further check on
* the ID of the {@code Element} corresponding to that item is compared to the
* original, and that item. Each item added to this sorted list increases the
* counter, the maximum number of items that should be placed in this list should be
* no greater than {@code Integer.MAX_VALUE - Integer.MIN_VALUE}, or 4,294,967,295
* total elements. Sizes greater than this value for an instance of this class
* may produce unknown behavior.
* <p>
* Removal and additions to this list binding are proportional to <i>O(logn)</i>
* runtime, where <i>n</i> is the current total number of elements in this sorted
* list.
*
* @param <T> The element type of the source and this list binding.
*/
class ListContentSortBinding<T> extends ListContentBinding<T, T> implements IListContentSortBinding<T> {
/**
* Each location in the source list has a random value associated it with to deal
* with duplicate elements that would return T1.compareTo(T2) == 0.
*/
private Element[] elements = newElementArray(10);
/**
* The same elements from {@link #elements} but placed in their correct sorted
* position according to the {@link #elementComparator element comparator}.
*/
protected Element[] sortedElements = newElementArray(10);
/**
* Create a new instance.
*
* @param source The source observable list. Sorted elements will be generated
* from the source and set as the content of this list binding.
* @param comparator The sorter. An observable that will update the comparator of
* this binding when invalidated. The sorter can be set to another
* {@code Comparator} function at anytime through the
* {@link #comparatorProperty() comparator} property.
* @param options The options of this binding. Considers {@code DependencyOption}
* instances.
* <p>
* All bindings consider {@code BeforeChangeOption} and
* {@code AfterChangeOption}.
*/
@SafeVarargs
ListContentSortBinding(ObservableList<T> source, ObservableObjectValue<Comparator<? super T>> comparator,
BindingOption<T, T>... options) {
this(source, comparator.get(), options);
comparatorProperty().bind(comparator);
}
/**
* Create a new instance.
*
* @param source The source observable list. Sorted elements will be generated
* from the source and set as the content of this list binding.
* @param comparator The sorter. The sorter can be set to another
* {@code Comparator} function at anytime through the
* {@link #comparatorProperty() comparator} property.
* @param options The options of this binding. Considers {@code DependencyOption}
* instances.
* <p>
* All bindings consider {@code BeforeChangeOption} and
* {@code AfterChangeOption}.
*/
@SafeVarargs
ListContentSortBinding(ObservableList<T> source, Comparator<? super T> comparator,
BindingOption<T, T>... options) {
super(new ArrayList<>(), options);
List<Observable> observables = new ArrayList<>(
Arrays.asList(BindingOptionBuilder.extractDependencies(options)));
setComparator(comparator);
observables.add(comparatorProperty());
bind(source, observables.toArray(new Observable[observables.size()]));
}
@Override
protected void sourceChanged(Change<? extends T> change) {
List<? extends T> source = change.getList();
while (change.next()) {
int from = change.getFrom();
if (change.wasPermutated() || change.wasUpdated()) {
List<? extends T> srcMod = source.subList(from, change.getTo());
removed(source, from, srcMod.size());
added(source, from, srcMod);
} else {
List<? extends T> removed = change.getRemoved();
List<? extends T> added = change.getAddedSubList();
if (change.wasReplaced()) {
int min = Math.min(added.size(), removed.size());
replaced(source, from, added.subList(0, min));
added = added.subList(min, added.size());
removed = removed.subList(min, removed.size());
}
if (removed.size() > 0) {
removed(source, from, removed.size());
}
if (added.size() > 0) {
if (source.size() >= elements.length) {
ensureSize(source.size());
}
added(source, from, added);
}
ensureSize(source.size());
}
}
}
/**
* Replace the items in this sorted list binding resulting from a replacement
* operation in the source list. For each of the items added starting at the
* <i>from</i> index in the source list, and items was removed at the same source
* position.
*
* @param source The source list.
* @param from The index of where the replacement started in the source
* (inclusive). The removed and added elements occurred starting at
* the same source position.
* @param added The added source elements from the change.
*/
@SuppressWarnings({})
private void replaced(List<? extends T> source, int from, List<? extends T> added) {
int oldSize = size();
for (int i = 0; i < added.size(); i++) {
int index = from + i;
Element e = elements[index];
// Find the old element and remove it
int pos = findPosition(e, index, oldSize);
System.arraycopy(sortedElements, pos + 1, sortedElements, pos, oldSize - pos - 1);
remove(pos);
T t = added.get(i);
// Create a new element and add it
e = new Element(t);
elements[index] = e;
pos = findPosition(e, index, oldSize - 1);
if (pos < 0) {
pos = ~pos;
}
System.arraycopy(sortedElements, pos, sortedElements, pos + 1, oldSize - pos - 1);
sortedElements[pos] = e;
add(pos, t);
}
}
/**
* Add the elements from the source observable list to this binding.
*
* @param source The source list.
* @param from The index of where the addition started in the source (inclusive).
* @param added The added source elements from the change.
*/
@SuppressWarnings({})
private void added(List<? extends T> source, int from, List<? extends T> added) {
if (size() == 0) {
int size = added.size();
Element[] temp = newElementArray(size);
for (int i = 0; i < added.size(); i++) {
T t = added.get(i);
Element e = new Element(t);
elements[i] = e;
temp[i] = e;
}
if (elementComparator == null) {
addAll(added);
return;
}
Arrays.sort(temp, elementComparator);
System.arraycopy(temp, 0, sortedElements, 0, temp.length);
addAll(Arrays.stream(temp).map(e -> (T) e.t).collect(Collectors.toList()));
return;
}
int size = size();
System.arraycopy(elements, from, elements, from + added.size(), size - from);
for (int i = 0; i < added.size(); i++) {
int index = from + i;
T t = added.get(i);
Element e = new Element(t);
int pos = findPosition(e, index, size);
if (pos < 0) {
pos = ~pos;
}
elements[index] = e;
if (pos < size) {
System.arraycopy(sortedElements, pos, sortedElements, pos + 1, size - pos);
}
sortedElements[pos] = e;
add(pos, t);
size++;
}
}
/**
* Remove the elements from this binding that were removed from the source list.
* Update the {@link #elements} mapping.
*
* @param source The source list.
* @param from The index of where the removal started in the source (inclusive).
* @param removedSize The total number of removed elements from the source list
* for the change.
*/
@SuppressWarnings({})
private void removed(List<? extends T> source, int from, int removedSize) {
if (source.size() == 0) {
elements = newElementArray(10);
sortedElements = newElementArray(10);
elementCounter = Integer.MIN_VALUE;
clear();
return;
}
int oldSize = size();
int size = oldSize;
for (int i = 0; i < removedSize; i++) {
int index = from + i;
Element e = elements[index];
int pos = findPosition(e, index, size);
System.arraycopy(sortedElements, pos + 1, sortedElements, pos, size - pos - 1);
remove(pos);
sortedElements[--size] = null;
}
System.arraycopy(elements, from + removedSize, elements, from, oldSize - from - removedSize);
for (int i = size; i < oldSize; i++) {
elements[i] = null;
}
}
/**
* Locate the position of the element in this sorted binding by performing a
* binary search. A binary search locates the index of the add in Olog(n) time.
*
* @param e The element to insert.
* @param sourceIndex The index of the source list of the modification.
* @param size The size of the array to search, exclusive.
*
* @return The position in this binding that the element should be inserted.
*/
private int findPosition(Element e, int sourceIndex, int size) {
if (size() == 0) {
return 0;
}
int pos;
if (elementComparator != null) {
pos = Arrays.binarySearch(sortedElements, 0, size, e, elementComparator);
} else {
pos = sourceIndex;
}
return pos;
}
/**
* Ensure that the element array is large enough to handle new elements from the
* source list. Also shrinks the size of the array if it has become too large
* with respect to the source list.
*
* @param size The minimum size of the array.
*/
private void ensureSize(int size) {
if (size >= elements.length) {
int newSize = size * 3 / 2 + 1;
Element[] replacement = newElementArray(newSize);
System.arraycopy(elements, 0, replacement, 0, elements.length);
elements = replacement;
replacement = newElementArray(newSize);
System.arraycopy(sortedElements, 0, replacement, 0, sortedElements.length);
sortedElements = replacement;
} else if (size < elements.length / 4) {
int newSize = size * 3 / 2 + 1;
Element[] replacement = newElementArray(newSize);
System.arraycopy(elements, 0, replacement, 0, replacement.length);
elements = replacement;
replacement = newElementArray(newSize);
System.arraycopy(sortedElements, 0, replacement, 0, replacement.length);
sortedElements = replacement;
}
}
/**
* Combines the {@link #comparatorProperty() item comparator} with a secondary
* comparison if the items are equal through the <i>compareTo</i> operation. This
* is used to quickly find the original item when 2 or more items have the same
* comparison.
*/
private Comparator<Element> elementComparator;
/**
* @see #comparatorProperty()
*/
private ObjectProperty<Comparator<? super T>> comparator =
new SimpleObjectProperty<Comparator<? super T>>(this, "comparator") {
@Override
protected void invalidated() {
Comparator<? super T> comp = get();
if (comp != null) {
elementComparator = Comparator.nullsLast((e1, e2) -> {
int c = comp.compare(e1.t, e2.t);
return c == 0 ? Integer.compare(e1.id, e2.id) : c;
});
} else {
elementComparator = null;
}
}
};
@Override
public final ObjectProperty<Comparator<? super T>> comparatorProperty() {
return comparator;
}
@Override
public final Comparator<? super T> getComparator() {
return comparatorProperty().get();
}
@Override
public final void setComparator(Comparator<? super T> comparator) {
comparatorProperty().set(comparator);
}
@Override
protected void onInvalidating(ObservableList<T> source) {
clear();
ensureSize(source.size());
added(source, 0, source);
}
/**
* Counter starts at the Integer min value, and increments each time a new
* element is requested. If this list becomes empty, the counter is restarted at
* the min value.
*/
private int elementCounter = Integer.MIN_VALUE;
/**
* Generate a new array of {@code Element}.
*
* @param size The size of the array.
*
* @return A new array of null Elements.
*/
@SuppressWarnings("unchecked")
private Element[] newElementArray(int size) {
return new ListContentSortBinding.Element[size];
}
ラッパー要素クラス
/**
* Wrapper class to further aid in comparison of two object types <T>. Since
* sorting in a list allows duplicates we must assure that when a removal occurs
* from the source list feeding this binding that the removed element matches. To
* do this we add an arbitrary <i>int</i> field inside this element class that
* wraps around the original object type <T>.
*/
final class Element {
/** Object */
private final T t;
/** ID helper for T type duplicates */
private int id;
Element(T t) {
this.t = Objects.requireNonNull(t);
this.id = elementCounter++;
}
@Override
public String toString() {
return t.toString() + " (" + id + ")";
}
}
}
JUNIT検証テスト
@Test
public void testSortBinding() {
ObservableList<IntWrapper> source = FXCollections.observableArrayList();
int size = 100000;
for (int i = 0; i < size / 2; i++) {
int index = (int) (Math.random() * size / 10);
source.add(new IntWrapper(index));
}
ListContentSortBinding<IntWrapper> binding =
(ListContentSortBinding<IntWrapper>) CollectionBindings.createListBinding(source).sortElements();
Assert.assertEquals("Sizes not equal for sorted binding | Expected: " +
source.size() + ", Actual: " + binding.size(),
source.size(), binding.size());
List<IntWrapper> sourceSorted = new ArrayList<>(source);
Collections.sort(sourceSorted);
for (int i = 0; i < source.size(); i++) {
IntWrapper expected = sourceSorted.get(i);
IntWrapper actual = binding.get(i);
Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
expected.value + ", Actual: " + actual.value,
expected.value, actual.value);
}
System.out.println("Sorted Elements Equal: Complete.");
// Randomly add chunks of elements at random locations in the source
int addSize = size / 10000;
for (int i = 0; i < size / 4; i++) {
List<IntWrapper> added = new ArrayList<>();
int toAdd = (int) (Math.random() * addSize);
for (int j = 0; j < toAdd; j++) {
int index = (int) (Math.random() * size / 10);
added.add(new IntWrapper(index));
}
int atIndex = (int) (Math.random() * source.size());
source.addAll(atIndex, added);
}
sourceSorted = new ArrayList<>(source);
Collections.sort(sourceSorted);
for (int i = 0; i < source.size(); i++) {
IntWrapper expected = sourceSorted.get(i);
IntWrapper actual = binding.get(i);
Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
expected.value + ", Actual: " + actual.value,
expected.value, actual.value);
}
System.out.println("Sorted Elements Equal - Add Multiple Elements: Complete.");
// Remove one element at a time from the source list and compare
// to the elements that were removed from the sorted binding
// as a result. They should all be identical index for index.
List<IntWrapper> sourceRemoved = new ArrayList<>();
List<IntWrapper> bindingRemoved = new ArrayList<>();
ListChangeListener<IntWrapper> bindingListener = change -> {
while (change.next()) {
if (change.wasRemoved()) {
bindingRemoved.addAll(change.getRemoved());
}
}
};
// Watch the binding for changes after the upstream source changes
binding.addListener(bindingListener);
for (int i = 0; i < size / 4; i++) {
int index = (int) (Math.random() * source.size());
IntWrapper removed = source.remove(index);
sourceRemoved.add(removed);
}
for (int i = 0; i < bindingRemoved.size(); i++) {
IntWrapper expected = bindingRemoved.get(i);
IntWrapper actual = sourceRemoved.get(i);
Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
expected + ", Actual: " + actual,
expected.value, actual.value);
Assert.assertEquals("Element refs not equal in expected sorted lists | Expected: " +
expected.value + ", Actual: " + actual.value,
expected.r, actual.r, 0);
}
System.out.println("Sorted Remove Single Element: Complete.");
// Replace random elements from the source list
bindingRemoved.clear();
sourceRemoved.clear();
int removeSize = size / 10000;
for (int i = 0; i < size / 1000; i++) {
int replaceIndex = (int) (Math.random() * source.size());
int index = (int) (Math.random() * size / 10);
IntWrapper replace = new IntWrapper(index);
source.set(replaceIndex, replace);
}
sourceSorted = new ArrayList<>(source);
Collections.sort(sourceSorted);
for (int i = 0; i < source.size(); i++) {
IntWrapper expected = sourceSorted.get(i);
IntWrapper actual = binding.get(i);
Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
expected.value + ", Actual: " + actual.value,
expected.value, actual.value);
}
System.out.println("Sorted Elements Replace: Complete.");
// Remove random chunks from the source list
bindingRemoved.clear();
sourceRemoved.clear();
Set<IntWrapper> sourceRemovedSet =
Collections.newSetFromMap(new IdentityHashMap<>()); // set for speed
while (source.size() > 0) {
int index = (int) (Math.random() * source.size());
int toRemove = (int) (Math.random() * removeSize);
toRemove = Math.min(toRemove, source.size() - index);
List<IntWrapper> removed = source.subList(index, index + toRemove);
sourceRemovedSet.addAll(new ArrayList<>(removed));
removed.clear(); // triggers list change update to binding
}
Assert.assertEquals(bindingRemoved.size(), sourceRemovedSet.size());
// The binding removed will not necessarily be placed in the same order
// since the change listener on the binding will make sure that the final
// order of the change from the binding is in the same order as the binding
// element sequence. We therefore must do a contains() to test.
for (int i = 0; i < bindingRemoved.size(); i++) {
IntWrapper expected = bindingRemoved.get(i);
Assert.assertTrue("Binding Removed Did Not Contain Source Removed",
sourceRemovedSet.contains(expected));
}
System.out.println("Sorted Removed Multiple Elements: Complete.");
}
JUNITベンチマークテスト
@Test
public void sortBindingBenchmark() {
ObservableList<IntWrapper> source = FXCollections.observableArrayList();
ObservableList<IntWrapper> binding =
(ListContentSortBinding<IntWrapper>) CollectionBindings.createListBinding(source).sortElements();
int size = 200000;
Set<IntWrapper> toAdd = new TreeSet<>();
while (toAdd.size() < size) {
int index = (int) (Math.random() * size * 20);
toAdd.add(new IntWrapper(index));
}
// Randomize the order
toAdd = new HashSet<>(toAdd);
System.out.println("Sorted Binding Benchmark Setup: Complete.");
long time = System.currentTimeMillis();
for (IntWrapper w : toAdd) {
source.add(w);
}
long bindingTime = System.currentTimeMillis() - time;
System.out.println("Sorted Binding Time: Complete.");
source.clear(); // clear the list and re-add
ObservableList<IntWrapper> sortedList = new SortedList<>(source);
time = System.currentTimeMillis();
for (IntWrapper w : toAdd) {
source.add(w);
}
long sortedListTime = System.currentTimeMillis() - time;
System.out.println("JavaFX Sorted List Time: Complete.");
// Make the test "fair" by adding a listener to an observable
// set that populates the sorted set
ObservableSet<IntWrapper> obsSet = FXCollections.observableSet(new HashSet<>());
Set<IntWrapper> sortedSet = new TreeSet<>();
obsSet.addListener((SetChangeListener<IntWrapper>) change -> {
sortedSet.add(change.getElementAdded());
});
time = System.currentTimeMillis();
for (IntWrapper w : toAdd) {
obsSet.add(w);
}
long setTime = System.currentTimeMillis() - time;
System.out.println("Sorted Binding Benchmark Time: Complete");
Assert.assertEquals(sortedSet.size(), binding.size());
System.out.println("Binding: " + bindingTime + " ms, " +
"JavaFX Sorted List: " + sortedListTime + " ms, " +
"TreeSet: " + setTime + " ms");
}
テスト専用のラッパークラス
/**
* Wrapper class for testing sort bindings. Verifies that duplicates were sorted
* and removed correctly based on the object instance.
*/
private static class IntWrapper implements Comparable<IntWrapper> {
static int counter = Integer.MIN_VALUE;
final int value;
final int id;
IntWrapper(int value) {
this.value = value;
this.id = counter++;
}
明らかな解決策は、_Java.util.List
_インターフェイスを実装し、コンストラクターへの引数としてComparator
を取る独自のクラスを作成することです。正しい場所でコンパレーターを使用します。つまり、add
メソッドは既存のアイテムを反復処理し、新しいアイテムを正しい場所に挿入します。 add(int index, Object obj)
などのメソッドの呼び出しを禁止します。
実際、誰かがこれをすでに作成している必要があります...簡単なGoogle検索で、少なくとも1つの例が明らかになります。
http://www.ltg.ed.ac.uk/NITE/nxt/apidoc/net/sourceforge/nite/util/SortedList.html
SortedSetとListの主な違いは次のとおりです。
自動ソートと(合理的な高速)インデックスアクセスの許可の両方の融合が必要なようです。データのサイズと、インデックス化された読み取りまたは新しい要素の追加の頻度に応じて、これらは私のアイデアです。
いずれの場合でも、SortedSetとListのインターフェイスとコントラクトは実際には互換性がないため、Listパーツは読み取り専用(または読み取りと削除のみ)にして、設定と追加を許可せず、余分なオブジェクトを追加するためのオブジェクト(Collectionインターフェイスを実装する場合があります)。
SortedSet
SortedSet
インターフェースの実装は、希望する動作を実行します。
デフォルトでは、追加されたオブジェクトは自然な順序で、つまり Comparable::compareTo
インターフェイスメソッドの実装に基づいてソートされます。
または、 Comparator
実装を渡してソートを決定できます。
TreeSet
TreeSet
は、SortedSet
の一般的な移植です。他の人も見つけることができます。
List
とSortedSet
の主な違いは、重複、等しいと比較されるオブジェクトです。 List
は重複を許可しますが、SortedSet
はSet
と同様に許可しません。
もう1つの違いは、Set
にインデックスでアクセスできないことです。コレクション内の位置番号でオブジェクトを見つけることはできません。
SortedSet
を作成した後にそのようなアクセスが必要な場合は、List
を作成します。これを行うには、SortedSet
をArrayList
のコンストラクターに渡すなど、複数の方法があります。 Java 10)の最近の方法は、List
をList.copyOf
に渡すことにより、変更可能なSortedSet
を作成することです。
これを行う最良の方法は、リストの追加実装をオーバーライドすることです。効率的な挿入を可能にするため、LinkedListを使用してデモを行います。
_public boolean add(Integer e)
{
int i = 0;
for (Iterator<Integer> it = this.iterator(); it.hasNext();)
{
int current = it.next();
if(current > e)
{
super.add(i, e);
return true;
}
i++;
}
return super.add(e);
}
_
上記のコードは、整数のソート済みリストを作成します。これは常にソートされます。他のデータ型で動作するように簡単に変更できます。ただし、ここではadd(index, value)
関数の使用を避ける必要があります。これは明らかに並べ替えを壊してしまうためです。
上記の人々はArrays.sort()を使用することを提案しましたが、特にリストへの追加ごとにsortメソッドを呼び出す必要があるため、大幅に非効率的なアプローチになる可能性があるため、それを避けます。
ListIteratorインターフェースのコントラクトは少し面倒ですが、このメソッドはリストの1回のスキャン(挿入ポイントまで)を使用して挿入を実行します。
private void add(Integer value) {
ListIterator<Integer> listIterator = list.listIterator();
Integer next = null;
while (listIterator.hasNext()) {
next = listIterator.next();
if (next.compareTo(value) > 0) {
break;
}
}
if (next == null || next.compareTo(value) < 0) {
listIterator.add(value);
} else {
listIterator.set(value);
listIterator.add(next);
}
}