Une implémentation de Ordered Set en Java?


98

Si quelqu'un est familier avec Objective-C, il existe une collection appelée NSOrderedSetqui agit comme Set et ses éléments sont accessibles comme ceux d'un Array .

Y a-t-il quelque chose comme ça en Java?

J'ai entendu dire qu'il y avait une collection appelée LinkedHashMap, mais je n'ai rien trouvé de semblable pour un ensemble.


Je travaille sur un problème similaire en C ++. avec NSOrderedSet, pouvons-nous accéder aux éléments dans l'ordre que nous y avons inséré?
Vinay

Savez-vous comment obtenir les fonctionnalités ci-dessus en C ++? i, e agissant en tant que SET et peut être accédé en tant qu'éléments d'un tableau?
Vinay

Réponses:


118

Jetez un œil à la classe LinkedHashSet

Depuis Java doc :

Implémentation de table de hachage et de liste liée de l'interface Set, avec ordre d'itération prévisible . Cette implémentation diffère de HashSet en ce qu'elle maintient une liste à double lien parcourant toutes ses entrées. Cette liste chaînée définit l'ordre des itérations, qui est l'ordre dans lequel les éléments ont été insérés dans l'ensemble (ordre d'insertion) . Notez que l'ordre d'insertion n'est pas affecté si un élément est réinséré dans l'ensemble . (Un élément e est réinséré dans un ensemble s si s.add (e) est invoqué alors que s.contains (e) renverrait true immédiatement avant l'invocation.).


Merci beaucoup. Cela semble trivial à regarder, LinkedHashMapmais je ne l'ai pas trouvé d'une manière ou d'une autre.
Uko


9
Pourquoi cette réponse reçoit-elle autant de votes positifs? Ce n'est pas du tout une réponse à la question. Il n'y a aucune fonction LinkedHashSetqui vous permet de déterminer dans quel index se trouve l'élément.
searchengine27

31

Chaque ensemble a un itérateur (). L'itérateur d'un HashSet normal est assez aléatoire, un TreeSet le fait par ordre de tri, un itérateur LinkedHashSet itère par ordre d'insertion.

Cependant, vous ne pouvez pas remplacer un élément dans un LinkedHashSet. Vous pouvez en supprimer un et en ajouter un autre, mais le nouvel élément ne sera pas à la place de l'original. Dans un LinkedHashMap, vous pouvez remplacer une valeur pour une clé existante, puis les valeurs seront toujours dans l'ordre d'origine.

De plus, vous ne pouvez pas insérer à une certaine position.

Vous feriez peut-être mieux d'utiliser une ArrayList avec une vérification explicite pour éviter d'insérer des doublons.


Je veux pouvoir définir / obtenir des éléments sur une position spécifique et les obtenir par ordre, je les ai ajoutés. Il semble que cela LinkedHashSetdevrait faire cela. Merci pour la réponse
Uko

11

Jetez un œil à la documentation de l'API standard Java . Juste à côté LinkedHashMap, il y a un fichier LinkedHashSet. Mais notez que l'ordre dans ceux-ci est l'ordre d'insertion, pas l'ordre naturel des éléments. Et vous ne pouvez itérer que dans cet ordre, pas d'accès aléatoire (sauf en comptant les étapes d'itération).

Il existe également une interface SortedSetimplémentée par TreeSetet ConcurrentSkipListSet. Les deux permettent l'itération dans l' ordre naturel de leurs éléments ou dans un Comparatorordre d'accès ou d'insertion aléatoire, mais pas.

Pour une structure de données qui a à la fois un accès efficace par index et peut mettre en œuvre efficacement le critère défini, vous auriez besoin d'un liste de sauts , mais il n'y a pas d'implémentation avec cette fonctionnalité dans l'API Java Standard, même si je suis certain qu'il est facile d'en trouver une sur Internet.


J'ai peut-être mal compris votre commentaire, mais j'avais l'impression que depuis Java 1.6, il y avait plusieurs collections par défaut basées sur des listes de sauts (comme, par exemple, ConcurrentSkipListSet etc.).
TacticalCoder

@ user988052: oui, mais ceux-ci n'implémentent pas l'accès aléatoire par index (bien que ma compréhension des listes de sauts indique que cela devrait être possible), ce qui semble être ce que Uko veut.
Michael Borgwardt

@MichaelBorgwardt Java 6 et versions ultérieures comprend une paire d'implémentations de liste de sauts: ConcurrentSkipListMapet ConcurrentSkipListSet. Les deux maintiennent un tri basé sur l'ordre naturel ou un comparateur. Je ne comprends pas s'ils fournissent l'accès aléatoire ou l'ordre d'entrée dont vous parlez.
Basil Bourque

@BasilBourque: bonne trouvaille, et merci pour les modifications. OP voulait un accès par index, et maintenant que je l'ai regardé et que j'y pense, je pense que les listes à sauter n'ont pas non plus cette capacité ...
Michael Borgwardt

5

C'est la bonne réponse. Contrairement à LHSet, TreeSet ne mettre en œuvre java.util.SortedSet.
vemv

40
ordonné et trié est des choses différentes. TreeSet est trié, pas commandé
andrii

2
Exactement, ordonné fait référence à l'ordre d'insertion (la façon dont une liste fonctionne), tandis que trié fait référence à l'ordre après le fait des éléments en fonction de certains critères.
Cornel Masson

5

Essayez d'utiliser java.util.TreeSetces outilsSortedSet .

Pour citer la doc:

"Les éléments sont ordonnés en utilisant leur ordre naturel, ou par un comparateur fourni au moment de la création de l'ensemble, selon le constructeur utilisé"

Notez que ajouter, supprimer et contient a un journal de coût en temps (n).

Si vous souhaitez accéder au contenu de l'ensemble en tant que tableau, vous pouvez le convertir en:

YourType[] array = someSet.toArray(new YourType[yourSet.size()]); 

Ce tableau sera trié avec les mêmes critères que le TreeSet (naturel ou par un comparateur), et dans de nombreux cas cela aura un avantage au lieu de faire un Arrays.sort ()


1
J'ai besoin de commander comme dans ArrayList ei si je mets premier élément cet élément a, comme je itérer sur une collection que je veux les obtenir dans le même ordre: c, aetc.
Uko

1

treeet est un ensemble ordonné, mais vous ne pouvez pas accéder via un index d'éléments, il suffit de parcourir ou d'aller au début / à la fin.


Avec treeSet, vous encourez des coûts supplémentaires. LinkedHashSet a un coût inférieur.
Carlos le

0

Si on parle de mise en œuvre peu coûteuse de la skip-list, je me demande en terme de big O, quel est le coût de cette opération:

YourType [] array = someSet.toArray (new YourType [yourSet.size ()]);

Je veux dire qu'il est toujours coincé dans une création de tableau entier, donc c'est O (n):

java.util.Arrays#copyOf

1
Cela dépend des caractéristiques de performance de l'itérateur et de la size()méthode de l'ensemble sous-jacent. L'itération est généralement O(n), la taille est généralement O(1)sauf ConcurrentSkipListSetlà où elle est O(n).
Ian Roberts


0

Vous pouvez également tirer parti d'une carte bidirectionnelle comme celle BiMapde Google Guava

Avec a BiMap, vous pouvez mapper assez efficacement un entier (pour un accès d'index aléatoire) à n'importe quel autre type d'objet. BiMaps sont un-à-un, donc tout entier donné a, au plus, un élément associé, et tout élément a un entier associé. Il est intelligemment soutenu par deux HashTableinstances, donc il utilise presque le double de la mémoire, mais c'est beaucoup plus efficace qu'un Listtraitement personnalisé en ce qui concerne le traitement car contains()(qui est appelé lorsqu'un élément est ajouté pour vérifier s'il existe déjà) est un temps constant et un fonctionnement parallèle comme celui de HashSet's, tandis que Listla mise en œuvre de' s est BEAUCOUP plus lente.


0

J'avais un problème similaire. Je n'avais pas vraiment besoin d'un ensemble ordonné mais plutôt d'une liste avec un indexOf/ contains. Comme je n'ai rien trouvé là-bas, j'en ai implémenté un moi-même. Voici le code, il implémente les deux Setet List, bien que toutes les opérations de liste en bloc ne soient pas aussi rapides que leArrayList versions.

avertissement: non testé

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.util.Collection;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import static java.util.Objects.requireNonNull;

/**
 * An ArrayList that keeps an index of its content so that contains()/indexOf() are fast. Duplicate entries are
 * ignored as most other java Set's do.
 */
public class IndexedArraySet<E> extends ArrayList<E> implements Set<E> {

    public IndexedArraySet() { super(); }

    public IndexedArraySet(Iterable<E> c) {
        super();
        addAll(c);
    }

    private HashMap<E, Integer> indexMap = new HashMap<>();

    private void reindex() {
        indexMap.clear();
        int idx = 0;
        for (E item: this) {
            addToIndex(item, idx++);
        }
    }

    private E addToIndex(E e, int idx) {
        indexMap.putIfAbsent(requireNonNull(e), idx);
        return e;
    }

    @Override
    public boolean add(E e) {
        if(indexMap.putIfAbsent(requireNonNull(e), size()) != null) return false;
        super.add(e);
        return true;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return addAll((Iterable<? extends E>) c);
    }
    public boolean addAll(Iterable<? extends E> c) {
        boolean rv = false;
        for (E item: c) {
            rv |= add(item);
        }
        return rv;
    }

    @Override
    public boolean contains(Object e) {
        return indexMap.containsKey(e);
    }

    @Override

    public int indexOf(Object e) {
        if (e == null) return -1;
        Integer i = indexMap.get(e);
        return (i == null) ? -1 : i;
    }

    @Override
    public int lastIndexOf(Object e) {
        return indexOf(e);
    }

    @Override @SuppressWarnings("unchecked")
    public Object clone() {
        IndexedArraySet clone = (IndexedArraySet) super.clone();
        clone.indexMap = (HashMap) indexMap.clone();
        return clone;
    }

    @Override
    public void add(int idx, E e) {
        if(indexMap.putIfAbsent(requireNonNull(e), -1) != null) return;
        super.add(idx, e);
        reindex();
    }

    @Override
    public boolean remove(Object e) {
        boolean rv;
        try { rv = super.remove(e); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public void clear() {
        super.clear();
        indexMap.clear();
    }

    @Override
    public boolean addAll(int idx, Collection<? extends E> c) {
        boolean rv;
        try {
            for(E item : c) {
                // check uniqueness
                addToIndex(item, -1);
            }
            rv = super.addAll(idx, c);
        } finally {
            reindex();
        }
        return rv;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean rv;
        try { rv = super.removeAll(c); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean rv;
        try { rv = super.retainAll(c); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        boolean rv;
        try { rv = super.removeIf(filter); }
        finally { reindex(); }
        return rv;
    }

    @Override
    public void replaceAll(final UnaryOperator<E> operator) {
        indexMap.clear();
        try {
            int duplicates = 0;
            for (int i = 0; i < size(); i++) {
                E newval = requireNonNull(operator.apply(this.get(i)));
                if(indexMap.putIfAbsent(newval, i-duplicates) == null) {
                    super.set(i-duplicates, newval);
                } else {
                    duplicates++;
                }
            }
            removeRange(size()-duplicates, size());
        } catch (Exception ex) {
            // If there's an exception the indexMap will be inconsistent
            reindex();
            throw ex;
        }

    }

    @Override
    public void sort(Comparator<? super E> c) {
        try { super.sort(c); }
        finally { reindex(); }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.