将两个列表组合成地图(Java)的最佳方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1839668/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the best way to combine two lists into a map (Java)?
提问by 13ren
It would be nice to use for (String item: list)
, but it will only iterate through one list, and you'd need an explicit iterator for the other list. Or, you could use an explicit iterator for both.
使用 会很好for (String item: list)
,但它只会遍历一个列表,并且您需要为另一个列表使用显式迭代器。或者,您可以对两者都使用显式迭代器。
Here's an example of the problem, and a solution using an indexed for
loop instead:
这是问题的示例,以及使用索引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);
}
}
Output:
输出:
{apple=123, orange=456, pear=789}
Is there a clearer way? Maybe in the collections API somewhere?
有没有更清楚的方法?也许在某个地方的集合 API 中?
采纳答案by Michael Borgwardt
Since the key-value relationship is implicit via the list index, I think the for-loop solution that uses the list index explicitly is actually quite clear - and short as well.
由于键值关系是通过列表索引隐式的,我认为显式使用列表索引的 for 循环解决方案实际上非常清晰 - 也很简短。
回答by Joel
ArrayUtils#toMap()doesn't combine two lists into a map, but does do so for a 2 dimensional array (so not quite what your looking for, but maybe of interest for future reference...)
ArrayUtils#toMap()不会将两个列表合并到一个映射中,而是将两个列表合并到一个映射中(因此不是您要查找的内容,但可能对将来的参考感兴趣...)
回答by Hans-Peter St?rr
I'd often use the following idiom. I admit it is debatable whether it is clearer.
我经常使用以下习语。我承认是否更清楚是值得商榷的。
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();
It has the advantage that it also works for Collections and similar things without random access or without efficient random access, like LinkedList, TreeSets or SQL ResultSets. For example, if you'd use the original algorithm on LinkedLists, you've got a slow Shlemiel the painter algorithmwhich actually needs n*n operations for lists of length n.
它的优点是它也适用于集合和没有随机访问或没有有效随机访问的类似事物,如 LinkedList、TreeSet 或 SQL ResultSet。例如,如果您在 LinkedLists 上使用原始算法,那么您将得到一个缓慢的Shlemiel 画家算法,它实际上需要 n*n 次操作来处理长度为 n 的列表。
As 13renpointed out, you can also use the fact that Iterator.next throws a NoSuchElementException if you try to read after the end of one list when the lengths are mismatched. So you'll get the terser but maybe a little confusing variant:
正如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());
回答by GabiMe
Use Clojure. one line is all it takes ;)
使用 Clojure。一行就够了;)
(zipmap list1 list2)
回答by CPerkins
Your solution above is correct of course, but your as question was about clarity, I'll address that.
你上面的解决方案当然是正确的,但你的问题是关于清晰度的,我会解决这个问题。
The clearestway to combine two lists would be to put the combination into a method with a nice clear name. I've just taken your solution and extracted it to a method here:
组合两个列表的最清晰方法是将组合放入一个具有清晰名称的方法中。我刚刚采用了您的解决方案并将其提取到此处的方法中:
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; }
And of course, your refactored main would now look like this:
当然,你重构的 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); }
I couldn't resist the length check.
我无法抗拒长度检查。
回答by fastcodejava
You need not even limit yourself to Strings. Modifying the code from CPerkins a little :
您甚至不必将自己限制在字符串中。稍微修改 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;
}
回答by Tom Hawtin - tackline
As well as clarity, I think there are other things that are worth considering:
除了清晰度之外,我认为还有其他值得考虑的事情:
- Correct rejection of illegal arguments, such as different sizes lists and
null
s (look what happens ifthings
isnull
in the question code). - Ability to handle lists that do not have fast random access.
- Ability to handle concurrent and synchronized collections.
- 正确的拒绝非法参数,例如不同尺寸列表和
null
S(看看会发生什么,如果things
是null
在问题的代码)。 - 能够处理没有快速随机访问的列表。
- 能够处理并发和同步集合。
So, for library code, perhaps something like this:
所以,对于库代码,可能是这样的:
@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;
}
(May want to check not putting multiple equal keys.)
(可能要检查没有放置多个相等的键。)
回答by Plap
There's no clear way. I still wonder if Apache Commons or Guava has something similar. Anyway I had my own static utility. But this one is aware of key collisions!
没有明确的方法。我仍然想知道 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;
}
回答by Kedar Mhaswade
Another perspective to this is to hide implementation. Would you like the caller of this functionality to enjoy the look and feel of Java's enhanced for-loop?
对此的另一个观点是隐藏实现。您希望此功能的调用者享受 Java增强的 for 循环的外观吗?
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);
}
If yes (Map.Entry
is chosen as a convenience), then here is the complete example (note: it is thread unsafe):
如果是(Map.Entry
选择是为了方便),那么这里是完整的示例(注意:它是线程不安全的):
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);
}
}
It prints (per requirement):
它打印(根据要求):
{apple=123, orange=456, pear=789}
回答by DrGodCarl
Been a while since this question was asked but these days I'm partial to something like:
自从提出这个问题以来已经有一段时间了,但这些天我偏爱这样的事情:
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));
}
For those unfamiliar with streams, what this does is gets an IntStream
from 0 to the length, then boxes it, making it a Stream<Integer>
so that it can be transformed into an object, then collects them using Collectors.toMap
which takes two suppliers, one of which generates the keys, the other the values.
对于那些不熟悉流的人来说,它的作用是IntStream
从 0 到长度,然后将其装箱,使其成为 aStream<Integer>
以便可以将其转换为对象,然后使用Collectors.toMap
which收集它们,其中需要两个供应商,其中一个生成密钥,其他值。
This could stand some validation (like requiring keys.size()
be less than values.size()
) but it works great as a simple solution.
这可能经得起一些验证(例如要求keys.size()
小于values.size()
),但它作为一个简单的解决方案非常有效。
EDIT: The above works great for anything with constant time lookup, but if you want something that will work on the same order (and still use this same sort of pattern) you could do something like:
编辑:以上适用于任何具有恒定时间查找的东西,但是如果您想要一些可以按相同顺序工作的东西(并且仍然使用相同类型的模式),您可以执行以下操作:
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()));
}
The output is the same (again, missing length checks, etc.) but the time complexity isn't dependent on the implementation of the get
method for whatever list is used.
输出是相同的(同样,缺少长度检查等)但时间复杂度不依赖于get
使用任何列表的方法的实现。