循環(huán)移除數(shù)組中的元素蜡坊,真的有那么簡(jiǎn)單嗎步责?

有這么一道題瞻凤,移除數(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 < 18Person

乍一看派敷,很簡(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)的將之后的元素全都往前移一位的凰浮,如下圖所示:

移除當(dāng)前元素后,需要將之后的元素向前移動(dòng)一位

代碼里之所以沒(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:已使用部分大小。
_offsetfirstObject所在的位置铃辖。

假設(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è)變量leftright虐呻,分別代表數(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)元素的元素并記錄(注意萤悴,rightleft遍歷的先后順序會(huì)影響到之后的處理邏輯瘾腰,大家可以思考下為什么),然后覆履,交換這兩個(gè)位置的元素:


重復(fù)這一過(guò)程蹋盆,直至leftright相遇:

然后將基準(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è)變量leftright治泥,分別指向數(shù)組的頭和尾:

image.png

然后left向后遍歷筹煮,找到第一個(gè)不符合要求的Person,right向前遍歷车摄,找到第一個(gè)符合要求的Person(這里的先后順序依然會(huì)影響后邊的邏輯):

然后交換這兩個(gè)Person:


重復(fù)這個(gè)過(guò)程寺谤,直到leftright相遇:

此時(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ā)生變化榴徐,如下圖所示:


交換過(guò)后守问,兩個(gè)“14”的相對(duì)位置發(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ù)組:


image.png

這次衬潦,我們不用leftright了,我們定義兩個(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)題反症,比如:

  1. 沒(méi)有對(duì)入?yún)⒌漠惓G闆r進(jìn)行處理辛块,比如輸入的是不可變數(shù)組,或者根本不是個(gè)數(shù)組铅碍。
  2. 入?yún)⒌臄?shù)組沒(méi)有判空润绵。
  3. 獲取數(shù)組元素后,沒(méi)有判斷元素類型胞谈,如果數(shù)組有非Person的元素尘盼,就會(huì)crash。
  4. 算法不通用烦绳,耦合了業(yè)務(wù)邏輯卿捎,只適用于判斷Personage
  5. 沒(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)中,這樣首尼,我們就可以把它歸類到CommonLibCategory分類當(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)給自己打滿分么??”

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呛谜,隨后出現(xiàn)的幾起案子在跳,更是在濱河造成了極大的恐慌,老刑警劉巖隐岛,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猫妙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡聚凹,警方通過(guò)查閱死者的電腦和手機(jī)割坠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妒牙,“玉大人彼哼,你說(shuō)我怎么就攤上這事∠娼瘢” “怎么了敢朱?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)摩瞎。 經(jīng)常有香客問(wèn)我拴签,道長(zhǎng),這世上最難降的妖魔是什么旗们? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任蚓哩,我火速辦了婚禮,結(jié)果婚禮上上渴,老公的妹妹穿的比我還像新娘杖剪。我一直安慰自己,他們只是感情好驰贷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布盛嘿。 她就那樣靜靜地躺著,像睡著了一般括袒。 火紅的嫁衣襯著肌膚如雪次兆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天锹锰,我揣著相機(jī)與錄音芥炭,去河邊找鬼。 笑死恃慧,一個(gè)胖子當(dāng)著我的面吹牛园蝠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播痢士,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼彪薛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起善延,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤少态,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后易遣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彼妻,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年豆茫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侨歉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揩魂,死狀恐怖幽邓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肤京,我是刑警寧澤颊艳,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站忘分,受9級(jí)特大地震影響棋枕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜妒峦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一重斑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肯骇,春花似錦窥浪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至胚鸯,卻和暖如春骨稿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姜钳。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工坦冠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哥桥。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓辙浑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拟糕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子判呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容