您的示例可以通过使用 max_by 来简化,以产生类似的结果:
10.times{|y| p x.max_by(y) {|t| t%2}}
我花了一些时间研究源代码,但找不到任何漏洞。
当我记得看到一份名为Switch: A Deep Embedding of Queries into Ruby(曼努埃尔·迈尔的论文)我找到了答案。
在第 104 页您可以找到以下问题的答案max_by
:
...这里,输入列表中的值假设最大值
返回由函数计算的值。如果产生多个值
最大值,在这些值中选择结果是任意的。
...
同样对于:
sort
& sort_by
来自评论@emu.c
不保证结果稳定。当两个键相等时,
相应元素的顺序是不可预测的。
第一、第二次编辑- “我们需要更深入” => 我希望你会喜欢这个“旅程”。
简短的回答:
排序看起来像这样的原因是 max_by 块的组合(导致开始排序max
值来自%2
这是1
然后继续0
)和 qsort_r (BSD 快速排序)实现了 @ruby。
长答案:全部基于 ruby 2.4.2 或当前 2.5.0(正在开发)的源代码。
快速排序算法可能因您使用的编译器而异。您可以使用 qsort_r:GNU 版本、BSD 版本(您可以查看配置文件)了解更多。 Visual Studio 使用 2012 年或更高版本的 BSD 版本。
+Tue Sep 15 12:44:32 2015 Nobuyoshi Nakada <[email protected]>
+
+ * util.c (ruby_qsort): use BSD-style qsort_r if available.
Thu May 12 00:18:19 2016 NAKAMURA Usaku <[email protected]>
* win32/Makefile.sub (HAVE_QSORT_S): use qsort_s only for Visual Studio
2012 or later, because VS2010 seems to causes a SEGV in
test/ruby/test_enum.rb.
-
如果您有 GNU qsort_r 但没有 BSD:
仅使用内部 ruby_qsort 实现。查看util.c用于快速排序的内部实现(ruby_qsort(void* base, const size_t nel, const size_t size, cmpfunc_t *cmp, void *d)
)由 Tomoyuki Kawamura 函数。@util.h
如果 HAVE_GNU_QSORT_R=1 那么#define ruby_qsort qsort_r
:
#ifdef HAVE_GNU_QSORT_R
#define ruby_qsort qsort_r
#else void ruby_qsort(void *, const size_t, const size_t,
int (*)(const void *, const void *, void *), void *);
#endif
如果检测到 BSD 风格:
然后使用下面的代码(可以在util.c)。请注意如何cmp_bsd_qsort
之前被调用ruby_qsort
。原因?可能是标准化、堆栈空间,也可能是速度(我自己没有测试过——必须创建基准测试,这非常耗时)。
BSD qsort.c 源代码中指出了节省堆栈空间:
/*
* To save stack space we sort the smaller side of the partition first
* using recursion and eliminate tail recursion for the larger side.
*/
ruby 源代码中的 BSD 分支:
#if defined HAVE_BSD_QSORT_R
typedef int (cmpfunc_t)(const void*, const void*, void*);
struct bsd_qsort_r_args {
cmpfunc_t *cmp;
void *arg;
};
static int
cmp_bsd_qsort(void *d, const void *a, const void *b)
{
const struct bsd_qsort_r_args *args = d;
return (*args->cmp)(a, b, args->arg);
}
void
ruby_qsort(void* base, const size_t nel, const size_t size, cmpfunc_t *cmp, void *d)
{
struct bsd_qsort_r_args args;
args.cmp = cmp;
args.arg = d;
qsort_r(base, nel, size, &args, cmp_bsd_qsort);
}
如果您使用 MSYS2 在 Windows 上编译 ruby(不再是 DevKit,而是用于 Windows 安装程序的 MSYS2,我大部分时间都在使用它)NetBSD 版本的 qsort_r(从 02-07-2012 开始)。最新的NetBSDqsort.c(修订版:1.23).
现在来看现实生活中的例子- “我们需要更深入”
测试将在两个(Windows)红宝石上进行:
代码:
x=[1,2,3,4,5,6,7,8,9]
10.times{|y| p x.max_by(y) {|t| t%2}}
Ruby 2.2.2p95
:
The result:
[]
[5]
[7, 1]
[3, 1, 5]
[7, 3, 1, 5]
[9, 7, 3, 1, 5]
[5, 9, 1, 3, 7, 6]
[5, 1, 9, 3, 7, 6, 4]
[5, 1, 3, 7, 9, 6, 4, 2]
[9, 1, 7, 3, 5, 4, 6, 8, 2]
Ruby 2.4.2-p198
:
The result:
[]
[1]
[7, 1]
[5, 3, 1]
[5, 7, 3, 1]
[5, 9, 7, 3, 1]
[5, 1, 9, 7, 3, 6]
[5, 1, 3, 9, 7, 4, 6]
[5, 1, 3, 7, 9, 2, 6, 4]
[9, 1, 3, 7, 5, 8, 4, 6, 2]
现在对于不同的x
:
x=[7,9,3,4,2,6,1,8,5]
Ruby 2.2.2p95
:
The result:
[]
[1]
[9, 7]
[1, 7, 3]
[5, 1, 7, 3]
[5, 1, 3, 9, 7]
[7, 5, 9, 3, 1, 2]
[7, 9, 5, 3, 1, 2, 4]
[7, 9, 3, 1, 5, 2, 4, 8]
[5, 9, 1, 3, 7, 4, 6, 8, 2]
Ruby 2.4.2-p198
:
The result:
[]
[9]
[9, 7]
[3, 1, 7]
[3, 5, 1, 7]
[7, 5, 1, 3, 9]
[7, 9, 5, 1, 3, 2]
[7, 9, 3, 5, 1, 4, 2]
[7, 9, 3, 1, 5, 8, 2, 4]
[5, 9, 3, 1, 7, 2, 4, 6, 8]
现在对于源数组中的相同项目(qsort 不稳定,请参见下文):x=[1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9]
使用以下代码对其进行处理:12.times{|y| p x.max_by(y) {|t| t%2}}
Ruby 2.2.2p95
:
The result:
[]
[3]
[1, 1]
[9, 1, 7]
[3, 9, 1, 7]
[5, 3, 9, 1, 7]
[1, 5, 3, 9, 1, 7]
[5, 9, 3, 7, 1, 1, 1]
[1, 5, 9, 1, 7, 1, 3, 4]
[1, 1, 5, 9, 1, 7, 3, 4, 2]
[1, 1, 1, 5, 7, 3, 9, 4, 2, 8]
[9, 1, 7, 1, 5, 3, 1, 2, 6, 8, 4]
Ruby 2.4.2-p198
:
The Result:
[]
[1]
[1, 1]
[7, 9, 1]
[7, 3, 9, 1]
[7, 5, 3, 9, 1]
[7, 1, 5, 3, 9, 1]
[1, 5, 9, 3, 7, 1, 1]
[1, 1, 5, 9, 3, 7, 1, 4]
[1, 1, 1, 5, 9, 3, 7, 2, 4]
[1, 7, 3, 1, 5, 9, 1, 2, 4, 8]
[9, 3, 1, 7, 1, 5, 1, 2, 8, 6, 4]
现在问一个大问题--> 为什么结果不同呢?
第一个明显的答案是,当使用 GNU 或 BSD 实现时,结果会有所不同吗?正确的?好吧,实现是不同的,但产生(检查链接的实现以了解详细信息)相同的结果。问题的核心在别处。
这里真正的问题是算法本身。当使用快速排序时,您得到的是不稳定的排序(当您比较两个相等的值时,它们的顺序不会保持不变)。当你有了 [1,2,3,4,5,6,7,8,9] 后,你可以在块中将其转换为 [1,0,1,0,1,0,1,0,1]使用 max(_by) 将数组排序为 [1,1,1,1,1,0,0,0,0]。你从 1 开始,但是哪一个呢?那么你会得到不可预测的结果。 (max(_by) 是先获取奇数然后获取偶数的原因)。
See GNU qsort评论:
警告:如果两个对象比较相等,则排序后它们的顺序是
不可预料的。也就是说排序并不稳定。这个可以
当比较仅考虑部分时会产生差异
元素。具有相同排序键的两个元素可能在其他方面有所不同
尊重。
现在像引擎一样对其进行排序:
[1,2,3,4,5,6,7,8,9]
-> 首先考虑的是奇数[1,3,5,7,9]
那些被认为等于max_by{|t| t%2}
产生[1,1,1,1,1]
.
结论:
现在该选择哪一个呢?好吧,就你的情况而言,这是不可预测的,那就是你得到的。即使对于与底层相同的 ruby 版本,我也会得到不同的版本快速排序算法本质上是不稳定的。