如何從兩個List中篩選出相同的值


問題

現有社保卡和身份證若干,想要匹配篩選出一一對應的社保卡和身份證。
轉換為List<社保卡> socialList,和List idList,從二者中找出匹配的社保卡。

模型

創建社保卡類

/**
 * @author Ryan Miao
 */
class SocialSecurity{
    private Integer id;//社保號碼
    private Integer idCard;//身份證號碼
    private String somethingElse;

    public SocialSecurity(Integer id, Integer idCard, String somethingElse) {
        this.id = id;
        this.idCard = idCard;
        this.somethingElse = somethingElse;
    }

    public Integer getId() {
        return id;
    }

    public Integer getIdCard() {
        return idCard;
    }

    public String getSomethingElse() {
        return somethingElse;
    }

    @Override
    public String toString() {
        return "SocialSecurity{" +
                "id=" + id +
                ", idCard=" + idCard +
                ", somethingElse='" + somethingElse + '\'' +
                '}';
    }
}

創建身份證類

class IdCard {
    private Integer id;//身份證號碼
    private String somethingElse;

    public IdCard(Integer id, String somethingElse) {
        this.id = id;
        this.somethingElse = somethingElse;
    }

    public Integer getId() {
        return id;
    }

    public String getSomethingElse() {
        return somethingElse;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "id=" + id +
                ", somethingElse='" + somethingElse + '\'' +
                '}';
    }
}

最簡單的辦法:遍歷

只要做兩輪循環即可。
准備初始化數據:



private ArrayList<SocialSecurity> socialSecurities;
private ArrayList<IdCard> idCards;

@Before
public void setUp(){
    socialSecurities = Lists.newArrayList(
            new SocialSecurity(1, 12, "小明"),
            new SocialSecurity(2, 13, "小紅"),
            new SocialSecurity(3, 14, "小王"),
            new SocialSecurity(4, 15, "小peng")
    );

    idCards = Lists.newArrayList(
            new IdCard(14, "xiaopeng"),
    new IdCard(13, "xiaohong"),
    new IdCard(12, "xiaoming")
    );

    //目標: 從socialSecurities中篩選出idCards中存在的卡片
}

遍歷

 @Test
public void testFilterForEach(){
    List<SocialSecurity> result = new ArrayList<>();
    int count = 0;
    for (SocialSecurity socialSecurity : socialSecurities) {
        for (IdCard idCard : idCards) {
            count++;
            if (socialSecurity.getIdCard().equals(idCard.getId())){
                result.add(socialSecurity);
            }
        }
    }

    System.out.println(result);
    System.out.println(count);//12 = 3 * 4
    //O(m,n) = m*n;
}

很容易看出,時間復雜度O(m,n)=m*n.

采用Hash

通過觀察發現,兩個list取相同的部分時,每次都遍歷兩個list。那么,可以把判斷條件放入Hash中,判斷hash是否存在來代替遍歷查找。

@Test
public void testFilterHash(){
    Set<Integer> ids = idCards
            .stream()
            .map(IdCard::getId)
            .collect(Collectors.toSet());
    List<SocialSecurity> result = socialSecurities
            .stream()
            .filter(e->ids.contains(e.getIdCard()))
            .collect(Collectors.toList());

    System.out.println(result);
    //初始化 hash 3
    //遍歷socialSecurities 4
    //從hash中判斷key是否存在  4
    //O(m,n)=2m+n=11
}

如此,假設hash算法特別好,hash的時間復雜度為O(n)=n。如此推出這種做法的時間復雜度為O(m,n)=2m+n. 當然,更重要的是這種寫法更讓人喜歡,天然不喜歡嵌套的判斷,喜歡扁平化的風格。

Hash一定會比遍歷快嗎

想當然的以為,hash肯定會比遍歷快,因為是hash啊。其實,可以算算比較結果。比較什么時候2m+n < m*n
從數據歸納法的角度,n必須大於2,不然即演變程2m+2 < 2m。於是,當n>2時:

@Test
public void testCondition(){
    int maxN = 0;
    for (int m = 2; m < 100; m++) {
        for (int n = 3; n < 100; n++) {
            if ((2*m+n)>m*n){
                System.out.println("m="+m +",n="+n);
                if (n>maxN){
                    maxN = n;
                }
            }
        }
    }

    System.out.println(maxN);
}

結果是:

m=2,n=3
3

也就是說n<=3的時候,遍歷要比hash快。事實上還要更快,因為hash還需要創建更多的對象。然而,大部分情況下,n也就是第二個數組的長度是大於3的。這就是為什么說hash要更好寫。當然,另一個很重要的原因是lambda stream的運算符號遠比嵌套循環讓人喜愛。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM