iOS開發·必會的算法操作:字符串數組排序+模型對象數組排序

傳送門:排序算法演示小DEMO

前面的話

為了給字符串數組排序,除了用C/C++的基本辦法,iOS開發者更應該學會利用蘋果專門為NSArray 排序提供的sortedArrayUsingComparator 方法:

- (NSArray<ObjectType> *)sortedArrayUsingComparator:(NSComparator NS_NOESCAPE)cmptr NS_AVAILABLE(10_6, 4_0);

其中,需要設置一個NSComparator 參數,它是一個block,查看定義如下:

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);

這個block體返回的NSComparisonResult 是一個枚舉類型,它的定義是:

typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};

問題來了,怎么設置?

  • 為了設置這個NSComparator 參數的block體,你可以在設置其block體的時候,手動返回一個NSComparisonResult 枚舉類型的某個具體值(NSOrderedAscending, NSOrderedSame, NSOrderedDescending 三選一):
image.png
  • 如果數組里面是字符串,在設置其block體的時候,你也可以利用蘋果專門為NSString 提供的字符串比較方法,獲得一個NSComparisonResult 類型,將其自動返回。
- (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.
image.png

這時候,就需要了解NSStringCompareOptions 的意思。但如果你搜索一下NSStringCompareOptions ,會發現很多文章中的翻譯或者中文解釋在誤導,或者很難看清什么意思?例如下面這篇博客:

image.png

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

image.png

于是,筆者決定寫此本文,好好展示他們的用途。

1. 第一種:數組的字符串元素里面是基本數據類型


1.1 字符串數組排序示例

1.1.1 實驗代碼
  • main.m
void handleSortingForIntStrArray(void){
    NSArray *originalArray = @[@"00",@"0",@"00",@"01",@"10",@"21",@"12",@"11",@"22"];
    //block比較方法,數組中可以是NSInteger,NSString(需要轉換)
    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;
        }
    };
    //數組排序:
    NSArray *resultArray = [originalArray sortedArrayUsingComparator:finderSort];
    NSLog(@"第一種排序結果:%@",resultArray);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Results of handleSortingForIntArray()**********************");
        handleSortingForIntStrArray();
    }
    return 0;
}
1.1.2 運行結果
image.png
1.1.3 實驗結論
  • 依據數組元素的數值大小返回升序數組

1.2 NSComparator與NSComparisonResult

上面的代碼中用到了NSComparator與NSComparisonResult,在本文的“前面的話”中已經介紹過,這里重新列一下定義。

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. 第二種:數組的字符串元素里面不是基本數據類型


2.1 示例:字符串數組排序

2.1.1 實驗代碼
  • 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 運行結果:
image.png
2.1.3 實驗結論:

如上實驗代碼中,有這樣一行代碼:

return [string1 compare:string2 options:nil range:string1Range locale:currentLocale];

根據運行結果,可知如下結論:

  • 即使在- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToCompare locale:(nullable id)locale;中將(NSStringCompareOptions)枚舉類型的參數設置為nil,也可以運行。但一般不這么做,這里只是為了觀察不指定該枚舉參數時候系統的默認設置,并與本文接下來指定該枚舉參數的排序結果對比。
  • 可以發現:
    • 默認同一字符的全角字符看做半角字符。不區分同一個字符(如日文的片假字)的半角與全角狀態。相同元素,維持原序。
    • 默認區分字母大小寫,同一個字符小寫在前,大寫在后。
    • 字母并非按unicode碼的大小升序排列。例如,全角a的unicode為FF41,半角a的unicode為0061,半角A的unicode為0041,半角b的unicode為0062,但排序結果是 全角a = 半角a < 半角A < 半角b
    • 默認不識別含有數字字符的數值大小,0030雖然數學意義比2大,但是,僅從字符串的角度看,第一個字符0比2小,所以d0030排在d2前面。
2.1.4 知識拓展:

半角與全角字符

  • 全角占兩個字節,半角占一個字節。通常我們碰到的英文字母、數字鍵、符號鍵這種ASCII碼系統里面的字符大多數情況下是半角的。

  • 國內漢字輸入法輸入的漢字為全角,字母數字為半角,但是標點則默認為全角,可切換為半角(可以通過輸入法工具條上的相應按鈕來切換標點符號的全角半角狀態)。

  • 日文里面的有漢字,也有片假字。這個片假字有兩套編碼,同一個片假字分別有半角和全角兩種編碼。例如:看起來像一樣的片假字組成的句子,全角狀態字符開頭的為アいろは????イウエ,半角狀態?字符開頭的為?いろは???????。可以看到,明顯同一個片假字的全角狀態半角狀態 “胖”一圈。

  • 英文字母其實也有全角字母,例如小寫的a,其半角形式的unicode碼為0061,其全角形式的unicode碼為FF41。可查閱Unicode?字符百科官網。

2.2 NSStringCompareOptions

NSStringCompareOptions是一個枚舉類型,并非一個類。打開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

  • 假設,將上例中的部分代碼修改為
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);
}
  • 運行結果
image.png
  • 結論
    NSStringCompareOptions指定為NSNumericSearch,當字符串中含有數字時,從數值大小的角度按升序排序。
2.2.2 NSCaseInsensitiveSearch

官方解釋:無。英文字面解釋:不區分字母大小寫。

  • 假設,將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch; 
  • 運行結果
image.png
  • 結論
    NSStringCompareOptions指定為NSCaseInsensitiveSearch,不區分同一個字母的大小寫狀態,如aA看做相同元素,若其它條件也一致則保持原序。
2.2.3 NSLiteralSearch

官方解釋:Exact character-by-character equivalence

  • 假設,將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSLiteralSearch;
  • 運行結果
image.png
  • 結論
    • 區分 同一個字符(如日文的片假字)的半角與全角狀態,同一片假字的全角狀態小于半角狀態。
    • 其它規則,繼續按系統默認排序規則排序,包括默認區分 字母大小寫,以及其它默認排序規則。
    • 按照官方英文說明,這個規則是指區分每個字符的等效狀態。只要unicode不同的字符,就不認可他們“等效”,即使他們的語言上的含義相同。
  • 題外話
    • 所以,有的文獻說NSLiteralSearch 是區分大小寫是誤導,系統本就默認區分 字母大小寫,這些人以為蘋果公司提供這個功能來畫蛇添足干嘛?而且可以看看官方英文說明,也不是這個意思。只有指定不區分 字母大小寫的NSCaseInsensitiveSearch,要么不寫,即默認區分
2.2.4 NSWidthInsensitiveSearch

官方解釋:If specified, ignores width differences ('a' == UFF41)

  • 假設,將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch;
  • 運行結果
image.png
  • 結論
    • 不區分 同一個字符(如日文的片假字)的半角與全角狀態,同一片假字的全角狀態等于半角狀態。
    • 其它規則,繼續按系統默認排序規則排序,包括默認區分 字母大小寫,以及其它默認排序規則。
    • 同時指定兩個時,NSWidthInsensitiveSearchNSLiteralSearch 的優先級高,綜合起來的結果是不區分 半角全角。
    • 官方英文說明中的UFF41是指全角a'a' 是指半角a,如果指定NSWidthInsensitiveSearch,則不區分字符的全角半角,即使你同時指定了NSLiteralSearch

即,當有如下代碼

NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch | NSLiteralSearch;

其作用相當于沒有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)

  • 假設,將上例中的部分代碼修改為
NSStringCompareOptions comparisonOptions = NSForcedOrderingSearch;
  • 運行結果
image.png
  • 結論
    • 不存在字符等不等效相不相等的概念了,只要unicode不一樣的字符,必須區分,必須返回一個誰大誰小的結果(NSOrderedAscending or NSOrderedDescending)。
    • 從英文說明也可以看出,NSForcedOrderingSearch 的優先級最高,即如果你同時指定了其它有可能作用沖突的枚舉類型,也以NSForcedOrderingSearch 的作用為準。
2.2.6 綜合應用
  • 一個比較多的應用示例是,區分字母大小寫,區分數值大小,區分半角全角,并強制性指定區分unicode不一樣的字符。綜合這些條件,寫起來就是:
NSStringCompareOptions comparisonOptions = NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
  • 運行結果
image.png
2.2.7 誤導用法
  • 我看過有很多其它博客用了這樣的誤導示例:
NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch|NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;

這里面,NSCaseInsensitiveSearch是為了不區分大小寫字母,但是后面再加個NSForcedOrderingSearch想強制區分字符又是怎么回事?雖然,這樣寫并不會報錯,運行效果跟上面的綜合示例一摸一樣。但這樣誤導的想法是個邏輯矛盾。不信,你看看它運行的結果:

image.png

3. 數組里面是類的對象


需求:假設我們根據后臺返回的JSON字典數組用MJExtension轉換成模型數組,現在我們需要根據ID或者Age對模型數組進行排序。

  • 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  
  • 根據int類型的屬性對模型數組進行排序
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;//相等  
    }  
      
}];
  • 根據str類型的屬性對模型數組進行排序
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的高級用法中,經常需要查看系統類或者某個自定義類中的私有屬性以及私有成員變量,并通過KVC的辦法強制修改這些私有成員變量的值,以取代系統或者自定義類中的默認設置。所以,如果你懶得創建一些假數據的數組,可以想到運用運行時的辦法獲取成員變量的數組,并進行排序操作訓練。

題1. 請取出NSString類的全部公有 屬性 并存放到一個數組,并利用NSArraysortedArrayUsingComparator的方法給這個數組進行升序排序操作。要求:排序過程中需要區分字符全角半角狀態,其它可按系統默認條件。

  • 參考代碼:
    main.m
void handlePrintingOfProperties(void){
    unsigned int count;// 記錄屬性個數
    objc_property_t *properties = class_copyPropertyList([NSString class], &count);
    // 生成一個屬性名稱組成的數組
    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語言字符串
        const char *cName = property_getName(property);
        // 轉換為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語言中,用完copy,create的東西之后,最好釋放
    free(properties);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"handlePrintingOfProperties()**********************");
        handlePrintingOfProperties();
    }
    return 0;
}
  • 運行結果
image.png

題2. 請取出NSURL類中包括私有 在內的全部 成員變量,并存放到一個數組,并利用NSArraysortedArrayUsingComparator的方法給這個數組進行升序排序操作。要求:排序過程中需要區分字符全角半角狀態,其它可按系統默認條件。

  • 參考代碼:
void handlePrintingOfIvars(void){
    unsigned int count;// 記錄屬性個數
    Ivar *properties = class_copyIvarList([NSURL class], &count);
    // 生成一個屬性名稱組成的數組
    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語言字符串
        const char *cName = ivar_getName(property);
        // 轉換為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語言中,用完copy,create的東西之后,最好釋放
    free(properties);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"handlePrintingOfIvars()**********************");
        handlePrintingOfIvars();
    }
    return 0;
}
  • 運行結果
image.png

5. 附錄:本實驗中創建工程說明


任何能在計算機上執行的項目稱之為程序,其中,有圖形化用戶界面的程序稱之為應用 ,沒有圖形界面的程序可以是守護進程 ,還有一種稱之為命令行工具。本文這里關注的是算法和數據結果,不關注圖形界面,所以新建一個命令行工具即可。創建方法:新建一個macOS工程,選擇Command Line Tool類型,點擊下一步配置工程信息即可。

創建一個命令行工具
工程創建成功
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。