将两个列表合并成一个映射(Java)最清晰的方法?
使用for (String item: list)
会很好,但是它只会遍历一个列表,并且你需要一个明确的迭代器用于另一个列表。 或者,你可以使用一个显式的迭代器。
下面是这个问题的一个例子,以及一个使用索引for
循环的解决scheme:
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?
由于键值关系是通过列表索引隐含的,所以我认为明确使用列表索引的for-loop解决scheme实际上是相当清晰的,也是很短的。
我经常使用下面的习语。 我承认这个问题是否清楚是值得商榷的。
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。 例如,如果你在LinkedList上使用原始algorithm,你就得到了一个缓慢的Shlemiel画家algorithm ,实际上对于长度为n的列表需要n * n个操作。
正如13ren所指出的那样,如果在长度不匹配的情况下尝试读取一个列表的结尾,则可以使用Iterator.next抛出NoSuchElementException的事实。 所以你会得到terser,但也许有点混乱的变种:
Iterator<String> i1 = names.iterator(); Iterator<String> i2 = things.iterator(); while (i1.hasNext() || i2.hasNext()) map.put(i1.next(), i2.next());
过了一段时间,因为这个问题被问到,但现在我偏向于:
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)); }
对于那些不熟悉stream的人来说,它所做的就是获得一个从0到长度的IntStream
,然后将它放在一个Stream<Integer>
以便它可以被转换成一个对象,然后使用Collectors.toMap
来收集它们,供应商,其中一个生成密钥,另一个生成值。
这可以忍受一些validation(如要求keys.size()
小于values.size()
),但它作为一个简单的解决scheme很好。
编辑:上面的工作伟大的任何与恒定的时间查找,但如果你想要的东西,将工作在相同的顺序(并仍然使用这种模式),你可以做这样的事情:
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())); }
输出是一样的(同样,缺less长度检查等),但是时间复杂度不依赖于get
方法的实现,无论使用什么列表。
您的解决scheme当然是正确的,但您的问题是清晰的,我会解决这个问题。
将两个列表结合起来的最清晰的方法是将组合放入一个名字清晰的方法中。 我刚刚采取了你的解决scheme,并提取到一个方法在这里:
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); }
我无法抗拒长度检查。
除了清晰之外,我认为还有其他值得考虑的事情:
- 正确拒绝非法参数,例如不同大小的列表和
null
(如果问题代码中的值为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; }
(可能要检查不要放置多个相同的密钥。)
没有明确的办法。 我仍然怀疑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; }
ArrayUtils#toMap()不会将两个列表组合到一个映射中,但是对于二维数组却是这样做的(所以不太需要查找,但可能对未来的引用感兴趣…)
使用Clojure。 只需要一条线;)
(zipmap list1 list2)
你甚至不需要限制自己的string。 稍微修改一下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;
}
另一个angular度是隐藏实现。 您是否希望此function的调用者享受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}
就个人而言,我认为一个简单的循环遍历索引是最明智的解决scheme,但这里有两个其他的可能性考虑。
另一种避免在IntStream
上调用boxed()
Java 8解决scheme是
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 ); );