我有一段代码如下:
sent_messages = messages.lazy.reject { |m| message_is_spam?(m) }
.each { |m| send_message(m) }
# Do something with sent_messages...
某些上下文:如果邮件的收件人在最近5分钟内收到消息,则message_is_spam?
方法返回true。当messages
包含同一收件人的多条邮件时,后一条邮件仅在发送第一条邮件后才会被视为垃圾邮件。为了确保后一条消息被视为垃圾邮件,我懒惰地拒绝垃圾邮件并发送它们。
我希望.each
返回包含所有项目的数组,但我得到nil
。 .each
总是返回一个数组,除了在这一个场景中:
[].each {} # => []
[].lazy.each {} # => []
[].select {}.each {} # => []
[].lazy.select {}.each {} # => nil
为了增加混淆,JRuby在上面的所有示例中都返回[]
。
为什么.each
在这样调用时返回nil?我在文档中找不到任何关于它的内容,很难弄清楚C代码中发生了什么。
我已经找到了彻底绕过这个问题的方法;如果我为每个收件人选择最多1封邮件(messages.uniq_by(&:recipient)
),则该操作不再需要 。尽管如此,这仍然让我感到惊讶。
答案 0 :(得分:3)
Enumerator::Lazy
的目的之一是避免在内存中有一个巨大的(或可能是无限的)数组。这可以解释为什么Enumerator#each
不返回所需的数组。
像Lazy#reject
这样的方法更倾向于返回nil
作为替代值(之后由each
返回的值),而不是冒着使用大型阵列耗尽内存的风险:
return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_reject_funcs);
相比之下,Enumerable#lazy
会返回:
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size);
我怀疑有不同的论点:
Qnil
reject
{li> sym_each
lazy
原因是:
[].lazy.each {}
返回[]
[].lazy.select{}.each {}
返回nil
。但each
返回数组或nil
似乎不一致
代码的更详细的替代方法可能是:
messages = %w(a b c)
messages_to_send = messages.lazy.reject{|x| puts "Is '#{x}' spam?"}
messages_to_send.each{ |m| puts "Send '#{m}'" }
# Is 'a' spam?
# Send 'a'
# Is 'b' spam?
# Send 'b'
# Is 'c' spam?
# Send 'c'
Lazy#reject
会返回Lazy
枚举器,因此第二个message_is_spam?
将在第一个send_message
之后执行。
但是有一个问题,在懒惰的枚举器上调用to_a
会再次调用reject
:
sent_messages = messages_to_send.to_a
# Is 'a' spam?
# Is 'b' spam?
# Is 'c' spam?
map
和修改后的方法您还可以在m
结束时返回send_message
并使用Lazy#map
:
sent_messages = messages.lazy.reject { |m| message_is_spam?(m) }
.map { |m| send_message(m) }.to_a
map
应该可靠地返回所需的Enumerator :: Lazy对象。调用Enumerable#to_a
可确保sent_messages
为数组。
map
并明确返回如果您不想修改send_message
,则可以在每次m
次迭代结束时明确返回map
:
messages = %w(a b c)
sent_messages = messages.lazy.reject{ |m| puts "Is '#{m}' spam?" }
.map{ |m| puts "Send '#{m}'"; m }.to_a
# Is 'a' spam?
# Send 'a'
# Is 'b' spam?
# Send 'b'
# Is 'c' spam?
# Send 'c'
p sent_messages
# ["a", "b", "c"]
另一种选择是在没有lazy
的情况下重新定义逻辑:
sent_messages = messages.map do |m|
next if message_is_spam?(m)
send_message(m)
m
end.compact
答案 1 :(得分:0)
如果使用.map,则返回预期结果,在这种情况下每个给出nil的原因都不清楚。
p [1,2,3].lazy.select{|x| x>1 }.map{|x| x}.to_a #=> [2, 3]
或只是
p [1,2,3].lazy.select{|x| x>1 }.to_a #=> [2, 3]
关于此主题,请阅读此http://railsware.com/blog/2012/03/13/ruby-2-0-enumerablelazy/博客