서로 다른 두 목록에 정확히 동일한 요소가 포함되어 있는지 확인하는 간단한 방법?


252

표준 Java 라이브러리에서 두 List에 정확히 동일한 요소가 포함되어 있는지 확인하는 가장 간단한 방법은 무엇입니까?

두 List가 동일한 인스턴스인지 여부는 중요하지 않으며 List의 type 매개 변수가 다른 경우에도 중요하지 않습니다.

예 :

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

내가 아는 얼굴에 나를 쳐다 보는 무언가가있을 것입니다 :-)


편집 : 명확히하기 위해, 정확히 동일한 요소와 요소 수를 순서대로 찾고있었습니다.


요소의 순서가 같아야합니까?
Michael Myers

이것은 당신에게 결코 영향을 미치지 않을 수도 있지만 최대 절전 모드에서는 때때로
등식

답변:


366

순서에 관심이 있다면 equals 메소드를 사용하십시오.

list1.equals(list2)

javadoc에서 :

지정된 객체와이리스트가 동일한 지 어떤지를 비교합니다. 지정된 객체도 목록이고 두 목록의 크기가 동일하고 두 목록의 모든 해당 요소 쌍이 동일한 경우에만 true를 반환합니다. ((e1 == null? e2 == null : e1.equals (e2)) 인 경우 두 요소 e1과 e2는 같습니다.) 즉, 동일한 순서로 동일한 요소를 포함하는 두 목록은 동일하도록 정의됩니다. . 이 정의는 equals 메소드가 List 인터페이스의 다른 구현에서 올바르게 작동하도록합니다.

순서와 무관하게 확인하려면 모든 요소를 ​​세트로 복사하고 결과 세트에서 equals를 사용할 수 있습니다.

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

이 방법의 한계는 순서를 무시할뿐만 아니라 중복 요소의 빈도도 무시한다는 것입니다. 예를 들어, list1""A ","B ","A "]이고 list2" "A", "B", "B"] 인 경우 Set접근 방식은 동일하다고 간주합니다.

순서에 민감하지 않지만 복제 빈도에 민감한 경우 다음 중 하나를 수행 할 수 있습니다.


54
주문과 독립적으로 확인하려면 containsAll을 사용할 수 없습니까?
laz

6
containsAll의 구현 세부 사항에 대해서는 잘 모르지만 좋지 않을 수 있습니다. containsAll 호출에 contains ()가 계속 반복되면 O (n ^ 2) alg가됩니다. 전체 세트는 O (nlogn)
Tom

6
실제로 집합이 O (nlogn)가 될 경우 다른 방법은 목록에서 Collections.sort ()를 호출 한 다음 equals를 사용하는 것입니다. 주문을 유지하려면 목록을 복사해야하며 비용이 많이 들고 세트 솔루션을 선호 할 수 있으므로 상황에 대해 생각해야합니다 :-).
Tom

1
@amischiefr : 당신이 할 수있는 최선을 O (n ^ 2) 제안하고 있습니까?
Tom

8
@Dennis 크기 확인은 각 목록에 고유 한 요소 만 포함되어있는 경우에만 작동합니다. 예를 들어, 주어진 a = [x, y, x]하고 b = [x, y, z]그 크기는 동일하고 b.containsAll(a)사실을 반환하지만 b에 요소를하지 포함되어 있습니다 a.
Laurence Gonsalves

95

나는 그것이 자신의 대답을 보증한다고 생각하는 의견에 많은 것을 게시했습니다.

모두가 여기에서 말했듯이 equals () 사용은 순서에 달려 있습니다. 주문에 신경 쓰지 않으면 3 가지 옵션이 있습니다.

옵션 1

사용하십시오 containsAll(). 이 옵션은 최악의 경우 성능을 제공하기 때문에 이상적이지 않습니다 .O (n ^ 2).

옵션 2

이에 대한 두 가지 변형이 있습니다.

2a) 목록의 순서를 유지하는 데 신경 쓰지 않는다면 Collections.sort()두 목록 모두에서 사용 하십시오. 그런 다음을 사용하십시오 equals(). 두 가지 정렬을 수행 한 다음 O (n) 비교를 수행하기 때문에 이것은 O (nlogn)입니다.

2b) 목록 순서를 유지해야하는 경우 두 목록을 먼저 복사 할 수 있습니다. 그런 다음 복사 된 목록 모두에서 솔루션 2a 를 사용할 수 있습니다 . 그러나 복사 비용이 매우 비싸면 매력적이지 않을 수 있습니다.

이것은 다음으로 이어진다 :

옵션 3

요구 사항이 파트 2b 와 동일 하지만 복사 비용이 너무 비싼 경우 TreeSet을 사용하여 정렬을 수행 할 수 있습니다. 각 목록을 자체 TreeSet에 덤프하십시오. 세트로 정렬되며 원래 목록은 그대로 유지됩니다. 그런 다음 equals()TreeSets 에서 비교를 수행하십시오 . TreeSets들에서 O (nlogn) 시간을 구축 할 수 있고,이 equals()O (N)이다.

선택해라 :-).

편집 : 나는 Laurence Gonsalves가 지적한것과 동일한 경고를 거의 잊었습니다. TreeSet 구현은 중복을 제거합니다. 중복에 관심이 있다면 일종의 정렬 된 다중 집합이 필요합니다.


복제본에 관심이 있다면 컬렉션의 크기가 다른 테스트 전에 항상 같은지 테스트 할 수 있습니다.
laz

보다 구체적으로, 중복이있는 것이 불평등을 나타내는 경우, 동일성 검사가 성공하기 전에 목록의 크기가 같아야합니다.
laz

7
@laz : 두 목록에 다른 요소가 중복되어 있으면 크기를 확인할 수 없습니다. 예 : [A, A, B] 대 [A, B, B]는 크기가 같습니다.
Laurence Gonsalves

@Laurence : laz의 게시물이 약간 혼란 스럽다는 것에 동의합니다 (나는 그것을 이해하기 전에 몇 번 읽었습니다). 나는 두 가지 조건이 유지 될 때 특별한 경우에 대한 "바로 가기"를 제공하려고 노력하고 있다고 생각합니다. 귀하의 예에서, laz는 여전히 우리가 논의한 동일한 검사를 모두 수행해야한다고 말합니다. (적어도 그것이 내가 읽는 방법입니다). 중복이 중요하지 않은 경우 크기를 특수한 경우 검사로 사용할 수 없습니다. 그러나 두 조건이 유지되면 "if (list1.size ()! = list2.size ()) false;를 반환 할 수 있습니다.
Tom

9
ContainsAll 내가 틀린 대답을 할 것이라고 생각한다면, 두 가지 방법 모두를 포함해야합니다. a.containsAll(b) && b.containsAll(a)
Richard Tingle

24

Apache Commons Collections를 사용하고 있거나 사용하기를 좋아하는 경우 CollectionUtils.isEqualCollection 을 사용할 수 있습니다. "주어진 Collections가 정확히 동일한 카디널리티를 가진 동일한 동일한 요소를 포함하는 경우 true를 반환합니다."


매우 멋진 해시 맵 기반 구현입니다. 런타임은 O (n)이어야하고 반복되는 요소가 많으면 최소한의 메모리를 사용하여 추적합니다 (기본적으로 각 컬렉션의 맵을 사용하여 요소의 빈도 (카디널리티)를 추적 함). 단점은 추가 O (n) 메모리 사용이 있다는 것입니다.
Muhd

17

파티에 매우 늦었지만이 null 안전 검사를 추가하고 싶었습니다.

Objects.equals(list1, list2)

8

나는 이것이 오래된 스레드라는 것을 알고 있지만 다른 대답은 내 유스 케이스를 완전히 해결하지 못했습니다 (구아바 멀티 세트가 똑같이 할 수도 있지만 여기에는 예가 없습니다). 내 서식을 변명 해주세요. 나는 여전히 스택 교환에 게시하는 것에 익숙하지 않습니다. 또한 오류가 있으면 알려주세요

당신이 말할 수 있습니다 List<T>A와 List<T>B를하고 그들이 다음과 같은 조건과 동일한 경우 확인하려면 :

1) O (n) 예상 실행 시간
2) 평등은 다음과 같이 정의됩니다. a 또는 b의 모든 요소에 대해 a에서 요소가 발생하는 횟수는 b에서 요소가 발생하는 횟수와 같습니다. 요소 평등은 T.equals ()로 정의됩니다

private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
    if(a==null) {
        if(b==null) {
            //Here 2 null lists are equivelent. You may want to change this.
            return true;
        } else {
            return false;
        }
    }
    if(b==null) {
        return false;
    }
    Map<Object, Integer> tempMap = new HashMap<>();
    for(Object element : a) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            tempMap.put(element, 1);
        } else {
            tempMap.put(element, currentCount+1);
        }
    }
    for(Object element : b) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            return false;
        } else {
            tempMap.put(element, currentCount-1);
        }
    }
    for(Integer count : tempMap.values()) {
        if(count != 0) {
            return false;
        }
    }
    return true;
}

우리가 해시 맵에 O (2 * n) 삽입을하고 O (3 * n) 해시 맵 선택을 수행하기 때문에 실행 시간은 O (n)입니다. 이 코드를 완전히 테스트하지 않았으므로주의하십시오 :)

//Returns true:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
listsAreEquivelent(null,null);
//Returns false:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),null);

5

순서가 같을 필요는 없지만 동일한 값의 배수를 지원하는이 버전을 사용해보십시오. 각 값의 수량이 같은 경우에만 일치합니다.

public boolean arraysMatch(List<String> elements1, List<String> elements2) {
    // Optional quick test since size must match
    if (elements1.size() != elements2.size()) {
        return false;
    }
    List<String> work = newArrayList(elements2);
    for (String element : elements1) {
        if (!work.remove(element)) {
            return false;
        }
    }
    return work.isEmpty();
}

work.remove (element)는 O (n)이므로이 솔루션은 O (n ^ 2)입니다.
Andrew

또는 O (n1 * n2) 같은 종류
Lee Meador

또한 모든 시나리오를 처리하고 수집 크기가 크지 않기 때문에 동일한 전략을 사용했습니다. O (n ^ 2)는 중요하지 않습니다
Naresh Joshi

3

List의 equals 메소드가이를 수행하고 List가 정렬되므로 두 List가 동일한 순서로 동일한 요소를 가져야합니다.

return list1.equals(list2);

3
정렬하지 않으면 목록이 정렬되지 않습니다.
Michael Myers

한숨. 분명한 대답입니다. 웹 페이지를 더 이상 Ctrl + F 할 수없는 날이 너무 길다는 것을 알고 있습니다. :)
Grundlefleck

2
@mmyers : 목록에있는 항목 정렬하지 않으면 주문되지 않습니다. 목록 자체에는 암시적인 항목 순서 (인덱스 별)가 있으며 목록의 항목을 변경하지 않으면 변경되지 않습니다. (vs. 세트 또는 컬렉션을 두 번 반복하면 일관된 순서가 보장되지 않는 세트 또는 컬렉션)
Jason S

daveb이 list가 정렬되었다고 말함으로써 의미하는 것은 List.equals가 요소의 순서를 고려하여 동등성을 결정한다는 것입니다. Javadoc을 참조하십시오.
laz

2
내 말은 { "A", "B"}를 포함하는 목록과 { "B", "A"}를 포함하는 목록은이 방법과 동일하지 않다는 것입니다. 그것은 의도 된 것일 수도 있지만 아무도 그것을 간과하지 않도록하고 싶었습니다.
Michael Myers

3

두 목록이 요소는 동일하지만 순서가 다른 경우에 대한 솔루션 :

public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) {
    if(isNullLists(listOne, listTwo)) {
        return false;
    }

    if (hasDifferentSize(listOne, listTwo)) {
        return true;
    }

    List<Integer> listOneCopy = Lists.newArrayList(listOne);
    List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
    listOneCopy.removeAll(listTwoCopy);

    return CollectionUtils.isNotEmpty(listOneCopy);
}

private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) {
    return listOne == null && listTwo == null;
}

private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) {
    return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());
}

2
나는 당신이 listTwo를 복사 할 필요가 없다고 생각합니다.
AjahnCharles

1
removeAll()대신에 왜 사용했는지에 주목 containsAll()하고 싶을 수도 있습니다 (목록 Two에 listOne에 한 번만 포함 된 중복 항목이 포함되어 있으면 containsAll () 접근 방식으로 목록이 동일하게 잘못보고된다는 것입니다).
AjahnCharles

3

Tom의 대답은 훌륭합니다. 나는 그의 대답에 전적으로 동의합니다!

이 질문의 흥미로운 측면은 List유형 자체와 고유 한 순서 가 필요한지 여부 입니다.

그렇지 않은 경우 성능을 저하시킬 수 Iterable있거나 Collection확인하려는 시점이 아니라 삽입시 정렬 된 데이터 구조를 전달할 때 약간의 유연성을 제공합니다.

순서가 중요하지 않고 중복 요소가없는 경우을 사용하는 것이 Set좋습니다.

순서가 중요하지만 삽입 시간에 의해 정의되고 중복이없는 경우 LinkedHashSetTreeSet과 같지만 삽입 시간에 따라 정렬됩니다 (중복은 계산되지 않음). 이것은 또한 당신에게 O(1)상각 액세스를 제공합니다 O(log n).


2

샘플 코드 :

public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
        List'<'T'>' newList) {

    int sizePrevoisList = -1;
    int sizeNewList = -1;

    if (previousList != null && !previousList.isEmpty()) {
        sizePrevoisList = previousList.size();
    }
    if (newList != null && !newList.isEmpty()) {
        sizeNewList = newList.size();
    }

    if ((sizePrevoisList == -1) && (sizeNewList == -1)) {
        return false;
    }

    if (sizeNewList != sizePrevoisList) {
        return true;
    }

    List n_prevois = new ArrayList(previousList);
    List n_new = new ArrayList(newList);

    try {
        Collections.sort(n_prevois);
        Collections.sort(n_new);
    } catch (ClassCastException exp) {
        return true;
    }

    for (int i = 0; i < sizeNewList; i++) {
        Object obj_prevois = n_prevois.get(i);
        Object obj_new = n_new.get(i);
        if (obj_new.equals(obj_prevois)) {
            // Object are same
        } else {
            return true;
        }
    }

    return false;
}

2

Laurence의 답변 외에도 null 안전을 원한다면 :

private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    if (list1 == null)
        return list2==null;
    if (list2 == null)
        return list1 == null;
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

1
점검을 단순화 할 수 있습니다.if (list1 == null) return list2==null; if (list2 == null) return false;
Xerus

목록이 [a, a, b, c] & [a, b, c] 인 경우 작동하지 않으며 목록 크기가 동일한 지 확인하기 위해 추가 검사를 추가하지 않으면 true를 반환합니다.
Venkat Madhav

2
list1.equals(list2);

목록에 사용자 정의 클래스 MyClass가 포함 된 경우이 클래스는 equals함수 를 대체해야합니다 .

 class MyClass
  {
  int field=0;
  @0verride
  public boolean equals(Object other)
        {
        if(this==other) return true;
        if(other==null || !(other instanceof MyClass)) return false;
        return this.field== MyClass.class.cast(other).field;
        }
  }

참고 :가 아닌 java.util.Set에서 equals를 테스트 java.util.List하려면 객체가 hashCode 함수 를 재정의해야합니다 .


1
라인을 반환해야합니다 : return this.field == MyClass.class.cast (other); return. this.field == MyClass.class.cast (other) .field;
alpere

@alpere 아! 네가 옳아 ! 내가 고칠거야. 감사 !
Pierre


0

Apache의 org.apache.commons.collections 라이브러리를 사용할 수 있습니다. http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)

또한 목록 요소의 순서가 동일해야합니다.
josh-cain

비교하기 전에 목록을 정렬 할 수 있습니다
David Zhao

물론, 목록에 저장된 유형 또는 정렬 가능한 경우 (또는 비교기가 설정되어 있음)이를 수행 할 수 있습니다. 그러나 Apache 구현 알고리즘은 정적 목록을 제외하고 일반 list1.equals (list2)와 다르지 않습니다. 나는 어디에서 질문을 오해했는지 알았으며 실제로 동일한 순서로 목록 항목을 비교하는 방법을 묻고있었습니다. 내 잘못이야!
josh-cain 2016 년

@DavidZhao : 링크가 죽었습니다.
Aniket Kulkarni


0

두 목록이 모두 널이 아닌지 확인하십시오. 크기가 다르면이 목록이 동일하지 않습니다. 목록의 요소를 키로 구성하고 그 반복을 값으로 구성하여 맵을 작성하고 맵을 비교하십시오.

가정, 두 목록이 모두 null 인 경우 가정합니다.

private boolean compareLists(List<?> l1, List<?> l2) {
    if (l1 == null && l2 == null) {
        return true;
    } else if (l1 == null || l2 == null) {
        return false;
    }

    if (l1.size() != l2.size()) {
        return false;
    }

    Map<?, Integer> m1 = toMap(l1);
    Map<?, Integer> m2 = toMap(l2);

    return m1.equals(m2);
}

private Map<Object, Integer> toMap(List<?> list) {
    //Effective size, not to resize in the future.
    int mapSize = (int) (list.size() / 0.75 + 1);
    Map<Object, Integer> map = new HashMap<>(mapSize);

    for (Object o : list) {
        Integer count = map.get(o);
        if (count == null) {
            map.put(o, 1);
        } else {
            map.put(o, ++count);
        }
    }

    System.out.println(map);
    return map;
}

이러한 객체에 대해 메소드 등가를 올바르게 정의해야합니다. https://stackoverflow.com/a/24814634/4587961


1
당신은 요소가 각 목록의 예에서 시간의 다른 번호를 제공 할 수없는 가정 한 [x, x, y][x, y, y]구현에 true를 반환한다.
AjahnCharles

@CodeConfident, 정말 고마워요! 나는 대답을 업데이트했다. 나는 마오를 사용할 것이다!
Yan Khonski

-2

사용중인 구체적인 List 클래스에 따라 다릅니다. 추상 클래스 AbstractCollection에는 다른 컬렉션 (List는 컬렉션 임)을 취하는 containsAll (Collection)이라는 메서드가 있습니다.

이 컬렉션에 지정된 컬렉션의 모든 요소가 포함되어있는 경우에 true를 리턴합니다.

따라서 ArrayList가 전달되면이 메소드를 호출하여 정확히 동일한 지 확인할 수 있습니다.

       List foo = new ArrayList();
    List bar = new ArrayList();
    String str = "foobar";

    foo.add(str);
    bar.add(str);

    foo.containsAll(bar);

containsAll ()의 이유는 두 번째 목록에서 일치하는 항목을 찾는 첫 번째 목록을 반복하기 때문입니다. 따라서 순서가 맞지 않으면 equals ()가 선택하지 않습니다.

편집 : 나는 다양한 옵션을 제공하는 상각 실행 시간에 대해 여기에 의견을 남기고 싶습니다. 러닝 타임이 중요합니까? 확실한. 고려해야 할 유일한 것입니까? 아니.

목록의 모든 단일 요소를 다른 목록으로 복사하는 데 시간이 걸리고 메모리를 많이 차지합니다 (사용중인 메모리를 두 배로 늘림).

따라서 JVM의 메모리가 중요하지 않은 경우 (일반적으로 있어야 함) 여전히 모든 요소를 ​​두 목록에서 두 개의 TreeSet으로 복사하는 데 걸리는 시간을 고려해야합니다. 입력되는 모든 요소를 ​​정렬한다는 것을 기억하십시오.

마지막 조언은? 여기에서 올바른 결정을 내리려면 데이터 세트와 데이터 세트에있는 요소 수, 데이터 세트의 각 객체의 크기를 고려해야합니다. 그들과 함께 놀면서 각 길을 하나씩 만들고 어느 쪽이 더 빨리 달리는지 확인하십시오. 좋은 운동입니다.


2
foo.containsAll (bar) 일 필요는 없습니까 && bar.containsAll (foo); ?
Carl Manaster

아니요, foo의 모든 요소를 ​​거치며 bar에 해당 요소가 포함되어 있는지 확인합니다. 그런 다음 길이가 두 목록과 동일하도록합니다. 모든 foo에 대해 foo.element == bar.element 및 foo.length == bar.length와 같은 bar에 요소가 있으면 동일한 요소를 포함합니다.
amischiefr

효율성 보장이 있는지 알고 있습니까? 아니면 이것이 일반적으로 O (n ^ 2)입니까?
Tom

일치하는 요소를 찾는 과정을 반복하는 다른 배열과 마찬가지로 최악의 실행 시간은 O (n ^ 2)입니다. 이 경우 구현이 실제로 한 번에 하나의 요소를 반복하여 일치하는 것을 찾는 것처럼 보입니다. 나는 상각 된 실행 시간을 추측하지는 않지만 최악의 경우는 O (n ^ 2)입니다.
amischiefr

1
{1,2,2} .containsAll ({1,1,2}) 및 그 반대는 작동하지 않으며 두 목록의 크기는 동일합니다.
comco
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.