【问题标题】:Hash "has_key" complexity in Ruby在 Ruby 中散列“has_key”复杂性
【发布时间】:2015-05-12 13:18:31
【问题描述】:

我有一个哈希vars = {"a" => "Name", "b" => "Address" , "c" => "Phone"}。我想检查这条线的性能:

vars.has_key(:b)?

是 O(1) 还是 O(散列大小)?

【问题讨论】:

  • 哈希是直接访问的,不像堆栈(数组),你需要遍历每个元素,直到找到你想要的。因此,has_key 不应该取决于哈希的大小(有待确认,但我很确定)

标签: ruby-on-rails ruby algorithm hash time-complexity


【解决方案1】:

简单的基准测试:

require 'benchmark'

iterations = 10_000
small      = 10
big        = 1_000_000

small_hash = {}
big_hash   = {}

(1..small).each do |i|
  small_hash[i] = i
end

(1..big).each do |i|
  big_hash[i] = i
end

Benchmark.bmbm do |bm|
  bm.report('Small Hash') do
    iterations.times { small_hash.has_key?(1) }
  end

  bm.report('Big Hash') do
    iterations.times { big_hash.has_key?(1) }
  end
end

运行测试:

$ ruby has_key_test.rb 
                 user     system      total        real
Small Hash   0.000000   0.000000   0.000000 (  0.001167)
Big Hash     0.000000   0.000000   0.000000 (  0.001171)

所以是的,我认为我们可以考虑成本常数 O(1)(至少,无需检查内部 MRI 实现)。

【讨论】:

  • 要执行良好的基准测试,您需要对正在调用的方法执行多次迭代。事实上,我建议您至少进行 10^5 次迭代以获得良好的近似值。
  • @IvayloStrandjev 是的,你说得对,这只是一个快速的近似值。我将使用Benchmark.bmbm 和更多迭代对其进行更新。
  • @markets - has_value? 怎么样?我尝试将您的代码修改为has_key?(1)has_value?(big - 1),然后大哈希将花费(大/小)x 时间。所以我认为has_value? 的时间复杂度是 O(n)?
  • 这只是为了证明最好的情况。 Ruby(和几种语言)使用打包数组来提高性能,您的示例使用的是打包结构。这意味着has_key?将使用 O(1) 的特定算法。并非总是如此,尝试将随机元素和字符串添加到哈希中,您将获得 O(n) 复杂度
【解决方案2】:

has_key的源码是(http://ruby-doc.org/core-1.9.3/Hash.html#method-i-has_key-3F)

rb_hash_has_key(VALUE hash, VALUE key)
{
    if (!RHASH(hash)->ntbl)
        return Qfalse;
    if (st_lookup(RHASH(hash)->ntbl, key, 0)) {
        return Qtrue;
    }
    return Qfalse;
}

st_lookup 有以下片段 (https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L383):

if (table->entries_packed) {
    st_index_t i = find_packed_index(table, hash_val, key);
    if (i < table->real_entries) {
        if (value != 0) *value = PVAL(table, i);
        return 1;
    }
        return 0;
    }

这告诉我们如果entries_packed 则 ruby​​ 使用索引 (O(1)) 否则它使用未索引搜索 (O(n))。

entries_packed 的值似乎取决于散列的大小:(https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L41)

#define MAX_PACKED_HASH (int)(ST_DEFAULT_PACKED_TABLE_SIZE * sizeof(st_table_entry*) / sizeof(st_packed_entry))

https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L219

tbl->entries_packed = size <= MAX_PACKED_HASH;

size 是一种索引大小。

您可以在 ruby​​ 源代码中找到更多详细信息,但其复杂性并不总是 O(1),而是取决于散列的大小。 (关于其索引的大小)

【讨论】:

    【解决方案3】:

    该方法的预期复杂性是恒定的。

    【讨论】:

    • 读者如何评估你的断言的有效性?如果您不是 Matz(或其他少数人之一),您需要提供参考或提供证明。
    【解决方案4】:
    def fake_h(n)
      n.times.inject({}){|o,x| o[x] = :a; o}
    end
    
    n = 1000000;
    h1 = fake_h(1);
    h10 = fake_h(10);
    h100 = fake_h(100);
    h1000 = fake_h(1000);
    h10000 = fake_h(10000);
    h100000 = fake_h(100000);
    h1000000 = fake_h(1000000);
    
    Benchmark.bm do |x| 
      x.report { n.times{|t| h1.has_key?(t) } }
      x.report { n.times{|t| h10.has_key?(t) } }
      x.report { n.times{|t| h100.has_key?(t) } }
      x.report { n.times{|t| h1000.has_key?(t) } }
      x.report { n.times{|t| h10000.has_key?(t) } }
      x.report { n.times{|t| h100000.has_key?(t) } }
      x.report { n.times{|t| h1000000.has_key?(t) } }
    end
    
    # Result :
        user     system      total         real
    0.200000   0.000000   0.200000 (  0.204647)
    0.210000   0.000000   0.210000 (  0.205677)
    0.210000   0.000000   0.210000 (  0.214393)
    0.210000   0.000000   0.210000 (  0.206382)
    0.210000   0.000000   0.210000 (  0.208998)
    0.200000   0.000000   0.200000 (  0.206821)
    0.220000   0.000000   0.220000 (  0.213316)
    

    具有 1 个条目或 100 万个条目的哈希之间的差异是...最小的。

    【讨论】:

    • 如果键不是符号而是字符串怎么办?
    • 我的键不是符号而是整数。只需将o[x] = :a 替换为o[x.to_s] = :a 并将h.has_key?(t) 替换为h.has_key?(t.to_s) 并执行代码...
    • @ForgetTheNorm 你为什么不使用数组?你会节省很多额外的行...
    • @IvayloStrandjev 我可以做到...我在 2 分钟内编写了这个基准测试,其中有很多复制粘贴。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-26
    • 2015-12-15
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多