【问题标题】:Is sortedArrayUsingComparator a safe way to randomize a NSArray?sortedArrayUsingComparator 是随机化 NSArray 的安全方法吗?
【发布时间】:2012-08-09 01:37:42
【问题描述】:

我之前在搞乱NSArray 函数,我想我可能遇到了随机化 NSArray 的最简单方法:

NSArray *randomize(NSArray *arr)
{
    return [arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return arc4random_uniform(3) - 1; // one of -1, 0, or 1
    }];
}

理论上,这应该彻底随机化 NSArray。然而,经过深思熟虑,我想知道这是否可能是不安全的,理论上会变成一个无限循环,具体取决于 NSArray 使用的排序算法。

我在大小为 10 - 100000 的数组上对此进行了测试,我看到了线性性能差异(大约 N * (log10(N) + 2) 每次随机化比较),这还不错。

但是,是否会出现 NSArray 理论上永远无法自行排序并导致应用程序崩溃的情况?在我看来,这不应该发生,但你永远不知道。

【问题讨论】:

  • 对于它的价值,Fisher-Yates shuffle 算法在 O(N) 时间内运行并保证一个真正均匀的随机数组作为输出。你建议的速度更快,真的很容易编码,应该让你所有的烦恼都没有了。也就是说,这是一个非常酷的问题,我肯定会玩弄它!
  • @templatetypedef 但是,它只适用于可变数组。在objective-c中,你必须有一个可变数组的开销,这可以说是相同的性能差异。
  • 啊,我不知道Objective-C。感谢您指出了这一点! (也就是说,制作一个可变数组、复制元素、Fisher-Yates 对其进行洗牌、然后将其复制回这么高的成本?)
  • @templatetypedef 在objective-c中,可以。可惜 NSArray 是一个类簇,所以我们没有具体的实现,只能做基准测试
  • @templatetypedef 我编写并分析了这个。结果如下,如果您有兴趣。

标签: algorithm sorting random comparison nsarray


【解决方案1】:

我认为这取决于底层的排序算法。

考虑一下如果底层排序是冒泡排序会发生什么。这意味着每次比较一对元素时,都有 1/3 的机会交换它们(如果比较使它们出现乱序)。因此,如果您要使用此比较函数对包含 n 个元素的数组进行排序,则算法在每一步终止的概率等于没有任何比较评估为“无序”的概率。由于每次比较都以概率 1/3 表示“无序”,这意味着算法在每次迭代中终止的概率为 (2/3)n。这意味着算法终止前的预期迭代次数为 (3/2)n = 3n / 2n。如果您尝试对一个大小合理的数组(例如,n = 1000)运行此算法,那么预期的迭代次数将是惊人的巨大; n = 1000 给出 1.233840597×10176 次预期迭代!该算法最终会终止,但预期的运行时间是如此之长,以至于从实际的角度来看它实际上是无限的。

另一方面,如果您尝试使用不同的算法,例如选择排序,则不能保证得到均匀分布。例如,考虑算法的第一遍,它将找到放置在位置 1 的元素。数组中的每个元素都应该(如果分布确实是均匀的)有 1/n 的概率被放置在第一个位置。但这种情况并非如此。请注意,第一个元素将保留在第一个位置,除非它与某些东西交换。仅当比较在第一次扫描期间的任何时候出现 +1(或 -1,取决于内部)时,才会发生这种情况。所有比较返回不同值的概率为 (2/3)n-1,与 1/n 不同。事实上,一旦您完成排序,序列中的第一个元素最终会排在最前面是天文数字。因此,即使算法将终止,也不能保证您得到均匀随机分布。

如果您尝试使用诸如快速排序、堆排序或合并排序之类的东西,那么算法最终将终止,但我不确定它是否保证是随机的。我会考虑一下这是否是统一随机的,然后更新我的答案。

希望这会有所帮助!

【讨论】:

  • 我从一些堆栈跟踪中知道,对于某些大小的数组,NSArray 使用合并排序。但是,这并不能保证,这是这种随机化方法的问题之一。因此,我专门针对 NSArray 提出了这个问题,而不是一般的数组。
  • @RichardJ.RossIII- 从我找到的文档 (developer.apple.com/library/mac/#documentation/Cocoa/Reference/…) 中,我没有看到任何迹象表明底层算法必须是合并排序。这是有保证的吗?另外,您是否介意更新您的问题以明确表示您只关心NSArray 在这种情况下,而不是更一般的问题“如果您使用随机比较器对数组进行排序会发生什么?”
【解决方案2】:

让我们假设 NSArray 使用或多或少的标准稳定合并排序算法。比较器只返回 -1 和 1 可能是最好的,因为合并排序不会将元素与其自身进行比较。

对于四元素数组 1 2 3 4,mergesort 将前半部分和后半部分随机化,然后合并。如果 L = [ab] = [1 2] 或 [2 1],并且 R = [cd] = [3 4] 或 [4 3],则合并决策树(抑制非决策)看起来像

       [a b c d]   [a c b d]
      /           /
   [a]-------[a c]-[a c d b]
  /
[]
  \   
   [c]-------[c a]-[c a b d]
      \           \
       [c d a b]   [c a d b]

[LLRR] 形式的序列(例如,[1 2 3 4]、[2 1 3 4]、[1 2 4 3]、[2 1 4 3])应该是总概率 1/6(每个1/24)但概率为 1/4。 [R R L L] 同上。 [L R L R] 形式的序列应该是总概率 1/6,但概率是 1/8。 [L R R L]、[R L L R]、[R L R L] 同上。 这不是统一的。

更重要的是,您违反了比较器给出与total order 一致的确定性答案的合同(显然隐含在我阅读的文档中,但该合同的紧密变体非常常见) .这意味着 Apple 的代码可以通过抛出异常或不终止来随意违反合同的结束。它真的会永远运行吗?可能不会,但是如果确实如此,并且您向 Apple 提交了错误报告,他们会笑着告诉您并 WONTFIX 您。我想大多数程序员都会同意他们的观点。依赖软件库的未指定方面不是一个好习惯。

【讨论】:

    【解决方案3】:

    这个问题已经解决了。 http://en.wikipedia.org/wiki/Knuth_shuffle

    templatetypedef 也对此发表了评论。

    Fisher-Yates 洗牌 mutableCopy 非常快,而且随机化效果更好。对于小型数组(10 个元素),您的建议比 Fisher-Yates shuffle 稍快,如下所示。对于大型数组(1000000 个元素),Fisher_Yates 比你的快 4 倍。如果您可以返回您制作的可变副本,那么对于 10 个元素,Fisher-Yates 也更快。

    我会选择更高级的随机播放算法,它对于小尺寸和大尺寸都很快。

    这是程序——你知道如何使用 Instruments!

    #import <Foundation/Foundation.h>
    
    static NSArray * imp_RandomizeUsingSortedArrayUsingComparator(NSArray * arr) {
        return [arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            return arc4random_uniform(3) - 1; // one of -1, 0, or 1
        }];
    }
    __attribute__((__noinline__)) static void RandomizeUsingSortedArrayUsingComparator(NSArray * arr) {
        @autoreleasepool { imp_RandomizeUsingSortedArrayUsingComparator(arr); }
    }
    
    static NSArray * imp_RandomizeUsingMutableCopy(NSArray * arr) {
        if (1 >= arr.count) {
            return [arr.copy autorelease];
        }
        NSMutableArray * cp = [arr.mutableCopy autorelease];
        u_int32_t i = (u_int32_t)cp.count;
        while (i > 1) {
            --i;
            const u_int32_t j = arc4random_uniform(i);
            [cp exchangeObjectAtIndex:i withObjectAtIndex:j];
        }
        // you may not favor creating the concrete copy
        return [cp.copy autorelease];
    }
    
    __attribute__((__noinline__)) static void RandomizeUsingMutableCopy(NSArray * arr) {
        @autoreleasepool { imp_RandomizeUsingMutableCopy(arr); }
    }
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSMutableArray * filled = [NSMutableArray array];
            for (NSUInteger i = 0; i < 1000000; ++i) {
                [filled addObject:@""];
            }
    
            NSArray * concrete = filled.copy;
            for (NSUInteger i = 0; i < 100; ++i) {
                RandomizeUsingSortedArrayUsingComparator(concrete);
                RandomizeUsingMutableCopy(concrete);
            }
            [concrete release];
        }
        return 0;
    }
    

    【讨论】:

    • 不,我不想要一个可变数组。这个想法是直接在 NSArray 上操作。
    猜你喜欢
    • 1970-01-01
    • 2010-10-21
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 2014-11-04
    相关资源
    最近更新 更多