web-dev-qa-db-fra.com

Le moyen le plus clair de combiner deux listes dans une carte (Java)?

Ce serait bien d'utiliser for (String item: list), mais cela ne fera que parcourir une liste et vous aurez besoin d'un itérateur explicite pour l'autre liste. Ou, vous pouvez utiliser un itérateur explicite pour les deux.

Voici un exemple du problème et une solution utilisant à la place une boucle indexée for:

import Java.util.*;
public class ListsToMap {
  static public void main(String[] args) {
    List<String> names = Arrays.asList("Apple,orange,pear".split(","));
    List<String> things = Arrays.asList("123,456,789".split(","));
    Map<String,String> map = new LinkedHashMap<String,String>();  // ordered

    for (int i=0; i<names.size(); i++) {
      map.put(names.get(i), things.get(i));    // is there a clearer way?
    }

    System.out.println(map);
  }
}

Sortie:

{Apple=123, orange=456, pear=789}

Y a-t-il un moyen plus clair? Peut-être que dans l'API de collections quelque part?

49
13ren

Étant donné que la relation clé-valeur est implicite via l'index de liste, je pense que la solution de boucle for qui utilise explicitement l'index de liste est en fait assez claire - et courte également.

19
Michael Borgwardt

J'utilisais souvent l'idiome suivant. J'admets qu'il soit discutable de savoir si c'est plus clair.

Iterator<String> i1 = names.iterator();
Iterator<String> i2 = things.iterator();
while (i1.hasNext() && i2.hasNext()) {
    map.put(i1.next(), i2.next());
}
if (i1.hasNext() || i2.hasNext()) complainAboutSizes();

Il présente l’avantage de fonctionner également pour les collections et les objets similaires sans accès aléatoire ou sans accès aléatoire efficace, comme LinkedList, TreeSets ou SQL ResultSets. Par exemple, si vous souhaitez utiliser l'algorithme d'origine sur LinkedLists, vous obtenez un algorithme lent Shlemiel the Painter qui nécessite en fait n * n opérations pour les listes de longueur n.

Comme indiqué par 13ren , vous pouvez également utiliser le fait que Iterator.next lève une exception NoSuchElementException si vous essayez de lire après la fin d'une liste lorsque les longueurs ne correspondent pas. Donc, vous aurez le terser, mais peut-être une petite variante déroutante:

Iterator<String> i1 = names.iterator();
Iterator<String> i2 = things.iterator();
while (i1.hasNext() || i2.hasNext()) map.put(i1.next(), i2.next());
37
Hans-Peter Störr

Cela fait longtemps que cette question a été posée, mais ces jours-ci, je suis plutôt porté sur quelque chose comme:

public static <K, V> Map<K, V> zipToMap(List<K> keys, List<V> values) {
    return IntStream.range(0, keys.size()).boxed()
            .collect(Collectors.toMap(keys::get, values::get));
}

Pour ceux qui ne sont pas familiers avec les flux, ceci obtient une IntStream de 0 à la longueur, puis l'encapsule, le transformant en Stream<Integer> afin qu'il puisse être transformé en objet, puis les collecte à l'aide de Collectors.toMap qui prend deux fournisseurs génère les clés, les autres les valeurs.

Cela pourrait nécessiter une validation (comme exiger que keys.size() soit inférieur à values.size()), mais cela fonctionne très bien comme solution simple.

EDIT: Ce qui précède fonctionne très bien pour tout ce qui consomme du temps, mais si vous voulez quelque chose qui fonctionne dans le même ordre (tout en utilisant le même modèle), vous pouvez faire quelque chose comme:

public static <K, V> Map<K, V> zipToMap(List<K> keys, List<V> values) {
    Iterator<K> keyIter = keys.iterator();
    Iterator<V> valIter = values.iterator();
    return IntStream.range(0, keys.size()).boxed()
            .collect(Collectors.toMap(_i -> keyIter.next(), _i -> valIter.next()));
}

La sortie est la même (encore une fois, vérification de la longueur manquante, etc.), mais la complexité temporelle ne dépend pas de la mise en œuvre de la méthode get pour la liste utilisée.

36
DrGodCarl

_ {Votre solution ci-dessus est correcte, bien sûr, mais votre question portait sur la clarté, je vais répondre à cela.} _

La la plus claire combinaison de deux listes consisterait à placer la combinaison dans une méthode portant un nom clair de Nice. Je viens de prendre votre solution et de l'extraire à une méthode ici:


  Map<String,String> combineListsIntoOrderedMap (List<String> keys, List<String> values) {
    if (keys.size() != values.size())
      throw new IllegalArgumentException ("Cannot combine lists with dissimilar sizes");
    Map<String,String> map = new LinkedHashMap<String,String>();
    for (int i=0; i<keys.size(); i++) {
      map.put(keys.get(i), values.get(i));
    }
    return map;
  }

Et bien sûr, votre main refactorisée ressemblerait maintenant à ceci:


  static public void main(String[] args) {
    List<String> names = Arrays.asList("Apple,orange,pear".split(","));
    List<String> things = Arrays.asList("123,456,789".split(","));
    Map<String,String> map = combineListsIntoOrderedMap (names, things);
    System.out.println(map);
  }

Je n'ai pas pu résister à la vérification de la longueur.

9
CPerkins

Personnellement, j’estime qu’une solution simple de boucle itérative sur les indices est la solution la plus claire, mais voici deux autres possibilités.

Une autre solution Java 8 évitant d’appeler boxed() sur une IntStream est la suivante:

List<String> keys = Arrays.asList("A", "B", "C");
List<String> values = Arrays.asList("1", "2", "3");

Map<String, String> map = IntStream.range(0, keys.size())
                                   .collect(
                                        HashMap::new, 
                                        (m, i) -> m.put(keys.get(i), values.get(i)), 
                                        Map::putAll
                                   );
                          );
6
Paul Boddington

ArrayUtils # toMap () ne combine pas deux listes dans une carte, mais le fait pour un tableau à 2 dimensions (donc pas tout à fait ce que vous cherchez, mais peut-être d'intérêt pour une référence future ...)

5
Joel

En plus de la clarté, je pense qu'il y a d'autres choses à considérer:

  • Rejet correct des arguments illégaux, tels que des listes de tailles différentes et nulls (regardez ce qui se passe si things est null dans le code de question).
  • Capacité à gérer des listes sans accès aléatoire rapide.
  • Capacité à gérer des collections simultanées et synchronisées.

Donc, pour le code de bibliothèque, peut-être quelque chose comme ceci:

@SuppressWarnings("unchecked")
public static <K,V> Map<K,V> linkedZip(List<? extends K> keys, List<? extends V> values) {
    Object[] keyArray = keys.toArray();
    Object[] valueArray = values.toArray();
    int len = keyArray.length;
    if (len != valueArray.length) {
        throwLengthMismatch(keyArray, valueArray);
    }
    Map<K,V> map = new Java.util.LinkedHashMap<K,V>((int)(len/0.75f)+1);
    for (int i=0; i<len; ++i) {
        map.put((K)keyArray[i], (V)valueArray[i]);
    }
    return map;
}

(Peut vouloir vérifier ne pas mettre plusieurs clés égales.)

5

Il n'y a pas de moyen clair. Je me demande encore si Apache Commons ou Guava a quelque chose de similaire. Quoi qu'il en soit, j'avais mon propre utilitaire statique. Mais celui-ci est conscient des collisions clés!

public static <K, V> Map<K, V> map(Collection<K> keys, Collection<V> values) {

    Map<K, V> map = new HashMap<K, V>();
    Iterator<K> keyIt = keys.iterator();
    Iterator<V> valueIt = values.iterator();
    while (keyIt.hasNext() && valueIt.hasNext()) {
        K k = keyIt.next();
        if (null != map.put(k, valueIt.next())){
            throw new IllegalArgumentException("Keys are not unique! Key " + k + " found more then once.");
        }
    }
    if (keyIt.hasNext() || valueIt.hasNext()) {
        throw new IllegalArgumentException("Keys and values collections have not the same size");
    };

    return map;
}
4
Plap

Vous n'avez même pas besoin de vous limiter aux chaînes. Modification du code de CPerkins un peu:

Map<K, V> <K, V> combineListsIntoOrderedMap (List<K> keys, List<V> values) {
      if (keys.size() != values.size())
          throw new IllegalArgumentException ("Cannot combine lists with dissimilar sizes");
Map<K, V> map = new LinkedHashMap<K, V>();
for (int i=0; i<keys.size(); i++) {
  map.put(keys.get(i), values.get(i));
}
return map;

}

2
fastcodejava

Utilisez Clojure. une ligne suffit

 (zipmap list1 list2)
1
GabiMe

Cela fonctionne avec Collections Eclipse .

Map<String, String> map =
        Maps.adapt(new LinkedHashMap<String, String>())
                .withAllKeyValues(
                        Lists.mutable.of("Apple,orange,pear".split(","))
                                .Zip(Lists.mutable.of("123,456,789".split(","))));

System.out.println(map);

Remarque: je suis un partisan des collections Eclipse.

1
Donald Raab

Une autre solution Java 8:

Si vous avez accès à la bibliothèque Guava (prise en charge la plus ancienne des flux en version 21 [1]), tu peux faire:

Streams.Zip(keyList.stream(), valueList.stream(), Maps::immutableEntry)
       .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Pour moi, l’avantage de cette méthode réside simplement dans le fait qu’il s’agit d’une couche unique et j’ai trouvé cela utile pour mon cas d’utilisation.

1
smac89

Avec Java 8, je voudrais simplement parcourir les deux, un par un, et remplir la carte:

public <K, V> Map<K, V> combineListsIntoOrderedMap (Iterable<K> keys, Iterable<V> values) {

    Map<K, V> map = new LinkedHashMap<>();

    Iterator<V> vit = values.iterator();
    for (K k: keys) {
        if (!vit.hasNext())
            throw new IllegalArgumentException ("Less values than keys.");

        map.put(k, vit.next());
    }

    return map;
}

Ou vous pourriez aller plus loin avec le style fonctionnel et faire:

/**
 * Usage:
 *
 *     Map<K, V> map2 = new LinkedHashMap<>();
 *     combineListsIntoOrderedMap(keys, values, map2::put);
 */
public <K, V> void combineListsIntoOrderedMap (Iterable<K> keys, Iterable<V> values, BiConsumer<K, V> onItem) {
    Iterator<V> vit = values.iterator();
    for (K k: keys) {
        if (!vit.hasNext())
            throw new IllegalArgumentException ("Less values than keys.");
        onItem.accept(k, vit.next());
    }
}
0
Ondra Žižka

Une autre perspective consiste à masquer la mise en œuvre. Souhaitez-vous que l'appelant de cette fonctionnalité profite de l'aspect et de la convivialité de la Enhanced For-Loop de Java?

public static void main(String[] args) {
    List<String> names = Arrays.asList("Apple,orange,pear".split(","));
    List<String> things = Arrays.asList("123,456,789".split(","));
    Map<String, String> map = new HashMap<>(4);
    for (Map.Entry<String, String> e : new DualIterator<>(names, things)) {
        map.put(e.getKey(), e.getValue());
    }
    System.out.println(map);
}

Si oui (Map.Entry est choisi par commodité), voici l'exemple complet (remarque: il s'agit de thread unsafe ):

import Java.util.*;
/** <p>
    A thread unsafe iterator over two lists to convert them 
    into a map such that keys in first list at a certain
    index map onto values in the second list <b> at the same index</b>.
    </p>
    Created by kmhaswade on 5/10/16.
 */
public class DualIterator<K, V> implements Iterable<Map.Entry<K, V>> {
    private final List<K> keys;
    private final List<V> values;
    private int anchor = 0;

    public DualIterator(List<K> keys, List<V> values) {
        // do all the validations here
        this.keys = keys;
        this.values = values;
    }
    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
        return new Iterator<Map.Entry<K, V>>() {
            @Override
            public boolean hasNext() {
                return keys.size() > anchor;
            }

            @Override
            public Map.Entry<K, V> next() {
                Map.Entry<K, V> e = new AbstractMap.SimpleEntry<>(keys.get(anchor), values.get(anchor));
                anchor += 1;
                return e;
            }
        };
    }

    public static void main(String[] args) {
        List<String> names = Arrays.asList("Apple,orange,pear".split(","));
        List<String> things = Arrays.asList("123,456,789".split(","));
        Map<String, String> map = new LinkedHashMap<>(4);
        for (Map.Entry<String, String> e : new DualIterator<>(names, things)) {
            map.put(e.getKey(), e.getValue());
        }
        System.out.println(map);
    }
}

Il imprime (par exigence):

{Apple=123, orange=456, pear=789}
0
Kedar Mhaswade