Ruby的Enumerable#zip是否在内部创建数组?

前端之家收集整理的这篇文章主要介绍了Ruby的Enumerable#zip是否在内部创建数组?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
据说,在 Ruby – Compare two Enumerators elegantly

The problem with zip is that it
creates arrays internally,no matter
what Enumerable you pass. There’s
another problem with length of input
params

我看了一下YARV中Enumerable#zip的实现,并看到了

  1. static VALUE
  2. enum_zip(int argc,VALUE *argv,VALUE obj)
  3. {
  4. int i;
  5. ID conv;
  6. NODE *memo;
  7. VALUE result = Qnil;
  8. VALUE args = rb_ary_new4(argc,argv);
  9. int allary = TRUE;
  10.  
  11. argv = RARRAY_PTR(args);
  12. for (i=0; i<argc; i++) {
  13. VALUE ary = rb_check_array_type(argv[i]);
  14. if (NIL_P(ary)) {
  15. allary = FALSE;
  16. break;
  17. }
  18. argv[i] = ary;
  19. }
  20. if (!allary) {
  21. CONST_ID(conv,"to_enum");
  22. for (i=0; i<argc; i++) {
  23. argv[i] = rb_funcall(argv[i],conv,1,ID2SYM(id_each));
  24. }
  25. }
  26. if (!rb_block_given_p()) {
  27. result = rb_ary_new();
  28. }
  29. /* use NODE_DOT2 as memo(v,v,-) */
  30. memo = rb_node_newnode(NODE_DOT2,result,args,0);
  31. rb_block_call(obj,id_each,allary ? zip_ary : zip_i,(VALUE)memo);
  32.  
  33. return result;
  34. }

我是否正确理解了以下位?

检查是否所有参数都是数组,如果是,则使用直接引用替换对数组的某些间接引用

  1. for (i=0; i<argc; i++) {
  2. VALUE ary = rb_check_array_type(argv[i]);
  3. if (NIL_P(ary)) {
  4. allary = FALSE;
  5. break;
  6. }
  7. argv[i] = ary;
  8. }

如果它们不是所有数组,请改为创建枚举器

  1. if (!allary) {
  2. CONST_ID(conv,ID2SYM(id_each));
  3. }
  4. }

仅在未给出块时才创建数组数组

  1. if (!rb_block_given_p()) {
  2. result = rb_ary_new();
  3. }

如果一切都是数组,请使用zip_ary,否则使用zip_i,并在每组值上调用一个块

  1. /* use NODE_DOT2 as memo(v,(VALUE)memo);

如果没有给出块,则返回一个数组数组,否则返回nil(Qnil)?

  1. return result;
  2. }

解决方法

我将使用1.9.2-p0,就像我手边的东西一样.

rb_check_array_type函数如下所示:

  1. VALUE
  2. rb_check_array_type(VALUE ary)
  3. {
  4. return rb_check_convert_type(ary,T_ARRAY,"Array","to_ary");
  5. }

并且rb_check_convert_type看起来像这样:

  1. VALUE
  2. rb_check_convert_type(VALUE val,int type,const char *tname,const char *method)
  3. {
  4. VALUE v;
  5.  
  6. /* always convert T_DATA */
  7. if (TYPE(val) == type && type != T_DATA) return val;
  8. v = convert_type(val,tname,method,FALSE);
  9. if (NIL_P(v)) return Qnil;
  10. if (TYPE(v) != type) {
  11. const char *cname = rb_obj_classname(val);
  12. rb_raise(rb_eTypeError,"can't convert %s to %s (%s#%s gives %s)",cname,rb_obj_classname(v));
  13. }
  14. return v;
  15. }

请注意convert_type调用.这看起来很像C版的Array.try_convert和try_convert恰好看起来像这样:

  1. /*
  2. * call-seq:
  3. * Array.try_convert(obj) -> array or nil
  4. *
  5. * Try to convert <i>obj</i> into an array,using +to_ary+ method.
  6. * Returns converted array or +nil+ if <i>obj</i> cannot be converted
  7. * for any reason. This method can be used to check if an argument is an
  8. * array.
  9. *
  10. * Array.try_convert([1]) #=> [1]
  11. * Array.try_convert("1") #=> nil
  12. *
  13. * if tmp = Array.try_convert(arg)
  14. * # the argument is an array
  15. * elsif tmp = String.try_convert(arg)
  16. * # the argument is a string
  17. * end
  18. *
  19. */
  20. static VALUE
  21. rb_ary_s_try_convert(VALUE dummy,VALUE ary)
  22. {
  23. return rb_check_array_type(ary);
  24. }

所以,是的,第一个循环是在argv中查找不是数组的任何东西,如果找到这样的东西,则设置allary标志.

在enum.c中,我们看到:

  1. id_each = rb_intern("each");

所以id_each是Ruby每个迭代器方法的内部引用.在vm_eval.c中,我们有:

  1. /*!
  2. * Calls a method
  3. * \param recv receiver of the method
  4. * \param mid an ID that represents the name of the method
  5. * \param n the number of arguments
  6. * \param ... arbitrary number of method arguments
  7. *
  8. * \pre each of arguments after \a n must be a VALUE.
  9. */
  10. VALUE
  11. rb_funcall(VALUE recv,ID mid,int n,...)

所以这:

  1. argv[i] = rb_funcall(argv[i],ID2SYM(id_each));

在argv [i]中的任何内容调用to_enum(基本上是default argument).

因此,第一个for和if块的最终结果是argv要么充满了数组,要么充满了枚举数,而不是两者的混合.但请注意逻辑是如何工作的:如果找到的东西不是数组,那么一切都变成了枚举器. enum_zip函数的第一部分将数组包装在枚举器中(基本上是免费的或至少足够便宜而不用担心)但不会将枚举器扩展为数组(这可能非常昂贵).早期的版本可能已经走了另一条路(更喜欢数组而不是枚举数),我将把它作为读者或历史学家的练习.

下一部分:

  1. if (!rb_block_given_p()) {
  2. result = rb_ary_new();
  3. }

如果在没有块的情况下调用zip,则创建一个新的空数组并将其保留在结果中.在这里我们应该注意zip returns

  1. enum.zip(arg,...) an_array_of_array
  2. enum.zip(arg,...) {|arr| block } nil

如果有一个区块,则没有任何东西可以返回,结果可以保持为Qnil;如果没有块,那么我们需要一个结果中的数组,以便可以返回一个数组.

从parse.c,我们看到NODE_DOT2是一个双点范围,但看起来他们只是将新节点用作一个简单的三元素结构; rb_new_node只是分配一个对象,设置一些位,并在结构中分配三个值:

  1. NODE*
  2. rb_node_newnode(enum node_type type,VALUE a0,VALUE a1,VALUE a2)
  3. {
  4. NODE *n = (NODE*)rb_newobj();
  5.  
  6. n->flags |= T_NODE;
  7. nd_set_type(n,type);
  8.  
  9. n->u1.value = a0;
  10. n->u2.value = a1;
  11. n->u3.value = a2;
  12.  
  13. return n;
  14. }

nd_set_type只是一个小小的宏.现在我们将备忘录作为三元素结构.这种NODE_DOT2的使用似乎是一种方便的方法.

rb_block_call函数似乎是核心内部迭代器.我们再次看到我们的朋友id_each,所以我们将进行每次迭代.然后我们看到zip_i和zip_ary之间的选择;这是创建内部数组并将其推送到结果的位置. zip_i和zip_ary之间的唯一区别似乎是zip_i中的StopIteration异常处理.

此时我们已经完成了压缩,我们要么在结果中有数组数组(如果没有块),要么我们在结果中有Qnil(如果有块).

执行摘要:第一个循环明确避免将枚举数扩展到数组中.如果zip_i和zip_ary调用必须构建一个数组数组作为返回值,则它们只能用于非临时数组.因此,如果您使用至少一个非数组枚举器调用zip并使用块形式,那么它一直是枚举器,并且“zip的问题是它在内部创建数组”不会发生.回顾1.8或其他Ruby实现是留给读者的练习.

猜你在找的Ruby相关文章