問題
現有社保卡和身份證若干,想要匹配篩選出一一對應的社保卡和身份證。
轉換為List<社保卡> socialList,和List
模型
創建社保卡類
/**
* @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的運算符號遠比嵌套循環讓人喜愛。