Самый простой способ объединить два списка в карту (Java)? - PullRequest
52 голосов
/ 03 декабря 2009

Было бы неплохо использовать for (String item: list), но он будет перебирать только один список, и вам понадобится явный итератор для другого списка. Или вы можете использовать явный итератор для обоих.

Вот пример проблемы и решение, использующее вместо этого индексированный цикл 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);
  }
}

Выход:

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

Есть ли более ясный способ? Может быть, в коллекции API где-нибудь?

Ответы [ 15 ]

39 голосов
/ 03 декабря 2009

Я бы часто использовал следующую идиому. Я признаю, что это спорно, является ли это понятнее.

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

Преимущество состоит в том, что он также работает для коллекций и аналогичных объектов без произвольного доступа или без эффективного произвольного доступа, таких как LinkedList, TreeSets или SQL ResultSets. Например, если вы будете использовать оригинальный алгоритм в LinkedLists, у вас будет медленный Shlemiel алгоритм рисования , который на самом деле нуждается в n * n операциях для списков длиной n.

Как указывалось 13ren , вы также можете использовать тот факт, что Iterator.next генерирует исключение NoSuchElementException, если вы пытаетесь читать после конца одного списка, когда длины не совпадают. Таким образом, вы получите более краткий, но, возможно, немного запутанный вариант:

Iterator<String> i1 = names.iterator();
Iterator<String> i2 = things.iterator();
while (i1.hasNext() || i2.hasNext()) map.put(i1.next(), i2.next());
38 голосов
/ 22 июля 2016

Прошло некоторое время с тех пор, как этот вопрос был задан, но в эти дни я неравнодушен к чему-то вроде:

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

Для тех, кто не знаком с потоками, он получает IntStream от 0 до длины, затем упаковывает его, превращая его в Stream<Integer>, чтобы его можно было преобразовать в объект, а затем собирает их, используя Collectors.toMap который принимает двух поставщиков, один из которых генерирует ключи, другой - значения.

Это может выдержать некоторую проверку (например, требование, чтобы keys.size() было меньше, чем values.size()), но прекрасно работает как простое решение.

EDIT: Вышеприведенное прекрасно работает для всего, что связано с поиском в постоянном времени, но если вы хотите что-то, что будет работать в том же порядке (и при этом использовать такой же шаблон), вы можете сделать что-то вроде:

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

Вывод такой же (опять же, пропуски проверок длины и т. Д.), Но сложность по времени не зависит от реализации метода get для любого используемого списка.

19 голосов
/ 03 декабря 2009

Поскольку отношение ключ-значение является неявным через индекс списка, я думаю, что решение for-loop, которое явно использует индекс списка, на самом деле довольно ясно - и коротко.

9 голосов
/ 03 декабря 2009

Ваше решение, конечно, верное, но ваш вопрос касался ясности, я отвечу на это.

Самый ясный способ объединить два списка - поместить комбинацию в метод с хорошим чистым именем. Я только что взял ваше решение и извлек его в метод здесь:

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

И, конечно, ваш рефакторированный main теперь будет выглядеть так:

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

Я не смог устоять перед проверкой длины.

6 голосов
/ 22 октября 2017

Лично я считаю простой цикл for, выполняющий итерации по индексам, самым ясным решением, но здесь есть две другие возможности для рассмотрения.

Альтернативное решение Java 8, которое избегает вызова boxed() на IntStream, равно

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
                                   );
                          );
5 голосов
/ 01 сентября 2010

Кроме ясности, я думаю, есть и другие вещи, которые стоит рассмотреть:

  • Правильный отказ от недопустимых аргументов, таких как списки разных размеров и null s (посмотрите, что произойдет, если things равно null в коде вопроса).
  • Возможность обрабатывать списки, которые не имеют быстрого произвольного доступа.
  • Возможность обрабатывать одновременные и синхронизированные коллекции.

Итак, для библиотечного кода, возможно, что-то вроде этого:

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

(Может потребоваться проверить, не ставить несколько одинаковых ключей.)

5 голосов
/ 03 декабря 2009

ArrayUtils # toMap () не объединяет два списка в карту, но делает это для двумерного массива (поэтому не совсем то, что вы ищете, но, возможно, будет интересно для дальнейшего использования. ..)

4 голосов
/ 20 мая 2015

Там нет четкого пути. Мне все еще интересно, есть ли что-то подобное у Apache Commons или Guava. В любом случае у меня была своя собственная статическая утилита. Но этот знает о ключевых столкновениях!

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;
}
2 голосов
/ 04 декабря 2009

Тебе даже не нужно ограничиваться Струнами. Немного изменив код из CPerkins:

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;

}

1 голос
/ 12 октября 2018

С vavr библиотекой:

List.ofAll(names).zip(things).toJavaMap(Function.identity());

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...