times 方法 returns nil with hash

times method returns nil with hash

我所做的是从散列中随机获取 2 个值并将它们与键匹配。我的代码有两个随机值,那部分效果很好。但是当涉及到 p hash[m] 时,我预计它会打印 hash 的键,而不是 returns nil。例如:

我们的随机值为“cat”和“ocean”。该程序使用时间方法自行选择此值。

所以当 m = "cat" 时 hash[m] 应该是 "dog",但它是 nil。同样的事情也适用于“海洋”。所以我有两个零值。为什么?我该如何解决这个问题并获得正确的密钥?

hash = { "foo" => "bar", "cat" => "dog", "ear" => "nose", "ocean" => "sea"}
value = hash.values

2.times do 
  m = value[rand(value.size)]
  p hash[m] #Erroneous part
end

Select 通过随机选择 随机 key/value 对,因为这样更容易:

my_hash = { "foo" => "bar", "cat" => "dog", "ear" => "nose", "ocean" => "sea"}
my_keys = my_hash.keys

2.times do
  rand_key = my_keys[rand(my_keys.size)]
  rand_val = my_hash[rand_key]
  puts "#{rand_key} => #{rand_val}"
end

我的理解是,给定一个散列并希望随机 select 两个值,然后识别与这些值关联的键。例如,如果

hash = { :a=>1, :b=>2, :c=>3 } 

值为

values = hash.values
  #=> [1, 2, 3]

在此示例中,值是唯一的。稍后我将解决值不唯一的一般情况(例如,{ :a=>1, :b=>2, :c=>3, :d=>2 })。

如果要随机 select 两个值,我们会写

random_values = values.sample(2)
  #=> [2, 1]

用统计术语来说,Array#sample samples without replacement(见第 3 段)。相比之下,

random_values = 2.times.map { values[rand(values.size)] }

对两个值 进行替换 采样,因此 random_values 可能等于,例如 [2, 2].

一旦获得random_values(假设[2, 1]),有两种方法可以获得关联的键:

第一个如下

associated_keys = random_values.map { |v| hash.key(v) }
  #=> [:b, :a]

尽管构建一个将键与随机值相关联的散列可能更有用:

random_values.each_with_object({}) { |v,h| h[v] = hash.key(v) }
  #=> {2=>:b, 1=>:a}

参见Hash#key. The second way is to use Hash#invert:

hsaH = hash.invert
  #=> { 1=>:a, 2=>:b, 3=>:c }

然后我们简单计算:

hsaH.values_at(*random_values)
  #=> [:b, :a]

hsaH.slice(*random_values)
  #=> {2=>:b, 1=>:a}

参见 Hash#values_at and Hash#slice


现在考虑值不唯一的一般情况。假设

hash = { :a=>1, :b=>2, :c=>3, :d=>2 }

其中键 :b:d 具有相同的值。

values = hash.values
  #=> [1, 2, 3, 2]

那么如果我们写

random_value = values.sample(2)

我们可以获得random_values #=> [2, 2]。这可能是允许的,但如果需要两个 unique 值,我们会写

unique_values = values.uniq
  #=> [1, 2, 3]
random_values = unique_values.sample(2)
  #=> [3, 2]

选择使用 values 还是 unique_values 取决于应用程序。无论如何,如果结果是[3,2],使用上面的第一种方法我们得到

keys = hash.keys
  #=> [:a, :b, :c, :d]
associated_keys = random_values.map { |v| hash.key(v) }
  #=> [:c, :b] 

然而,这可能并不令人满意,因为它取决于 hash 键的顺序。假设:

hash = { :a=>1, :d=>2, :c=>3, :b=>2 }

在这种情况下我们得到不同的答案:

keys = hash.keys
  #=> [:a, :d, :c, :b]
associated_keys = random_values.map { |v| hash.key(v) }
  #=> [:c, :d] 

同样,

{ :a=>1, :b=>2, :c=>3, :d=>2 }.invert
  #=> {1=>:a, 2=>:d, 3=>:c} 
{ :a=>1, :d=>2, :c=>3, :b=>2 }.invert
  #=> {1=>:a, 2=>:b, 3=>:c}

2 的值不同。

对于一般情况(同样,取决于应用程序),我们可能会考虑编写以下内容。

hash = { :a=>1, :d=>2, :c=>3, :b=>2 }
values_to_keys = hash.each_with_object({}) do |(k,v),h|
  (h[v] ||= []) << k
end
  #=> {1=>[:a], 2=>[:d, :b], 3=>[:c]}

然后

values = hash.values
random_values = values.sample(2)
  #=> [3, 1] 
random_values.map { |v| [v, values_to_keys[v].shift] }
  #=> [[3, :c], [1, :a]] 

第二个例子:

random_values = values.sample(2)
  #=> [2, 2] 
random_values.map { |v| [v, values_to_keys[v].shift] }
  #=> [[2, :d], [2, :b]]

注意在values_to_keys的计算中,(h[v] ||= []) << k展开为

(h[v] = h[v] || []) << k

所以如果 h 没有密钥 v,这就变成了

(h[v] = nil || []) << k
(h[v] = []) << k

导致 h[v] 被设置为一个空数组,它被返回,然后 k 被附加到该空数组。相比之下,如果 h 已经有一个键 v

(h[v] = h[v] || []) << k
  #=> (h[v] = h[v]) << k
  #=> h[v] << k