Làm cách nào để tính toán sự khác biệt giữa hai ArrayLists?


81

Tôi có hai ArrayLists.

ArrayList A chứa:

['2009-05-18','2009-05-19','2009-05-21']

ArrayList B chứa:

['2009-05-18','2009-05-18','2009-05-19','2009-05-19','2009-05-20','2009-05-21','2009-05-21','2009-05-22']

Tôi phải so sánh ArrayList A và ArrayList B. Kết quả ArrayList phải chứa Danh sách không tồn tại trong ArrayList A.

Kết quả ArrayList phải là:

['2009-05-20','2009-05-22']

làm thế nào để so sánh?

Câu trả lời:


193

Trong Java, bạn có thể sử dụng phương thức Collectioncủa giao diện removeAll.

// Create a couple ArrayList objects and populate them
// with some delicious fruits.
Collection firstList = new ArrayList() {{
    add("apple");
    add("orange");
}};

Collection secondList = new ArrayList() {{
    add("apple");
    add("orange");
    add("banana");
    add("strawberry");
}};

// Show the "before" lists
System.out.println("First List: " + firstList);
System.out.println("Second List: " + secondList);

// Remove all elements in firstList from secondList
secondList.removeAll(firstList);

// Show the "after" list
System.out.println("Result: " + secondList);

Đoạn mã trên sẽ tạo ra kết quả sau:

First List: [apple, orange]
Second List: [apple, orange, banana, strawberry]
Result: [banana, strawberry]

7
Nếu danh sách của bạn là một lớp tùy chỉnh, thì bạn sẽ phải ghi đè phương thức bằng của lớp mình, phải không?
RTF

5
@RTF Có, bạn cần cung cấp cách triển khai equalscho phép so sánh các đối tượng của bạn. Đọc thêm về cách triển khai hashCode. Ví dụ: lưu ý thế nào String::equalslà phân biệt chữ hoa chữ thường , vì vậy "apple" và "Apple" sẽ không được coi là giống nhau.
Basil Bourque

1
Thực ra câu trả lời phụ thuộc vào những gì bạn muốn làm. RemoveAll sẽ không giữ lại các bản sao. Nếu bạn thêm một chuỗi "quả táo" khác vào danh sách thứ hai của mình, chuỗi này cũng sẽ bị xóa, có thể không phải lúc nào bạn cũng muốn.
jules testard

2
Điều này thật không hiệu quả. Thật đáng buồn khi đây là cả hai câu trả lời được lựa chọn và đánh giá tốt nhất. removeAllgọi firstList.containstrên mọi phần tử của secondList. Sử dụng một HashSetsẽ ngăn chặn điều đó và có một vài câu trả lời tốt thấp hơn.
Vlasec

20

Bạn đã có câu trả lời đúng. Và nếu bạn muốn thực hiện các thao tác phức tạp và thú vị hơn giữa các Danh sách (bộ sưu tập), hãy sử dụng bộ sưu tập dấu phẩy apache ( CollectionUtils ) Nó cho phép bạn thực hiện phép chia / tách, tìm giao điểm, kiểm tra xem một bộ sưu tập có phải là tập hợp con của một tập hợp khác hay không và những thứ hay ho khác.



12

Trong Java 8 với các luồng, nó thực sự khá đơn giản. CHỈNH SỬA: Có thể hiệu quả mà không cần luồng, xem thấp hơn.

List<String> listA = Arrays.asList("2009-05-18","2009-05-19","2009-05-21");
List<String> listB = Arrays.asList("2009-05-18","2009-05-18","2009-05-19","2009-05-19",
                                   "2009-05-20","2009-05-21","2009-05-21","2009-05-22");

List<String> result = listB.stream()
                           .filter(not(new HashSet<>(listA)::contains))
                           .collect(Collectors.toList());

Lưu ý rằng tập hợp băm chỉ được tạo một lần: Tham chiếu phương thức được gắn với phương thức chứa của nó. Làm tương tự với lambda sẽ yêu cầu phải có tập hợp trong một biến. Tạo một biến không phải là một ý tưởng tồi, đặc biệt nếu bạn thấy nó khó coi hoặc khó hiểu hơn.

Bạn không thể dễ dàng phủ định vị từ mà không có một cái gì đó như phương thức tiện ích này (hoặc ép kiểu rõ ràng), vì bạn không thể gọi trực tiếp tham chiếu phương thức phủ định (trước tiên cần có suy luận kiểu).

private static <T> Predicate<T> not(Predicate<T> predicate) {
    return predicate.negate();
}

Nếu các luồng có một filterOutphương pháp hoặc một cái gì đó, nó sẽ trông đẹp hơn.


Ngoài ra, @Holger đã cho tôi một ý tưởng. ArrayListremoveAllphương pháp được tối ưu hóa cho nhiều lần xóa, nó chỉ sắp xếp lại các phần tử của nó một lần. Tuy nhiên, nó sử dụng containsphương pháp được cung cấp bởi bộ sưu tập nhất định, vì vậy chúng ta cần tối ưu hóa phần đó nếu listAlà bất cứ thứ gì ngoại trừ nhỏ.

Với listAvà được listBkhai báo trước đó, giải pháp này không cần Java 8 và nó rất hiệu quả.

List<String> result = new ArrayList(listB);
result.removeAll(new HashSet<>(listA));

1
@Bax Tại sao lại chỉnh sửa? Bản gốc sạch hơn và giống hệt nhau về mặt chức năng.
shmosel

1
@Bax Không, nó không.
shmosel

1
Với Ổi, bạn có thể làm được Predicates.in(new HashSet<>(listA)).negate().
shmosel

1
Tôi vừa chạy một số thử nghiệm và giải pháp này nhanh hơn listB.removeAll (HashSet mới <> (listA)) khoảng 10-20%. và Guava Sets.difference (...) si chậm hơn stream 2 lần.
telebog,

1
@Vlasec ArrayList.removecó độ phức tạp tuyến tính, nhưng ArrayList.removeAllkhông dựa vào removemà thực hiện thao tác cập nhật mảng tuyến tính, sao chép từng phần tử còn lại vào vị trí cuối cùng của nó. Ngược lại, việc triển khai tham chiếu của LinkedListkhông được tối ưu hóa removeAllnhưng thực hiện một removehoạt động cho từng phần tử bị ảnh hưởng, sẽ cập nhật tối đa năm tham chiếu mỗi lần. Vì vậy, tùy thuộc vào tỷ lệ giữa các phần tử bị loại bỏ và phần tử còn lại, ArrayList's removeAllvẫn có thể hoạt động tốt hơn đáng kể so với LinkedList', ngay cả đối với danh sách lớn.
Holger

9

CHỈNH SỬA: Câu hỏi ban đầu không chỉ định ngôn ngữ. Câu trả lời của tôi là trong C #.

Thay vào đó, bạn nên sử dụng HashSet cho mục đích này. Nếu bạn phải sử dụng ArrayList, bạn có thể sử dụng các phương thức mở rộng sau:

var a = arrayListA.Cast<DateTime>();
var b = arrayListB.Cast<DateTime>();    
var c = b.Except(a);

var arrayListC = new ArrayList(c.ToArray());

sử dụng HashSet ...

var a = new HashSet<DateTime>(); // ...and fill it
var b = new HashSet<DateTime>(); // ...and fill it
b.ExceptWith(a); // removes from b items that are in a

8

Tôi đã sử dụng Guava Sets.difference .

Các tham số là tập hợp chứ không phải tập hợp chung, nhưng một cách tiện dụng để tạo tập hợp từ bất kỳ tập hợp nào (với các mục duy nhất) là Guava ImmutableSet.copyOf (Lặp lại).

(Lần đầu tiên tôi đăng điều này trên một câu hỏi liên quan / dupe , nhưng tôi cũng đang sao chép nó ở đây vì tôi cảm thấy nó là một lựa chọn tốt mà cho đến nay vẫn còn thiếu.)


8

Mặc dù đây là một câu hỏi rất cũ trong Java 8, bạn có thể làm điều gì đó như

 List<String> a1 = Arrays.asList("2009-05-18", "2009-05-19", "2009-05-21");
 List<String> a2 = Arrays.asList("2009-05-18", "2009-05-18", "2009-05-19", "2009-05-19", "2009-05-20", "2009-05-21","2009-05-21", "2009-05-22");

 List<String> result = a2.stream().filter(elem -> !a1.contains(elem)).collect(Collectors.toList());

Tôi yêu Java 8, nhưng chúng ta vẫn nên nghĩ đến sự phức tạp. Trong khi danh sách cũng có Collectionphương thức contains, nó rất kém hiệu quả. Nó cần phải chuyển qua toàn bộ danh sách nếu không được tìm thấy. Làm điều đó cho mọi phần tử của a2có thể rất chậm trên các danh sách lớn hơn, đó là lý do tại sao tôi đưa ra một tập hợp a1trong câu trả lời của mình.
Vlasec

2

Tôi đoán bạn đang nói về C #. Nếu vậy, bạn có thể thử cái này

    ArrayList CompareArrayList(ArrayList a, ArrayList b)
    {
        ArrayList output = new ArrayList();
        for (int i = 0; i < a.Count; i++)
        {
            string str = (string)a[i];
            if (!b.Contains(str))
            {
                if(!output.Contains(str)) // check for dupes
                    output.Add(str);
            }
        }
        return output;
    }

Xin lỗi, tôi không đề cập đến ngôn ngữ lập trình, không sao, nhưng tôi cần java, cảm ơn bạn đã phát lại
naveen

Chính xác. Đó cũng là một cách làm rất kém hiệu quả. Về cơ bản, bạn sẽ duyệt qua toàn bộ thời gian của bdanh sách a.Count. Bạn có thể tạo một HashSetthay thế để sử dụng cho Containshoặc sử dụng RemoveAllphương pháp trên tập hợp để nhận được chính xác kết quả bạn muốn.
Vlasec

1

Bạn chỉ đang so sánh các chuỗi.

Đặt các giá trị trong ArrayList A làm khóa trong HashTable A.
Đặt các giá trị trong ArrayList B làm khóa trong HashTable B.

Sau đó, đối với mỗi khóa trong HashTable A, hãy xóa nó khỏi HashTable B nếu nó tồn tại.

Những gì bạn còn lại trong HashTable B là các chuỗi (khóa) không phải là giá trị trong ArrayList A.

Ví dụ về C # (3.0) được thêm vào để phản hồi yêu cầu mã:

List<string> listA = new List<string>{"2009-05-18","2009-05-19","2009-05-21'"};
List<string> listB = new List<string>{"2009-05-18","2009-05-18","2009-05-19","2009-05-19","2009-05-20","2009-05-21","2009-05-21","2009-05-22"};

HashSet<string> hashA = new HashSet<string>();
HashSet<string> hashB = new HashSet<string>();

foreach (string dateStrA in listA) hashA.Add(dateStrA);
foreach (string dateStrB in listB) hashB.Add(dateStrB);

foreach (string dateStrA in hashA)
{
    if (hashB.Contains(dateStrA)) hashB.Remove(dateStrA);
}

List<string> result = hashB.ToList<string>();

Trong mã C # của bạn, hashAbiến này thực sự vô dụng. listAThay vào đó, bạn có thể tạo foreach với vì hashAchỉ được lặp lại và Containskhông bao giờ được gọi.
Vlasec

(Ngoài ra, với điều kiện C # có phương thức RemoveAll giống như Java, bạn có thể tránh phải thực hiện chu trình của riêng mình ... nhưng một lần nữa, tôi ủng hộ bạn vì giải pháp này ít nhất là hiệu quả hơn giải pháp đã chọn.)
Vlasec

1

Hi sử dụng lớp này, nó sẽ so sánh cả hai danh sách và hiển thị chính xác b / w không khớp của cả hai danh sách.

import java.util.ArrayList;
import java.util.List;


public class ListCompare {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> dbVinList;
        dbVinList = new ArrayList<String>();
        List<String> ediVinList;
        ediVinList = new ArrayList<String>();           

        dbVinList.add("A");
        dbVinList.add("B");
        dbVinList.add("C");
        dbVinList.add("D");

        ediVinList.add("A");
        ediVinList.add("C");
        ediVinList.add("E");
        ediVinList.add("F");
        /*ediVinList.add("G");
        ediVinList.add("H");
        ediVinList.add("I");
        ediVinList.add("J");*/  

        List<String> dbVinListClone = dbVinList;
        List<String> ediVinListClone = ediVinList;

        boolean flag;
        String mismatchVins = null;
        if(dbVinListClone.containsAll(ediVinListClone)){
            flag = dbVinListClone.removeAll(ediVinListClone);   
            if(flag){
                mismatchVins = getMismatchVins(dbVinListClone);
            }
        }else{
            flag = ediVinListClone.removeAll(dbVinListClone);
            if(flag){
                mismatchVins = getMismatchVins(ediVinListClone);
            }
        }
        if(mismatchVins != null){
            System.out.println("mismatch vins : "+mismatchVins);
        }       

    }

    private static String getMismatchVins(List<String> mismatchList){
        StringBuilder mismatchVins = new StringBuilder();
        int i = 0;
        for(String mismatch : mismatchList){
            i++;
            if(i < mismatchList.size() && i!=5){
                mismatchVins.append(mismatch).append(",");  
            }else{
                mismatchVins.append(mismatch);
            }
            if(i==5){               
                break;
            }
        }
        String mismatch1;
        if(mismatchVins.length() > 100){
            mismatch1 = mismatchVins.substring(0, 99);
        }else{
            mismatch1 = mismatchVins.toString();
        }       
        return mismatch1;
    }

}

Bạn có biết rằng nhân bản thực sự không phải là nhân bản không?
Vlasec

1

CÔNG VIỆC NÀY CŨNG VỚI Arraylist

    // Create a couple ArrayList objects and populate them
    // with some delicious fruits.
    ArrayList<String> firstList = new ArrayList<String>() {/**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("pea");
    }};

    ArrayList<String> secondList = new ArrayList<String>() {

    /**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("banana");
        add("strawberry");
    }};

    // Show the "before" lists
    System.out.println("First List: " + firstList);
    System.out.println("Second List: " + secondList);

    // Remove all elements in firstList from secondList
    secondList.removeAll(firstList);

    // Show the "after" list
    System.out.println("Result: " + secondList);

1
kết quả đầu ra: Danh sách đầu tiên: [táo, cam, pippo] Danh sách thứ hai: [táo, cam, chuối, dâu tây] Kết quả: [chuối, dâu tây]
psycho

Nó có. Nhưng khi bạn nói như vậy, bạn không nên quên lưu ý rằng nó có thể rất chậm trên các danh sách lớn. Hãy nhớ rằng các phương pháp thích removecontainscần phải tìm kiếm trong toàn bộ danh sách. Nếu được gọi lặp lại trong một chu kỳ (xảy ra trong removeAll), bạn sẽ có độ phức tạp bậc hai. Tuy nhiên, bạn có thể sử dụng một bộ băm và nó chỉ tuyến tính.
Vlasec
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.