En Java, vous pouvez créer une ArrayList avec des éléments, puis appeler:
Collections.sort(list, comparator);
Est-il possible de passer le comparateur au moment de la création de la liste, comme vous pouvez le faire avec TreeMap? Le but est de pouvoir ajouter un élément à la liste et au lieu de l’ajouter automatiquement à la fin de la liste, la liste se tiendrait elle-même en fonction du comparateur et insérerait le nouvel élément à l’index déterminé par le comparateur. Il est donc possible que la liste doive être triée à chaque nouvel élément ajouté.
Est-il possible d'y parvenir de cette manière avec le comparateur ou par un autre moyen similaire?
Vous pouvez changer le comportement de ArrayList
List<MyType> list = new ArrayList<MyType>() {
public boolean add(MyType mt) {
super.add(mt);
Collections.sort(list, comparator);
return true;
}
};
Remarque: une PriorityQueue n'est PAS une liste. Si vous ne vouliez pas savoir quel type de collection il s'agissait, le plus simple serait d'utiliser un TreeSet, qui ressemble à un TreeMap mais qui est une collection. Le seul avantage de PriorityQueue est d’autoriser les doublons.
Remarque: le recours aux ressources n'est pas très efficace pour les grandes collections. Il serait plus rapide d'utiliser une recherche binaire et d'insérer une entrée. (mais plus compliqué)
EDIT: Beaucoup dépend de ce que vous avez besoin de la "liste" à faire. Je vous suggère d'écrire un wrapper de liste pour un ArrayList, LinkedList, PriorityQueue, TreeSet ou l'une des autres collections triées et d'implémenter les méthodes qui seront réellement utilisées. De cette façon, vous comprenez bien les exigences de la collection et vous pouvez vous assurer que cela fonctionne correctement pour vous.
EDIT (2): Puisqu'il y avait tellement d'intérêt d'utiliser binarySearch à la place. ;)
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;
}
};
Tout le monde suggère PriorityQueue
. Cependant, il est important de réaliser que si vous parcourez le contenu de PriorityQueue
, les éléments ne seront pas triés. Vous êtes uniquement assuré d'obtenir l'élément "minimum" des méthodes peek()
, poll()
, etc.
Un TreeSet
semble être un meilleur ajustement. Les avertissements seraient que, en tant que Set
, il ne peut pas contenir d'éléments en double et ne prend pas en charge l'accès aléatoire avec un index.
Il y a probablement une bonne raison pour laquelle il n'y a pas d'implémentation SortedList
dans le JDK. Personnellement, je ne vois aucune raison d'avoir un tri automatique dans le JDK.
Cela sent mauvais l'optimisation prématurée qui a mal tourné. Si la liste n'est pas lue aussi souvent qu'elle est insérée, vous gaspillez des cycles de tri répétitif sans raison. Un tri juste avant une lecture serait beaucoup plus réactif et avoir un boolean
quelque part indiquant que la liste a ou non besoin d'être triée avant d'être lu serait encore mieux.
Le fait est que vous ne vous souciez que de l'ordre lorsque vous parcourez la liste avec une boucle Iterator
ou for each
, appelez donc Collections.sort()
avant tout code itérant serait probablement plus performant que d'essayer de garder la liste triée tout le temps à chaque insertion.
Il y a des ambiguïtés avec List
en raison des doublons, comment commander des doublons de manière déterministe? Il y a SortedSet
et cela a du sens en raison de l'unicité. Mais trier une List
peut être plus compliqué par les effets secondaires des doublons et d’autres contraintes, telles que rendre chaque objet Comparable
ou comme je le montre dans mon code, comme ayant une Comparator
capable de faire le travail à la place.
.add()
Si vous rencontrez une situation très spéciale dans laquelle un tri automatique List
serait utile, vous pouvez sous-classer une implémentation List
et ignorer .add()
pour créer un Collections.sort(this, comparator)
que vous transmettez à un constructeur personnalisé. J'ai utilisé LinkedList
au lieu de ArrayList
pour une raison, ArrayList
est une insertion naturelle triée par ordre List
pour commencer. Il a également la capacité de .add()
à un index qui est plutôt inutile si vous voulez une constante List
, qui devra être gérée de façon quelque peu moins idéale. Selon le Javadoc;
void add(int index, Object element)
Insère l'élément spécifié dans le fichier position spécifiée dans cette liste (opération facultative).
Donc, lancer UnSupportedOperationException
serait acceptable, ou vous pourriez simplement ignorer index
et déléguer à .add(Object element);
si vous le documentez dans un JavaDoc sur la méthode.
Habituellement, lorsque vous souhaitez effectuer un grand nombre d'insertions/extractions et un tri, vous devez utiliser un paramètre LinkedList
en raison de meilleures performances, compte tenu de l'utilisation de `List '.
Voici un exemple rapide:
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;
}
}
Sinon, vous pouvez uniquement trier lorsque vous obtenez la variable Iterator
. Ce serait davantage axé sur les performances si l'ordre trié n'était vraiment important que lors de l'itération sur la variable List
. Cela couvrirait le cas d'utilisation du code client n'ayant pas à appeler, Collections.sort()
avant chaque itération et encapsulant ce comportement dans la classe.
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();
}
}
Bien sûr, il faudrait effectuer une vérification et une gestion des erreurs pour déterminer si la variable Comparator
était null
ou non et ce qu'il fallait faire si c'était le cas, mais cela vous en donne l'idée. Vous n'avez toujours pas de méthode déterministe pour traiter les doublons.
Si vous utilisez Guava et vous devriez le faire, vous pouvez simplement utiliser
Ordering.immutableSortedCopy()
uniquement lorsque vous devez effectuer une itération et en avoir terminé.
Quelque chose comme TreeSet (ou TreeMultiset si vous avez besoin de doublons) avec un accès aléatoire plus efficace est possible, mais je doute que cela ait été implémenté en Java. Faire en sorte que chaque nœud de l’arbre se souvienne de la taille de son sous-arbre de gauche permet d’accéder à un élément par index dans le temps O(log(size))
qui n’est pas mauvais.
Pour l'implémenter, vous devez réécrire une bonne partie de la TreeMap sous-jacente.
Je voudrais utiliser un GuavaTreeMultiset en supposant que vous voulez un List
parce que vous pouvez avoir des éléments en double. Ça fera tout ce que tu veux. La seule chose dont il n’aura pas accès, c’est l’accès indexé, ce qui n’a pas beaucoup de sens étant donné que vous ne mettez pas des éléments dans les index de votre choix. L’autre élément à prendre en compte est qu’il ne stockera pas les doublons d’objets equal
, mais simplement leur nombre.
commons-collections ont TreeBag
Au départ, j'ai suggéré PriorityQueue
, mais son ordre d'itération n'est pas défini, il est donc inutile, sauf si vous le répétez en récupérant la tête d'un clone de la file jusqu'à ce qu'elle soit vide.
Puisque vous êtes probablement concerné par l'ordre des itérations, je pense que vous pouvez remplacer la méthode 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)
}
}
Vous pouvez améliorer cela en stockant un instantané de la collection triée et utilisez modCount
pour vérifier si la collection n'est pas modifiée.
Selon les cas d'utilisation, cela peut être moins ou plus efficace que la suggestion de Peter. Par exemple, si vous ajoutez plusieurs éléments et effectuez une itération. (sans ajouter d'éléments entre les itérations), cela pourrait alors être plus efficace.
Considérez indexed-tree-map que j'ai créé en faisant face à un problème similaire, vous pourrez accéder aux éléments par index et obtenir un index des éléments tout en conservant l'ordre de tri. Les doublons peuvent être placés dans des tableaux en tant que valeurs sous la même clé.
Le seul moyen d'avoir une structure triée avec moins de O(n) temps pour ajouter/indexOf/remove/get est d'utiliser un arbre. Dans ce cas, les opérations ont généralement O(log2n) et traverse est comme O (1).
O (n) est juste une liste chaînée.
Edition: insertion dans la liste chaînée avec recherche binaire. Pour les opérations d'insertion, n'utilisant pas de structure binaire, ni de petites tailles, cela devrait être optimal.
@Peter: Il y a l'algo w/O(log2n) compare (qui sont lents) pour insérer et O(n) se déplace . Si vous devez remplacer LinkedList, alors que ce soit. Mais c'est aussi beau que possible. Je garde l'algorithme aussi propre que possible pour qu'il soit facilement compréhensible, il peut être optimisé un peu.
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);
}
Dans la hiérarchie JavaFX TransformationList, il existe quelque chose appelé SortedList. La liste est entièrement observable afin que les ajouts/suppressions avertissent les autres auditeurs qui la regardent.
L’approche de base consiste à surveiller les autres modifications dans ObservableList et à utiliser de manière stratégique Collections.binarySearch () comme d’autres l’ont suggéré pour localiser l’index de l’ajout ou de la suppression dans le temps Olog (n).
Il y a un problème que je n'ai pas vu mentionné ici et qui concerne la possibilité de suivre les éléments ajoutés ayant la même signature compareTo, c'est-à-dire T1.compareTo (T2) == 0. Dans ce cas, la liste triée (I posterai mon propre code source ci-dessous) doit avoir un type d’élément wrapper, que j’appellerai Element. Ceci est similaire à ce que les créateurs de JavaFX ont fait avec SortedList . La raison en est entièrement due aux opérations de suppression, il est impossible de localiser l'élément d'origine s'il existe des doublons compareTo. Normalement, dans une implémentation NavigableSet telle que TreeSet, ces doublons n'entrent jamais dans l'ensemble. Une liste est différente.
J'ai une bibliothèque de listes observables qui peuvent être efficacement chaînées (très similaire à Java Streams) et qui propagent entièrement les résultats en aval en tant que source précédente dans les mises à jour de la chaîne.
Hiérarchie des classes
Interface
/**
* 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 ....}
Classe de base abstraite pour tous les types de reliure (Tri, Distinct, Map, FlatMap, etc.)
/**
* 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 ....}
Classer la classe de reliure
/**
* 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];
}
Classe d'élément d'emballage
/**
* 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 + ")";
}
}
}
TEST DE VÉRIFICATION DE 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.");
}
TEST BENCHMARK 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");
}
Classe d'emballage pour les tests uniquement
/**
* 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++;
}
La solution évidente consiste à créer votre propre classe qui implémente l'interface Java.util.List
et prend un Comparator
comme argument du constructeur. Vous utiliseriez le comparateur aux bons endroits, c’est-à-dire que la méthode add
parcourait les éléments existants et insérait le nouvel élément au bon endroit. Vous interdiriez les appels à des méthodes telles que add(int index, Object obj)
et ainsi de suite.
En fait, il faut que quelqu'un ait déjà créé cela ... une recherche rapide sur Google en révèle au moins un exemple:
http://www.ltg.ed.ac.uk/NITE/nxt/apidoc/net/sourceforge/nite/util/SortedList.html
La principale différence entre SortedSet et List est:
Vous semblez vouloir une fusion des deux: le tri automatique et l’autorisation d’un accès (rapide) aux index. Selon la taille des données et la fréquence de lecture indexée ou d’ajout de nouveaux éléments, voici mes idées:
Dans tous les cas, les interfaces et les contrats de SortedSet et List ne sont pas vraiment compatibles. Vous souhaitez donc que la partie List soit en lecture seule (ou en lecture-suppression uniquement), ne permettant pas la configuration et l'ajout, ni object (peut-être en implémentant l'interface Collection) pour l'ajout d'objets.
SortedSet
Toute implémentation de l'interface SortedSet
porte le comportement souhaité.
Par défaut, les objets ajoutés sont triés dans leur ordre naturel, c'est-à-dire en fonction de leur implémentation de la méthode Comparable::compareTo
interface.
Vous pouvez également passer une implémentation Comparator
pour déterminer le tri.
TreeSet
La TreeSet
est une implantation couramment utilisée de SortedSet
. Vous pouvez aussi en trouver d'autres.
La principale différence entre une List
et une SortedSet
réside dans les doublons, objets comparés de la même manière. Un List
autorise les doublons, alors qu'un SortedSet
, comme tout Set
, ne le permet pas.
Une autre différence est qu’on ne peut pas accéder à Set
par index. Vous ne pouvez pas localiser un objet par son numéro de position dans la collection.
Si vous avez besoin de cet accès après avoir construit votre SortedSet
, créez un List
. Il existe plusieurs façons de procéder, telles que passer la SortedSet
au constructeur de ArrayList
. Une façon récente de faire, à partir de Java 10, consiste à créer une List
non modifiable en passant la SortedSet
à List.copyOf
.
La meilleure façon de le faire serait de remplacer l'implémentation d'une liste par l'ajout ..__ Je vais utiliser une liste LinkedList pour le démontrer, car cela permet une insertion efficace.
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);
}
Le code ci-dessus crée une liste triée d'entiers, toujours triée. Il peut facilement être modifié pour fonctionner avec tout autre type de données. Cependant, dans ce cas, vous devrez éviter d'utiliser la fonction add(index, value)
, car cela romprait évidemment le tri.
Bien que les personnes ci-dessus aient suggéré d'utiliser Arrays.sort (), j'éviterais cela, car l'approche peut être nettement moins efficace, d'autant plus que la méthode de tri doit être appelée à chaque ajout à la liste.
Le contrat de l'interface ListIterator est un peu lourd, mais cette méthode effectuera l'insertion à l'aide d'un seul balayage de la liste (jusqu'au point d'insertion):
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);
}
}