33 votes

Goyave: ensemble <K> + Fonction <K,V> = Carte <K,V> ?

Est-il un idiomatiques façon de prendre un Set<K> et Function<K,V>, et d'obtenir un Map<K,V> live view? (c'est à dire l' Map "est soutenu par l' Set et Function combo, et si par exemple, un élément est ajouté à l' Set, puis l'entrée correspondante existe aussi dans l' Map).

(voir, par exemple, Collections2.filter pour plus de discussion sur les affichages en direct)


Que faire si une vue en direct n'est pas nécessaire? Est-il quelque chose de mieux que cela:

public static <K,V> Map<K,V> newMapFrom(Set<K> keys, Function<? super K,V> f) {
    Map<K,V> map = Maps.newHashMap();
    for (K k : keys) {
        map.put(k, f.apply(k));
    }
    return map;
}

29voto

Sean Patrick Floyd Points 109428

Créer une Carte à partir d'un Ensemble et d'une Fonction

Ici sont deux classes qui doivent faire le travail. La première montre une vue de la carte de l'ensemble, alors que la seconde permet d'écrire les valeurs de l'ensemble grâce à une interface spéciale.

La Syntaxe D'Appel:

Map<K,V> immutable = new SetBackedMap<K,V>(Set<K> keys, Function<K,V> func);
Map<K,V> mutable = new MutableSetBackedMap<K,V>(Set<K> keys, Function<K,V> func);

Où mettre ce code?

Remarque: Si la goyave ont été ma bibliothèque, j'avais les rendre accessibles à travers les Cartes de classe:

Map<K,V> immutable = Maps.immutableComputingMap(Set<K> keys, Function<K,V> func);
Map<K,V> mutable = Maps.mutableComputingMap(Set<K> keys, Function<K,V> func);

Immuable version:

J'ai mis en œuvre ce qu'une seule façon de voir:

  • Modifications de cette série sont reflétés dans la carte, mais pas vice-versa (et vous ne pouvez pas changer la carte de toute façon, l' put(key, value) méthode n'est pas mis en œuvre).
  • L' entrySet() itérateur utilise le ensemble itérateur interne, de sorte qu'il sera aussi hériter de l'itérateur interne de l' la manipulation de ConcurrentModificationException.
  • Les deux put(k,v)et entrySet().iterator().remove()sera jeter UnsupportedOperationException.
  • Les valeurs sont mises en cache dans un WeakHashMap, avec le n ° spécial de la simultanéité de la manipulation, c'est à dire il n'y a pas de synchronisation à de tout niveau. Cela va faire dans la plupart des cas, mais si votre fonction est cher, vous pouvez ajouter certains de verrouillage.

Code:

public class SetBackedMap<K, V> extends AbstractMap<K, V>{

    private class MapEntry implements Entry<K, V>{
        private final K key;
        public MapEntry(final K key){
            this.key = key;
        }
        @Override
        public K getKey(){
            return this.key;
        }
        @Override
        public V getValue(){
            V value = SetBackedMap.this.cache.get(this.key);
            if(value == null){
                value = SetBackedMap.this.funk.apply(this.key);
                SetBackedMap.this.cache.put(this.key, value);
            }
            return value;
        }
        @Override
        public V setValue(final V value){
            throw new UnsupportedOperationException();
        }
    }

    private class EntrySet extends AbstractSet<Entry<K, V>>{

        public class EntryIterator implements Iterator<Entry<K, V>>{
            private final Iterator<K> inner;
            public EntryIterator(){
                this.inner = EntrySet.this.keys.iterator();
            }
            @Override
            public boolean hasNext(){
                return this.inner.hasNext();
            }
            @Override
            public Map.Entry<K, V> next(){
                final K key = this.inner.next();
                return new MapEntry(key);
            }
            @Override
            public void remove(){
                throw new UnsupportedOperationException();
            }
        }

        private final Set<K> keys;

        public EntrySet(final Set<K> keys){
            this.keys = keys;
        }

        @Override
        public Iterator<Map.Entry<K, V>> iterator(){
            return new EntryIterator();
        }

        @Override
        public int size(){
            return this.keys.size();
        }

    }

    private final WeakHashMap<K, V> cache;
    private final Set<Entry<K, V>> entries;
    private final Function<? super K, ? extends V> funk;

    public SetBackedMap(
        final Set<K> keys, Function<? super K, ? extends V> funk){
        this.funk = funk;
        this.cache = new WeakHashMap<K, V>();
        this.entries = new EntrySet(keys);
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet(){
        return this.entries;
    }

}

Test:

final Map<Integer, String> map =
    new SetBackedMap<Integer, String>(
        new TreeSet<Integer>(Arrays.asList(
            1, 2, 4, 8, 16, 32, 64, 128, 256)),
        new Function<Integer, String>(){

            @Override
            public String apply(final Integer from){
                return Integer.toBinaryString(from.intValue());
            }
        });
for(final Map.Entry<Integer, String> entry : map.entrySet()){
    System.out.println(
        "Key: " + entry.getKey()
        + ", value: " + entry.getValue());
}

Sortie:

Key: 1, value: 1
Key: 2, value: 10
Key: 4, value: 100
Key: 8, value: 1000
Key: 16, value: 10000
Key: 32, value: 100000
Key: 64, value: 1000000
Key: 128, value: 10000000
Key: 256, value: 100000000

Mutable Version:

Alors que je pense que c'est une bonne idée de faire de cette façon, voici une version pour Emil, qui fournit les deux sens de la vue (c'est une variante de Emil de la variation de ma solution :-)). Il nécessite une extension de la carte d'interface que j'appellerai ComputingMap à préciser que c'est une carte où il n'est pas logique de faire appel put(key, value).

Interface de carte:

public interface ComputingMap<K, V> extends Map<K, V>{
    boolean removeKey(final K key);
    boolean addKey(final K key);
}

Carte de mise en œuvre:

public class MutableSetBackedMap<K, V> extends AbstractMap<K, V> implements
    ComputingMap<K, V>{

    public class MapEntry implements Entry<K, V>{

        private final K key;

        public MapEntry(final K key){
            this.key = key;
        }

        @Override
        public K getKey(){
            return this.key;
        }

        @Override
        public V getValue(){
            V value = MutableSetBackedMap.this.cache.get(this.key);
            if(value == null){
                value = MutableSetBackedMap.this.funk.apply(this.key);
                MutableSetBackedMap.this.cache.put(this.key, value);
            }
            return value;
        }

        @Override
        public V setValue(final V value){
            throw new UnsupportedOperationException();
        }

    }

    public class EntrySet extends AbstractSet<Entry<K, V>>{

        public class EntryIterator implements Iterator<Entry<K, V>>{

            private final Iterator<K> inner;

            public EntryIterator(){
                this.inner = MutableSetBackedMap.this.keys.iterator();
            }

            @Override
            public boolean hasNext(){
                return this.inner.hasNext();
            }

            @Override
            public Map.Entry<K, V> next(){
                final K key = this.inner.next();
                return new MapEntry(key);
            }

            @Override
            public void remove(){
                throw new UnsupportedOperationException();
            }

        }

        public EntrySet(){
        }

        @Override
        public Iterator<Map.Entry<K, V>> iterator(){
            return new EntryIterator();
        }

        @Override
        public int size(){
            return MutableSetBackedMap.this.keys.size();
        }

    }

    private final WeakHashMap<K, V> cache;
    private final Set<Entry<K, V>> entries;
    private final Function<? super K, ? extends V> funk;
    private final Set<K> keys;

    public MutableSetBackedMap(final Set<K> keys,
        final Function<? super K, ? extends V> funk){
        this.keys = keys;
        this.funk = funk;
        this.cache = new WeakHashMap<K, V>();
        this.entries = new EntrySet();
    }

    @Override
    public boolean addKey(final K key){
        return this.keys.add(key);
    }

    @Override
    public boolean removeKey(final K key){
        return this.keys.remove(key);
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet(){
        return this.entries;
    }

}

Test:

public static void main(final String[] args){
    final ComputingMap<Integer, String> map =
        new MutableSetBackedMap<Integer, String>(
            new TreeSet<Integer>(Arrays.asList(
                1, 2, 4, 8, 16, 32, 64, 128, 256)),
            new Function<Integer, String>(){

                @Override
                public String apply(final Integer from){
                    return Integer.toBinaryString(from.intValue());
                }
            });
    System.out.println(map);
    map.addKey(3);
    map.addKey(217);
    map.removeKey(8);
    System.out.println(map);
}

Sortie:

{1=1, 2=10, 4=100, 8=1000, 16=10000, 32=100000, 64=1000000, 128=10000000, 256=100000000}
{1=1, 2=10, 3=11, 4=100, 16=10000, 32=100000, 64=1000000, 128=10000000, 217=11011001, 256=100000000}

22voto

Olivier Cailloux Points 453

Mise en garde. La réponse de Sean Patrick Floyd, bien que très utile, a un défaut. Un simple, mais il m'a fallu un certain temps pour déboguer, alors ne tombez pas dans le même piège: la classe MapEntry nécessite des implémentations d'égaux et de hashcode. Voici les miennes (copie simple du javadoc).

 @Override
public boolean equals(Object obj) {
    if (!(obj instanceof Entry)) {
        return false;
    }
    Entry<?, ?> e2 = (Entry<?, ?>) obj;
    return (getKey() == null ? e2.getKey() == null : getKey().equals(e2.getKey()))
        && (getValue() == null ? e2.getValue() == null : getValue().equals(e2.getValue()));
}

@Override
public int hashCode() {
    return (getKey() == null ? 0 : getKey().hashCode()) ^
        (getValue() == null ? 0 : getValue().hashCode());
}
 

Cette réponse serait préférable comme commentaire de la réponse pertinente, mais AFAIU, je n'ai pas le droit de poster un commentaire (ou n'ai pas trouvé comment!).

14voto

Dave L. Points 19623

Goyave 14 a maintenant des Cartes.asMap pour une vue de l'Ensemble et des Cartes.toMap pour un immuable copie.

Vous pouvez voir une grande partie de la discussion des questions en jeu ici: http://code.google.com/p/guava-libraries/issues/detail?id=56

5voto

Colin Hebert Points 40084

Pour le non affichage en direct le code existe dans lambdaJ avec Lambda.map(Set, Converter).

Set<K> setKs = new Set<K>();
Converter<K, V> converterKv = new Converter<K,V>{
    @Override
    public V convert(K from){
        return null; //Not useful here but you can do whatever you want
    }
}
Map<K, V> mapKvs = Lambda.map(setKs, converterKv);

J'ai essayé de mon propre mise en œuvre : http://ideone.com/Kkpcn Comme dit dans les commentaires, je étend une autre classe alors j'ai juste mis en oeuvre Map, c'est pourquoi il y a beaucoup de code.

Il est totalement inutile (ou pas ?) fonctionnalité qui vous permet de changer le convertisseur à la volée.

2voto

kebry Points 21

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X