Combine the values of two hash keys in an array of hashes - ruby

I have an array of hashes:
a = [{"ID"=>"FOO", "Type"=>"Name"}, {"ID"=>"1234", "Type"=>"CID"}]
I'm trying to extract the hash where the Type=='CID', then combine the two values to result in CID=1234.
I can do this in multiple steps:
h = a.find{|x| x['Type']=='CID'}
# => {"ID"=>"1234", "Type"=>"CID"}
"#{h['Type']}=#{h['ID']}"
# => "CID=1234"
Is there a way to do this in a one liner?

a.find { |h| h["Type"] == "CID" }&.values_at("Type", "ID")&.join("=")
#=>"CID=1234"
a.find { |h| h["Type"] == "cat" }&.values_at("Type", "ID")&.join("=")
#=> nil
& is Ruby's safe navigation operator, which made it's debut in Ruby v2.3. I added it to cause nil to be returned if there is no match on h["Type"].

You can do it in one line using:
a.select{|x| x['Type']=='CID'}
.map{|x| "type=#{x['Type']},id=#{x['ID']}"}[0]

You may try this:
if we are not having multiple values of Type = "CID":
a.select{|x| x["Type"] == "CID"}.map{|x| x.values_at("Type", "ID")}.join("=")
if we have are having Type="CID"
a.detect{|x| x["Type"]=="CID"}.values_at("Type", "ID").join("=")
If we don't have Type="CID" in above array will throw an error, be cautious.
Need to work in all cases we need to do:
a.detect{|x| x["Type"]=="CID"}.values_at("Type", "ID").join("=") if a.detect{|x| x["Type"]=="CID"}

Related

How to remove duplicate pair values from given array in Ruby?

I want to remove a pair of 'duplicates' from an array of strings, where each element has the form R1,R2, with varying numbers. In my case, a duplicate would be R2,R1 because it has the same elements of R1,R2 but inverted.
Given:
a = ['R1,R2', 'R3,R4', 'R2,R1', 'R5,R6']
The resulting array should be like so:
a = ['R1,R2', 'R3,R4', 'R5,R6']
How could I remove the duplicates so I would have the following?
A solution with Set
require 'set'
a.uniq { |item| Set.new(item.split(",")) } # => ["R1,R2", "R3,R4", "R5,R6"]
Here is a working example :
array = ['R1,R2', 'R3,R4', 'R2,R1', 'R5,R6']
array.uniq { |a| a.split(',').sort }
try this,
def unique(array)
pure = Array.new
for i in array
flag = false
for j in pure
flag = true if (j.split(",").sort == i.split(",").sort)
end
pure << i unless flag
end
return pure
end
reference: https://www.rosettacode.org/wiki/Remove_duplicate_elements#Ruby
If the elements of your array are "pairs", they should maybe be actual pairs and not strings, like this:
pairs = [['R1', 'R2'], ['R3', 'R4'], ['R2', 'R1'], ['R5', 'R6']]
And, in fact, since order doesn't seem to matter, it looks like they really should be sets:
require 'set'
sets = [Set['R1', 'R2'], Set['R3', 'R4'], Set['R2', 'R1'], Set['R5', 'R6']]
If that is the case, then Array#uniq will simply work as expected:
sets.uniq
#=> [#<Set: {"R1", "R2"}>, #<Set: {"R3", "R4"}>, #<Set: {"R5", "R6"}>]
So, the best way would be to change the code that produces this value to return an array of two-element sets.
If that is not possible, then you should transform the value at your system boundary when it enters the system, something like this:
sets = a.map {|el| el.split(',') }.map(&Set.method(:new))

Unpacking a Hash in ruby

I wonder if there is any method which can unpack the hashes inside an array. For example
array = [{:code=>"404"}, {:code=>"302"}, {:code=>"200"}]
After unpacking it should give
array = [code: "404", code: "302", code: "200"]
I have tried using flat_map as follows
array.flat_map { |h| h.values } & array.flat_map { |h| h.keys }
But these are 2 separate arrays.
For your updated version of the question, the answer's very straightforward:
array.reduce(&:merge)
reduce is a method to iterate through an object and accumulate the values in some way. Using &:merge is shorthand for merging your hashes together.
The result of this gives you:
# => {:code1=>"404", :code2=>"302", :code3=>"200"}
Your expected output [code: "404", code: "302", code: "200"] is not a valid ruby to declare an array having three elements. The actual outcome from the above would be an array having one single element:
[code: "404", code: "302", code: "200"]
(pry):39: warning: key :code is duplicated and overwritten on line 39
(pry):39: warning: key :code is duplicated and overwritten on line 39
#⇒ [{ :code => "200" }]
That is because one might omit curly brackets around hash when it introduces no ambiguity.
The best you can get is:
array.each_with_object({}) do |h, acc|
acc.merge!(h) { |_, v1, v2| [*v1, v2] }
end
#⇒ {:code=>["404", "302", "200"]}
Here you go:
array = [{:code=>"404"}, {:code=>"302"}, {:code=>"200"}]
[array.last] # => [{:code=>"200"}]
Now that you have changed your question, it has become clear that you basically just want to merge all the hashes into one hash. You could do it with this simple loop:
m = {}
array.each do |h|
m.merge!(h)
end
Ruby also provides a fancy way to do the same thing in one line:
array.each_with_object({}) { |h, m| m.merge!(h) }
Oh yeah, I almost forgot. You wrote your desired output with square brackets, which technically means that you are asking for an array that contains a hash. The code above just gives you a simple hash. In Ruby, we write arrays with square brackets and hashes with curly brackets, but the curly ones can be omitted in some cases. There is no obvious reason to throw your hash into an array that just has one element, but if you really want to do that, then you can of course do it like this, assuming m holds the merged hash:
weird_array = [m]

Ruby filtering array of hash

I have the following array of arguments that has a hash with the args keys and their relevant passed values:
args_array = [{:collection=>["abe", "<mus>", "hest"], :include_blank=>true, :required=>true}]
I need to filter this hash to just get only some keys (like :include_blank and :required) in this example, with their relevant values like so:
filtered_args_hash = {:include_blank=>true, :required=>true}
So the collection arg is excluded, and just the include_blank, and required in which I specified are the ones returned.
Update:
Here is a keys array of symbols in which I need to filter according to them:
filter_keys = %i(include_blank required)
How I can do that?
One way is to use Hash#keep_if:
args_array[0].dup.keep_if {|k| filter_keys.include? k}
# => {:include_blank=>true, :required=>true}
Note that .dup is here to prevent args_array from being modified, don't use it if you do want args_array to be updated.
The following should do solve your problem:
args_array.collect {|a| a if a[:include_blank] == true && a[:required=>true] == true }.compact
or
dup_list = args_array.dup
dup_list.delete_if {|a| !(a[:include_blank] == true && a[:required=>true] == true) }
args_array.map do |elem|
elem.select do |k, _|
%i(include_blank required).include? k
end
end
You tagged as Rails so you can use Hash#slice:
args_array[0].slice *filter_keys
# => {:include_blank=>true, :required=>true}

Ruby Array of hash, and map function

I have an array of hashes, like this:
my_array = [{foo:1,bar:"hello",baz:3},{foo:2,bar:"hello2",baz:495,foo_baz:"some_string"},...]
#there can be arbitrary many in this list.
#There can also be arbitrary many keys on the hashes.
I want to create a new array that is a copy of the last array, except that I remove any :bar entries.
my_array2 = [{foo:1,baz:3},{foo:2,baz:495,foo_baz:"some_string"},...]
I can get the my_array2 by doing this:
my_array2 = my_array.map{|h| h.delete(:bar)}
However, this changes the original my_array, which I want to stay the same.
Is there a way of doing this without having to duplicate my_array first?
one of many ways to accomplish this:
my_array2 = my_array.map{|h| h.reject{|k,v| k == :bar}}
my_array.map {|h| h.select{|k, _| k != :bar} }
# => [{:foo=>1, :baz=>3}, {:foo=>2, :baz=>495, :foo_baz=>"some_string"}]

How to remove duplicates from array of hashes based on keys only?

How would you go about removing duplicates based on the key?
values = [{"a"=>"1"}, {"a"=>"2"}, {"b"=>"1"}, {"a"=>"4"}]
How can I ignore the value and run uniq based on key so that it returns:
[{'a' => '1'}, {'b' => '1'}]
Assuming you don't care which value gets clobbered, just run them into a hash (which does have unique keys and is therefore probably the right collection class for this case):
h = {}
values.each{|i|i.each{|k,v|h[k] = v}}
puts h # => {"a"=>"4", "b"=>"1"}
... or if you want the first of each key:
h = {}
values.each{|i|i.each{|k,v|h[k] = v unless h[k]}}
If you want to get back to a Array:
h.each{|k,v|a << {k=>v}}
The following will work only in ruby 1.9, so it might be useless.
Hash[values.map(&:first).reverse].map{|a| Hash[*a]}
If you need it in the original order,
values & Hash[values.map(&:first).reverse].map{|a| Hash[*a]}
Without introducing any intermediate variables the following 1 liner will do the trick:
> [{"a"=>"1"}, {"a"=>"2"}, {"b"=>"1"}, {"a"=>"4"}].inject({}) {|s,h| s.merge(h) unless s.keys.include? h.keys}.inject([]) {|s,h| s << {h[0]=>h[1]}}
=> [{"a"=>"4"}, {"b"=>"1"}]

Resources