比较来自两个 NSArray 的对象的有效方法

Efficient way of comparing objects from two NSArrays

我有两个数组,一个有 1400 条记录,一个有 450 条记录。较大的数组是 'search terms' 的列表,其中包含具有相应药物 ID 的药物。第二个数组包含 'drug objects' 本身,大约 15 个字段包含有关药物的各种信息。

我需要的是一个 'search term objects' 的新数组,它实际上是搜索词数组,其中所有搜索词都已替换为药物(但搜索词已添加到对象中)。

到目前为止,我所做的是将另一个 属性 (searchTerm) 添加到我的药物对象中。我循环遍历搜索词数组,然后针对每个搜索词循环遍历药物对象数组,当我找到匹配的 DrugID 时,我创建了一个新的药物对象实例,从原始药物对象复制所有信息并填充 searchTerm来自搜索词数组。

这看起来效率不高,需要超过 600,000 次迭代才能完全填充新数组,大约需要 1 分钟。

    FormularyDBManager *formularyDBManagerInstance = [FormularyDBManager new];
    NSArray *searchTermArray = [NSMutableArray arrayWithArray:[formularyDBManagerInstance getSearchDrugs:@"*" from:@"formulary_searchname" orderBy:@"DrugName"]];

    int counter = 0; // this counter is here purely for testing

    for (NSDictionary *searchDrug in searchTermArray) {

        for (Drug *aDrug in arrayOfDrugs) { // this array is populated in a previous method

            counter ++; 
            NSLog(@"Counter = %i", counter);

            if ([searchDrug[@"DrugID"] isEqualToString:aDrug.drugID]) {

                Drug *currentDrug = [Drug new];

                currentDrug.therapeuticGroup2 = aDrug.therapeuticGroup2;
                currentDrug.use = aDrug.use;
                currentDrug.action = aDrug.action;
                currentDrug.therapeuticGroup1 = aDrug.therapeuticGroup1;
                // more properties.... then the search term is added
                currentDrug.drugName = searchDrug[@"searchTerm"];

                [arrayOfSearchDrugs addObject:currentDrug];

            }

        }

    }

    [_tableViewDrugs reloadData];

正确的做法是什么?谢谢

解决方案 根据下面纪尧姆的回答

NSArray *resultArray = [NSMutableArray arrayWithArray:[formularyDBManagerInstance getSearchDrugs:@"*" from:@"formulary_searchname" orderBy:@"DrugName"]];

DataBaseMananger *dataBaseManagerInstance = [DataBaseMananger new];

for (NSDictionary *searchDrug in resultArray) {

    // get the drug object from the new drugs dictionary where the drugID matches the current item in the returned 'search items' array
    Drug *aDrug = [dictOfDrugs objectForKey:searchDrug[@"DrugID"]];  

    Drug *currentDrug = [Drug new];
    NSMutableArray *columns = [NSMutableArray new];
    NSMutableArray *values = [NSMutableArray new];

    currentDrug.therapeuticGroup2 = aDrug.therapeuticGroup2;
    [columns addObject:@"TherapeuticGroup2"];
    [values  addObject:currentDrug.therapeuticGroup2];

    currentDrug.use = aDrug.use;
    [columns addObject:@"Use"];
    [values addObject:currentDrug.use];

    currentDrug.action = aDrug.action;
    [columns addObject:@"Action"];
    [values addObject:currentDrug.action];

    currentDrug.therapeuticGroup1 = aDrug.therapeuticGroup1;
    [columns addObject:@"TherapeuticGroup1"];
    [values addObject:currentDrug.therapeuticGroup1];

    // other properties.....

    currentDrug.searchTerm = searchDrug[@"searchTerm"];
    [columns addObject:@"searchTerm"];
    [values addObject:currentDrug.searchTerm];

    // now add the new 'search term objects' to a database table so they can be retrieved even quicker later on.
    [dataBaseManagerInstance insertToTable:@"formulary_searchDrug" setColumns:columns equals:values];
    [arrayOfSearchDrugs addObject:currentDrug];

}

[_tableViewDrugs reloadData];

更新

将数组(进入我的数据库导入方法)的初始化从循环中取出来有很大帮助,不确定为什么我真的把它们放在那里。

从相关数组中获取数据的 UITableView 的加载时间从 55 秒开始,在修改代码后下降到 21 秒,在将数组初始化从循环中取出后下降到 10 秒.

如果您必须检查两个数组的每个对象并且您期望得到不止一个结果,那么您无能为力。如果你期待一个结果,你可以在找到它时打破循环。但是,如果你从循环中删除 NSLog 东西,它肯定会提高性能和所花费的时间。

如果 arrayOfDrugs 中的所有药物都具有唯一性 drugID,那么您可以创建一个药物映射(字典),通过其 ID 索引,并放弃内部循环。

这应该使复杂度从 O(n * m) 变为 O(n + m)(nsearchTermArray 的大小,m 是arrayOfDrugs)*.

这看起来应该是这样的:

FormularyDBManager *formularyDBManagerInstance = [FormularyDBManager new];
NSArray *searchTermArray = [NSMutableArray arrayWithArray:[formularyDBManagerInstance getSearchDrugs:@"*" from:@"formulary_searchname" orderBy:@"DrugName"]];

for (NSDictionary *searchDrug in searchTermArray) {

        Drug *aDrug = dictOfDrugs[aDrug.drugID]
        Drug *currentDrug = [Drug new];

        currentDrug.therapeuticGroup2 = aDrug.therapeuticGroup2;
        currentDrug.use = aDrug.use;
        currentDrug.action = aDrug.action;
        currentDrug.therapeuticGroup1 = aDrug.therapeuticGroup1;
        // more properties.... then the search term is added
        currentDrug.drugName = searchDrug[@"searchTerm"];

        [arrayOfSearchDrugs addObject:currentDrug];
    }
}

当然不要忘记将创建 arrayOfDrugs 数组的方法更改为 return 字典。

* 不要相信我的话,自从我上次尝试正式表达我的代码复杂性以来已经有好几年了。

您可以使用 containsObject 方法

例如:

for (NSDictionary *checkData1 in  objects) {

      if (![objects2 containsObject:checkData1]) {

     }
}

像这样....

这里只是一个想法,不确定它是否真的会有所改进,但是如果您循环较小的数组并使用 nspredicate 为您需要的药物 ID 过滤较大的数组呢

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"propertyName == %@", @"value"];
NSArray *filteredArray = [myArray filteredArrayUsingPredicate:predicate];

在所有情况下,不同类型对象的匹配可以减少为同一时间某个对象的相等性(例如在您的情况下,成员 drugID 和键 "DrugID" 的字典值),它是最快将一组项目放入以该对象为键的临时字典中。

您可以通过 NS(Mutable)Set 的集合算法获得 O(n), n = number of drugs

我的示例会将搜索词映射到字典中包含该搜索词的药物列表。

她的主要操作是将一组与另一组相交。

让我们开始吧:

您没有为药物提供 class,所以我创建了自己的。
由于我对药物一无所知,所以我只是使用随机数作为属性。我将通过一种方法使这些可访问,该方法将 return 一组来执行交集。

@interface Drug : NSObject
@property (nonatomic, strong) NSNumber *f1;
@property (nonatomic, strong) NSNumber *f2;
@property (nonatomic, strong) NSNumber *f3;
@property (nonatomic, strong) NSNumber *f4;
@property (nonatomic, strong) NSNumber *f5;
@property (nonatomic, strong) NSNumber *f6;
@property (nonatomic, strong) NSNumber *f7;
@property (nonatomic, strong) NSNumber *f8;
@property (nonatomic, strong) NSNumber *f9;
@property (nonatomic, strong) NSNumber *f10;
@property (nonatomic, strong) NSNumber *f11;
@property (nonatomic, strong) NSNumber *f12;
@property (nonatomic, strong) NSNumber *f13;
@property (nonatomic, strong) NSNumber *f14;
@property (nonatomic, strong) NSNumber *f15;

@property (nonatomic, strong, readonly) NSSet *drugProperties;
@end


static unsigned int numberOfSearchTerms = 14000;
static unsigned int numberOfDrugs = 450;
@implementation Drug

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.f1 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f2 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f3 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f4 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f5 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f6 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f7 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f8 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f9 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f10 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f11 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f12 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f13 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f14 = @(arc4random_uniform(numberOfSearchTerms) +1);
        self.f15 = @(arc4random_uniform(numberOfSearchTerms) +1);
    }
    return self;
}

-(NSSet *)drugProperties
{
    return [NSSet setWithArray:@[_f1, _f2, _f3, _f4, _f5, _f6, _f7,_f8, _f9, _f10, _f11, _f12, _f13, _f14, _f15]];
}

@end

我的示例中的搜索词也是数字。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    for (int x = 0; x<5; ++x) {
        NSMutableDictionary *searchTermDrugMapping =[@{} mutableCopy];
        NSMutableSet *searchTerms = [NSMutableSet set];

        for(NSUInteger i = 1; i< numberOfSearchTerms+1; ++i){
            //                [searchTerms addObject:[[SeachrTermSetWrapper alloc] initWithSearchTerm:@(i)]];
            [searchTerms addObject:@(i)];
            searchTermDrugMapping[@(i)] = [@[] mutableCopy];

        }

        NSMutableArray *drugs = [@[] mutableCopy];
        for (NSUInteger i = 0; i< (numberOfDrugs << x) ; ++i) {
            [drugs addObject: [[Drug alloc] init]];
        }

        NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
        for (Drug *d in drugs) {
            NSMutableSet *dp = [d.drugProperties mutableCopy];
            [dp intersectSet:searchTerms];
            [dp enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
                [searchTermDrugMapping[obj] addObject:d];
            }];
        }
        NSTimeInterval stop = [NSDate timeIntervalSinceReferenceDate];
        //NSLog(@"%@", searchTermDrugMapping);

        NSLog(@"%u \t\t%f",(numberOfDrugs << x), stop -start);
    }


    return YES;
}

输出(药物数量,以秒为单位的时间)iOS 8.1 iPhone 5s

450         0.015040
900         0.027976
1800        0.057547
3600        0.117761
7200        0.235752

药物数量加倍会使时间加倍 -> 线性行为 -> O(n)

这很容易。但它可能会变得更复杂。要进行这样的集合操作,您需要不同集合中的对象相等。 NSHipster has a great article about it。但是我有什么不同种类的对象。这很简单:写 类 来包装这些对象并让它们平等地接受彼此。将您的真实对象包裹在它们的实例中并将它们放入集合中。