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

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

前面的話

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

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

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

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
複製代碼

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

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

問題來了,怎麼設置?express

  • 爲了設置這個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 ,會發現不少文章中的翻譯或者中文解釋在誤導,或者很難看清什麼意思?例以下面這篇博客:macos

image.png

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

image.png

因而,筆者決定寫此本文,好好展現他們的用途。bash

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,在本文的「前面的話」中已經介紹過,這裏從新列一下定義。app

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類型,點擊下一步配置工程信息便可。

建立一個命令行工具

工程建立成功
相關文章
相關標籤/搜索