Сравнения хеша в среднем O (ключи) + ostCost (значение).
Если значения простые, то сравнение каждого значения равно O (1). Сравнение хешей: O (ключи) + ∑O (1), что равно O (ключи).
Ruby делает несколько дополнительных вещей, но основной алгоритм прост.
- Если они имеют одинаковый хэш, они равны .
- Если у них разное количество ключей, они не равны .
- Если какая-либо пара в hash1 отсутствует в hash2, они не равны .
- Они равны .
def hash_eq(hash1, hash2)
# O(1)
return true if hash1.equal?(hash2)
# O(1)
return false if hash1.size != hash2.size
# O(keys)
hash1.each {|k,v|
# O(1) O(value)
return false if hash2[k] != v
}
return true
end
Проверка равенства объектов - это просто сравнение двух указателей, поэтому O (1).
Хэши Ruby сохраняют свой размер в num_entries
, поэтому при сравнении их размер равен O (1).
Итерируя по ключам и значениям хэша, вы получаете O (ключи), в основном вы проходите массив.
Поиск значения в хэше - это O (1). В худшем случае O (n), но это очень редко встречается в современных хэшах, особенно после того, как было обнаружено, что является проблемой безопасности .
Наконец, сравнение значений имеет свою стоимость. Для простых значений, таких как числа, это O (1). Все остальное имеет свою цену, которую я назову O (val). Это могут быть большие строки, которые представляют собой O (n), где n - размер строки, или сложные вложенные структуры данных. Мы должны сделать это для каждого значения, и каждое значение несет свою стоимость. Я запишу это как сумму всех этих затрат: ostCost (значение).
Таким образом, O (1) + O (1) + O (ключи) + ostCost (значение). O (1) S затопляются O (ключи) и исчезают, таким образом, O (ключи) + (Cost (значение).
Простой тест подтверждает это.
require 'benchmark'
def benchmark_hash_eq(&block)
# We can't use h2 = h1.clone because that will not clone
# the values. Then Ruby can cheat and use object equality to
# compare the values.
h1 = block.call
h2 = block.call
puts "Size: #{h1.size}"
puts Benchmark.measure {
10_000.times { h1 == h2 }
}
end
puts "With number values"
benchmark_hash_eq do
Hash[(1..100).collect { |i| [i, i] }]
end
benchmark_hash_eq do
Hash[(1..1_000).collect { |i| [i, i] }]
end
benchmark_hash_eq do
Hash[(1..10_000).collect { |i| [i, i] }]
end
puts "With large, equal size strings"
benchmark_hash_eq do
Hash[(1..100).collect { |i| [i, "a" * 1000] }]
end
benchmark_hash_eq do
Hash[(1..1_000).collect { |i| [i, "a" * 1000] }]
end
benchmark_hash_eq do
Hash[(1..10_000).collect { |i| [i, "a" * 1000] }]
end
With number values
Size: 100
0.019237 0.000043 0.019280 ( 0.019306)
Size: 1000
0.195047 0.000515 0.195562 ( 0.196367)
Size: 10000
1.913112 0.003115 1.916227 ( 1.920030)
With large, equal size strings
Size: 100
0.065414 0.000225 0.065639 ( 0.065839)
Size: 1000
0.669863 0.001145 0.671008 ( 0.672279)
Size: 10000
10.041987 0.013201 10.055188 ( 10.066840)
Совершенно линейный, за исключением последнего, что может быть связано с размером хеша, достигающим некоторого порога памяти.
Обратите внимание, что Ruby может выполнять сравнение параллельно. Это не меняет сложности, но может заставить работать быстрее.