ruby Enumerable / Array first(n)
和take(n)
之间的区别是什么?
我模糊地回忆起take
与懒惰评估有关,但我无法弄清楚如何使用它来做到这一点,并且找不到任何有用的谷歌搜索或文档。 "取"是google的硬方法名称。
first(n)
和take(n)
documented完全相同,不太有帮助。
first → obj or nil
first(n) → an_array
Returns the first element, or the first n elements, of the enumerable. If the enumerable is empty, the first form returns nil, and the second form returns an empty array.
-
take(n) → array
Returns first n elements from enum.
告诉我"采取与懒惰评估有关的事情"还不够,我有点记得,我需要一个如何使用它的例子,与first
相比。
答案 0 :(得分:5)
好吧,我看过源代码(Ruby 2.1.5)。 引擎,如果为first
提供了参数,则会将其转发到take
。否则,它返回一个值:
static VALUE
enum_first(int argc, VALUE *argv, VALUE obj)
{
NODE *memo;
rb_check_arity(argc, 0, 1);
if (argc > 0) {
return enum_take(obj, argv[0]);
}
else {
memo = NEW_MEMO(Qnil, 0, 0);
rb_block_call(obj, id_each, 0, 0, first_i, (VALUE)memo);
return memo->u1.value;
}
}
另一方面, take
需要一个参数并始终返回给定大小或更小的数组,其中包含从头开始的元素。
static VALUE
enum_take(VALUE obj, VALUE n)
{
NODE *memo;
VALUE result;
long len = NUM2LONG(n);
if (len < 0) {
rb_raise(rb_eArgError, "attempt to take negative size");
}
if (len == 0) return rb_ary_new2(0);
result = rb_ary_new2(len);
memo = NEW_MEMO(result, 0, len);
rb_block_call(obj, id_each, 0, 0, take_i, (VALUE)memo);
return result;
}
是的,这就是为什么这两者如此相似的原因。唯一的区别似乎是,first
可以不带参数调用,不会输出数组,而是输出单值。另一方面,<...>.first(1)
相当于<...>.take(1)
。就这么简单。
然而,对于懒惰的集合,情况有所不同。懒惰集合中的first
仍为enum_first
,如上所示,它是enum_take
的快捷方式。但是,take
是C编码lazy_take
:
static VALUE
lazy_take(VALUE obj, VALUE n)
{
long len = NUM2LONG(n);
VALUE lazy;
if (len < 0) {
rb_raise(rb_eArgError, "attempt to take negative size");
}
if (len == 0) {
VALUE len = INT2FIX(0);
lazy = lazy_to_enum_i(obj, sym_cycle, 1, &len, 0);
}
else {
lazy = rb_block_call(rb_cLazy, id_new, 1, &obj,
lazy_take_func, n);
}
return lazy_set_method(lazy, rb_ary_new3(1, n), lazy_take_size);
}
...没有立即评估,需要.force
来电。{/ p>
事实上,它已暗示in the docs under lazy
,它列出了所有懒惰实施的方法。该列表包含take
,但不包含first
。也就是说,懒惰序列take
保持懒惰而first
不会。
以下是一个示例,它们的工作方式不同:
lz = (1..Float::INFINITY).lazy.map{|i| i }
# An infinite sequence, evaluating it head-on won't do
# Ruby 2.2 also offers `.map(&:itself)`
lz.take(5)
#=> #<Enumerator::Lazy: ...>
# Well, `take` is lazy then
# Still, we need values
lz.take(5).force
#=> [1, 2, 3, 4, 5]
# Why yes, values, finally
lz.first(5)
#=> [1, 2, 3, 4, 5]
# So `first` is not lazy, it evaluates values immediately
通过在2.2之前的版本中运行并使用2.2(<...>.lazy.map(&:itself)
)的代码可以获得一些额外的乐趣,因为在你失去懒惰的那一刻,这会立即引发NoMethodError
。