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