傳送門:排序算法演示小DEMO
前面的話
為了給字符串?dāng)?shù)組排序,除了用C/C++的基本辦法,iOS開發(fā)者更應(yīng)該學(xué)會(huì)利用蘋果專門為NSArray 排序提供的sortedArrayUsingComparator 方法:
- (NSArray<ObjectType> *)sortedArrayUsingComparator:(NSComparator NS_NOESCAPE)cmptr NS_AVAILABLE(10_6, 4_0);
其中,需要設(shè)置一個(gè)NSComparator 參數(shù),它是一個(gè)block,查看定義如下:
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
這個(gè)block體返回的NSComparisonResult 是一個(gè)枚舉類型,它的定義是:
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};
問(wèn)題來(lái)了,怎么設(shè)置?
- 為了設(shè)置這個(gè)NSComparator 參數(shù)的block體,你可以在設(shè)置其block體的時(shí)候,手動(dòng)返回一個(gè)NSComparisonResult 枚舉類型的某個(gè)具體值(NSOrderedAscending, NSOrderedSame, NSOrderedDescending 三選一):

- 如果數(shù)組里面是字符串,在設(shè)置其block體的時(shí)候,你也可以利用蘋果專門為NSString 提供的字符串比較方法,獲得一個(gè)NSComparisonResult 類型,將其自動(dòng)返回。
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToCompare locale:(nullable id)locale; // locale arg used to be a dictionary pre-Leopard. We now accept NSLocale. Assumes the current locale if non-nil and non-NSLocale. nil continues to mean canonical compare, which doesn't depend on user's locale choice.

這時(shí)候,就需要了解NSStringCompareOptions 的意思。但如果你搜索一下NSStringCompareOptions ,會(huì)發(fā)現(xiàn)很多文章中的翻譯或者中文解釋在誤導(dǎo),或者很難看清什么意思?例如下面這篇博客:

然后,相同的解釋文案還以訛傳訛的傳開來(lái)了,例如你看下面這個(gè)博客:

于是,筆者決定寫此本文,好好展示他們的用途。
1. 第一種:數(shù)組的字符串元素里面是基本數(shù)據(jù)類型
1.1 字符串?dāng)?shù)組排序示例
1.1.1 實(shí)驗(yàn)代碼
- main.m
void handleSortingForIntStrArray(void){
NSArray *originalArray = @[@"00",@"0",@"00",@"01",@"10",@"21",@"12",@"11",@"22"];
//block比較方法,數(shù)組中可以是NSInteger,NSString(需要轉(zhuǎn)換)
NSComparator finderSort = ^(id string1,id string2){
if ([string1 integerValue] > [string2 integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}else if ([string1 integerValue] < [string2 integerValue]){
return (NSComparisonResult)NSOrderedAscending;
}else{
return (NSComparisonResult)NSOrderedSame;
}
};
//數(shù)組排序:
NSArray *resultArray = [originalArray sortedArrayUsingComparator:finderSort];
NSLog(@"第一種排序結(jié)果:%@",resultArray);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Results of handleSortingForIntArray()**********************");
handleSortingForIntStrArray();
}
return 0;
}
1.1.2 運(yùn)行結(jié)果

1.1.3 實(shí)驗(yàn)結(jié)論
- 依據(jù)數(shù)組元素的數(shù)值大小返回升序數(shù)組
1.2 NSComparator與NSComparisonResult
上面的代碼中用到了NSComparator與NSComparisonResult,在本文的“前面的話”中已經(jīng)介紹過(guò),這里重新列一下定義。
1.2.1 NSComparator
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
1.2.2 NSComparisonResult
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};
2. 第二種:數(shù)組的字符串元素里面不是基本數(shù)據(jù)類型
2.1 示例:字符串?dāng)?shù)組排序
2.1.1 實(shí)驗(yàn)代碼
- main.m
//
// main.m
// SortingForArray
//
// Created by ChenMan on 2017/12/20.
// Copyright ? 2017年 ChenMan. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <stdio.h>
void handleSortingForStrArray(void){
NSArray *stringsArray = [NSArray arrayWithObjects:
@"string b",
@"string A",
@"string a",
@"string \uFF41",
@"string a",
@"string A",
@"string c",
@"string d0030",
@"string d2",
@"アいろは????イウエ",
@"?いろは???????",
@"アいろは????イウエ",nil];
NSLocale *currentLocale = [NSLocale currentLocale];
NSComparator finderSortBlock = ^(id string1,id string2) {
NSRange string1Range =NSMakeRange(0, [string1 length]);
return [string1 compare:string2 options:nil range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Results of handleSortingForStrArray()**********************");
handleSortingForStrArray();
}
return 0;
}
2.1.2 運(yùn)行結(jié)果:

2.1.3 實(shí)驗(yàn)結(jié)論:
如上實(shí)驗(yàn)代碼中,有這樣一行代碼:
return [string1 compare:string2 options:nil range:string1Range locale:currentLocale];
根據(jù)運(yùn)行結(jié)果,可知如下結(jié)論:
- 即使在
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToCompare locale:(nullable id)locale;中將(NSStringCompareOptions)枚舉類型的參數(shù)設(shè)置為nil,也可以運(yùn)行。但一般不這么做,這里只是為了觀察不指定該枚舉參數(shù)時(shí)候系統(tǒng)的默認(rèn)設(shè)置,并與本文接下來(lái)指定該枚舉參數(shù)的排序結(jié)果對(duì)比。 - 可以發(fā)現(xiàn):
- 默認(rèn)同一字符的全角字符看做半角字符。不區(qū)分同一個(gè)字符(如日文的片假字)的半角與全角狀態(tài)。相同元素,維持原序。
- 默認(rèn)區(qū)分字母大小寫,同一個(gè)字符小寫在前,大寫在后。
- 字母并非按unicode碼的大小升序排列。例如,
全角a的unicode為FF41,半角a的unicode為0061,半角A的unicode為0041,半角b的unicode為0062,但排序結(jié)果是全角a=半角a<半角A<半角b。 - 默認(rèn)不識(shí)別含有數(shù)字字符的數(shù)值大小,0030雖然數(shù)學(xué)意義比2大,但是,僅從字符串的角度看,第一個(gè)字符0比2小,所以d0030排在d2前面。
2.1.4 知識(shí)拓展:
半角與全角字符
全角占兩個(gè)字節(jié),半角占一個(gè)字節(jié)。通常我們碰到的英文字母、數(shù)字鍵、符號(hào)鍵這種ASCII碼系統(tǒng)里面的字符大多數(shù)情況下是半角的。
國(guó)內(nèi)漢字輸入法輸入的漢字為全角,字母數(shù)字為半角,但是標(biāo)點(diǎn)則默認(rèn)為全角,可切換為半角(可以通過(guò)輸入法工具條上的相應(yīng)按鈕來(lái)切換標(biāo)點(diǎn)符號(hào)的全角半角狀態(tài))。
日文里面的有漢字,也有片假字。這個(gè)片假字有兩套編碼,同一個(gè)片假字分別有半角和全角兩種編碼。例如:看起來(lái)像一樣的片假字組成的句子,全角狀態(tài)
ア字符開頭的為アいろは????イウエ,半角狀態(tài)?字符開頭的為?いろは?????????梢钥吹?,明顯同一個(gè)片假字的全角狀態(tài) 比半角狀態(tài) “胖”一圈。英文字母其實(shí)也有全角字母,例如小寫的
a,其半角形式的unicode碼為0061,其全角形式的unicode碼為FF41??刹殚?a target="_blank" rel="nofollow">Unicode?字符百科官網(wǎng)。
2.2 NSStringCompareOptions
NSStringCompareOptions是一個(gè)枚舉類型,并非一個(gè)類。打開NSStringCompareOptions的定義,可查看如下
typedef NS_OPTIONS(NSUInteger, NSStringCompareOptions) {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2, /* Exact character-by-character equivalence */
NSBackwardsSearch = 4, /* Search from end of source string */
NSAnchoredSearch = 8, /* Search is limited to start (or end, if NSBackwardsSearch) of source string */
NSNumericSearch = 64, /* Added in 10.2; Numbers within strings are compared using numeric value, that is, Foo2.txt < Foo7.txt < Foo25.txt; only applies to compare methods, not find */
NSDiacriticInsensitiveSearch API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 128, /* If specified, ignores diacritics (o-umlaut == o) */
NSWidthInsensitiveSearch API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 256, /* If specified, ignores width differences ('a' == UFF41) */
NSForcedOrderingSearch API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 512, /* If specified, comparisons are forced to return either NSOrderedAscending or NSOrderedDescending if the strings are equivalent but not strictly equal, for stability when sorting (e.g. "aaa" > "AAA" with NSCaseInsensitiveSearch specified) */
NSRegularExpressionSearch API_AVAILABLE(macos(10.7), ios(3.2), watchos(2.0), tvos(9.0)) = 1024 /* Applies to rangeOfString:..., stringByReplacingOccurrencesOfString:..., and replaceOccurrencesOfString:... methods only; the search string is treated as an ICU-compatible regular expression; if set, no other options can apply except NSCaseInsensitiveSearch and NSAnchoredSearch */
};
2.2.1 NSNumericSearch
官方解釋:Added in 10.2; Numbers within strings are compared using numeric value, that is, Foo2.txt < Foo7.txt < Foo25.txt; only applies to compare methods, not find
- 假設(shè),將上例中的部分代碼修改為
void handleSortingForStrArray(void){
NSArray *stringsArray = [NSArray arrayWithObjects:
@"string b",
@"string A",
@"string a",
@"string \uFF41",
@"string a",
@"string A",
@"string c",
@"string d0030",
@"string d2",
@"アいろは????イウエ",
@"?いろは???????",
@"アいろは????イウエ",nil];
NSStringCompareOptions comparisonOptions = NSNumericSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
NSComparator finderSortBlock = ^(id string1,id string2) {
NSRange string1Range =NSMakeRange(0, [string1 length]);
return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
}
- 運(yùn)行結(jié)果

- 結(jié)論
NSStringCompareOptions指定為NSNumericSearch,當(dāng)字符串中含有數(shù)字時(shí),從數(shù)值大小的角度按升序排序。
2.2.2 NSCaseInsensitiveSearch
官方解釋:無(wú)。英文字面解釋:不區(qū)分字母大小寫。
- 假設(shè),將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch;
- 運(yùn)行結(jié)果

- 結(jié)論
NSStringCompareOptions指定為NSCaseInsensitiveSearch,不區(qū)分同一個(gè)字母的大小寫狀態(tài),如a與A看做相同元素,若其它條件也一致則保持原序。
2.2.3 NSLiteralSearch
官方解釋:Exact character-by-character equivalence
- 假設(shè),將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSLiteralSearch;
- 運(yùn)行結(jié)果

- 結(jié)論
- 區(qū)分 同一個(gè)字符(如日文的片假字)的半角與全角狀態(tài),同一片假字的全角狀態(tài)小于半角狀態(tài)。
- 其它規(guī)則,繼續(xù)按系統(tǒng)默認(rèn)排序規(guī)則排序,包括默認(rèn)區(qū)分 字母大小寫,以及其它默認(rèn)排序規(guī)則。
- 按照官方英文說(shuō)明,這個(gè)規(guī)則是指區(qū)分每個(gè)字符的等效狀態(tài)。只要unicode不同的字符,就不認(rèn)可他們“等效”,即使他們的語(yǔ)言上的含義相同。
- 題外話
- 所以,有的文獻(xiàn)說(shuō)NSLiteralSearch 是區(qū)分大小寫是誤導(dǎo),系統(tǒng)本就默認(rèn)區(qū)分 字母大小寫,這些人以為蘋果公司提供這個(gè)功能來(lái)畫蛇添足干嘛?而且可以看看官方英文說(shuō)明,也不是這個(gè)意思。只有指定不區(qū)分 字母大小寫的NSCaseInsensitiveSearch,要么不寫,即默認(rèn)區(qū)分。
2.2.4 NSWidthInsensitiveSearch
官方解釋:If specified, ignores width differences ('a' == UFF41)
- 假設(shè),將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch;
- 運(yùn)行結(jié)果

- 結(jié)論
- 不區(qū)分 同一個(gè)字符(如日文的片假字)的半角與全角狀態(tài),同一片假字的全角狀態(tài)等于半角狀態(tài)。
- 其它規(guī)則,繼續(xù)按系統(tǒng)默認(rèn)排序規(guī)則排序,包括默認(rèn)區(qū)分 字母大小寫,以及其它默認(rèn)排序規(guī)則。
- 同時(shí)指定兩個(gè)時(shí),NSWidthInsensitiveSearch 比NSLiteralSearch 的優(yōu)先級(jí)高,綜合起來(lái)的結(jié)果是不區(qū)分 半角全角。
- 官方英文說(shuō)明中的
UFF41是指全角a,'a'是指半角a,如果指定NSWidthInsensitiveSearch,則不區(qū)分字符的全角半角,即使你同時(shí)指定了NSLiteralSearch。
即,當(dāng)有如下代碼
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch | NSLiteralSearch;
其作用相當(dāng)于沒有NSLiteralSearch的代碼
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch;
2.2.5 NSForcedOrderingSearch
官方解釋:If specified, comparisons are forced to return either NSOrderedAscending or NSOrderedDescending if the strings are equivalent but not strictly equal, for stability when sorting (e.g. "aaa" > "AAA" with NSCaseInsensitiveSearch specified)
- 假設(shè),將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSForcedOrderingSearch;
- 運(yùn)行結(jié)果

- 結(jié)論
- 不存在字符等不等效相不相等的概念了,只要unicode不一樣的字符,必須區(qū)分,必須返回一個(gè)誰(shuí)大誰(shuí)小的結(jié)果(NSOrderedAscending or NSOrderedDescending)。
- 從英文說(shuō)明也可以看出,NSForcedOrderingSearch 的優(yōu)先級(jí)最高,即如果你同時(shí)指定了其它有可能作用沖突的枚舉類型,也以NSForcedOrderingSearch 的作用為準(zhǔn)。
2.2.6 綜合應(yīng)用
- 一個(gè)比較多的應(yīng)用示例是,區(qū)分字母大小寫,區(qū)分?jǐn)?shù)值大小,區(qū)分半角全角,并強(qiáng)制性指定區(qū)分unicode不一樣的字符。綜合這些條件,寫起來(lái)就是:
NSStringCompareOptions comparisonOptions = NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
- 運(yùn)行結(jié)果

2.2.7 誤導(dǎo)用法
- 我看過(guò)有很多其它博客用了這樣的誤導(dǎo)示例:
NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch|NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
這里面,NSCaseInsensitiveSearch是為了不區(qū)分大小寫字母,但是后面再加個(gè)NSForcedOrderingSearch想強(qiáng)制區(qū)分字符又是怎么回事?雖然,這樣寫并不會(huì)報(bào)錯(cuò),運(yùn)行效果跟上面的綜合示例一摸一樣。但這樣誤導(dǎo)的想法是個(gè)邏輯矛盾。不信,你看看它運(yùn)行的結(jié)果:

3. 數(shù)組里面是類的對(duì)象
需求:假設(shè)我們根據(jù)后臺(tái)返回的JSON字典數(shù)組用MJExtension轉(zhuǎn)換成模型數(shù)組,現(xiàn)在我們需要根據(jù)ID或者Age對(duì)模型數(shù)組進(jìn)行排序。
- Pesson.m
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy) NSString *ID;
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@end
- 根據(jù)int類型的屬性對(duì)模型數(shù)組進(jìn)行排序
NSArray *sortArrayByAgeInt = [self.dataArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
Person *pModel1 = obj1;
Person *pModel2 = obj2;
if (pModel1.age > pModel2.age) {
return NSOrderedDescending;//降序
}else if (pModel1.name < pModel2.name){
return NSOrderedAscending;//升序
}else {
return NSOrderedSame;//相等
}
}];
- 根據(jù)str類型的屬性對(duì)模型數(shù)組進(jìn)行排序
NSArray *sortArrayByIDStr = [self.dataArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
Person *pModel1 = obj1;
Person *pModel2 = obj2;
if ([pModel1.ID intValue]> [pModel2.ID intValue]) {
return NSOrderedDescending;//降序
}else if (pModel1.name < pModel2.name){
return NSOrderedAscending;//升序
}else {
return NSOrderedSame;//相等
}
}];
4. 花樣玩法:例題
在OC的高級(jí)用法中,經(jīng)常需要查看系統(tǒng)類或者某個(gè)自定義類中的私有屬性以及私有成員變量,并通過(guò)KVC的辦法強(qiáng)制修改這些私有成員變量的值,以取代系統(tǒng)或者自定義類中的默認(rèn)設(shè)置。所以,如果你懶得創(chuàng)建一些假數(shù)據(jù)的數(shù)組,可以想到運(yùn)用運(yùn)行時(shí)的辦法獲取成員變量的數(shù)組,并進(jìn)行排序操作訓(xùn)練。
題1. 請(qǐng)取出
NSString類的全部公有 屬性 并存放到一個(gè)數(shù)組,并利用NSArray的sortedArrayUsingComparator的方法給這個(gè)數(shù)組進(jìn)行升序排序操作。要求:排序過(guò)程中需要區(qū)分字符全角半角狀態(tài),其它可按系統(tǒng)默認(rèn)條件。
- 參考代碼:
main.m
void handlePrintingOfProperties(void){
unsigned int count;// 記錄屬性個(gè)數(shù)
objc_property_t *properties = class_copyPropertyList([NSString class], &count);
// 生成一個(gè)屬性名稱組成的數(shù)組
NSMutableArray *propertyNameArray = [NSMutableArray array];
for (int i = 0; i < count; i++) {
// An opaque type that represents an Objective-C declared property.
// objc_property_t 屬性類型
objc_property_t property = properties[i];
// 獲取屬性的名稱 C語(yǔ)言字符串
const char *cName = property_getName(property);
// 轉(zhuǎn)換為Objective C 字符串
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
[propertyNameArray addObject:name];
}
NSLog(@"排序前的屬性列表 = %@",propertyNameArray);
NSComparator cmptr = ^(NSString *obj1, NSString *obj2){
return [obj1 compare:obj2 options:NSLiteralSearch];
};
NSArray *afterSort = [propertyNameArray sortedArrayUsingComparator:cmptr];
NSLog(@"排序后的屬性列表 = %@",afterSort);
//C語(yǔ)言中,用完copy,create的東西之后,最好釋放
free(properties);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"handlePrintingOfProperties()**********************");
handlePrintingOfProperties();
}
return 0;
}
- 運(yùn)行結(jié)果

題2. 請(qǐng)取出
NSURL類中包括私有 在內(nèi)的全部 成員變量,并存放到一個(gè)數(shù)組,并利用NSArray的sortedArrayUsingComparator的方法給這個(gè)數(shù)組進(jìn)行升序排序操作。要求:排序過(guò)程中需要區(qū)分字符全角半角狀態(tài),其它可按系統(tǒng)默認(rèn)條件。
- 參考代碼:
void handlePrintingOfIvars(void){
unsigned int count;// 記錄屬性個(gè)數(shù)
Ivar *properties = class_copyIvarList([NSURL class], &count);
// 生成一個(gè)屬性名稱組成的數(shù)組
NSMutableArray *propertyNameArray = [NSMutableArray array];
for (int i = 0; i < count; i++) {
// An opaque type that represents an Objective-C declared property.
// objc_property_t 屬性類型
Ivar property = properties[i];
// 獲取屬性的名稱 C語(yǔ)言字符串
const char *cName = ivar_getName(property);
// 轉(zhuǎn)換為Objective C 字符串
NSString *name = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
[propertyNameArray addObject:name];
}
NSLog(@"排序前的成員變量列表 = %@",propertyNameArray);
NSComparator cmptr = ^(NSString *obj1, NSString *obj2){
return [obj1 compare:obj2 options:NSLiteralSearch];
};
NSArray *afterSort = [propertyNameArray sortedArrayUsingComparator:cmptr];
NSLog(@"排序后的成員變量列表 = %@",afterSort);
//C語(yǔ)言中,用完copy,create的東西之后,最好釋放
free(properties);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"handlePrintingOfIvars()**********************");
handlePrintingOfIvars();
}
return 0;
}
- 運(yùn)行結(jié)果

5. 附錄:本實(shí)驗(yàn)中創(chuàng)建工程說(shuō)明
任何能在計(jì)算機(jī)上執(zhí)行的項(xiàng)目稱之為程序,其中,有圖形化用戶界面的程序稱之為應(yīng)用 ,沒有圖形界面的程序可以是守護(hù)進(jìn)程 ,還有一種稱之為命令行工具。本文這里關(guān)注的是算法和數(shù)據(jù)結(jié)果,不關(guān)注圖形界面,所以新建一個(gè)命令行工具即可。創(chuàng)建方法:新建一個(gè)macOS工程,選擇Command Line Tool類型,點(diǎn)擊下一步配置工程信息即可。

