And*_*imm 8 c ruby yarv array-merge
在Ruby中 -有人说,优雅地比较两个枚举器
zip的问题在于它在内部创建数组,无论你通过什么Enumerable.输入参数的长度还有另一个问题
我看了一下YARV中Enumerable#zip的实现,并看到了
static VALUE
enum_zip(int argc, VALUE *argv, VALUE obj)
{
int i;
ID conv;
NODE *memo;
VALUE result = Qnil;
VALUE args = rb_ary_new4(argc, argv);
int allary = TRUE;
argv = RARRAY_PTR(args);
for (i=0; i<argc; i++) {
VALUE ary = rb_check_array_type(argv[i]);
if (NIL_P(ary)) {
allary = FALSE;
break;
}
argv[i] = ary;
}
if (!allary) {
CONST_ID(conv, "to_enum");
for (i=0; i<argc; i++) {
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
}
}
if (!rb_block_given_p()) {
result = rb_ary_new();
}
/* use NODE_DOT2 as memo(v, v, -) */
memo = rb_node_newnode(NODE_DOT2, result, args, 0);
rb_block_call(obj, id_each, 0, 0, allary ? zip_ary : zip_i, (VALUE)memo);
return result;
}
Run Code Online (Sandbox Code Playgroud)
我能正确理解以下位吗?
检查是否所有参数都是数组,如果是,则使用直接引用替换对该数组的某些间接引用
for (i=0; i<argc; i++) {
VALUE ary = rb_check_array_type(argv[i]);
if (NIL_P(ary)) {
allary = FALSE;
break;
}
argv[i] = ary;
}
Run Code Online (Sandbox Code Playgroud)
如果它们不是所有数组,请改为创建枚举器
if (!allary) {
CONST_ID(conv, "to_enum");
for (i=0; i<argc; i++) {
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
}
}
Run Code Online (Sandbox Code Playgroud)
仅在未给出块时才创建数组数组
if (!rb_block_given_p()) {
result = rb_ary_new();
}
Run Code Online (Sandbox Code Playgroud)
如果一切都是数组,请使用zip_ary,否则使用zip_i,并在每组值上调用一个块
/* use NODE_DOT2 as memo(v, v, -) */
memo = rb_node_newnode(NODE_DOT2, result, args, 0);
rb_block_call(obj, id_each, 0, 0, allary ? zip_ary : zip_i, (VALUE)memo);
Run Code Online (Sandbox Code Playgroud)
如果没有给出块,则返回一个数组数组,否则返回nil(Qnil)?
return result;
}
Run Code Online (Sandbox Code Playgroud)
我将使用1.9.2-p0,就像我现有的那样.
该rb_check_array_type函数如下所示:
VALUE
rb_check_array_type(VALUE ary)
{
return rb_check_convert_type(ary, T_ARRAY, "Array", "to_ary");
}
Run Code Online (Sandbox Code Playgroud)
而且rb_check_convert_type看起来是这样的:
VALUE
rb_check_convert_type(VALUE val, int type, const char *tname, const char *method)
{
VALUE v;
/* always convert T_DATA */
if (TYPE(val) == type && type != T_DATA) return val;
v = convert_type(val, tname, method, FALSE);
if (NIL_P(v)) return Qnil;
if (TYPE(v) != type) {
const char *cname = rb_obj_classname(val);
rb_raise(rb_eTypeError, "can't convert %s to %s (%s#%s gives %s)",
cname, tname, cname, method, rb_obj_classname(v));
}
return v;
}
Run Code Online (Sandbox Code Playgroud)
记下convert_type电话.这看起来很像C版的Array.try_convert和try_convert恰好是这样的:
/*
* call-seq:
* Array.try_convert(obj) -> array or nil
*
* Try to convert <i>obj</i> into an array, using +to_ary+ method.
* Returns converted array or +nil+ if <i>obj</i> cannot be converted
* for any reason. This method can be used to check if an argument is an
* array.
*
* Array.try_convert([1]) #=> [1]
* Array.try_convert("1") #=> nil
*
* if tmp = Array.try_convert(arg)
* # the argument is an array
* elsif tmp = String.try_convert(arg)
* # the argument is a string
* end
*
*/
static VALUE
rb_ary_s_try_convert(VALUE dummy, VALUE ary)
{
return rb_check_array_type(ary);
}
Run Code Online (Sandbox Code Playgroud)
所以,是的,第一个循环是寻找argv不是数组的任何东西,allary如果它找到这样的东西就设置标志.
在enum.c,我们看到这个:
id_each = rb_intern("each");
Run Code Online (Sandbox Code Playgroud)
这id_each是Ruby each迭代器方法的内部引用.在vm_eval.c,我们有这个:
/*!
* Calls a method
* \param recv receiver of the method
* \param mid an ID that represents the name of the method
* \param n the number of arguments
* \param ... arbitrary number of method arguments
*
* \pre each of arguments after \a n must be a VALUE.
*/
VALUE
rb_funcall(VALUE recv, ID mid, int n, ...)
Run Code Online (Sandbox Code Playgroud)
所以这:
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
Run Code Online (Sandbox Code Playgroud)
正在调用to_enum(基本上是默认参数)argv[i].
因此,第一个for和if块的最终结果argv是要么充满了数组,要么充满了枚举数,而不是两者的混合.但请注意逻辑是如何工作的:如果找到的东西不是数组,那么一切都变成了枚举器.enum_zip函数的第一部分将数组包装在枚举器中(基本上是免费的或至少足够便宜而不用担心)但不会将枚举器扩展为数组(这可能非常昂贵).早期的版本可能已经走了另一条路(更喜欢数组而不是枚举数),我会将其作为读者或历史学家的练习.
下一部分:
if (!rb_block_given_p()) {
result = rb_ary_new();
}
Run Code Online (Sandbox Code Playgroud)
创建一个新的空数组并使其处于result当zip被称为没有一个块.在这里我们应该注意到什么zip回报:
enum.zip(arg, ...) ? an_array_of_array
enum.zip(arg, ...) {|arr| block } ? nil
Run Code Online (Sandbox Code Playgroud)
如果有一个区块,则没有任何东西可以返回并且result可以保持为Qnil; 如果没有块,那么我们需要一个数组,result以便可以返回一个数组.
从parse.c,我们看到这NODE_DOT2是一个双点范围,但看起来他们只是将新节点用作一个简单的三元素结构; rb_new_node只需分配一个对象,设置一些位,并在结构中分配三个值:
NODE*
rb_node_newnode(enum node_type type, VALUE a0, VALUE a1, VALUE a2)
{
NODE *n = (NODE*)rb_newobj();
n->flags |= T_NODE;
nd_set_type(n, type);
n->u1.value = a0;
n->u2.value = a1;
n->u3.value = a2;
return n;
}
Run Code Online (Sandbox Code Playgroud)
nd_set_type只是有点摆弄宏.现在我们memo只有一个三元素结构.这种使用NODE_DOT2似乎是一个方便的kludge.
该rb_block_call函数似乎是核心内部迭代器.我们id_each再次看到我们的朋友,所以我们将进行each迭代.然后我们看到zip_i和之间的选择zip_ary; 这是创建内部数组并将其推送到的位置result.之间唯一的区别zip_i,并zip_ary似乎在StopIteration异常处理zip_i.
此时我们已经完成了压缩,我们要么拥有数组数组result(如果没有阻塞),要么我们Qnil进入result(如果有阻塞).
执行摘要:第一个循环明确避免将枚举数扩展到数组中.如果必须将数组数组构建为返回值,则zip_i和zip_ary调用仅适用于非临时数组.因此,如果您zip使用至少一个非数组枚举器调用并使用块形式,那么它一直是枚举器,并且"zip的问题在于它在内部创建数组"不会发生.回顾1.8或其他Ruby实现是留给读者的练习.