在Ruby中用高索引初始化空数组是否昂贵?

时间:2015-07-10 17:02:04

标签: ruby-on-rails arrays ruby performance

我想通过创建3d数组来保存year-month-day在ruby中以查找O(1):

dates = [Date.new(2014,2,15), Date.new(2015, 8, 27), Date.new(2014, 7, 4), ...]
res = []
dates.each do |d|
    # Init year if DNE
    if res[d.year].nil?
        res[d.year] = []
    end
    # Init month if DNE
    if res[d.year][d.month].nil?
        res[d.year][d.month] = []
    end
    # Set the [year][month][day] = 1
    res[d.year][d.month][d.day] = 1
end
# Use case
def date_in_array?(date)
    !res[self.year].nil? && !res[self.year][self.month].nil? && !res[self.year][self.month][self.day].nil?
end

date_in_array?(Date.new(2014, 2, 15))
=> true

date_in_array?(Date.new(2014, 9, 21))
=> false

另一种方法是使用哈希来保存日期,但就内存而言可能会变得昂贵。

所以我的问题是,ruby如何管理数组索引超出范围?

我想确保通过执行res[2015] = [],ruby不会初始化res[0..2014]集,这确实是在这种情况下存储数据的好方法。

3 个答案:

答案 0 :(得分:3)

  

我想确保通过执行res[2015] = [],ruby不会初始化res[0..2014]集,这确实是在这种情况下存储数据的好方法。

不。确实如此。此片段只是通过占用大部分内存而使我的系统无法响应。坚实的证明。

@a = []
@a[1_000_000_000] = 1

如何存储数据取决于您期望作为键的值。

根据定义,数组是一大块连续的存储单元,每个存储单元存储一个值。实现了恒定的访问时间,因为对于任何已知的索引,它需要一个算术运算(基数+索引)来计算所需单元的存储器地址。如果键是非顺序的,那么你不应该首先使用数组。

磁盘不用于阵列存储,只能用于虚拟RAM(可配置为使用交换空间,但不要依赖它)。

如果按键是顺序的,但是不要接近0(即1990及以上),你可以为内置数组创建一个包装器,其中包含一个数组和一个"下限"用于计算真实指数"在那个数组中。实施[][]=,您将获得类似阵列的访问权限。

class ShiftedArray
  def initialize(lower_bound)
    @lower_bound = lower_bound
    @storage     = []
  end

  def []=(key, value)
    @storage[key - @lower_bound] = value
  end

  def [](key)
    @storage[key - @lower_bound]
  end
end

ShiftedArray.new(1)实际上是1索引数组。

当然,这远非理想,它不允许写入低于施工时设定界限的值。您可以实现这一点,但这超出了本答案的范围。

如果密钥是高度分散的数字,最好使用Hash或搜索树作为数据结构。

答案 1 :(得分:2)

  

在Ruby中使用高索引初始化空数组是否很昂贵?

是。 Ruby将为您的示例中的条目0..2015分配内存。在这种情况下你确实想要哈希;散列查找(如tadman所述)是分摊的O(1),因此速度不应该是一个问题,并且内存使用量也应该比稀疏数组大大提高。

举例(MRI 2.2.2):

require 'objspace'
res = []; ObjectSpace.memsize_of res                          #=> 0
res = []; res[100] = true; ObjectSpace.memsize_of res         # => 928
res = []; res[2015] = true; ObjectSpace.memsize_of res        # => 16248
res = {}; res[100] = true; ObjectSpace.memsize_of res         # => 192
res = {}; res[2015] = true; ObjectSpace.memsize_of res        # => 192

答案 2 :(得分:1)

Hash可能比三重嵌套数组结构便宜得多。任何单个数组的开销都高于单个Hash条目的开销。

不要忘记Hash查找在技术上是 O(1),是恒定时间,所以这里不关心性能。还有其他选择。

这个超级松弛的版本是使用Set,它就像是Array和Hash之间的混合:

require 'set'
dates = Set.new([ Date.new(2014,2,15), Date.new(2015, 8, 27), Date.new(2014, 7, 4) ])

dates.include?(Date.new(2014,2,15))
# => true
dates.include?(Date.new(2014,2,5))
# => false

您可以将其调整为哈希,其中键是日期或字符串。您经常会发现最简单的解决方案足够快