将两个列表组合成地图(Java)的最简单方法?

13r*_*ren 56 java collections data-structures

使用它会很好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);
  }
}
Run Code Online (Sandbox Code Playgroud)

输出:

{apple=123, orange=456, pear=789}
Run Code Online (Sandbox Code Playgroud)

有更清晰的方法吗?也许在某处的馆藏API?

DrG*_*arl 47

从问这个问题已经有一段时间了,但是这些天我偏向于:

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));
}
Run Code Online (Sandbox Code Playgroud)

对于那些不熟悉流的人来说,这样做是IntStream从0到长度,然后将其打包,使其Stream<Integer>成为可以转换为对象,然后使用Collectors.toMap两个供应商收集它们,其中一个生成密钥,其他的价值观.

这可以进行一些验证(比如需要keys.size()小于values.size()),但它作为一个简单的解决方案很有效.

编辑:以上工作非常适合任何具有恒定时间查找的东西,但是如果你想要一些可以在同一个订单上运行的东西(并且仍然使用相同类型的模式),你可以做类似的事情:

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()));
}
Run Code Online (Sandbox Code Playgroud)

输出是相同的(同样,缺少长度检查等),但时间复杂度不依赖于所get使用的任何列表的方法的实现.

  • 我会有点担心性能。如果输入 List 是一个 ArrayList,那么应该没问题,因为您可以在 O(1) 中对 ArrayList 调用 .get()。但是对于 O(n) 访问时间的链表,该解决方案具有二次复杂度,这对于大型列表可能是一个问题。 (4认同)

Han*_*örr 41

我经常使用以下习语.我承认它是否更清楚是有争议的.

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();
Run Code Online (Sandbox Code Playgroud)

它的优点是它也适用于集合和类似的东西,无需随机访问或没有有效的随机访问,如LinkedList,TreeSet或SQL ResultSet.例如,如果你在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());
Run Code Online (Sandbox Code Playgroud)

  • 这至少适用于不等大小的列表,没有任何预先检查. (2认同)

Mic*_*rdt 19

由于键值关系是通过列表索引隐式的,我认为显式使用列表索引的for循环解决方案实际上非常清楚 - 也很短.

  • 但是使用.get(i)是一个坏主意,如果你结合使用LinkedLists - 在这种情况下它不再是一个恒定的时间操作. (2认同)

sma*_*c89 12

另一个 Java 8 解决方案:

如果您有权访问 Guava 库(最早支持版本 21 [1] 中的流),您可以执行以下操作:

Streams.zip(keyList.stream(), valueList.stream(), Maps::immutableEntry)
       .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Run Code Online (Sandbox Code Playgroud)

对我来说,这种方法的优势在于它是一个评估为 a 的单一表达式(即一个行),Map我发现这对我需要做的事情特别有用。


CPe*_*ins 10

上面的解决方案当然是正确的,但你的问题是清晰度,我会解决这个问题.

组合两个列表的明确方法是将组合放入一个名称清晰的方法中.我刚刚把你的解决方案解压缩到一个方法:

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

我无法抗拒长度检查.

  • 该算法在长链表上效率很低,因为它们没有有效的随机访问。我认为如果你正在编写一个可重用的函数,你应该考虑一下。 (2认同)

Pau*_*ton 6

就个人而言,我认为一个简单的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
                                   );
                          );
Run Code Online (Sandbox Code Playgroud)


Joe*_*oel 5

ArrayUtils#toMap()不会将两个列表组合到一个映射中,但是对于二维数组这样做(所以不是你想要的,但可能对将来的参考感兴趣...)


Tom*_*ine 5

除了清晰度之外,我认为还有其他一些值得考虑的事情:

  • 正确的拒绝非法参数,例如不同尺寸列表和nullS(看看会发生什么,如果thingsnull在问题的代码).
  • 能够处理没有快速随机访问的列表.
  • 能够处理并发和同步的集合.

所以,对于库代码,可能是这样的:

@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;
}
Run Code Online (Sandbox Code Playgroud)

(可能想检查不要放多个相等的键.)