有這么一道題瞻凤,移除數(shù)組中所有不符合要求的元素荞彼。
因?yàn)槲夷壳笆莻€(gè)iOS開發(fā)骡送,所以這道題的內(nèi)容我用OC語(yǔ)言來(lái)重新描述下:
假設(shè)有一個(gè)NSMutableArray
昂羡,里邊存放了n
個(gè)Person
的實(shí)例,Person
里有個(gè)屬性age
摔踱,現(xiàn)在要求你寫個(gè)方法虐先,移除數(shù)組中所有age < 18
的Person
。
乍一看派敷,很簡(jiǎn)單對(duì)吧蛹批?
一般的寫法
遇到類似問(wèn)題,我們一般都是直接寫個(gè)循環(huán)篮愉,然后在循環(huán)里邊進(jìn)行判斷腐芍,移除不符合要求的元素,大致的寫法如下:
for (int i = 0; i < arr.count; i++) {
Person *person = arr[i];
if (person.age < 18){
[arr removeObjectAtIndex:i];
I--;
}
}
這里有一點(diǎn)需要注意的是试躏,每次移除數(shù)組的元素之后猪勇,當(dāng)前索引后邊的元素索引都會(huì)變,所以這時(shí)候需要將i
減一颠蕴,否則會(huì)有部分元素?zé)o法遍歷到泣刹。
看上去完成了要求助析,可是如果你這么寫的話,我只能給你打一個(gè)60分椅您,因?yàn)橥瓿闪巳蝿?wù)外冀,所以及格了,但僅僅完成了任務(wù)掀泳,所以只是及格雪隧。
這樣的寫法,只考慮到了完成任務(wù)本身员舵,而沒(méi)考慮到算法的效率問(wèn)題膀跌。上邊的寫法,時(shí)間復(fù)雜度是O(n2)
固灵,而我們的目標(biāo)是捅伤,要寫出時(shí)間復(fù)雜度為O(n)
的算法。
等等...上邊的寫法只有一層循環(huán)巫玻,主要的操作就是移除元素丛忆,時(shí)間復(fù)雜度應(yīng)該是O(n)
啊,是不是那里算錯(cuò)了仍秤?
那么問(wèn)題出在哪了熄诡?我們知道,當(dāng)我們?cè)谝瞥龜?shù)組的元素之后诗力,實(shí)際上我們是需要循環(huán)的將之后的元素全都往前移一位的凰浮,如下圖所示:
代碼里之所以沒(méi)有體現(xiàn)出來(lái)苇本,是因?yàn)?code>OC的
NSMutableArray
已經(jīng)幫我們做了這個(gè)工作袜茧,所以這樣的寫法實(shí)際上是有兩層循環(huán)的,假設(shè)數(shù)組里有n
個(gè)元素瓣窄,每個(gè)元素都是需要移除的笛厦,那么第1
次需要移動(dòng)n
次(移除當(dāng)前元素,前移剩下的n-1
個(gè)元素)俺夕,第2
次需要移動(dòng)n-1
次...第n
次需要移動(dòng)1
次裳凸。
這是典型的前n項(xiàng)和公式
,計(jì)算結(jié)果是(1+n)*n/2
劝贸,所以時(shí)間復(fù)雜度是O(n2)
另一種寫法
我們很容易會(huì)想到另一個(gè)比較巧的寫法姨谷,就是倒著循環(huán):
for (int i = arr.count - 1; i >= 0 ; i--) {
Person *person = arr[i];
if (person.age < 18){
[arr removeObjectAtIndex:i];
}
}
這樣寫的好處是,可以避免索引亂掉的問(wèn)題映九,因?yàn)槊蜗妫?dāng)你移除掉當(dāng)前元素之后,影響的是后邊的元素,而因?yàn)槟闶堑怪h(huán)的践叠,后邊的元素你已經(jīng)遍歷過(guò)了,所以不會(huì)有影響嚼蚀。
但最關(guān)鍵的是禁灼,這種寫法看上去
要比上一個(gè)方法效率高點(diǎn),因?yàn)槭堑怪闅v的轿曙,所以當(dāng)我們移除當(dāng)前元素弄捕,將之后的元素向前移動(dòng)的時(shí)候,由于后邊不符合要求的元素已經(jīng)被我們移除了导帝,所以避免了沒(méi)有必要的移動(dòng)守谓。反過(guò)來(lái),正循環(huán)每次移除元素后您单,需要把后邊的所有元素前移斋荞,即便有些元素也是要移除的不符合要求的,也不例外虐秦。
舉個(gè)例子就是平酿,如果數(shù)組里的所有元素都是需要移除的,正循的時(shí)間復(fù)雜度是O(n2)
悦陋,而倒循環(huán)則達(dá)到了驚人的O(n)
蜈彼,因?yàn)楹筮叺脑囟家呀?jīng)移除了,不需要進(jìn)行前移操作俺驶。
如果你這么寫幸逆,我會(huì)給你65分,畢竟這種寫法解決了索引的問(wèn)題暮现,比正循環(huán)少了一個(gè)i--
的操作还绘,多少會(huì)有些效率的提高,可也只能比前一種方法多5分栖袋,因?yàn)樗匀粵](méi)有突破O(n2)
的坎兒蚕甥,并且,至少在iOS中栋荸,這兩種寫法幾乎是沒(méi)有任何區(qū)別的菇怀。
我們剛才認(rèn)為的效率高點(diǎn)
,其實(shí)并不存在晌块,因?yàn)椋?code>iOS的數(shù)組已經(jīng)針對(duì)這樣的問(wèn)題進(jìn)行了優(yōu)化爱沟。
NSMutableArray的底層實(shí)現(xiàn)
首先,NSMutableArray
的底層是C
數(shù)組匆背;
NSMutableArray
使用了環(huán)形緩沖區(qū)
的概念呼伸,簡(jiǎn)單來(lái)說(shuō)就是,數(shù)組還是那個(gè)數(shù)組,但是firstObject
可以在C
數(shù)組的任何位置括享。
NSMutableArray
內(nèi)部有三個(gè)屬性:
_size
:數(shù)組大小搂根。
_used
:已使用部分大小。
_offset
:firstObject
所在的位置铃辖。
假設(shè)有這么一個(gè)數(shù)組:
現(xiàn)在剩愧,我們刪除第4
個(gè)元素:
這個(gè)比較好理解,接下來(lái)我們刪除第1
個(gè)元素娇斩,按照正常邏輯仁卷,應(yīng)該是這樣的:
但實(shí)際上,NSMutableArray
對(duì)增刪操作的內(nèi)存移動(dòng)做了優(yōu)化犬第,它會(huì)進(jìn)行計(jì)算锦积,然后以最小的內(nèi)存移動(dòng)代價(jià)來(lái)完成這次移除操作,所以實(shí)際上是這樣的:
現(xiàn)在我們繼續(xù)移除1歉嗓,2丰介,3
位置的元素,然后再在6鉴分,7基矮,8,9
位置上添加新的元素:
如果我們此時(shí)再向數(shù)組尾部添加一個(gè)元素冠场,則會(huì)變成這樣:
這就是
環(huán)形緩沖區(qū)
的概念家浇。
跨過(guò)O(n2)的坎兒
現(xiàn)在我們明白了,因?yàn)閕OS數(shù)組底層的優(yōu)化碴裙,前兩種方法的時(shí)間復(fù)雜度是一樣的钢悲,不管是從哪頭開始遍歷,NSMutableArray
都會(huì)用最小的開銷來(lái)移動(dòng)元素舔株。
因此莺琳,我們想到了第三種實(shí)現(xiàn)方式,創(chuàng)建一個(gè)新的數(shù)組载慈,將原數(shù)組中符合要求的元素移動(dòng)到新的數(shù)組中惭等,并將原數(shù)組銷毀,這樣办铡,每次循環(huán)只需要將元素移動(dòng)到另一個(gè)數(shù)組辞做,再也不用擔(dān)心內(nèi)存移動(dòng)所帶來(lái)的消耗了!代碼大致如下:
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:originArray.count];
for (int i = 0; i < originArray.count ; i++) {
Person *person = originArray[I];
if (person.age >= 18){
[newArray addObject:person];
}
}
好了寡具,現(xiàn)在時(shí)間復(fù)雜度是O(n)
了秤茅!是不是很有成就感!然而童叠,這個(gè)O(n)
是有代價(jià)的...
雖然時(shí)間復(fù)雜度達(dá)到O(n)
了框喳,但是相應(yīng)的,空間復(fù)雜度也從O(0)
變成O(n)
了。所以五垮,這只是犧牲空間換時(shí)間乍惊,而時(shí)間和空間的重要性,會(huì)根據(jù)實(shí)際的場(chǎng)景而變化放仗,所以并不存在誰(shuí)一定比誰(shuí)重要(雖然大部分情況下大家認(rèn)為時(shí)間更重要)润绎。
但作為第一個(gè)達(dá)到O(n)
的算法,我們可以將之作為備選方案匙监,這里標(biāo)記為空間換O(n)算法
。
降低時(shí)間復(fù)雜度和空間復(fù)雜度的重要性
也許有些人會(huì)不以為然小作,時(shí)間復(fù)雜度和空間復(fù)雜度真的有那么重要么亭姥?就這幾個(gè)元素,真的有必要搞得這么復(fù)雜顾稀?
其實(shí)是很有必要的达罗,如果數(shù)組里只有十幾個(gè)元素,可能O(n)
和O(n2)
的差距不大静秆,但如果有幾萬(wàn)粮揉,乃至于上百萬(wàn)個(gè)元素呢?為了節(jié)省時(shí)間而需要多開辟這么大的空間是不理智的抚笔,而為了節(jié)省空間讓計(jì)算的時(shí)間成指數(shù)倍增加也是不能接受的扶认。
也許還有人會(huì)說(shuō),作為客戶端殊橙,感覺日常開發(fā)中辐宾,即便遇到了這樣的需求,也不會(huì)真的需要操作大量的數(shù)據(jù)膨蛮。
我想說(shuō)的是叠纹,作為一個(gè)優(yōu)秀的程序員,我們不能放過(guò)任何一個(gè)微小的細(xì)節(jié)敞葛,雖然優(yōu)化過(guò)后帶來(lái)的收益可能微乎其微誉察,但積少成多,也許就能看到明顯的效率提升惹谐,就能改善用戶的體驗(yàn)持偏。
退一步說(shuō),萬(wàn)一以后我們轉(zhuǎn)崗做其他端了呢氨肌?對(duì)吧~
下面我們來(lái)實(shí)際模擬下综液,看一下O(n)
和O(n2)
的差距到底有多大。
我們隨機(jī)生成十萬(wàn)個(gè)Person
儒飒,分別用第一個(gè)算法和空間換O(n)算法
來(lái)移除谬莹,看看時(shí)間上能差多少:
可以看到,O(n)
的算法只用了0.012
秒,而O(n2)
則用了0.206
秒附帽,時(shí)間上相差了16.9
倍埠戳!
而O(n)
算法的空間開銷則達(dá)到了781KB
!
那么蕉扮,有沒(méi)有時(shí)間復(fù)雜度O(n)
整胃,空間復(fù)雜度O(0)
的方案呢?有的喳钟!
快速排序
在講下一個(gè)方案之前屁使,我先講一下快速排序,可能大家已經(jīng)猜到了奔则,突破O(n2)的關(guān)鍵蛮寂,就在快排的核心思想中。
快排的核心思想是易茬,找一個(gè)基準(zhǔn)元素
酬蹋,將比基準(zhǔn)元素大
的元素放到基準(zhǔn)元素
的右邊
,將比基準(zhǔn)元素小
的元素放到基準(zhǔn)元素
的左邊
抽莱,這樣一圈下來(lái)范抓,左邊的部分就是全都小于基準(zhǔn)元素
的元素,右邊是全都大于的食铐。然后再對(duì)左右兩邊的部分重復(fù)上述過(guò)程匕垫,直到最后一個(gè)元素。下面進(jìn)行圖解說(shuō)明:
假設(shè)我們有個(gè)未排序的數(shù)組:
我們需要定義兩個(gè)變量left
和right
虐呻,分別代表數(shù)組的第一個(gè)元素和最后一個(gè)元素的索引值年缎,然后,將第一個(gè)元素18
作為基準(zhǔn)元素
铃慷。
關(guān)于
基準(zhǔn)元素
的選擇单芜,其實(shí)是有策略的,這里為了簡(jiǎn)單犁柜,直接取第一個(gè)元素洲鸠。
接下來(lái),right
從后向前遍歷馋缅,找到第一個(gè)小于基準(zhǔn)元素
的元素并記錄扒腕,left
從前向后遍歷,找到第一個(gè)大于基準(zhǔn)元素
的元素并記錄(注意萤悴,right
和left
遍歷的先后順序會(huì)影響到之后的處理邏輯瘾腰,大家可以思考下為什么),然后覆履,交換這兩個(gè)位置的元素:
重復(fù)這一過(guò)程蹋盆,直至
left
和right
相遇:然后將基準(zhǔn)元素
與left(right)
所在的元素調(diào)換位置:
這樣费薄,數(shù)組就被
基準(zhǔn)元素
分割成了兩個(gè)部分,接下來(lái)只需要重復(fù)上邊的過(guò)程栖雾,直到排序完畢:真·跨過(guò)O(n2)的坎兒
知道了快排的原理楞抡,我們就可以對(duì)我們的算法進(jìn)行升級(jí)了,不過(guò)析藕,畢竟我們的需求不是排序召廷,所以實(shí)現(xiàn)上會(huì)稍微有些不同。
仔細(xì)一想账胧,本題中的18歲
就是快排中的基準(zhǔn)元素
竞慢,按照這個(gè)思想,假設(shè)我們有個(gè)數(shù)組:
我們定義兩個(gè)變量left
和right
治泥,分別指向數(shù)組的頭和尾:
然后left
向后遍歷筹煮,找到第一個(gè)不符合
要求的Person,right
向前遍歷车摄,找到第一個(gè)符合
要求的Person(這里的先后順序依然會(huì)影響后邊的邏輯):
然后交換這兩個(gè)Person:
重復(fù)這個(gè)過(guò)程寺谤,直到left
和right
相遇:
此時(shí)仑鸥,從left(right)
到數(shù)組末尾的元素吮播,就全都是不符合的了,然后我們將這些不符合的元素全部移除眼俊,得到最終結(jié)果:
大致代碼如下:
- (void)removeInconformityElementOfArray:(NSMutableArray *)arr{
int left = 0;
int right = arr.count - 1;
do {
while (left <= right) {
Person *person = arr[left];
if (person.age < 18){
//找到了不符合的元素
break;
}
left++;
}
while (left < right) {
Person *person = arr[right];
if (person.age >= 18){
//找到了符合的元素
[arr exchangeObjectAtIndex:left withObjectAtIndex:right];
left++;
if (left != right - 1) {
right--;
}
break;
}
right--;
}
} while (left < right);
[arr removeObjectsInRange:NSMakeRange(left, arr.count - left)];
}
不需要多余的內(nèi)存移動(dòng)意狠,只需要簡(jiǎn)單的元素交換,時(shí)間復(fù)雜度O(n)
疮胖。沒(méi)有多余的內(nèi)存開銷环戈,空間復(fù)雜度O(0)
,現(xiàn)在澎灸,我們找到了第二個(gè)備選方案院塞,我們標(biāo)記為不穩(wěn)定O(n)算法
。
是的性昭,這個(gè)算法是不穩(wěn)定的拦止。
排序算法的穩(wěn)定性指的是,排序結(jié)束后糜颠,相等元素的相對(duì)順序是否保持不變汹族。如果不變,那么我們說(shuō)這個(gè)排序算法是穩(wěn)定的其兴。
我們知道顶瞒,快速排序是不穩(wěn)定的,因?yàn)榻粨Q的過(guò)程元旬,可能會(huì)導(dǎo)致相同的元素的位置發(fā)生變化榴徐,如下圖所示:
因此,上邊的算法也是不穩(wěn)定的箕速,用上邊的算法操作后的數(shù)組酪碘,里邊元素的順序會(huì)亂掉。
試想盐茎,如果這是n個(gè)人在排隊(duì)買票兴垦,現(xiàn)在臨時(shí)決定讓未成年人去另一個(gè)窗口排隊(duì)了,所以需要在當(dāng)前隊(duì)列中移除未成年人字柠,可是當(dāng)移除了所有的未成年人之后探越,隊(duì)伍亂掉了,剛才排第一的人現(xiàn)在在隊(duì)尾??窑业,是不是很可怕钦幔!
真·穩(wěn)定的·跨過(guò)O(n2)的坎兒
那么,如何寫出一個(gè)穩(wěn)定的O(n)
算法呢常柄?
快排雖然不穩(wěn)定鲤氢,但給我們提供了正確的思路,我們能不能在快排的思想基礎(chǔ)上西潘,換個(gè)穩(wěn)定的實(shí)現(xiàn)方式呢卷玉?答案是肯定的。
既然從兩邊往中間走不穩(wěn)定喷市,那么我們可以嘗試都從一邊走相种,不符合標(biāo)準(zhǔn)的從左邊開始找,符合的也從左邊找品姓,這樣是不是就避免了不穩(wěn)定性呢寝并?讓我們來(lái)看下。
首先腹备,依然是那個(gè)數(shù)組:
這次衬潦,我們不用left
和right
了,我們定義兩個(gè)變量retain(保留)
和remove(移除)
植酥,我們先將remove
設(shè)置為0
:
然后向右遍歷镀岛,找到第一個(gè)不符合要求的元素:
因?yàn)?code>remove之前的一定都是符合要求的元素,所以直接將retain
的初始值賦值為remove + 1
:
然后找到第一個(gè)符合要求的元素:
交換這兩個(gè)元素:
重復(fù)上述步驟惧互,直到retain
完成遍歷:
移除remove
以及之后的所有元素哎媚,得到最終結(jié)果:
代碼大致如下:
- (void)removeInconformityElementOfArray:(NSMutableArray *)arr{
//記錄需要移除的元素
int remove = 0;
for (int i = 0; i < arr.count; i++) {
Person *person = arr[i];
if (person.age >= 18){
[arr exchangeObjectAtIndex:remove withObjectAtIndex:i];
remove++;
}
}
//移除不符合的元素并結(jié)束
[arr removeObjectsInRange:NSMakeRange(remove, arr.count - remove)];
}
這是最后一個(gè)備選方案,我們標(biāo)記為穩(wěn)定O(n)算法
喊儡。
三個(gè)備選方案的對(duì)比
現(xiàn)在拨与,我們?cè)賮?lái)模擬一下,還是100000
個(gè)Person
艾猜,用O(n2)
算法和三個(gè)備選方案作對(duì)比买喧。
這次我們對(duì)生成Person實(shí)例的策略進(jìn)行了調(diào)整捻悯,棄用了完全隨機(jī)的方案,取而代之的是淤毛,將奇數(shù)索引的age
設(shè)置為17
今缚,偶數(shù)索引的設(shè)置為19
,這樣低淡,就能保證生成的實(shí)例中姓言,有一半是需要移除的。
這么做的原因是蔗蹋,空間換O(n)算法
主要的時(shí)間開銷是將符合
的元素移動(dòng)到新的數(shù)組何荚,而另外的兩個(gè)O(n)算法
是將不符合
的元素交換到數(shù)組末尾并移除。因此猪杭,需要移除的元素越少餐塘,空間換O(n)算法
執(zhí)行的時(shí)間反而越長(zhǎng),反之亦然皂吮,為了得到更準(zhǔn)確的數(shù)據(jù)戒傻,符合
和不符合
的元素?cái)?shù)量要相等。
運(yùn)行后的結(jié)果如下:
我們可以看到蜂筹,O(n)
和O(n2)
的差距還是很顯而易見的需纳,不過(guò),三個(gè)O(n)
算法的耗時(shí)也是有一些差別的狂票,下面我詳細(xì)的分析下候齿。
空間換O(n)算法
是最快的熙暴,因?yàn)闋奚丝臻g闺属,所以,該算法主要的時(shí)間開銷是周霉,將符合的元素放到新的數(shù)組掂器,相當(dāng)于只做了一次賦值操作,所以是最快的俱箱。
不穩(wěn)定O(n)算法
稍慢国瓮,因?yàn)樵撍惴ǔ艘瞥环系脑兀€需要交換元素的位置狞谱,比空間換O(n)算法
多了一些操作乃摹,所以會(huì)有一些額外的開銷,不過(guò)差的并不多跟衅,大概多出了6.4%
左右孵睬。
穩(wěn)定O(n)算法
最慢,比空間換O(n)算法
大概多出了11.9%
的時(shí)間伶跷,因?yàn)樵撍惴ㄏ喈?dāng)于是在不穩(wěn)定O(n)算法
的基礎(chǔ)上掰读,又做了額外的元素交換秘狞。不穩(wěn)定O(n)算法
相當(dāng)于是犧牲了穩(wěn)定性
來(lái)?yè)Q取時(shí)間上的效率,下面舉例來(lái)說(shuō)明:
還是以本題為例蹈集,如圖所示的數(shù)組烁试,不穩(wěn)定O(n)算法
是直接交換不符合的元素11
與最后一個(gè)元素19
,只有一次交換就完成了任務(wù)拢肆,但是相應(yīng)的减响,數(shù)組元素的順序會(huì)亂掉,所以不穩(wěn)定郭怪。
而穩(wěn)定O(n)算法
在找到了不符合的元素11
之后辩蛋,要先找到其后的第一個(gè)符合的元素50
,并與之交換移盆,然后繼續(xù)尋找后邊的其他符合的元素交換悼院,直到結(jié)束。本例中進(jìn)行了四次交換咒循,正是那多出的三次交換据途,保證了算法的穩(wěn)定性,但同時(shí)時(shí)間上也會(huì)稍慢一些叙甸。
時(shí)間復(fù)雜度和空間復(fù)雜度的取舍
那么颖医,以上三個(gè)算法哪個(gè)最好呢?如果數(shù)組里的元素?cái)?shù)量較少裆蒸,我們可能就不太關(guān)心空間開銷熔萧,空間換O(n)算法
無(wú)疑是最棒的。對(duì)于大量數(shù)據(jù)的操作僚祷,就要看具體的使用場(chǎng)景(需求)了佛致,那么你是否愿意犧牲11.9%
的時(shí)間來(lái)節(jié)省大量的空間開銷呢?如果對(duì)穩(wěn)定性沒(méi)要求辙谜,付出的代價(jià)可以更少俺榆。
又或者,當(dāng)前場(chǎng)景下装哆,需要移除的元素不會(huì)太多罐脊,那么看上去速度最快的空間換O(n)算法
反而拖了后腿。
所以蜕琴,還是要根據(jù)實(shí)際情況具體分析的萍桌。
結(jié)論就是,這三個(gè)算法都很優(yōu)秀~在不同的使用場(chǎng)景凌简,我都可以給你85分上炎!
還想得更高的分怎么辦?繼續(xù)往下看~
繼續(xù)優(yōu)化
雖然這個(gè)算法已經(jīng)很優(yōu)秀了号醉,但還是有一些問(wèn)題反症,比如:
- 沒(méi)有對(duì)入?yún)⒌漠惓G闆r進(jìn)行處理辛块,比如輸入的是不可變數(shù)組,或者根本不是個(gè)數(shù)組铅碍。
- 入?yún)⒌臄?shù)組沒(méi)有判空润绵。
- 獲取數(shù)組元素后,沒(méi)有判斷元素類型胞谈,如果數(shù)組有非
Person
的元素尘盼,就會(huì)crash。 - 算法不通用烦绳,耦合了業(yè)務(wù)邏輯卿捎,只適用于判斷
Person
的age
。 - 沒(méi)有運(yùn)用
OC
的特性径密,讓算法更獨(dú)立午阵,架構(gòu)更清晰。
下面我們針對(duì)這些問(wèn)題進(jìn)行優(yōu)化享扔。
添加對(duì)入?yún)惓G闆r的處理:(+1分)
if (![arr isKindOfClass:NSMutableArray.class]){
return;
}
添加數(shù)組判空邏輯:(+1分)
if (!arr.count){
return;
}
判斷元素類型:(+1分)
if (![obj isKindOfClass:Person.class]){
//遇到數(shù)據(jù)類型不匹配問(wèn)題,根據(jù)業(yè)務(wù)需求返回
return YES;
}
return ((Person *)obj).age < 18;
算法解耦底桂,我們可以仿照NSMutableArray
的排序方法進(jìn)行優(yōu)化,這里使用Block的方式惧眠,修改后的方法名如下:(這個(gè)得加2分W雅场)
- (void)removeInconformityElementOfArray:(NSMutableArray *)arr
compareBlock:(BOOL (^)(id obj1,id obj2))compareBlock
相應(yīng)的,原來(lái)方法里判斷的地方就變成了這樣:
if (judgmentBlock(arr[remove])){
//找到了不符合的元素
break;
}
調(diào)用大概是這個(gè)樣子的:
[self removeInconformityElementOfArray:arr withJudgmentBlock:^BOOL(id obj) {
if (![obj isKindOfClass:Person.class]){
//遇到數(shù)據(jù)類型不匹配問(wèn)題,根據(jù)業(yè)務(wù)需求返回
return YES;
}
return ((Person *)obj).age < 18;
}];
下面重點(diǎn)說(shuō)下運(yùn)用OC特性
這一點(diǎn)氛魁,我們知道暮顺,目前的方法是定義在我們需要操作數(shù)組的類里,顯然這個(gè)方法放在這里是不合適的秀存,如此通用的一個(gè)方法捶码,應(yīng)當(dāng)服務(wù)更多的類才是。
于是我們定義了一個(gè)工具類XXXUtil
应又,將這個(gè)方法變成了類方法宙项,這樣乏苦,需要的地方就都可以用了...但是這樣還是不夠好株扛。
不好的原因有很多,比如汇荐,這個(gè)類應(yīng)該放在哪洞就?如何歸類?這個(gè)類里還可以放一些什么方法掀淘?最關(guān)鍵的是旬蟋,別人需要用的時(shí)候,他怎么知道有沒(méi)有這樣的方法革娄?應(yīng)該去哪個(gè)類里找倾贰?
這個(gè)時(shí)候冕碟,我們想到了OC
的一個(gè)特性
,Category
匆浙,我們可以給NSMutableArray
寫一個(gè)Category
安寺,將這個(gè)方法放到Category
當(dāng)中,這樣首尼,我們就可以把它歸類到CommonLib
的Category
分類當(dāng)中挑庶,別人要用到了相似的功能,可以優(yōu)先去找NSMutableArray
的分類软能,看看有沒(méi)有類似的功能迎捺,思路清晰,分類準(zhǔn)確查排,尋找方便凳枝,架構(gòu)清晰,很合理對(duì)不對(duì)~
下面我們繼續(xù)對(duì)方法進(jìn)行優(yōu)化跋核,首先我們創(chuàng)建個(gè)分類NSMutableArray+LSYRemoveElement
范舀,然后將方法修改后放到這個(gè)類中。最終的代碼如下:
- (void)lsy_stabilizeRemoveInconformityElementWithJudgmentBlock:(BOOL (^)(id obj))judgmentBlock{
if (![self isKindOfClass:NSMutableArray.class] || !self.count){
return;
}
//記錄需要移除的元素
int remove = 0;
for (int i = 0; i < self.count; i++) {
if (!judgmentBlock(self[i])){
[self exchangeObjectAtIndex:remove withObjectAtIndex:i];
remove++;
}
}
//移除不符合的元素并結(jié)束
[self removeObjectsInRange:NSMakeRange(remove, self.count - remove)];
}
調(diào)用代碼大致如下:
[arr lsy_removeInconformityElementWithJudgmentBlock:^BOOL(id _Nonnull obj) {
if (![obj isKindOfClass:Person.class]){
//遇到數(shù)據(jù)類型不匹配問(wèn)題,根據(jù)業(yè)務(wù)需求返回
return YES;
}
return ((Person *)obj).age < 18;
}];
到此了罪,優(yōu)化完畢锭环,我可以給你92分!
那么泊藕,還有可以優(yōu)化的地方嘛辅辩?
其實(shí)還有...
emmm...別嫌煩啊,有時(shí)候娃圆,看著簡(jiǎn)單的事情玫锋,做起來(lái)確實(shí)可能會(huì)很麻煩。
看上去讼呢,該考慮的都考慮到了撩鹿,那么還差什么呢?
我們還沒(méi)有考慮線程安全
問(wèn)題霸闷痢=诼佟!础爬!
如果全程只在一個(gè)線程操作甫贯,那么這個(gè)方法已經(jīng)非常完美,可萬(wàn)一在移除的過(guò)程中看蚜,其他線程對(duì)數(shù)組做了增刪改交換等操作呢叫搁?或者其他線程也調(diào)用了這個(gè)方法處理同一個(gè)數(shù)組會(huì)怎么樣?所以線程安全是非常必要的。
保證線程安全的方式有很多種渴逻,具體如何實(shí)現(xiàn)疾党,我這里就不舉例了,因?yàn)橛嘘P(guān)多線程的東西太多了惨奕,以后可以慢慢講仿贬。
還有最后一點(diǎn)...
最后一點(diǎn)要考慮的就是,如果數(shù)組里的元素太多墓贿,操作太耗時(shí)茧泪,就會(huì)導(dǎo)致卡頓。所以我們可以提供個(gè)同步
和異步
的選項(xiàng)聋袋,由使用者來(lái)決定是否要在子線程執(zhí)行队伟。
到此,我可以給你95分了幽勒!少給你5分是怕你驕傲嗜侮!
Demo我已經(jīng)上傳到了GitHub
,有興趣的小伙伴可以下下來(lái)看看~
寫在最后的話
剛工作的時(shí)候啥容,我的一個(gè)領(lǐng)導(dǎo)曾經(jīng)對(duì)我說(shuō)過(guò)這樣一句話:
不管你覺得你寫的代碼有多棒剑肯,也許你現(xiàn)在覺得已經(jīng)是最好了二蓝,但三個(gè)月后再看疲牵,你依然能夠找到很多可以優(yōu)化的地方疏尿。
然后我用我的懶惰和退步向他證明了,他錯(cuò)了遥昧!
當(dāng)然不是覆醇!其實(shí)是我明白了他這句話的意思,所謂沒(méi)有最好炭臭,只有更好永脓,所謂學(xué)無(wú)止境。我們現(xiàn)在覺得自己寫的代碼無(wú)可挑剔鞋仍,是因?yàn)橐晕覀儸F(xiàn)在的知識(shí)水平常摧,還沒(méi)找到更好的寫法,隨著我們技術(shù)水平的提高威创,我們一定能寫出更好的代碼~
所以落午,少給5分不是怕驕傲,是因?yàn)榇_實(shí)不知道滿分是什么樣子的那婉。
最主要的是板甘,萬(wàn)一有一天,有人用更好的寫法反駁了我详炬,我就可以理直氣壯地說(shuō),“我不是沒(méi)給自己打滿分么??”