web-dev-qa-db-ja.com

2つのリストを1つのマップに結合する最適な方法は何ですか(Java)?

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にあるのでしょうか?

53
13ren

キーと値の関係はリストインデックスを介して暗黙的であるため、リストインデックスを明示的に使用するforループソリューションは、実際には非常に明確であり、短いものでもあると思います。

19

この質問が尋ねられてからしばらく経ちましたが、最近は次のようなことに偏っています:

_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メソッドの実装に依存しません。

40
DrGodCarl

次のイディオムをよく使用します。それが明確かどうかは議論の余地があると認めます。

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());
39

上記のあなたの解決策はもちろん正しいですが、あなたの質問は明快さに関するものでした、私はそれに対処します。

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

長さのチェックに抵抗できませんでした。

9
CPerkins

個人的には、インデックスを反復処理する単純なループが最も明確な解決策であると考えていますが、他に考えられる2つの可能性があります。

代替のJava 8ソリューションは、IntStreamboxed()を呼び出さないようにします

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
                                   );
                          );
6
Paul Boddington

ArrayUtils#toMap() は2つのリストを1つのマップに結合しませんが、2次元配列の場合は結合します(したがって、探しているものとは異なりますが、将来の参照に役立つかもしれません... )

5
Joel

明快さだけでなく、検討する価値がある他のことがあると思います。

  • サイズの異なるリストや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;
}

(複数の等しいキーを入れていないことを確認したい場合があります。)

明確な方法はありません。 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;
}
4
Plap

文字列に制限する必要さえありません。 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;

}

2
fastcodejava

Clojureを使用します。 1行で十分です;)

 (zipmap list1 list2)
1
GabiMe

これは 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のコミッターです。

1
Donald Raab

vavrライブラリーの場合:

List.ofAll(names).Zip(things).toJavaMap(Function.identity());

1
Tomasz Werszko

別のJava 8ソリューション:

Guavaライブラリにアクセスできる場合(バージョン21のストリームの最も早いサポート [1])、 できるよ:

Streams.Zip(keyList.stream(), valueList.stream(), Maps::immutableEntry)
       .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

私にとってこの方法の利点は、それがライナーであるという事実にあり、それが私のユースケースに役立つことを発見しました。

1
smac89

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 よりも優れており、エントリを作成し、マップに入れるためにすぐにアンラップします。

0
wilmol

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());
    }
}
0
Ondra Žižka

これに対する別の観点は、実装を隠すことです。この機能の呼び出し元に、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}
0
Kedar Mhaswade