在 Ruby 中将迭代器转换为数组的单行程序

问题描述

这必须是一个常见问题解答,但经过大量网络搜索后,我未能找到答案。我是 Ruby 编程语言的新手,所以我可能误解了一些基础知识,或者我将 C#、F# 或 Haskell 的知识投射到 Ruby 上。

如果我有一个像下面这样的方法,是否有一个单行将它变成数组或其他可变数据结构?

def foo
  yield 8
  yield 2
  yield 5
end

我知道我可以做到:

ary = Array.new
foo { |x| ary << x }

然而,这不是单线。例如,在 C# 中,我可以使用 ToListToArray 扩展方法,或者在 F# 中,例如Seq.toList

let foo = seq {
    yield 8
    yield 2
    yield 5
}

let arr = foo |> Seq.toList

Ruby 中是否有类似的单行代码

还是我完全误解了 foo 的性质?

解决方法

实际上,您的问题中有两个独立的问题。

第一个问题在标题中:

Ruby 中将迭代器转换为数组的单行代码

这很简单:Enumerator,在 Ruby 中被称为迭代器,混入(继承自)EnumerableEnumerable 具有 Enumerable#to_a 方法,该方法从 Enumerable 创建 Array,因此也从 Enumerator 创建,因为 Enumerator IS -AN Enumerable

因此,如果您有 Enumerator,您只需调用 to_a,例如

some_enum.to_a

但是,您没有 Enumerator。你有一个方法。因此,您没有问的真正问题是问题的关键:如何从方法创建 Enumerator

这就是 Object#enum_for 的作用:

enum = enum_for(:foo)
#=> #<Enumerator: ...>

ary = enum.to_a
#=> [8,2,5]

但是请注意,在许多情况下,您实际上并不需要 Array,因为 Enumerator 就像 Enumerable 一样继承自 Array,因此响应于许多相同的消息。

另请注意,按照惯例,Ruby 中有许多“迭代器”方法,例如Enumerable#map#each 等在没有块的情况下被调用时将返回 Enumerator

def foo
  return enum_for(__callee__) { 3 } unless block_given?

  yield 8
  yield 2
  yield 5
end

然后你可以简单地做:

enum = foo
#=> #<Enumerator: ...>

ary = enum.to_a
#=> [8,5]
,

一种惯用的方法是如果没有给出块就返回一个枚举器:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button

y = np.random.randint(1,100,size=50)
y2 = np.random.randint(1,size=50)
fig = plt.figure()
ax = fig.add_subplot(111)

ax2 = ax.twinx()#Comment out the second axes,the annotation works.
dots1,= ax.plot(y,'r.',picker=True,label='data1')
dots2,= ax2.plot(y2,'g.',label='data2')

fig.legend()
anno = ax.annotate('N/A',xy=(0,0),xytext=(15,15),# update
                   textcoords=('offset pixels'),bbox=dict(boxstyle='round',fc='w',alpha=1),arrowprops=dict(arrowstyle='fancy')
)

anno.set_visible(False)

def motion(event):
    x = event.xdata
    y = event.ydata
    v = anno.get_visible()
    if event.inaxes in (ax,ax2): # update
        c1,ind1 = dots1.contains(event)
        c2,ind2 = dots2.contains(event)

        if c1 or c2:
            anno.xy = (x,y)
            anno.set_text(f'{x}-{y}')
            anno.set_visible(True)
    if v:
        anno.set_visible(False)
    event.canvas.draw_idle()

fig.canvas.mpl_connect('motion_notify_event',motion)
plt.show()

这给你:

def foo
  return enum_for(__method__) unless block_given?

  yield 8
  yield 2
  yield 5
end