使用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中?
答案 0 :(得分:39)
我经常使用以下习语。我承认它是否更清楚是值得商榷的。
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,TreeSet或SQL ResultSet。例如,如果你在LinkedLists上使用原始算法,那么你有一个慢Shlemiel the painter algorithm,它实际上需要对长度为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());
答案 1 :(得分:38)
自问这个问题以来已经有一段时间了但是这些天我偏爱:
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()
),但它可以作为一个简单的解决方案。
编辑: 以上内容适用于任何具有恒定时间查找的内容,但如果您想要一些可以在同一个订单上工作的东西(并且仍然使用相同类型的模式),您可以执行以下操作:
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
方法对任何列表的实现。
答案 2 :(得分:19)
由于键值关系是通过列表索引隐式的,我认为明确使用列表索引的for循环解决方案实际上非常清楚 - 也很短。
答案 3 :(得分:9)
您的上述解决方案当然是正确的,但您的问题是关于清晰度,我会解决这个问题。
组合两个列表的最清晰方法是将组合放入一个名称清晰的方法中。我刚刚把你的解决方案解压缩到一个方法:
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; }
当然,你重构的主体现在看起来像这样:
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); }
我无法抗拒长度检查。
答案 4 :(得分:6)
就个人而言,我认为一个简单的for循环遍历索引是最清晰的解决方案,但这里还有两个可以考虑的方法。
另一种避免在boxed()
上调用IntStream
的Java 8解决方案是
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 :(得分:5)
ArrayUtils#toMap()没有将两个列表组合到一个地图中,但是对于一个二维数组这样做了(所以不是你想要的,但可能有兴趣将来参考...)
答案 6 :(得分:5)
除了清晰度之外,我认为还有其他值得考虑的事情:
null
(如果问题代码中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;
}
(可能想检查不要放多个相等的键。)
答案 7 :(得分:4)
没有明确的方法。我仍然想知道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;
}
答案 8 :(得分:2)
你甚至不需要限制自己的字符串。稍微修改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;
}
答案 9 :(得分:1)
使用Clojure。只需一行;)
(zipmap list1 list2)
答案 10 :(得分:1)
这可以使用Eclipse Collections。
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);
注意:我是Eclipse Collections的提交者。
答案 11 :(得分:1)
另一个Java 8解决方案:
如果您有权访问Guava库(最早支持版本21 [1] 中的流),则可以执行以下操作:
master
对我来说,这种方法的优点仅在于它是一个衬里,我发现这对我的用例很有帮助。
答案 12 :(得分:1)
使用vavr
库:
List.ofAll(names).zip(things).toJavaMap(Function.identity());
答案 13 :(得分:0)
另一个观点是隐藏实现。您是否希望此功能的调用者能够享受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);
}
如果是(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);
}
}
打印(按要求):
{apple=123, orange=456, pear=789}
答案 14 :(得分:0)
使用Java 8,我只需逐个遍历两者,然后填充地图:
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;
}
或者您可以更进一步使用功能样式并执行:
/**
* 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());
}
}
答案 15 :(得分:0)
利用AbstractMap
和AbstractSet
:
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class ZippedMap<A, B> extends AbstractMap<A, B> {
private final List<A> first;
private final List<B> second;
public ZippedMap(List<A> first, List<B> second) {
if (first.size() != second.size()) {
throw new IllegalArgumentException("Expected lists of equal size");
}
this.first = first;
this.second = second;
}
@Override
public Set<Entry<A, B>> entrySet() {
return new AbstractSet<>() {
@Override
public Iterator<Entry<A, B>> iterator() {
Iterator<A> i = first.iterator();
Iterator<B> i2 = second.iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return i.hasNext();
}
@Override
public Entry<A, B> next() {
return new SimpleImmutableEntry<>(i.next(), i2.next());
}
};
}
@Override
public int size() {
return first.size();
}
};
}
}
用法:
public static void main(String... args) {
Map<Integer, Integer> zippedMap = new ZippedMap<>(List.of(1, 2, 3), List.of(1, 2, 3));
zippedMap.forEach((k, v) -> System.out.println("key = " + k + " value = " + v));
}
输出:
key = 1 value = 1
key = 2 value = 2
key = 3 value = 3
我从java.util.stream.Collectors.Partition
类中得到了这个想法,该类基本上具有相同的作用。
它提供封装和清晰的意图(压缩两个列表)以及可重用性和性能。
这比other answer更好,后者创建条目,然后立即将其解包以放入地图。
答案 16 :(得分:0)
我认为这很不言自明(假设列表大小相等)
Map<K, V> map = new HashMap<>();
for (int i = 0; i < keys.size(); i++) {
map.put(keys.get(i), vals.get(i));
}
答案 17 :(得分:0)
您可以利用kotlin-stdlib
@Test
void zipDemo() {
List<String> names = Arrays.asList("apple", "orange", "pear");
List<String> things = Arrays.asList("123", "456", "789");
Map<String, String> map = MapsKt.toMap(CollectionsKt.zip(names, things));
assertThat(map.toString()).isEqualTo("{apple=123, orange=456, pear=789}");
}
当然,使用Kotlin语言更有趣:
@Test
fun zipDemo() {
val names = listOf("apple", "orange", "pear");
val things = listOf("123", "456", "789");
val map = (names zip things).toMap()
assertThat(map).isEqualTo(mapOf("apple" to "123", "orange" to "456", "pear" to "789"))
}