ProgramingTip

두 목록을 하나의 맵 (Java)으로 결합하는 가장 좋은 방법은 무엇입니까?

bestdevel 2021. 1. 5. 21:15
반응형

두 목록을 하나의 맵 (Java)으로 결합하는 가장 좋은 방법은 무엇입니까?


을 사용하는 것이 좋지만 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에서?


루프 솔루션은 매우 명확하고 짧다고 생각합니다.


이 질문을 한 지 오래 계속해서 나는 다음과 같은 부분적입니다.

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

스트림에 익숙하지 않은 사람들을 위해 가장 많이 사용하는 일은 IntStream0에서 길이까지의 값을 변환하는 다음 상자에 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()));
}

출력은 동일하지만 (다시 말하지만, 길이 검사 누락 등) 복잡성 time- 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와 같이 임의의 액세스없이 또는 쉬운 액세스없이 Collections 및 이와 일치하는 것들에 작동한다는 점이 있습니다. 예를 들어 LinkedLists에서 원래 알고리즘을 사용하는 경우 길이 n의 목록에 실제로 n * n 작업이 필요한 느린 Shlemiel the painter 알고리즘 이 있습니다.

으로 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());

귀하의 질문은 명확성에 관한 것이 었습니다.

두 목록을 결합 하는 가장 명확한 방법은 그 조합을 깔끔한 이름의 방법에 넣는 것입니다. 방금 귀하의 솔루션을 가져 오기 방법으로 추출했습니다.

Map <String, String> combineListsIntoOrderedMap (List <String> 키, List <String> 값) {
    if (keys.size ()! = values.size ())
        throw new IllegalArgumentException ( "다른 크기의 목록을 결합 할 수 없음");
    지도 <문자열, 문자열> 맵 = 새 LinkedHashMap <문자열, 문자열> ();
    for (int i = 0; i <keys.size (); i ++) {
        map.put (keys.get (i), values.get (i));
    }
    반환지도;
}

물론 리팩토링 된 메인은 다음과 다양합니다.

static public void main (String [] args) {
    List <String> 이름 = Arrays.asList ( "apple, orange, pear".split ( ","));
    List <String> things = Arrays.asList ( "123,456,789".split ( ","));
    Map <String, String> map = combineListsIntoOrderedMap (이름, 사물);
    System.out.println (지도);
}

나는 길이 체크에 저항 할 수 없었다.


개인적으로 수준을 반복하는 간단한 루프가 가장 명확한 솔루션이라고 생각하지만 여기에 할 다른 두 가지 가능성이 있습니다.

전화 피한다 대안 자바 (8) 솔루션을 boxed()온은 IntStream이다

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

명확성 포함 할 다른 사항이 생각합니다.

  • 서로 다른 크기 목록 및 불법 인수의 올바른 거부 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;
}

(더 많은 등호 키를 넣지 않는지 확인하고 싶을 수 있습니다.)


ArrayUtils # toMap () 은 두 개의 목록을지도로 결합하지 않지만 2 개의 배열을 그렇게합니다 (모든 차원에서 어떤 내용은 나중에 참조 할 수 있습니다 ...)


명확한 방법이 없습니다. 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;
}

Strings로 제한 할 필요도 없습니다. 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;

}


vavr라이브러리 :

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


Clojure를 사용하십시오. 한 줄이면 충분합니다.)

 (zipmap list1 list2)

Eclipse 컬렉션을 사용하여 작동합니다 .

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의 커미터입니다.


또 다른 Java 8 솔루션 :

Guava 라이브러리에 액세스 할 수있는 경우 (버전 21 [1]의 스트림에 대한 초기 지원 ) 다음을 수행 할 수 있습니다.

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

저 에게이 방법의 장점은 하나의 라이너라는 사실에 사용 사례에 도움이되는 것을 알았습니다.


이에 대한 또 다른 관점은 구현을 숨기는 것입니다. 이 기능의 호출자가 자바의 사용 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편의상 선택됨)이면 완전한 예가 다음과 같습니다 (참고 : 스레드가 안전하지 않음).

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}

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

활용 AbstractMapAbstractSet:

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기본적으로 같은 일 을하는 수업 에서이 아이디어를 얻었다 .

캡슐화 및 명확한 의도 (두 목록 압축)는 물론 재사용 가능성과 성능을 제공합니다.

이것은 다른 답변 보다 낫습니다. 항목을 만든 다음 즉시 맵에 배치하기 위해 래핑을 해제 .

참조 URL : https://stackoverflow.com/questions/1839668/what-is-the-best-way-to-combine-two-lists-into-a-map-java

반응형