Код
require 'matrix'
def pair_wise_distances(h)
h.map { |k,v| [k, Vector[*v]] }.
combination(2).
each_with_object({}) { |((k1,v1),(k2,v2)),g|
g[[k1,k2]] = (v2-v1).magnitude.round(4) }.
tap { |g| g.default_proc = Proc.new { |f,k| f[k.reverse] } }
end
Пример
h = { A: [24,2], B: [32,42], C: [3,11], D: [5,9], E: [10,5], F: [14,2] }
g = pair_wise_distances(h)
#=> {[:A, :B]=>40.7922, [:A, :C]=>22.8473, [:A, :D]=>20.2485,
# [:A, :E]=>14.3178, [:A, :F]=>10.0, [:B, :C]=>42.45,
# [:B, :D]=>42.638, [:B, :E]=>43.0465, [:B, :F]=>43.8634,
# [:C, :D]=>2.8284, [:C, :E]=>9.2195, [:C, :F]=>14.2127,
# [:D, :E]=>6.4031, [:D, :F]=>11.4018, [:E, :F]=>5.0}
g[[:A, :B]]
#=> 40.7922
g[[:B, :A]]
#=> 40.7922
Пояснение
См. Vector :: [] , Vector # - , Vector # magnitude (aka r
) и Array # комбинация .
Обратите внимание, что метод не требует, чтобы значения в хэше были двухэлементными массивами.Они могут быть массивов любого размера.
Шаги следующие.
f = h.map { |k,v| [k, Vector[*v]] }
#=> [
# [:A, Vector[24, 2]], [:B, Vector[32, 42]], [:C, Vector[3, 11]],
# [:D, Vector[5, 9]], [:E, Vector[10, 5]], [:F, Vector[14, 2]]
# ]
e = f.combination(2)
#=> #<Enumerator: [
# [:A, Vector[24, 2]], [:B, Vector[32, 42]], [:C, Vector[3, 11]],
# [:D, Vector[5, 9]], [:E, Vector[10, 5]], [:F, Vector[14, 2]]
# ]:combination(2)>
Мы можем преобразовать e
в массив, чтобы увидеть (e.size = 5*4/2 = 15
) значения, которые будутгенерируется перечислителем.
e.to_a
#=> [
# [[:A, Vector[24, 2]], [:B, Vector[32, 42]]],
# [[:A, Vector[24, 2]], [:C, Vector[3, 11]]],
# ...
# [[:A, Vector[24, 2]], [:F, Vector[14, 2]]],
# [[:B, Vector[32, 42]], [:C, Vector[3, 11]]],
# ...
# [[:C, Vector[3, 11]], [:D, Vector[5, 9]]],
# ...
# [[:D, Vector[5, 9]], [:E, Vector[10, 5]]],
# ...
# [[:E, Vector[10, 5]], [:F, Vector[14, 2]]]
# ]
Продолжение,
f = e.each_with_object({}) { |((k1,v1), (k2,v2)),g|
g[[k1,k2]] = (v2-v1).magnitude.round(4) }
#=> (as shown in "Example" section)
Например, вычисляется значение [:A, :B]
(расстояние между :A
и :B
)следующим образом.
diff = Vector[32, 42] - Vector[24, 2]
#=> Vector[8, 40]
diff.magnitude.round(4)
#=> 40.7922
, что, как и следовало ожидать, равно
Math.sqrt(8**2 + 40**2).round(4)
Наконец, для каждого ключа k
в хэше f
нам нужно иметь f[k.reverse]
возврат f[k]
.(Для ключа [:A, :B]
, например, необходимо вернуть значение [:B, :A]
, которое совпадает со значением [:A, :B]
).Мы могли бы добавить «обратные» ключи:
g.keys.each { |k| g[k.reverse] = g[k] }
, но это удваивает размер хэша.Вместо этого я прикрепил процесс по умолчанию к f
:
f.default_proc = Proc.new { |g,k| g[k.reverse] }
Это приводит к возвращению f[k.reverse]
, когда f
не имеет ключа k
:
g[[:A, :B]]
#=> 40.7922
g[[:B, :A]]
#=> 40.7922
Если мы хотим, чтобы f[[k, k]]
(например, f[[:C, :C]]
) возвращал ноль, мы могли бы изменить процедуру по умолчанию на следующую.
f.default_proc = Proc.new { |g,k| k.first==k.last ? 0 : g[k.reverse] }
f[[:C, :C]]
#=> 0
f[[:B, :A]]
#=> 40.7922