Ruby:操纵迭代器?

发布于 2024-11-28 12:30:08 字数 1297 浏览 2 评论 0原文

我在使用 Ruby 时遇到了一些关于创建单向、惰性求值、可能无限的迭代器的问题。基本上,我尝试像使用 Haskell 列表一样使用 Ruby,并且在较小程度上使用 Python 生成器。

并不是我不理解它们本身;而是我不理解它们。我只是不知道如何像其他语言一样随意使用它们,而且我也不确定 Ruby 中的哪些方法会在我背后将它们变成数组,不必要地将整个序列卸载到内存中。

是的,我一直在研究 Ruby 参考手册。事实上,花了半个小时,很专注。或者显然不是。

例如,如果我要实现一副卡片组,它在 Python 中看起来像这样(未经测试):

# Python 3

from itertools import chain, count

face_ranks =
    dict(
        zip(
            ('jack', 'queen', 'king', 'ace'),
            count(11)))

sorted_deck =
    map(
        lambda suit:
            map(
                lambda rank:
                    {
                        'rank' : rank,
                        'suit' : suit
                    },
                chain(
                    range(2, 11),
                    face_ranks.keys())),
        ('clubs', 'diamonds', 'hearts', 'spades'))

那么,我将如何在 Ruby 中做到这一点,完全避免数组?请注意,据我所知,上面的代码仅使用元组和生成器:整个序列不会像我使用数组一样转储到内存中。我对上面的代码可能是错的,但你得到了我想要的。

如何链接迭代器(如 Python 的 chain())?如何生成无限范围的迭代器(如 Python 的 count())?如何将数组添加到迭代器(例如将元组传递给 Python 的 chain()),而不在此过程中将整个数组转换为数组?

我见过解决方案,但它们涉及阵列或不必要的复杂性(例如光纤)。

在 Python 中,我可以像数组一样简单地操作和抛出迭代器。我几乎可以把它们当作 Haskell 列表来对待,这是我最熟悉的,也是我在编码时所思考的。我对 Ruby 数组不太满意,这就是为什么我寻求其替代方案的帮助。

我设法在互联网上找到了有关它的大量信息,但我找不到任何涵盖 Ruby 中此类数据结构的基本操作的信息?有什么帮助吗?

I'm having teething problems with Ruby, with regards to creating single-direction, lazily-evaluated, potentially-infinite iterators. Basically, I'm trying to use Ruby like I'd use Haskell lists and, to a lesser extent, Python generators.

It isn't that I don't understand them per se; I just don't know how to use them as casually as other languages, and I'm also unsure about what methods in Ruby will turn them into arrays behind my back, unloading the entire sequence into memory unnecessarily.

And yes, I've been studying the Ruby reference manual. For half an hour in fact, attentively. Or perhaps evidently not.

For example, if I were to implement a card deck, it would look something like this in Python (untested):

# Python 3

from itertools import chain, count

face_ranks =
    dict(
        zip(
            ('jack', 'queen', 'king', 'ace'),
            count(11)))

sorted_deck =
    map(
        lambda suit:
            map(
                lambda rank:
                    {
                        'rank' : rank,
                        'suit' : suit
                    },
                chain(
                    range(2, 11),
                    face_ranks.keys())),
        ('clubs', 'diamonds', 'hearts', 'spades'))

So, how would I do this in Ruby, completely avoiding arrays? Note that the above code uses, to my knowledge, only tuples and generators: at no point is the whole sequence dumped into memory like if I had used an array. I could be wrong about the above code, but you get what I want.

How do I chain iterators (like Python's chain())? How do I generate an iterator of an infinite range (like Python's count())? How can I add an array to an iterator (like passing a tuple to Python's chain()) without converting the whole thing to an array in the process?

I've seen solutions, but they involve arrays or unnecessary complexities like fibers.

In Python I can manipulate and throw around iterators with the same simplicity as arrays. I can almost treat them like Haskell lists, which I'm most comfortable with, and is really what I think in when coding. I'm not comfortable with Ruby arrays, which is why I seek help with its alternative(s).

I've managed to pick up nuggets of information on the internet about it, but I couldn't find any that covers basic manipulation of such data structures in Ruby? Any help?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

空城之時有危險 2024-12-05 12:30:08

Ruby 似乎没有很多内置方法来执行您想要使用枚举器执行的不同操作,但您可以创建自己的方法。这就是我在这里所做的,使用 Ruby 1.9:

iter.rb

def get_enums_from_args(args)
  args.collect { |e| e.is_a?(Enumerator) ? e.dup : e.to_enum }
end

def build(y, &block)
  while true
    y << (begin yield; rescue StopIteration; break; end)
  end
end

def zip(*args)
  enums = get_enums_from_args args
  Enumerator.new do |y|
    build y do
      enums.collect { |e| e.next }
    end
  end
end

def chain(*args)
  enums = get_enums_from_args args
  Enumerator.new do |y|
    enums.each do |e|
      build y do
        e.next
      end
    end
  end
end

def multiply(*args)
  enums = get_enums_from_args args
  duped_enums = enums.collect { |e| e.dup }
  Enumerator.new do |y|
    begin
      while true
        y << (begin; enums.collect { |e| e.peek }; rescue StopIteration; break; end )

        index = enums.length - 1
        while true
          begin
            enums[index].next
            enums[index].peek
            break
          rescue StopIteration
            # Some iterator ran out of items.

            # If it was the first iterator, we are done,
            raise if index == 0

            # If it was a different iterator, reset it
            # and then look at the iterator before it.
            enums[index] = duped_enums[index].dup
            index -= 1
          end
        end
      end
    rescue StopIteration
    end
  end
end

我使用 rspec 编写了一个规范来测试函数并演示它们的功能:

iter_spec.rb:

require_relative 'iter'

describe "zip" do
  it "zips together enumerators" do
    e1 = "Louis".chars
    e2 = "198".chars
    zip(e1,e2).to_a.should == [ ['L','1'], ['o','9'], ['u','8'] ]
  end

  it "works with arrays too" do
    zip([1,2], [:a, nil]).to_a.should == [ [1,:a], [2,nil] ]
  end
end

describe "chain" do
  it "chains enumerators" do
    e1 = "Jon".chars
    e2 = 0..99999999999
    e = chain(e1, e2)
    e.next.should == "J"
    e.next.should == "o"
    e.next.should == "n"
    e.next.should == 0
    e.next.should == 1
  end
end

describe "multiply" do
  it "multiplies enumerators" do
    e1 = "ABC".chars
    e2 = 1..3
    multiply(e1, e2).to_a.should == [["A", 1], ["A", 2], ["A", 3], ["B", 1], ["B", 2], ["B", 3], ["C", 1], ["C", 2], ["C", 3]]
  end

  it "is lazily evalutated" do
    e1 = 0..999999999
    e2 = 1..3
    e = multiply(e1, e2)
    e.next.should == [0, 1]
    e.next.should == [0, 2]
    e.next.should == [0, 3]
    e.next.should == [1, 1]
    e.next.should == [1, 2]
  end

  it "resulting enumerator can not be cloned effectively" do
    ranks = chain(2..10, [:jack, :queen, :king, :ace])
    suits = [:clubs, :diamonds, :hearts, :spades]
    cards = multiply(suits, ranks)
    c2 = cards.clone
    cards.next.should == [:clubs, 2]
    c2.next.should == [:clubs, 2]
    c2.next.should == [:clubs, 3]
    c2.next.should == [:clubs, 4]
    c2.next.should == [:clubs, 5]
    cards.next.should == [:clubs, 6]
  end

  it "resulting enumerator can not be duplicated after first item is evaluated" do
    ranks = chain(2..10, [:jack, :queen, :king, :ace])
    suits = [:clubs, :diamonds, :hearts, :spades]
    cards = multiply(ranks, suits)
    cards.peek
    lambda { cards.dup }.should raise_error TypeError
  end
end

如图所示在上面的规范中,这些方法使用惰性求值。

此外,此处定义的 zipchainmultiply 函数的主要弱点是生成的枚举器无法轻松复制或克隆,因为我们还没有编写任何代码来复制这些新枚举器所依赖的枚举参数。您可能需要创建一个 Enumerator 的子类,或者创建一个包含 Enumerable 模块或类似内容的类,以使 dup 正常工作。

Ruby doesn't seem to have a lot of built-in methods for doing the different things you wanted to do with enumerators, but you can make your own methods. That's what I did here, using Ruby 1.9:

iter.rb

def get_enums_from_args(args)
  args.collect { |e| e.is_a?(Enumerator) ? e.dup : e.to_enum }
end

def build(y, &block)
  while true
    y << (begin yield; rescue StopIteration; break; end)
  end
end

def zip(*args)
  enums = get_enums_from_args args
  Enumerator.new do |y|
    build y do
      enums.collect { |e| e.next }
    end
  end
end

def chain(*args)
  enums = get_enums_from_args args
  Enumerator.new do |y|
    enums.each do |e|
      build y do
        e.next
      end
    end
  end
end

def multiply(*args)
  enums = get_enums_from_args args
  duped_enums = enums.collect { |e| e.dup }
  Enumerator.new do |y|
    begin
      while true
        y << (begin; enums.collect { |e| e.peek }; rescue StopIteration; break; end )

        index = enums.length - 1
        while true
          begin
            enums[index].next
            enums[index].peek
            break
          rescue StopIteration
            # Some iterator ran out of items.

            # If it was the first iterator, we are done,
            raise if index == 0

            # If it was a different iterator, reset it
            # and then look at the iterator before it.
            enums[index] = duped_enums[index].dup
            index -= 1
          end
        end
      end
    rescue StopIteration
    end
  end
end

And I wrote a spec using rspec to test the functions and demonstrate what they do:

iter_spec.rb:

require_relative 'iter'

describe "zip" do
  it "zips together enumerators" do
    e1 = "Louis".chars
    e2 = "198".chars
    zip(e1,e2).to_a.should == [ ['L','1'], ['o','9'], ['u','8'] ]
  end

  it "works with arrays too" do
    zip([1,2], [:a, nil]).to_a.should == [ [1,:a], [2,nil] ]
  end
end

describe "chain" do
  it "chains enumerators" do
    e1 = "Jon".chars
    e2 = 0..99999999999
    e = chain(e1, e2)
    e.next.should == "J"
    e.next.should == "o"
    e.next.should == "n"
    e.next.should == 0
    e.next.should == 1
  end
end

describe "multiply" do
  it "multiplies enumerators" do
    e1 = "ABC".chars
    e2 = 1..3
    multiply(e1, e2).to_a.should == [["A", 1], ["A", 2], ["A", 3], ["B", 1], ["B", 2], ["B", 3], ["C", 1], ["C", 2], ["C", 3]]
  end

  it "is lazily evalutated" do
    e1 = 0..999999999
    e2 = 1..3
    e = multiply(e1, e2)
    e.next.should == [0, 1]
    e.next.should == [0, 2]
    e.next.should == [0, 3]
    e.next.should == [1, 1]
    e.next.should == [1, 2]
  end

  it "resulting enumerator can not be cloned effectively" do
    ranks = chain(2..10, [:jack, :queen, :king, :ace])
    suits = [:clubs, :diamonds, :hearts, :spades]
    cards = multiply(suits, ranks)
    c2 = cards.clone
    cards.next.should == [:clubs, 2]
    c2.next.should == [:clubs, 2]
    c2.next.should == [:clubs, 3]
    c2.next.should == [:clubs, 4]
    c2.next.should == [:clubs, 5]
    cards.next.should == [:clubs, 6]
  end

  it "resulting enumerator can not be duplicated after first item is evaluated" do
    ranks = chain(2..10, [:jack, :queen, :king, :ace])
    suits = [:clubs, :diamonds, :hearts, :spades]
    cards = multiply(ranks, suits)
    cards.peek
    lambda { cards.dup }.should raise_error TypeError
  end
end

As shown in the specs above, these methods use lazy evaluation.

Also, the major weakness of the zip, chain, and multiply functions defined here is that the resulting enumerator can not be easily duplicated or cloned because we haven't written any code to duplicate the enum arguments that these new enumerators rely on. You would probably need to make a subclass of Enumerator or make a class the includes the Enumerable module or something like that to make dup work nicely.

遗忘曾经 2024-12-05 12:30:08

Ruby 中最接近的等价物是 Enumerator。它可以让你做惰性生成器。

The closest equivalent in Ruby is an Enumerator. It lets you do lazy generators.

夜雨飘雪 2024-12-05 12:30:08

您似乎出于性能焦虑而避免使用 Ruby 数组,这可能是由于您在其他语言中使用数组的经验。您无需避免使用 Ruby 数组 — 它们是您在 Ruby 中得到的最接近元组的东西。

foo = 1, 2, 3, 4
foo.class       #=> Array

看起来您正在寻找 Range 来代替生成器:

range = 1..4
range.class     #=> Range
range.count     #=> 4

('a'..'z').each { |letter| letter.do_something }

Range 不会转换为数组,但它确实包含 Enumerable,因此您可以使用所有常规枚举器。至于循环/迭代——Ruby 中的本机循环是通过 Enumerable 进行的。 for i in group 实际上是枚举器循环的语法糖(如.each)。枚举方法通常返回发送者,因此您可以链接它们:

(1..10).map { |n| n * 2 }.each { |n| print "##{n}" }
# outputs #2#4#6#8#10#12#14#16#18#20
# returns an array:
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

我很乐意为您提供有关 Python»Ruby 等效项的更具体答案,但我对 Python 不熟悉。

更新

您可以将范围压缩到一个嵌套数组中,如下所示:

(1..26).zip('a'..'z') #=> [[1, 'a'], [2, 'b'], ...]

...但范围不可变。您可以使用 (1..5).to_a 将范围转换为数组,也可以像上面所示的那样对其进行迭代。如果您有多个定义的数据范围想要测试包含情况,则可以使用几个范围和一个映射:

allowed = 'a'..'z', 1..100
input = # whatever
allowed.each do |range|
  return false unless range.cover? input
end

当然,您始终可以使用具有范围的枚举器来动态“生成”值。

It seems like you're avoiding Ruby arrays out of performance anxiety, perhaps due to experience you've had with arrays in other languages. You needn't avoid Ruby arrays — they are the closest thing you will get to a tuple in Ruby.

foo = 1, 2, 3, 4
foo.class       #=> Array

It looks like you're looking for a Range in place of a generator:

range = 1..4
range.class     #=> Range
range.count     #=> 4

('a'..'z').each { |letter| letter.do_something }

A range isn't converted to an array, but it does include Enumerable so you can use all of your regular enumerators. As far as looping/iteration — the native looping in Ruby is through Enumerable. for i in group is actually syntactic sugar for enumerator loops (like .each). Enumerable methods usually return the sender, so you can chain them:

(1..10).map { |n| n * 2 }.each { |n| print "##{n}" }
# outputs #2#4#6#8#10#12#14#16#18#20
# returns an array:
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

I'd love to give you more specific answers about your Python»Ruby equivalents, but I'm unfamiliar with Python.

Update

You can zip ranges together into a nested array like this:

(1..26).zip('a'..'z') #=> [[1, 'a'], [2, 'b'], ...]

…but Ranges aren't mutable. You can convert a range to an array with (1..5).to_a, or you can iterate over it like I showed above. In the case that you have multiple defined ranges of data that you want to test for inclusion you can use a couple ranges and a map:

allowed = 'a'..'z', 1..100
input = # whatever
allowed.each do |range|
  return false unless range.cover? input
end

Of course you can always use enumerators with ranges to "generate" values on the fly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文