簡體   English   中英

Ruby的Enumerable#zip是否在內部創建數組?

[英]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_converttry_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 (基本上是默認參數 )。

因此,第一個forif塊的最終結果是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_izip_ary之間的選擇; 這是創建內部數組並將其推送到result 之間的唯一區別zip_izip_ary似乎是StopIteration異常在處理zip_i

此時我們已經完成了壓縮,我們要么在result有數組數組(如果沒有塊),要么我們在resultQnil (如果有塊)。


執行摘要 :第一個循環明確避免將枚舉數擴展到數組中。 如果zip_izip_ary調用必須構建一個數組數組作為返回值,則它們只能用於非臨時數組。 因此,如果您使用至少一個非數組枚舉器調用zip並使用塊形式,那么它一直是枚舉器,並且“zip的問題是它在內部創建數組”不會發生。 回顧1.8或其他Ruby實現是留給讀者的練習。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM