[英]Does Ruby's Enumerable#zip create arrays internally?
在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;
}
我能正確理解以下位嗎?
檢查是否所有參數都是數組,如果是,則使用直接引用替換對該數組的某些間接引用
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();
}
如果一切都是數組,請使用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);
如果沒有給出塊,則返回一個數組數組,否則返回nil( Qnil
)?
return result;
}
我將使用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");
}
並且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;
}
請注意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);
}
所以,是的,第一個循環是在argv
中查找不是數組的任何東西,如果找到這樣的東西,則設置allary
標志。
在enum.c
,我們看到:
id_each = rb_intern("each");
所以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, ...)
所以這:
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
在argv[i]
調用to_enum
(基本上是默認參數 )。
因此,第一個for
和if
塊的最終結果是argv
要么充滿了數組,要么充滿了枚舉數,而不是兩者的混合。 但請注意邏輯是如何工作的:如果找到的東西不是數組,那么一切都變成了枚舉器。 enum_zip
函數的第一部分將數組包裝在枚舉器中(基本上是免費的或者至少足夠便宜而不用擔心)但是不會將枚舉器擴展到數組(這可能非常昂貴)。 早期的版本可能已經走了另一條路(更喜歡數組而不是枚舉數),我會將其作為讀者或歷史學家的練習。
下一部分:
if (!rb_block_given_p()) {
result = rb_ary_new();
}
如果在沒有塊的情況下調用zip
則創建一個新的空數組並將其保留在result
。 在這里我們應該注意zip
返回的內容 :
enum.zip(arg, ...) → an_array_of_array
enum.zip(arg, ...) {|arr| block } → nil
如果有一個塊,則沒有任何東西可以返回, 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;
}
nd_set_type
只是一個nd_set_type
宏。 現在我們將memo
作為一個三元素結構。 使用NODE_DOT2
似乎是一個方便的NODE_DOT2
。
rb_block_call
函數似乎是核心內部迭代器。 我們再次看到我們的朋友id_each
,所以我們將進行each
迭代。 然后我們看到zip_i
和zip_ary
之間的選擇; 這是創建內部數組並將其推送到result
。 之間的唯一區別zip_i
和zip_ary
似乎是StopIteration異常在處理zip_i
。
此時我們已經完成了壓縮,我們要么在result
有數組數組(如果沒有塊),要么我們在result
有Qnil
(如果有塊)。
執行摘要 :第一個循環明確避免將枚舉數擴展到數組中。 如果zip_i
和zip_ary
調用必須構建一個數組數組作為返回值,則它們只能用於非臨時數組。 因此,如果您使用至少一個非數組枚舉器調用zip
並使用塊形式,那么它一直是枚舉器,並且“zip的問題是它在內部創建數組”不會發生。 回顧1.8或其他Ruby實現是留給讀者的練習。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.