2015-02-10 66 views
2

我無法準確理解ruby enumerable保持多少狀態。 我知道一些python,所以我期待在我從一個枚舉中取出一個項目後,它就消失了,下一個項目將被返回,因爲我將另一個項目返回。 奇怪的是,當我使用next時會發生這種情況,但當我使用takefirst時,不會發生這種情況。 下面是一個例子:Ruby枚舉重置?

a = [1,2,3].to_enum 
# => #<Enumerator: [1, 2, 3]:each> 
a.take(2) 
# => [1, 2] 
a.next 
# => 1 
a.next 
# => 2 
a.take(2) 
# => [1, 2] 
a.next 
# => 3 
a.next 
# StopIteration: iteration reached an end 
#  from (irb):58:in `next' 
#  from (irb):58 
#  from /usr/bin/irb:12:in `<main>' 
a.take(2) 
# => [1, 2] 

這似乎是枚舉保持next調用之間的狀態,但每個take調用之前重置?

+0

請[閱讀此](https://bugs.ruby-lang.org/issues/10534) – 2015-02-10 18:22:57

+1

然後再讀一遍,,,有答案。 **請注意,+ next +不會影響其他非外部枚舉方法,除非底層迭代方法本身具有副作用,例如:IO#each_line。** – 2015-02-10 18:34:17

回答

2

它可能是一個有點混亂,不過要注意的是,在Ruby中的Enumerator類和Enumerable是非常重要的模塊。

Enumerator類包括Enumerable(最喜歡的可枚舉的對象,如ArrayHash

next方法被提供作爲Enumerator,這的確有一個內部狀態的一部分,用戶可以考慮一個Enumerator非常接近Iterator其他語言暴露的概念。

當你實例枚舉器,內部指針指向集合中的第一項。

2.1.5 :021 > a = [1,2,3].to_enum 
=> #<Enumerator: [1, 2, 3]:each> 
2.1.5 :022 > a.next 
=> 1 
2.1.5 :023 > a.next 
=> 2 

這不是枚舉器的唯一目的(否則它可能會被稱爲Iterator)。但是,這是記錄的功能之一。

枚舉器也可以用作外部迭代器。例如,#next返回迭代器的下一個值,或者如果枚舉器在最後,則引發StopIteration。

e = [1,2,3].each # returns an enumerator object. 
puts e.next # => 1 
puts e.next # => 2 
puts e.next # => 3 
puts e.next # raises StopIteration 

但正如我前面所說的,Enumerator類包括Enumerable。這意味着Enumerator的每個實例都會暴露Enumerable設計用於集合的方法。在這種情況下,集合是Enumerator所包含的集合。

take是一種通用的Enumerable方法。它旨在返回來自enum的前N個元素。請注意,enum指的是包含Enumerable而不是Enumerator的任何通用類。因此,take(2)將返回集合的前兩個元素,無論指針在Enumerator實例內的位置如何。

讓我告訴你一個實際的例子。我可以創建一個自定義類,並執行Enumerable

class Example 
    include Enumerable 

    def initialize(array) 
    @array = array 
    end 

    def each(*args, &block) 
    @array.each(*args, &block) 
    end 
end 

我可以混合Enumerable,只要我爲each提供實現我得到所有其他方法免費,包括take

e = Example.new([1, 2, 3]) 
=> #<Example:0x007fa9529be760 @array=[1, 2, 3]> 
e.take(2) 
=> [1, 2] 

正如所料,take返回前2個元素。 take忽略了我實現的任何其他內容,完全與Enumerable一樣,包括狀態或指針。

+0

我知道,我不知道枚舉上的所有內容都只依賴於每個' 。謝謝。小的跟進:那麼從一個枚舉中取出第一個項目,將其存儲在一個變量中,然後對其餘項目進行操作的唯一方法是類似於以下內容? 'val = enum.first; res = enum.drop(1).map {| i |我* 2}' – jgpaiva 2015-02-10 18:53:41

+0

不可以。您可以使用範圍切片。 '[1,2,3,4] .to_enum.to_a [1 ..- 1]'只能獲得你想要的物品部分。 – 2015-02-10 18:56:37

2

根據文檔,Enumerable#take返回前n個元素,而不是光標後面的n個元素。只有Enumerator中的方法纔會對該內部遊標進行操作; Enumerable混合輸入只是枚舉哪些不一定共享遊標的方法的集合。

如果你願意,你可以實現枚舉#拿去做你所期望的:

class Enumerator 
    def take(n = 1) 
    n.times.map { self.next } 
    end 
end