这是一种随机化NSArray的安全方法吗?

时间:2012-08-12 16:40:16

标签: algorithm sorting random comparison nsarray

我之前正在使用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理论上无法对自身进行排序,并导致应用程序崩溃?在我看来它不应该发生,但你永远不会知道。

3 个答案:

答案 0 :(得分:1)

我认为这取决于基础排序算法。

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

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

如果您尝试使用类似quicksort,heapsort或mergesort的东西,那么算法最终会终止,但我不确定它是否保证是随机的。我会考虑这是否是一致的随机,然后更新我的答案。

希望这有帮助!

答案 1 :(得分:0)

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

对于四元素阵列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]同上。 这不统一。

更重要的是,你违反了合同(显然隐含在我读过的文档中,但这个合同的近似变体非常普遍)比较器给出了与{{3}一致的确定性答案}}。这意味着Apple的代码可以通过抛出异常或不终止而自由地违反其合同的终止。它确实会永远运行吗?可能不是,但如果确实如此,并且你向Apple提交了一份错误报告,那么他们会笑一笑,WONTFIX你。我想大多数程序员都同意这些。依赖软件库的未指定方面并不是一个好习惯。

答案 2 :(得分:0)

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

templatetypedef也对此发表了评论。

Fisher-Yates改组mutableCopy速度快,随机化程度更高。对于小型阵列(10个元素),您的提议比Fisher-Yates shuffle略快,如下所示。对于大型阵列(1000000个元素),Fisher_Yates比您的快4倍。如果您可以返回您制作的可变副本,那么Fisher-Yates也可以更快地获得10个元素。

我会选择优质的洗牌算法,这对于小型和大型都很快。

这是程序 - 您知道如何使用仪器!

#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;
}