web-dev-qa-db-fra.com

Liste des tableaux triés dans Java

Je suis perplexe de ne pas trouver de réponse rapide à cette question. Je cherche essentiellement une structure de données dans Java qui implémente l'interface Java.util.List, Mais stocke ses membres dans un ordre trié. Je sais que vous pouvez utiliser un ArrayList et utiliser Collections.sort() dessus, mais j'ai un scénario dans lequel j'ajoute et récupère parfois des membres de ma liste et je ne veux pas avoir à le trier à chaque fois que je récupère un membre Si quelqu'un en a ajouté un autre, est-ce que quelqu'un peut me dire quelque chose qui existe dans le JDK ou même dans des bibliothèques tierces?

[~ # ~] edit [~ # ~] : la structure de données devra conserver les doublons.

RÉSUMÉ DE LA RÉPONSE : J'ai trouvé tout cela très intéressant et beaucoup appris. Aioobe, en particulier, mérite d’être mentionné pour sa persévérance dans l’obtention de mes exigences précédentes (principalement une implémentation Java.util.List triée prenant en charge les doublons). J'ai accepté sa réponse comme étant la plus précise pour ce que j'ai demandé et la plupart des gens ont réfléchi sur les implications de ce que je cherchais, même si ce que je demandais n'était pas exactement ce dont j'avais besoin.

Le problème avec ce que j'ai demandé réside dans l'interface List elle-même et dans le concept de méthodes facultatives dans une interface. Pour citer le javadoc:

L'utilisateur de cette interface a un contrôle précis sur l'endroit où chaque élément est inséré dans la liste.

L'insertion dans une liste triée n'a pas de contrôle précis sur le point d'insertion. Ensuite, vous devez penser à la façon dont vous allez gérer certaines des méthodes. Prenons add par exemple:

public boolean add (Object o)

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

Vous vous retrouvez maintenant dans la situation inconfortable suivante: 1) Rompez le contrat et implémentez une version triée de add 2) Laisser add ajouter un élément à la fin de la liste, en cassant votre commande triée 3) Quitter add out (comme facultatif) en lançant un UnsupportedOperationException et en implémentant une autre méthode qui ajoute des éléments dans un ordre trié.

L'option 3 est probablement la meilleure, mais je trouve déplorable d'avoir une méthode add que vous ne pouvez pas utiliser et une autre méthode sortAdd qui ne figure pas dans l'interface.

Autres solutions connexes (sans ordre particulier):

  • Java.util.PriorityQueue qui est probablement le plus proche de ce dont j'avais besoin que de ce que j'avais demandé. Une file d'attente n'est pas la définition la plus précise d'une collection d'objets dans mon cas, mais fonctionnellement, elle fait tout ce dont j'ai besoin.
  • net.sourceforge.nite.util.SortedList . Cependant, cette implémentation rompt le contrat de l'interface List en implémentant le tri dans la méthode add(Object obj) et a bizarrement une méthode sans effet pour add(int index, Object obj). Le consensus général suggère que throw new UnsupportedOperationException() pourrait être un meilleur choix dans ce scénario.
  • TreeMultiSet de Guava Une implémentation d'ensemble qui supporte les doublons
  • ca.odell.glazedlists.SortedList Cette classe est mise en garde dans son javadoc: Warning: This class breaks the contract required by List
82
Chris Knight

Solution minimaliste

Voici une solution "minimale".

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);
    }
}

L'insertion s'exécute en temps linéaire, mais ce serait ce que vous obtiendriez de toute façon avec un ArrayList (tous les éléments situés à droite de l'élément inséré devraient être décalés d'une manière ou d'une autre).

L'insertion de résultats non comparables dans une exception ClassCastException. (C’est l’approche adoptée par PriorityQueue également: Une file d’attente prioritaire reposant sur un ordre naturel ne permet pas non plus l’insertion d’objets non comparables (cela pourrait entraîner ClassCastException).)

Remplacer List.add

Notez que remplacer List.add (ou List.addAll d'ailleurs) pour insérer des éléments de manière triée serait un violation directe de la spécification d'interface . Ce que vous pouvez faites, est de surcharger cette méthode pour lancer un UnsupportedOperationException.

Parmi les documents de List.add:

boolean add(E e)
Ajoute l'élément spécifié à la fin de cette liste (opération facultative).

Le même raisonnement s'applique aux deux versions de add, aux deux versions de addAll et à set. (Toutes ces opérations sont facultatives selon l'interface de liste.)


Quelques tests

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);

.... impressions:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]
62
aioobe
11
Gadolin

Vous pouvez essayer 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

Regardez SortedList

Cette classe implémente une liste triée. Il est construit avec un comparateur capable de comparer deux objets et de trier les objets en conséquence. Lorsque vous ajoutez un objet à la liste, il est inséré au bon endroit. Les objets qui sont égaux selon le comparateur seront dans la liste dans l'ordre dans lequel ils ont été ajoutés à cette liste. Ajoutez uniquement des objets que le comparateur peut comparer.


Lorsque la liste contient déjà des objets égaux par rapport au comparateur, le nouvel objet est inséré immédiatement après ces autres objets.

6
Jigar Joshi

L'approche d'Aioobe est la voie à suivre. Je voudrais cependant suggérer l’amélioration suivante par rapport à sa solution.

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
    }
}

la solution d'aioobe devient très lente lors de l'utilisation de grandes listes. Le fait que la liste soit triée nous permet de trouver le point d’insertion de nouvelles valeurs à l’aide de la recherche binaire.

Je voudrais aussi utiliser la composition sur l'héritage, quelque chose dans le sens de

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

Les listes préservent généralement l'ordre dans lequel les éléments sont ajoutés. Avez-vous vraiment besoin d’une liste ou d’un ensemble trié (par exemple TreeSet<E> ) ça va pour vous? Fondamentalement, avez-vous besoin de préserver les doublons?

4
Jon Skeet

C'est peut-être un peu trop lourd pour vous, mais GlazedLists possède un SortedList , parfait à utiliser comme modèle de table ou JList

2
I82Much

Vous pouvez sous-classer ArrayList et appeler Collections.sort (this) après l'ajout de tout élément. Pour ce faire, vous devez remplacer deux versions de add et deux de addAll.

Les performances ne seraient pas aussi bonnes qu'une implémentation plus intelligente qui insèrerait des éléments au bon endroit, mais cela ferait l'affaire. Si l'ajout à la liste est rare, le coût amorti de toutes les opérations figurant sur la liste doit être faible.

1
Tom Anderson

Étant donné que les implémentations actuellement proposées qui implémentent une liste triée en cassant l'API Collection et possèdent une implémentation propre d'un arbre ou quelque chose de similaire, j'étais curieux de savoir comment une implémentation basée sur TreeMap fonctionnerait. (Surtout parce que le TreeSet se base aussi sur TreeMap)

Si quelqu'un s'intéresse à cela aussi, il ou elle peut se sentir libre de l'étudier:

TreeList

Cela fait partie de la bibliothèque principale , vous pouvez bien sûr l’ajouter via la dépendance Maven. (Licence Apache)

Actuellement, la mise en œuvre semble se comparer assez bien au même niveau que la goyave SortedMultiSet et à la TreeList de la bibliothèque Apache Commons.

Mais je serais heureux si plus que moi testait la mise en œuvre pour être sûr de ne pas manquer quelque chose d'important.

Meilleures salutations!

0
Omnaest

J'ai eu le même problème. J'ai donc pris le code source de Java.util.TreeMap et écrit IndexedTreeMap . Il implémente mon propre IndexedNavigableMap :

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

L'implémentation est basée sur la mise à jour des poids de nœud dans l'arborescence rouge-noir lors de sa modification. Weight est le nombre de nœuds enfants situés sous un nœud donné, plus un - self. Par exemple, lorsque vous faites pivoter un arbre vers la gauche:

    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 met simplement à jour les poids à la racine:

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

Et lorsque nous avons besoin de trouver l'élément par index, voici l'implémentation qui utilise des poids:

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);
}

Il est également très utile de trouver l’index d’une clé:

    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;
}

Vous pouvez trouver le résultat de ce travail à l'adresse http://code.google.com/p/indexed-tree-map/

TreeSet/TreeMap (ainsi que leurs équivalents indexés du projet indexé-arbre-carte) n'autorisent pas les clés en double, vous pouvez utiliser 1 clé pour un tableau de valeurs. Si vous avez besoin d'un SortedSet avec des doublons, utilisez TreeMap avec des valeurs sous forme de tableaux. Je ferais ça.

0
Vitaly Sazanovich

Je pense que le choix entre SortedSets/Lists et des collections triables "normales" dépend du fait que vous ayez besoin de trier uniquement à des fins de présentation ou à presque tous les moments de l'exécution. L'utilisation d'une collection triée peut s'avérer beaucoup plus coûteuse car le tri est effectué chaque fois que vous insérez un élément.

Si vous ne pouvez pas opter pour une collection dans le JDK, vous pouvez jeter un oeil à Apache Commons Collections

0
codeporn