elixir
elixir

Reputation: 147

times method returns nil with hash

What I did is get random 2 values from hash and match them with the keys. My code gets two random values, that part works well. But when it comes to p hash[m], I expected it would print the key of hash, instead it returns nil. For example :

Our random values are "cat" and "ocean". The program pick this values by itself with times method.

So when m = "cat" hash[m] should be "dog", but it's nil. The same thing goes for "ocean" too. So I have two nil values. Why? How can I fix that and get the right key?

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

Upvotes: 1

Views: 141

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110685

My understanding is that you are given a hash and wish to select two values at random and then identify the keys associated with those values. For example, if

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

the values are

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

In this example the values are unique. Later I will address the general case where the values are not unique (e.g., { :a=>1, :b=>2, :c=>3, :d=>2 }).

If two values are to be selected randomly we would write

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

In statistical terms, Array#sample samples without replacement (see paragraph 3). By contrast,

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

samples two values with replacement, so random_values might equal, for example, [2, 2].

Once random_values is obtained (assume [2, 1]) there are two ways to obtain the associated keys:

The first is as follows.

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

though it might be more useful to construct a hash that associates keys with the random values:

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

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

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

Then we simply compute:

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

or

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

See Hash#values_at and Hash#slice.


Now consider the general case where the values are not unique. Suppose

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

where keys :b and :d have the same value.

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

Then if we write

random_value = values.sample(2)

we could obtain random_values #=> [2, 2]. That may be permitted, but if two unique values are needed we would write

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

The choice of whether to use values or unique_values depends on the application. Regardless, if the result is [3,2], using the first approach above we obtain

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

This may be unsatisfactory, however, as it depends on the ordering of the hash's keys. Suppose:

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

In this case we get a different answer:

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

Simlarly,

{ :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}

the values of 2 being different.

For the general case (again, depending on the application), we might consider writing the following.

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]}

Then

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

A second example:

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

Note that in the calculation of values_to_keys, (h[v] ||= []) << k expands to

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

so if h does not have a key v, this becomes

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

causing h[v] to be set equal to an empty array, which is returned, followed by k being appended to that empty array. By contrast, if h already has a key v,

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

Upvotes: 2

Timur Shtatland
Timur Shtatland

Reputation: 12347

Select the key/value pairs at random by selecting the keys at random, since that's easier:

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

Upvotes: 1

Related Questions