2

In a Ruby project where I have the two hashes below (from two models): "users" and "conflicts".

I would like to find the way to iterate them in order to search conflicts'values within users'values and get a hash that would contain, for each conflict, which users would contain all its values:

users = {
  'user1' => ['resp1', 'resp2', 'resp3', 'resp4'],
  'user2' => ['resp1', 'resp2', 'resp3', 'resp4'],
  'user3' => ['resp1', 'resp4']
}

conflicts = {
  'c1'=> ['resp1', 'resp2'], 'c2'=> ['resp1', 'resp3'],
  'c3'=> ['resp1', 'resp4'], 'c4'=> ['resp3', 'resp5','resp6']
}

Desired result:

conflict_users = {
  'c1'=> ['user1', 'user2'], 'c2'=> ['user1', 'user2'],
  'c3'=> ['user1', 'user2','user3'],'c4'=> ['nill']
}

-- Any idea on how this could be accomplished?

garatex
  • 21
  • 3
  • is this your input: `{'resp1', 'resp2', 'resp3', 'resp4'}`? or is more like a list: `['resp1', 'resp2', 'resp3', 'resp4']`? – r4cc00n Jan 17 '21 at 19:13
  • Thanks everyone!, I have amended the question to make it clearer following your comments/questions. – garatex Jan 17 '21 at 20:37

3 Answers3

4

The desired return value can be obtained as follows.

conflicts.transform_values do |c_resps|
  users.each_with_object([]) do |(user,user_resps),c_users|
    c_users << user if (c_resps & user_resps) == c_resps
  end.tap { |c_users| c_users.replace(['nill']) if c_users.empty? }
end
  #=> {"c1"=>["user1", "user2"], "c2"=>["user1", "user2"],
  #    "c3"=>["user1", "user2", "user3"], "c4"=>["nill"]}

See Hash#transform_values, Array#& and #tap.

(c_resps & user_resps) in

c_users << user if (c_resps & user_resps) == c_resps

is an array containing all elements in c_resps (a value in conflicts) that are also in user_resps (a value in users). Therefore all elements in c_resps are contained in user_resps if and only if (c_resps & user_resps) == c_resps1.

Note that it would be easier (and more Ruby-like) to leave "c4"=>["nill"] as "c4"=>[], in which case .tap { |c_users| c_users.replace(['nill']) if arr1.empty? } could be removed.

To help you understand what is going on I've reorganized the method slightly and inserted puts statements to display the values of different objects when the method is run.

conflicts.transform_values do |c_resps|
  puts "\nc_resps = #{c_resps}"
  c_users = []
  users.each do |user,user_resps|
    puts "  user=#{user}, user_resps=#{user_resps}"
    puts "  c_resps & user_resps = #{c_resps & user_resps}"
    c_users << user if (c_resps & user_resps) == c_resps
    puts "  c_users after c_users << user if... = #{c_users}"
  end
  c_users = ['nill'] if c_users.empty?
  puts "c_users after c_users = ['nill'] if c_users.empty? = #{c_users}"
  c_users
end      #=> {"c1"=>["user1", "user2"], "c2"=>["user1", "user2"],
  #    "c3"=>["user1", "user2", "user3"], "c4"=>["nill"]}

This displays the following.

c_resps = ["resp1", "resp2"]
  user=user1, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp1", "resp2"]
  c_users after c_users << user if... = ["user1"]
  user=user2, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp1", "resp2"]
  c_users after c_users << user if... = ["user1", "user2"]
  user=user3, user_resps=["resp1", "resp4"]
  c_resps & user_resps = ["resp1"]
  c_users after c_users << user if... = ["user1", "user2"]
c_users after c_users = ['nill'] if c_users.empty? = ["user1", "user2"]
c_resps = ["resp1", "resp3"]
  user=user1, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp1", "resp3"]
  c_users after c_users << user if... = ["user1"]
  user=user2, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp1", "resp3"]
  c_users after c_users << user if... = ["user1", "user2"]
  user=user3, user_resps=["resp1", "resp4"]
  c_resps & user_resps = ["resp1"]
  c_users after c_users << user if... = ["user1", "user2"]
c_users after c_users = ['nill'] if c_users.empty? = ["user1", "user2"]
c_resps = ["resp1", "resp4"]
  user=user1, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp1", "resp4"]
  c_users after c_users << user if... = ["user1"]
  user=user2, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp1", "resp4"]
  c_users after c_users << user if... = ["user1", "user2"]
  user=user3, user_resps=["resp1", "resp4"]
  c_resps & user_resps = ["resp1", "resp4"]
  c_users after c_users << user if... = ["user1", "user2", "user3"]
c_users after c_users = ['nill'] if c_users.empty? = ["user1", "user2", "user3"]
c_resps = ["resp3", "resp5", "resp6"]
  user=user1, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp3"]
  c_users after c_users << user if... = []
  user=user2, user_resps=["resp1", "resp2", "resp3", "resp4"]
  c_resps & user_resps = ["resp3"]
  c_users after c_users << user if... = []
  user=user3, user_resps=["resp1", "resp4"]
  c_resps & user_resps = []
  c_users after c_users << user if... = []
c_users after c_users = ['nill'] if c_users.empty? = ["nill"]

1. (c_resps & v) == c_resps could be replaced with (among others) (v-c_resps).empty? or arr1 << k if v.all? { |e| c_resps.include?(e) }.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
3

I would first transform the users structure, so you can lookup all users by providing a resp.

require 'set'

users = {
  'user1' => ['resp1', 'resp2', 'resp3', 'resp4'],
  'user2' => ['resp1', 'resp2', 'resp3', 'resp4'],
  'user3' => ['resp1', 'resp4'],
}

users_by_resp = Hash.new { |hash, key| hash[key] = Set[] } 

users.each do |user, resps|
  resps.each { |resp| users_by_resp[resp] << user }
end

# optional, disable the creation of new sets when accessing unknown keys
users_by_resp.default = Set[]

# resulting in the following structure
pp users_by_resp
# {"resp1"=>#<Set: {"user1", "user2", "user3"}>,
#  "resp2"=>#<Set: {"user1", "user2"}>,
#  "resp3"=>#<Set: {"user1", "user2"}>,
#  "resp4"=>#<Set: {"user1", "user2", "user3"}>}

Then transform the values of the conflicts hash. First you get the users of each resp, then only keep those that are present in all resps. (By taking the intersection of all the sets.)

conflicts = {
  'c1'=> ['resp1', 'resp2'],
  'c2'=> ['resp1', 'resp3'],
  'c3'=> ['resp1', 'resp4'],
  'c4'=> ['resp3', 'resp5','resp6'],
}

result = conflicts.transform_values do |resps|
  resps.map(&users_by_resp).reduce(:&).to_a
end

pp result 
# {"c1"=>["user1", "user2"],
#  "c2"=>["user1", "user2"],
#  "c3"=>["user1", "user2", "user3"],
#  "c4"=>[]}
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
0

Given that input, a simple way to do it is as follow:

users = {
  'user1' => ['resp1', 'resp2', 'resp3', 'resp4'],
  'user2' => ['resp1', 'resp2', 'resp3', 'resp4'],
  'user3' => ['resp1', 'resp4']
}

conflicts = {
  'c1'=> ['resp1', 'resp2'], 'c2'=> ['resp1', 'resp3'],
  'c3'=> ['resp1', 'resp4'], 'c4'=> ['resp3', 'resp5','resp6']
}

result = users.inject({}) do |conflicts_result , (k_user, v_user)|
    conflicts.each_with_object(conflicts_result) do |(k_conflict, v_conflict), conflicts_result |
        conflicts_result[k_conflict] ||= []
        conflicts_result[k_conflict] << k_user if (v_conflict - v_user).size.zero?
    end
end.each {|k, v| v << 'nill' if v.size.zero?}



p result

You can test the snipper above here, also here are some docs: Inject, Ruby each for hash

r4cc00n
  • 1,927
  • 1
  • 8
  • 24