Delete duplicated elements in an array that's a value in a hash and its corresponding ids - ruby

I have a hash with values that's an array. How do I delete repeated elements in the array and the corresponding ids in the most performant way?
Here's an example of my hash
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["Language", "Question", "Question", "Answer", "Answer"],
"option_ids" => ["12345", "23456", "34567", "45678", "56789"]
}
The idea I have is something like this
hash["options"].each_with_index { |value, index |
h = {}
if h.key?(value)
delete(value)
delete hash["option_ids"].delete_at(index)
else
h[value] = index
end
}
The result should be
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["Language", "Question", "Answer"],
"option_ids" => ["12345", "23456", "45678"]
}
I know I have to put into consideration that when I delete the values of the options and option_ids the indexes of those values are going to change. But not sure how to do this

The first idea I had is to zip the values and call uniq, then think a way to return back to the initial form:
h['options'].zip(h['option_ids']).uniq(&:first).transpose
#=> [["Language", "Question", "Answer"], ["12345", "23456", "45678"]]
Then, via parallel assignment:
h['options'], h['option_ids'] = h['options'].zip(h['option_ids']).uniq(&:first).transpose
h #=> {"id"=>"sjfdkjfd", "name"=>"Field Name", "type"=>"field", "options"=>["Language", "Question", "Answer"], "option_ids"=>["12345", "23456", "45678"]}
These are the steps:
h['options'].zip(h['option_ids'])
#=> [["Language", "12345"], ["Question", "23456"], ["Question", "34567"], ["Answer", "45678"], ["Answer", "56789"]]
h['options'].zip(h['option_ids']).uniq(&:first)
#=> [["Language", "12345"], ["Question", "23456"], ["Answer", "45678"]]

hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["L", "Q", "Q", "Q", "A", "A", "Q"],
"option_ids" => ["12345", "23456", "34567", "dog", "45678", "56789", "cat"]
}
I assume that "repeated elements" refers to contiguous equal elements (2 only in [1,2,2,1]) as opposed to "duplicated elements" (both 1 and 2 in the previous example). I do show how the code would be altered (simplified, in fact) if the second interpretation applies.
idx = hash["options"].
each_with_index.
chunk_while { |(a,_),(b,_)| a==b }.
map { |(_,i),*| i }
#=> [0, 1, 4, 6]
hash.merge(
["options", "option_ids"].each_with_object({}) { |k,h| h[k] = hash[k].values_at(*idx) }
)
#=> {"id"=>"sjfdkjfd",
# "name"=>"Field Name",
# "type"=>"field",
# "options"=>["L", "Q", "A", "Q"],
# "option_ids"=>["12345", "23456", "45678", "cat"]}
If "repeated elements" is interpreted to mean that the values of "options" and "option_ids" are to only have the first three elements shown above, calculate idx as follows:
idx = hash["options"].
each_with_index.
uniq { |s,_| s }.
map(&:last)
#=> [0, 1, 4]
See Enumerable#chunk_while (Enumerable#slice_when could be used instead) and Array#values_at. The steps are as follows.
a = hash["options"]
#=> ["L", "Q", "Q", "Q", "A", "A", "Q"]
e0 = a.each_with_index
#=> #<Enumerator: ["L", "Q", "Q", "Q", "A", "A", "Q"]:each_with_index>
e1 = e0.chunk_while { |(a,_),(b,_)| a==b }
#=> #<Enumerator: #<Enumerator::Generator:0x000055e4bcf17740>:each>
We can see the values the enumerator e1 will generate and pass to map by converting it to an array:
e1.to_a
#=> [[["L", 0]],
# [["Q", 1], ["Q", 2], ["Q", 3]],
# [["A", 4], ["A", 5]], [["Q", 6]]]
Continuing,
idx = e1.map { |(_,i),*| i }
#=> [0, 1, 4, 6]
c = ["options", "option_ids"].
each_with_object({}) { |k,h| h[k] = hash[k].values_at(*idx) }
#=> {"options"=>["L", "Q", "A", "Q"],
# "option_ids"=>["12345", "23456", "45678", "cat"]}
hash.merge(c)
#=> {"id"=>"sjfdkjfd",
# "name"=>"Field Name",
# "type"=>"field",
# "options"=>["L", "Q", "A", "Q"],
# "option_ids"=>["12345", "23456", "45678", "cat"]}

Using Array#transpose
hash = {
"options" => ["Language", "Question", "Question", "Answer", "Answer"],
"option_ids" => ["12345", "23456", "34567", "45678", "56789"]
}
hash.values.transpose.uniq(&:first).transpose.map.with_index {|v,i| [hash.keys[i], v]}.to_h
#=> {"options"=>["Language", "Question", "Answer"], "option_ids"=>["12345", "23456", "45678"]}
After the OP edit:
hash = {
"id" => "sjfdkjfd",
"name" => "Field Name",
"type" => "field",
"options" => ["Language", "Question", "Question", "Answer", "Answer"],
"option_ids" => ["12345", "23456", "34567", "45678", "56789"]
}
hash_array = hash.to_a.select {|v| v.last.is_a?(Array)}.transpose
hash.merge([hash_array.first].push(hash_array.last.transpose.uniq(&:first).transpose).transpose.to_h)
#=> {"id"=>"sjfdkjfd", "name"=>"Field Name", "type"=>"field", "options"=>["Language", "Question", "Answer"], "option_ids"=>["12345", "23456", "45678"]}

Related

Mapping one hash value to other hash ruby

I have a hash which gives me the data in following manner:
details = [{"severity_longevity" => "Medium", "operating_leverage" => "High",
"financial_leverage"=> "Low", "revenue_growth"=> "Low"}]
I have one hash which gives me the score that I am supposed to assign.
score = [{"Low"=> 5},{"Medium"=> 10}, {"High"=> 15}]
How can I change the "Medium" "Low" and "High" in details hash with their number scores from
score hash ?
For Hashes you can use transform_values method
details = {
"severity_longevity" => "Medium",
"operating_leverage" => "High",
"financial_leverage"=> "Low",
"revenue_growth"=> "Low"
}
score = {"Low" => 5, "Medium" => 10, "High" => 15}
updated = details.transform_values { |v| score[v] }
# => { "severity_longevity" => 10, ... }

Setting hash values from array

I have a hash:
row = {
'name' => '',
'description' => '',
'auth' => '',
'https' => '',
'cors' => '',
'url' => ''
}
and I also have an array:
["Cat Facts", "Daily cat facts", "No", "Yes", "No", "https://example.com/"]
How can I grab the array elements and set them as values for each key in the hash?
Let's say row is your hash and values is your array
row.keys.zip(values).to_h
=> {"name"=>"Cat Facts", "description"=>"Daily cat facts", "auth"=>"No", "https"=>"Yes", "cors"=>"No", "url"=>"https://example.com/"}
It works if they are in the right order, of course
h = { 'name'=>'',
'description'=>'',
'auth'=>'',
'https'=>'',
'cors'=>'',
'url'=>'' }
arr = ["Cat Facts", "Daily cat facts", "No", "Yes", "No",
"https://example.com/"]
enum = arr.to_enum
#=> #<Enumerator: ["Cat Facts", "Daily cat facts", "No",
# "Yes", "No", "https://example.com/"]:each>
h.transform_values { enum.next }
#=> { "name"=>"Cat Facts",
# "description"=>"Daily cat facts",
# "auth"=>"No",
# "https"=>"Yes",
# "cors"=>"No",
# "url"=>"https://example.com/" }
See Hash#transform_values. Array#each can be used in place of Kernel#to_enum.
If arr can be mutated enum.next can be replaced with arr.shift.
Given the hash and the array:
row = { 'name' => '', 'description' => '', 'auth' => '', 'https' => '', 'cors' => '', 'url' => '' }
val = ["Cat Facts", "Daily cat facts", "No", "Yes", "No", "https://example.com/"]
One option is to use Enumerable#each_with_index while transforming the values of the hash:
row.transform_values!.with_index { |_, i| val[i] }
row
#=> {"name"=>"Cat Facts", "description"=>"Daily cat facts", "auth"=>"No", "https"=>"Yes", "cors"=>"No", "url"=>"https://example.com/"}
The bang ! changes the original Hash.

How do you check if an array element is empty or not in Ruby?

How do you check if an array element is empty or not in Ruby?
passwd.where { user =~ /.*/ }.uids
=> ["0", "108", "109", "110", "111", "112", "994", "995", "1001", "1002", "", "65534"]
To check if the array has an empty element, one of the many ways to do it is:
arr.any?(&:blank?)
Not sure what you want to do with it, but there are quite a few ways to skin this cat. More info would help narrow it down some...
["0", "108", "109", "110", "111", "112", "994", "995", "1001", "1002", "", "65534"].map { |v| v.empty? }
=> [false, false, false, false, false, false, false, false, false, false, true, false]
["0", "108", "109", "110", "111", "112", "994", "995", "1001", "1002", "", "65534"].each_with_index { |v,i| puts i if v.empty? }
10
arr = [ "0", "108", "", [], {}, nil, 2..1, 109, 3.2, :'' ]
arr.select { |e| e.respond_to?(:empty?) && e.empty? }
#=> ["", [], {}, :""]
Assuming your array is an array of strings
arr = [ "name", "address", "phone", "city", "country", "occupation"]
if arr.empty?
p "Array is empty"
else
p "Array has values inside"
These test for emptiness:
'foo'.empty? # => false
''.empty? # => true
[1].empty? # => false
[].empty? # => true
{a:1}.empty? # => false
{}.empty? # => true
Testing to see if an element in an array is empty would use a similar test:
['foo', '', [], {}].select { |i| i.empty? } # => ["", [], {}]
['foo', '', [], {}].reject { |i| i.empty? } # => ["foo"]
or, using shorthand:
['foo', '', [], {}].select(&:empty?) # => ["", [], {}]
['foo', '', [], {}].reject(&:empty?) # => ["foo"]

Group Array of Hashes by Key followed by values

Assuming I have the following dataset
[
{
:name => "sam",
:animal => "dog",
:gender => "male"
}, {
:name => "max",
:animal => "cat",
:gender => "female"
}, {
:name => "joe",
:animal => "snake",
:gender => "male"
}
]
How would you group the array of hashes to:
{
:name => ["sam", "max", "joe"]
:animal => ["dog", "cat", "snake"]
:gender => ["male", "female", "male"]
}
I've read similar articles such as this and Group array of hashes by key
However, most examples return the values as increment counts where I'm looking for actual separate values.
My attempt
keys = []
values = []
arr.each do |a|
a.each do |k, v|
keys << k
#this is where it goes wrong and where I'm stuck at
values << v
end
end
keys = keys.uniq
I understand where I went wrong is how I'm trying to segment the values. Any direction would be appreciated!
input.reduce { |e, acc| acc.merge(e) { |_, e1, e2| [*e2, *e1] } }
#⇒ {:name=>["sam", "max", "joe"],
# :animal=>["dog", "cat", "snake"],
# :gender=>["male", "female", "male"]}
few more approaches
data.each_with_object({}){ |i,r| i.each{ |k,v| (r[k] ||= []) << v } }
data.flat_map(&:to_a).each_with_object({}){ |(k,v), r| (r[k] ||= []) << v }
data.flat_map(&:to_a).group_by(&:first).inject({}){ |r, (k,v)| r[k] = v.map(&:last); r }

ruby sort hash based on another hash

I have the following two hashes:
db = {"1" => "first_name", "2" => "last_name", "5" => "status", "10" => "city" }
csv = {"1" => "first_name", "2" => "last_name", "5" => "status", "7" => "address", "10" => "city" }
I want to order csv based on db, and if there are any keys in csv not in db, then I want to move them to the end of csv, so in the above example the result would look like this:
{"1" => "first_name", "2" => "last_name", "5" => "status", "10" => "city", "7" => "address" }
Since the key "7" wasn't in db hash, we just moved it to the end of the csv hash.
This is what I tried:
db = {"1" => "first_name", "2" => "last_name", "5" => "status", "10" => "city" }
csv = {"1" => "first_name", "2" => "last_name", "5" => "status", "7" => "address", "10" => "city" }
rejects = csv.reject {|k| db.include? k }
result = csv.keep_if {|k,_| db.include? k }
result.merge!(rejects)
result
=> {"1"=>"first_name", "2"=>"last_name", "5"=>"status", "10"=>"city", "7"=>"address"}
It seems to work. But is it guaranteed to work? Will the merger always put the second hash at the end, or is there a possibility that the merger could mix the hashes together without consideration of order?
You could do the following:
db_keys = db.keys
#=> ["1", "2", "5", "10"]
keys = db_keys + (csv.keys-db_keys)
#=> ["1", "2", "5", "10", "7"]
Hash[keys.zip(csv.values_at(*keys))]
#=> { "1"=>"first_name", "2"=>"last_name", "5"=>"status",
# "10"=>"city", "7"=>"address"}

Resources