【问题标题】:How to create enumerable class without another enumerable?如何在没有另一个可枚举的情况下创建可枚举类?
【发布时间】:2017-11-21 13:45:24
【问题描述】:

几天前我问了同样的问题,但不知何故没有发生沟通。我已经重新构建了我的问题,希望我们能够将我需要解决的问题归零。

问题基本上是这样的:如何创建一个不只是使用另一个可枚举的可枚举类?作为我需要做的一个简化但完全有效的示例,请考虑这个类:

class MyClass
    include Enumerable

    def get_next
        return_val = rand.to_s

        if return_val.match(/33/)
            return nil
        else
            return return_val
        end
    end

    def each
        # ??????
    end
end

在这个类中,get_next 计算一个随机值。然后,如果该值有两个相邻的 3,则该方法返回 nil。否则返回值。因此,您可以一遍又一遍地运行 get_next,通常不到十次:

myob = MyClass.new

while not myob.get_next.nil?
    puts 'got one'
end

实际上,get_next 会搜索数千个数据点。当它找到下一个项目时,它会返回一个相当庞大的对象。 MyClass 对象不可能将所有项目都放入数组中。一次只需要一个。

事实上,我的班级按照上面的结构工作得很好。但是,我想以一种 ruby​​ish 的方式来实现它。

因此,鉴于上述工人阶级,请填写空白。 each() 中的哪些内容可以使它成为一个可枚举的类?

【问题讨论】:

标签: ruby


【解决方案1】:

填空。 each() 中的哪些内容可以使它成为一个可枚举的类?

文档说"The class must provide a method each, which yields successive members of the collection."。所以你可以这样做,只要它们不是nil,就继续产生值。

def each
  while val = get_next
    yield val
  end
end

演示:

irb(main):002:0> MyClass.new.each { |e| puts e }
0.9368650618038962
0.9784286059513836
0.9881624526762572

另一个演示(展示来自Enumerable 的方法):

irb(main):003:0> MyClass.new.map(&:to_f)
=> [0.6368065208805999, 0.6926393662295052, 0.9911564544581742, 0.35788099831068787]

所以这足以使它“可枚举”,在成功混合Enumerable 的意义上(我不认为有“可枚举”的官方 Ruby 定义)。顺便说一句,从你的问题中不清楚你是否真的需要那个。如果您只想用each 遍历值,那么您甚至不需要包含Enumerable。所做的只是为您提供额外的方法,例如mapselect。即使是for 循环也能工作,这要归功于each,而不包括Enumerable

irb(main):002:0> for e in MyClass.new
irb(main):003:1>   puts e
irb(main):004:1> end
0.7638166357043203
0.18409214000273166
0.39757329498164495
0.14658393540113002
0.948396753859117
0.3157122887487579
0.4804571082701993

(但请注意,for 循环通常不鼓励使用:[1][2]。我只是为了进一步证明each 的重要性和Enumerable 对于基本迭代的重要性。)

【讨论】:

  • ...如果您希望 each 不带参数返回枚举器(就像 stdlib 枚举一样),请将 return to_enum unless block_given? 添加到方法的顶部。
  • 这个实现与next 合约不兼容,它期望StopIteration 在没有要迭代的项目时被抛出。也就是说,这个实现与 ruby​​ 根本不兼容,并且可能有许多隐藏的故障,这些故障稍后会成为生产运行时的错误。正确的方法是Enumerator,正如我在回答中所显示的那样,这是完全错误的,甚至更危险。
  • @mudasobwa 嗯?我正在按照文档中的说明进行操作。而“根本不兼容ruby”?您是否忽略了我证明它有效的部分? 做的更多,顺便说一句,因为你的演示甚至没有使用来自Enumerable的任何东西。还有“next合约”?它甚至没有 next 方法。这是Enumerator 的事情,而不是Enumerable 的事情。如果您确实想要一个非阻塞 Enumerator 结果,只需添加 mwp 所说的内容。你甚至可以自己做(只是更长的时间)。然后你也会有一个next,它会做StopIteration。你能展示一个实际的问题吗?
  • 当然。尝试使其成为lazy
  • @mudasobwa 好的,我刚试过,效果很好。 (顺便说一句,它已经很懒了,不是吗?)
【解决方案2】:
class MyClass
  include Enumerable

  def enum
    @enum ||= Enumerator.new do |y|
      loop do  
        value = get_next
        raise StopIteration.new("nil") if value.nil?
        y << value
      end    
    end  
  end

  def each
    return enum_for(:each) unless block_given?
    enum.each(&Proc.new)
  end

  def get_next
    return_val = rand.to_s
    return_val.match(/33/) ? nil : return_val
  end
end

mc = MyClass.new
mc.each { |e| puts e }
#⇒ 0.9354594959902887
#  0.6425492923024724
#  0.24551401459676037
#  0.17117672854168786
#  0.15611540754420283
#  0.3914491388594509
#  0.40243662946930725
#  0.3792875585148314
#  0.11978219941961588
#  0.49891129730441874

【讨论】:

  • 太棒了!就像我需要的那样工作。谢谢!
猜你喜欢
  • 1970-01-01
  • 2017-03-30
  • 1970-01-01
  • 1970-01-01
  • 2015-12-24
  • 2017-04-01
  • 1970-01-01
  • 2010-09-11
相关资源
最近更新 更多