Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说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};
问题来了,怎么设置?
- (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.
这时候,就需要了解NSStringCompareOptions 的意思。但如果你搜索一下NSStringCompareOptions ,会发现很多文章中的翻译或者中文解释在误导,或者很难看清什么意思?例如下面这篇博客:
然后,相同的解释文案还以讹传讹的传开来了,例如你看下面这个博客:
于是,笔者决定写此本文,好好展示他们的用途。
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;
}
上面的代码中用到了NSComparator与NSComparisonResult,在本文的“前面的话”中已经介绍过,这里重新列一下定义。
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};
//
// 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;
}
如上实验代码中,有这样一行代码:
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
,也可以运行。但一般不这么做,这里只是为了观察不指定该枚举参数时候系统的默认设置,并与本文接下来指定该枚举参数的排序结果对比。全角a
的unicode为FF41,半角a
的unicode为0061,半角A
的unicode为0041,半角b
的unicode为0062,但排序结果是 全角a
= 半角a
< 半角A
< 半角b
。半角与全角字符
全角占两个字节,半角占一个字节。通常我们碰到的英文字母、数字键、符号键这种ASCII码系统里面的字符大多数情况下是半角的。
国内汉字输入法输入的汉字为全角,字母数字为半角,但是标点则默认为全角,可切换为半角(可以通过输入法工具条上的相应按钮来切换标点符号的全角半角状态)。
日文里面的有汉字,也有片假字。这个片假字有两套编码,同一个片假字分别有半角和全角两种编码。例如:看起来像一样的片假字组成的句子,全角状态ア
字符开头的为アいろはアイウエイウエ
,半角状态ア
字符开头的为アいろはアイウエイウエ
。可以看到,明显同一个片假字的全角状态 比半角状态 “胖”一圈。
英文字母其实也有全角字母,例如小写的a
,其半角形式的unicode码为0061,其全角形式的unicode码为FF41。可查阅Unicode®字符百科官网。
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 */
};
官方解释: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);
}
NSStringCompareOptions
指定为NSNumericSearch
,当字符串中含有数字时,从数值大小的角度按升序排序。官方解释:无。英文字面解释:不区分字母大小写。
NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch;
NSStringCompareOptions
指定为NSCaseInsensitiveSearch
,不区分同一个字母的大小写状态,如a
与A
看做相同元素,若其它条件也一致则保持原序。官方解释:Exact character-by-character equivalence
NSStringCompareOptions comparisonOptions = NSLiteralSearch;
结论
题外话
官方解释:If specified, ignores width differences ('a' == UFF41)
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch;
UFF41
是指全角a
,'a'
是指半角a
,如果指定NSWidthInsensitiveSearch,则不区分字符的全角半角,即使你同时指定了NSLiteralSearch。即,当有如下代码
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch | NSLiteralSearch;
其作用相当于没有NSLiteralSearch的代码
NSStringCompareOptions comparisonOptions = NSWidthInsensitiveSearch;
官方解释: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;
NSStringCompareOptions comparisonOptions = NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch|NSNumericSearch|NSWidthInsensitiveSearch|NSForcedOrderingSearch;
这里面,NSCaseInsensitiveSearch是为了不区分大小写字母,但是后面再加个NSForcedOrderingSearch想强制区分字符又是怎么回事?虽然,这样写并不会报错,运行效果跟上面的综合示例一摸一样。但这样误导的想法是个逻辑矛盾。不信,你看看它运行的结果:
需求:假设我们根据后台返回的JSON字典数组用MJExtension转换成模型数组,现在我们需要根据ID或者Age对模型数组进行排序。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,copy) NSString *ID;
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@end
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;//相等
}
}];
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;//相等
}
}];
在OC的高级用法中,经常需要查看系统类或者某个自定义类中的私有属性以及私有成员变量,并通过KVC的办法强制修改这些私有成员变量的值,以取代系统或者自定义类中的默认设置。所以,如果你懒得创建一些假数据的数组,可以想到运用运行时的办法获取成员变量的数组,并进行排序操作训练。
题1. 请取出
NSString
类的全部公有 属性 并存放到一个数组,并利用NSArray
的sortedArrayUsingComparator
的方法给这个数组进行升序排序操作。要求:排序过程中需要区分字符全角半角状态,其它可按系统默认条件。
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;
}
题2. 请取出
NSURL
类中包括私有 在内的全部 成员变量,并存放到一个数组,并利用NSArray
的sortedArrayUsingComparator
的方法给这个数组进行升序排序操作。要求:排序过程中需要区分字符全角半角状态,其它可按系统默认条件。
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;
}
任何能在计算机上执行的项目称之为程序,其中,有图形化用户界面的程序称之为应用 ,没有图形界面的程序可以是守护进程 ,还有一种称之为命令行工具。本文这里关注的是算法和数据结果,不关注图形界面,所以新建一个命令行工具即可。创建方法:新建一个macOS工程,选择Command Line Tool类型,点击下一步配置工程信息即可。
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。