核心数据——高效地查找或创建

Posted

技术标签:

【中文标题】核心数据——高效地查找或创建【英文标题】:Core Data — Find-or-Create Efficiently 【发布时间】:2013-10-15 23:05:10 【问题描述】:

根据 Apple 的文档 (link)——

在很多情况下,您可能需要查找现有对象 (已保存在商店中的对象)用于一组离散输入值。 一个简单的解决方案是创建一个循环,然后依次为每个值 执行一次 fetch 判断是否有匹配的持久化 对象等等。这种模式不能很好地扩展。如果你配置文件 您的应用程序使用这种模式,您通常会发现 fetch 是 循环中更昂贵的操作之一(与刚刚相比 迭代项目集合)。更糟糕的是,这种模式转向 将O(n) 问题转化为O(n^2) 问题。

尽可能高效地创建所有托管 单遍中的对象,然后修复任何关系 第二关。例如,如果您导入的数据您知道不会 包含任何重复项(比如因为您的初始数据集为空), 您可以只创建托管对象来表示您的数据,而不是这样做 任何搜索。或者,如果您导入“平面”数据而没有 关系,您可以为整个集合创建托管对象,并 在使用单个大 IN 保存之前清除(删除)任何重复项 谓词。

问题一:考虑到我导入的数据没有任何关系,我该如何实现最后一行的描述。

如果您确实需要遵循“查找或创建”模式 - 比如说因为您是 导入关系信息混杂的异构数据 包含属性信息——您可以优化如何找到现有的 通过将您执行的提取次数减少到最低限度来创建对象。 如何做到这一点取决于您的参考数据量 必须合作。如果您要导入 100 个潜在的新对象,并且 您的数据库中只有 2000 个,获取所有现有的和 缓存它们可能不会造成重大损失(特别是如果 您必须多次执行该操作)。但是,如果你 在你的数据库中有 100,000 个项目,保持的内存压力 那些缓存的可能会让人望而却步。

您可以结合使用 IN 谓词和排序来减少 你对 Core Data 的使用到一个单独的 fetch 请求。

示例代码:

// Get the names to parse in sorted order.
NSArray *employeeIDs = [[listOfIDsAsString componentsSeparatedByString:@"\n"]
        sortedArrayUsingSelector: @selector(compare:)];

// create the fetch request to get all Employees matching the IDs
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:
        [NSEntityDescription entityForName:@"Employee" inManagedObjectContext:aMOC]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(employeeID IN %@)", employeeIDs]];

// Make sure the results are sorted as well.
[fetchRequest setSortDescriptors:
    @[ [[NSSortDescriptor alloc] initWithKey: @"employeeID" ascending:YES] ]];
// Execute the fetch.
NSError *error;
NSArray *employeesMatchingNames = [aMOC executeFetchRequest:fetchRequest error:&error];

您最终得到两个排序的数组 - 一个传递了员工 ID 进入 fetch 请求,一个与匹配的托管对象 他们。要处理它们,请按照这些排序列表 步骤:

获取下一个 ID 和 Employee。如果 ID 与员工 ID 不匹配, 为该 ID 创建一个新员工。获取下一个员工:如果 ID 匹配,移动到下一个 ID 和 Employee。

问题 2: 在上面的示例中,我得到了两个如上所述的排序数组。考虑到所有要插入的对象都存在于商店中的最坏情况,我无论如何都看不到我可以在O(n) 时间内解决问题。 Apple 描述了上述两个步骤,但这是一个O(n^2) 工作。对于输入数组中的任何kth 元素,在输出数组的第一个k 元素中可能存在也可能不存在与其匹配的元素。所以在最坏的情况下,复杂度将是O(nC2) = O(n^2)

所以,我相信 Apple 正在做的是确保 fetch 只处理一次,即使需要进行 O(n^2) 检查。如果是这样,那么我会这样做;但是有没有其他方法可以有效地做到这一点。

请理解,我不想一次又一次地获取 - 为大小为 100 个标识符的输入数组获取一次。

【问题讨论】:

【参考方案1】:

广告。 1 建立关系的事实在这里并不重要。这个解释只是说如果你从例如下载你的数据。远程服务器和您的项目有一些 ID,然后您可以在一个请求中从持久存储中获取它们,而不是在单独的请求中获取每个对象。

广告。 2

Apple 描述了上述两个步骤,但这是一个 O(n^2) 的工作。

不是。请仔细阅读以下几行:

要处理它们,请按照以下步骤遍历排序列表:

获取下一个 ID 和 Employee。如果 ID 与员工 ID 不匹配, 为该 ID 创建一个新员工。获取下一个员工:如果 ID 匹配,移动到下一个 ID 和 Employee。

您同时遍历数组/列表,因此您永远不必进行以下检查:“在输出数组中的前 k 个元素中可能存在也可能不存在与其匹配的元素。”您无需检查之前的元素,因为它们已排序,它们肯定不会包含您感兴趣的对象。

【讨论】:

好的,我明白了,我错了。它基本上是一个 O(n) 过程。但这仍然是第 1 部分。如何实现这一点 - > 或者如果您导入没有关系的“平面”数据,您可以为整个集合创建托管对象并在使用单个大 IN 保存之前清除(删除)任何重复项谓词。 @flippex17:我认为他们的意思是这样。您将托管对象插入到上下文中。然后您获取这些对象的 ID(或其他标识符),将它们放入 IN 谓词并从持久存储中获取对象数组(您刚刚插入的对象不会被获取,因为它们还没有保存)。然后您遍历NSManagedObjectContextinsertedObjects 并删除在持久存储中找到的对象。这基本上是我在回答中写的,只是以不同的顺序实现。 @flippex17:它不适合我之前的评论:您还必须将数组 (fetch array of the objects) 转换为由您的 IDs 索引的 NSDictionary 以获得摊销 O(1 ) 查找。 当你说 fetch array 时,你的意思是这样的: NSArray *matches = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];我不是专业人士,但我只是想知道如何将其转换为由给定testID 的 ID 索引的 NSDictionary 是ATest 类型的托管对象的字段之一。抱歉,这是一个简单的问题,但我无法解决。 @flippex17:是的,这就是我所说的fetching an array 的意思。你可以像这样得到NSDictionaryNSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init]; for (ATest *test in matches) mutableDict[test.testID] = test; 【参考方案2】:

如果有人正在寻找原始 Apple 文档,这里有快照:

http://web.archive.org/web/20150908024050/https://developer.apple.com/library/mac/documentation/cocoa/conceptual/coredata/articles/cdimporting.html

【讨论】:

以上是关于核心数据——高效地查找或创建的主要内容,如果未能解决你的问题,请参考以下文章

高效地在 OpenCV Mat 中查找元素

原创 | 基于红黑树的高效IP归属地查询方案

如何使用 Python 基于交易数据高效地创建用户图?

高效的 SQL 查询或索引来查找所有列是不是只有 1 个值

Spark:对 RDD 中的高效批量查找

需要一个高效的内存缓存,每秒可以处理 4k 到 7k 的查找或写入