How do I convert a Ruby hash so that all of its keys are strings - ruby

I have a ruby hash which looks like:
{ id: 123, name: "test" }
I would like to convert it to:
{ "id" => 123, "name" => "test" }

If you are using Rails or ActiveSupport:
hash = { id: 123, description: "desc" }
hash.stringify #=> { "id" => 123, "name" => "test" }
If you are not:
hash = { id: 123, name: "test" }
Hash[hash.map { |key, value| [key.to_s, value] }] #=> { "id" => 123, "name" => "test" }

I love each_with_object in this case :
hash = { id: 123, name: "test" }
hash.each_with_object({}) { |(key, value), h| h[key.to_s] = value }
#=> { "id" => 123, "name" => "test" }

In pure Ruby (without Rails), you can do this with a combination of Enumerable#map and Array#to_h:
hash = { id: 123, name: "test" }
hash.map{|key, v| [key.to_s, v] }.to_h

{ id: 123, name: "test" }.transform_keys(&:to_s)
#=> {"id"=>123, "name"=>"test"}
See Hash#transform_keys.

Related

How to concatenate differents values for one keys

I have a trouble for several days and I need help to solve it.
I have an hash with multiple values for the same key :
{"answers":
[
{"id":1,"value":true},
{"id":3,"value":false},
{"id":2,"value":3},
{"id":1,"value":false},
{"id":2,"value":false},
{"id":2,"value":1}
]
}
I want a method to group all the values for one key, as an exemple :
{
"answers": [
{
"id":1, "value": [true, false]
},
{
"id":3, "value": [false]
},
{
"id":2, "value":[3, false, 1]
}
]
}
I've tried with the reduce method, but I cant find a way to link values to keys.
Anyone can help me with that ?
Thanks!
It looks like you want Enumerable#group_by to regroup the array of hashes by the :id key in each hash.
This method takes the answers array and returns a new, transformed answers array:
def transform_answers(answers)
answers
.group_by { |h| h[:id] }
.each_value { |a| a.map! { |h| h[:value] } }
.map { |id, value| { id: id, value: value } }
end
You can use it like this:
hash = {
answers: [
{ id: 1, value: true },
{ id: 1, value: false },
{ id: 2, value: 3 },
{ id: 2, value: false },
{ id: 2, value: 1 },
{ id: 3, value: false }
]
}
transformed_answers = transform_answers(hash[:answers]) # => [{:id=>1, :value=>[true, false]}, {:id=>2, :value=>[3, false, 1]}, {:id=>3, :value=>[false]}]
You can easily take the transformed answers and put them back into a hash resembling the original input:
transformed_hash = { answers: transformed_answers }
hash = {
answers: [
{ id: 1, value: true },
{ id: 1, value: false },
{ id: 2, value: 3 },
{ id: 2, value: false },
{ id: 2, value: 1 },
{ id: 3, value: false }
]
}
def doit(answers)
answers.each_with_object({}) do |g,h|
h.update(g[:id]=>{ id: g[:id], value: [g[:value]] }) do |_,o,n|
{ id: o[:id], value: o[:value]+n[:value] }
end
end.values
end
{ answers: doit(hash[:answers]) }
#=> {:answers=>[
# {:id=>1, :value=>[true, false]},
# {:id=>2, :value=>[3, false, 1]},
# {:id=>3, :value=>[false]}
# ]
# }
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged. That block is
do |_k,o,n|
{ id: o[:id], value: o[:value]+n[:value] }
end
See the doc for update for definitions of the three block variables, _k, o and n. I've written the first block variable (the common key) _k, rather than k, to signify that it is not used in the block calculation.
Note that before values is executed in doit the method has constructed the following hash.
{1=>{:id=>1, :value=>[true, false]},
2=>{:id=>2, :value=>[3, false, 1]},
3=>{:id=>3, :value=>[false]}}

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 }

Grouping hashes together to concat a key's values

I have an array of hashes like the following:
records = [
{ id: "PU525", note: "Foo" },
{ id: "PU525", note: "Bar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
The end result should be:
result = [
{ id: "PU525", note: "FooBar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
I started playing a little bit with Enumerable#group_by with the following:
results = records.group_by { |record| record[:id] }
# Results in:
# {
# "PU525": [
# { id: "PU525", note: "Foo" },
# { id: "PU525", note: "Bar" }
# ],
# "DT525": { id: "DT525", note: "Hello World" },
# "PU680": { id: "PU680", note: "Fubar" }
# }
The next step would be to use inject to reduce the data down further, but I'm wondering if there's an easier way to reduce down the original array to what I'm looking for as a result without so many steps?
You are nearly done:
records = [
{ id: "PU525", note: "Foo" },
{ id: "PU525", note: "Bar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
arr = records.group_by { |record| record[:id] }
.map do |k, v|
[k, v.reduce('') { |memo, h| memo + h[:note] } ]
end
.map do |a|
{ id: a.first, note: a.last }
end
#⇒ [
# { id: "PU525", note: "FooBar" },
# { id: "DT525", note: "Hello World" },
# { id: "PU680", note: "Fubar" }
# ]
UPD After all, you have me entagled with this silly group_by approach. Everything is just easier.
records.inject({}) do |memo, el|
(memo[el[:id]] ||= '') << el[:note]
memo
end.map { |k, v| { id: k, note: v } }
Here is another way, after using #group_by :
records = [
{ id: "PU525", note: "Foo" },
{ id: "PU525", note: "Bar" },
{ id: "DT525", note: "Hello World" },
{ id: "PU680", note: "Fubar" }
]
hsh = records.group_by { |record| record[:id] }.map do |_, v|
v.inject do |h1, h2|
h1.update(h2) { |k, o, n| k == :note ? o << n : n }
end
end
p hsh
# >> [{:id=>"PU525", :note=>"FooBar"}, {:id=>"DT525", :note=>"Hello World"}, {:id=>"PU680", :note=>"Fubar"}]
Here are a couple of variants of the above.
Using Enumerable#group_by
records.group_by { |h| h[:id] }.map { |id, arr|
{ id: id, note: arr.map { |h| h[:note] }.join } }
#=> [{:id=>"PU525", :note=>"FooBar"},
# {:id=>"DT525", :note=>"Hello World"},
# {:id=>"PU680", :note=>"Fubar"}]
Using Hash#update (aka merge!)
records.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,og,ng|
{ id: g[:id], note: og[:note]+ng[:note] } } }.values
#=>[{:id=>"PU525", :note=>"FooBar"},
# {:id=>"DT525", :note=>"Hello World"},
# {:id=>"PU680", :note=>"Fubar"}]

What is a good way to sort an array by attribute that is not naturally ordered in Ruby?

array = [{ name:'Joe', class:'foo' },
{ name:'Bob', class:'bar' },
{ name:'Hal', class:'baz' },
{ name:'Kim', class:'qux' },
{ name:'Zoe', class:'bar' }
]
What is a good way to sort by class in the following order: qux, bar, foo, baz?
Like this, for example:
array = [{ name:'Joe', class:'foo' },
{ name:'Bob', class:'bar' },
{ name:'Hal', class:'baz' },
{ name:'Kim', class:'qux' },
{ name:'Zoe', class:'bar' }
]
order = %w[qux bar foo baz]
sorted = array.sort_by{|el| order.index(el[:class])}
sorted # => [{:name=>"Kim", :class=>"qux"},
# {:name=>"Bob", :class=>"bar"},
# {:name=>"Zoe", :class=>"bar"},
# {:name=>"Joe", :class=>"foo"},
# {:name=>"Hal", :class=>"baz"}]
order = %w[qux bar foo baz]
array.sort_by{|h| order.index(h[:class])}
gives:
[
{
:name => "Kim",
:class => "qux"
},
{
:name => "Bob",
:class => "bar"
},
{
:name => "Zoe",
:class => "bar"
},
{
:name => "Joe",
:class => "foo"
},
{
:name => "Hal",
:class => "baz"
}
]

Parse Array hashes in new object with ruby

I am struggling with some arrays with hashes inside. I want to parse them into a new object but have no idea how to do this.
Here is the data:
[
{
"name" => "itemHref",
"value" => "https://192.168.75.145:8281/api/workflows/16da1fa1-7c8b-4602-8d53-17fc5e1fa3ff/"
},
{
"name" => "id",
"value" => "16da1fa1-7c8b-4602-8d53-17fc5e1fa3ff"
},
{
"name" => "categoryName",
"value" => "FinanzInformatik"
},
{
"name" => "canExecute",
"value" => "true"
},
{
"name" => "categoryHref",
"value" => "https://192.168.75.145:8281/api/catalog/System/WorkflowCategory/ff8080813b90a145013b90cac51b0006/"
},
{
"name" => "description",
"value" => "bekommt alle VMs"
},
{
"name" => "name",
"value" => "getAllVms"
},
{
"name" => "type",
"value" => "Workflow"
},
{
"name" => "canEdit",
"value" => "true"
}
]
And, here is my code:
require 'rest-client'
require 'json'
class Workflow
def initialize(itemHref, id, categoryName, canExecute, categoryHref, description, name, type, canEdit)
#itemHref = itemHref
#id = id
#categoryName = categoryName
#canExecute = canExecute
#categoryHref = categoryHref
#description = description
#name = name
#type = type
#canEdit = canEdit
end
end
json_string = RestClient.get( "http://vcoadmin:vcoadmin#192.168.75.145:8280/api/workflows", :content_type => 'application/json', :accept => 'application/json')
parsed = JSON.parse(json_string)
parsed.each do |a, b|
if(b.class == Array)
b.flatten.each do |c|
p c['attributes']
#c['attributes'].each
{
|f| p f['name'], f['value'] }
end
end
end
How do I put the hash value into the object? I think about something based on the 'name' which is the identifier for the value.
Any ideas?
Assuming that the order of attributes shouldn't be changed:
Workflow.new(*parsed.map {|attr| attr['value']})
I would implement a PORO that can be initialized with a hash. So then you are able to pass your hash directly in to creating the workflow.
An example of this is can be seen: http://pullmonkey.com/2008/01/06/convert-a-ruby-hash-into-a-class-object/

Resources