for (String item: list)
を使用するのは良いことですが、1つのリストのみを反復処理するため、他のリストには明示的な反復子が必要です。または、両方に明示的な反復子を使用できます。
問題の例と、代わりにインデックス付き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にあるのでしょうか?
キーと値の関係はリストインデックスを介して暗黙的であるため、リストインデックスを明示的に使用するforループソリューションは、実際には非常に明確であり、短いものでもあると思います。
この質問が尋ねられてからしばらく経ちましたが、最近は次のようなことに偏っています:
_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));
}
_
ストリームに不慣れな人のために、これは0から長さまでIntStream
を取得し、それをボックス化して_Stream<Integer>
_にし、オブジェクトに変換できるようにしてから、 _Collectors.toMap
_は2つのサプライヤを取り、1つはキーを生成し、もう1つは値を生成します。
これはいくつかの検証に耐えることができます(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
メソッドの実装に依存しません。
次のイディオムをよく使用します。それが明確かどうかは議論の余地があると認めます。
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 が指摘したように、長さが一致しない場合に1つのリストの最後の後に読み取りを試みると、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());
上記のあなたの解決策はもちろん正しいですが、あなたの質問は明快さに関するものでした、私はそれに対処します。
2つのリストを結合するclearestの方法は、その組み合わせをニースの明確な名前を持つメソッドに入れることです。私はあなたの解決策をここでメソッドに抽出しました:
Map <String、String> composeListsIntoOrderedMap(List <String>キー、List <String>値){ if(keys.size()!= values.size()) throw new IllegalArgumentException( "異なるサイズのリストを組み合わせることはできません"); 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 = composeListsIntoOrderedMap(names、things); システム.out.println(map); }
長さのチェックに抵抗できませんでした。
個人的には、インデックスを反復処理する単純なループが最も明確な解決策であると考えていますが、他に考えられる2つの可能性があります。
代替のJava 8ソリューションは、IntStream
でboxed()
を呼び出さないようにします
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
);
);
ArrayUtils#toMap() は2つのリストを1つのマップに結合しませんが、2次元配列の場合は結合します(したがって、探しているものとは異なりますが、将来の参照に役立つかもしれません... )
明快さだけでなく、検討する価値がある他のことがあると思います。
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;
}
(複数の等しいキーを入れていないことを確認したい場合があります。)
明確な方法はありません。 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;
}
文字列に制限する必要さえありません。 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;
}
Clojureを使用します。 1行で十分です;)
(zipmap list1 list2)
これは 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のコミッターです。
vavr
ライブラリーの場合:
List.ofAll(names).Zip(things).toJavaMap(Function.identity());
別のJava 8ソリューション:
Guavaライブラリにアクセスできる場合(バージョン21のストリームの最も早いサポート [1])、 できるよ:
Streams.Zip(keyList.stream(), valueList.stream(), Maps::immutableEntry)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
私にとってこの方法の利点は、それがライナーであるという事実にあり、それが私のユースケースに役立つことを発見しました。
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
クラス、基本的に同じことを行います。
カプセル化と明確な意図(2つのリストの圧縮)、および再利用性とパフォーマンスを提供します。
これは other answer よりも優れており、エントリを作成し、マップに入れるためにすぐにアンラップします。
Java 8の場合、両方を1つずつ繰り返し、マップを埋めるだけです。
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());
}
}
これに対する別の観点は、実装を隠すことです。この機能の呼び出し元に、Javaの拡張for-loopのルックアンドフィールを楽しんでもらいたいですか?
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
が便宜上選択されています)、次に完全な例を示します(注:thread unsafe):
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}