在Ruby中创建Enumerable In Place Slice Of Array。

问题描述 投票:0回答:1

我正在寻找一种方法,在ruby中获取一个数组,数组中的两个索引,然后返回一个可枚举的对象,这个对象将按顺序产生两个索引之间的所有元素。但出于性能考虑,我想这样做要满足以下两个条件。

  • 这个分片到枚举的过程不会在我想要返回枚举的子数组上创建一个副本。这就排除了 array[i..j].to_enum例如,因为 array[i..j] 正在创建一个新的数组。
  • 不需要循环整个数组来创建枚举。

我想知道是否有办法使用标准库的枚举或数组功能来实现这个功能,而不需要显式地创建我自己的自定义枚举器。

我所寻找的是一种更简洁的方法来创建下面的枚举器。

def enum_slice(array, i, j)
  Enumerator.new do |y|
    while i <= j
      y << array[i] # this is confusing syntax for yield (see here: https://ruby-doc.org/core-2.6/Enumerator.html#method-c-new)
      i += 1
    end
  end
end
arrays ruby collections enumerator
1个回答
1
投票

这似乎很合理,甚至可以变成Array本身的一个扩展。

module EnumSlice
  def enum_slice(i, j)
    Enumerator.new do |y|
      while i <= j
        y << self[i]
        i += 1
      end
    end
  end
end

现在在数组中 Enumerator 块。y 代表 Proc 当你有更多数据时,你就会调用它。如果该块结束,则假定你已经完成了枚举。没有要求永远终止,一个无限的枚举器是允许的,在这种情况下,由调用者决定是否停止迭代。

所以换句话说, y 块参数可以调用 零次以上,每次调用它的时候,输出都会从枚举器中 "发射 "出来。当该块退出时,枚举器被认为是完成了,并被关闭。y 在这一点上是无效的。

所有 y << x 是调用 << 办法 Enumerator::Yielder,这是一种语法上的糖分,以避免必须进行 y.call(x)y[x],这两个看起来都有点丑。

现在你可以把它添加到Array中。

Array.include(EnumSlice)

现在你可以做这样的事情

[ 1, 2, 3, 4, 5, 6 ].enum_slice(2, 4).each do |v|
  p v
end

给你正确的输出。

值得注意的是,尽管做了这么多工作,但这并不能为你节省任何时间。已经有了内置的方法。你的 enum_slice(a, i, j) 法相当于。

a.drop(i).take(j)

在性能上是否接近?一个快速的基准可以帮助测试这个理论。

require 'benchmark'

Benchmark.bm do |bm|
  count = 10000
  a = (0..100_000).to_a

  bm.report(:enum_slice) do
    count.times do
      a.enum_slice(50_000, 25_000).each do
      end
    end
  end
  bm.report(:drop_take) do
    count.times do
      a.drop(50_000).take(25_000).each do
      end
    end
  end
end

结果是:

       user     system      total        real
enum_slice  0.020536   0.000200   0.020736 (  0.020751)
drop_take  7.682218   0.019815   7.702033 (  7.720876)

所以你的方法大约快了374倍 还不错!

© www.soinside.com 2019 - 2024. All rights reserved.