A Hash with many arrays - ruby

I am trying to define a structure that can create a one-to-many relationship, sort of. For example, let's say an organization named "ACO" has some stuff:
KEY_PERF_INDS = [ {'ACO' => [2,3,4] , [2,34,5]} ]
But this is syntactically wrong. Is it possible to write something that achieves this?

If your other groups HOSPITAL, BLAH, ETC (per the comments) are all to be at the same level as ACO, then the entire structure KEY_PERF_INDS should be a hash {} rather than an array []. Make each of those a key to the main hash, and each is an array containing sub-arrays.
# The main structure is a hash {}
KEY_PERF_INDS = {
'ACO' => [
[1,2,3],
[4,5,6]
],
'HOSPITAL' => [
[3,2,1],
[9,8,7]
],
'BLAH' => [
[99,88],
[11,22],
[33,44]
]
}
Access these then as:
KEY_PERF_INDS['HOSPITAL'][1][2]
# prints 7
KEY_PERF_INDS['BLAH'].last.first
# prints 33

Related

Difficulty when constructing a nested data structure

While trying to create a JSON message for an API, I found myself struggling to do something that I thought would be simple. I needed to create a message like the following:
{ "list": [ { "foo": 1, "bar": 2 } ] }
However, my first attempt did not work:
say to-json { foo => [ { a => 1, b => 2 } ] };
# {"foo":[{"a":1},{"b":2}]}
Trying to simplify things further confused me more:
say { foo => [ { a => 1 } ] };
# {foo => [a => 1]}
# Note that this is not JSON, but I expected to see curly braces
Then I tried to use some temporary variables, and that worked:
my #list = { a => 1 };
say to-json { foo => #list };
# {"foo":[{"a":1}]}
my %hash = ( a => 1 );
say to-json { foo => [ %hash ] };
# {"foo":[{"a":1}]}
What's going on here?
And is there a way I can achieve my desired output without an extra temporary variable?
You've discovered the single argument rule. Numerous constructs in Raku will iterate the argument they are provided with. This includes the [...] array composer. This is why when we say:
say [1..10];
We get an array that contains 10 elements, not 1. However, it also means that:
say [[1,2]];
Iterates the [1,2], and thus results in [1,2] - as if the inner array were not there. A Hash iterates to its pairs, thus:
{ foo => [ { a => 1, b => 2 } ] }
Actually produces:
{ foo => [ a => 1, b => 2 ] }
That is, the array has the pairs. The JSON serializer then serializes each pair as a one-element object.
The solution is to produce a single-element iterable. The infix , operator is what produces lists, so we can use that:
say to-json { foo => [ { a => 1, b => 2 }, ] };
# note the , here ^
Then the single argument to be iterated is a 1-element list with a hash, and you get the result you want.
Easy way to remember it: always use trailing commas when specifying the values of a list, array or hash, even with a single element list, unless you actually are specifying the single iterable from which to populate it.

How to get the right csv format from hash in ruby

Hash to csv
hash :
{
"employee" => [
{
"name" => "Claude",
"lastname"=> "David",
"profile" => [
"age" => "43",
"jobs" => [
{
"name" => "Ingeneer",
"year" => "5"
}
],
"graduate" => [
{
"place" => "Oxford",
"year" => "1990"
},
],
"kids" => [
{
"name" => "Viktor",
"age" => "18",
}
]
}
}]
this is an example of an hash I would work on. So, as you can see, there is many level of array in it.
My question is, how do I put it properly in a CSV file?
I tried this :
column_names = hash['employee'].first.keys
s=CSV.generate do |csv|
csv << column_names
hash['scrap'].each do |x|
csv << x.values
end
end
File.write('myCSV.csv', s)
but I only get name, lastname and profile as keys, when I would catch all of them (age, jobs, name , year, graduate, place...).
Beside, how can I associate one value per case?
Because I actually have all employee[x] which take a cell alone. Is there any parameters I have missed?
Ps: This could be the following of this post
A valid CSV output has a fixed number of columns, your hash has a variable number of values. The keys jobs, graduate and kids could all have multiple values.
If your only goal is to make a CSV output that can be read in Excel for example, you could enumerate your Hash, take the maximum number of key/value pairs per key, total it and then write your CSV output, filling the blank values with "".
There are plenty of examples here on Stack Overflow, search for "deep hash" to start with.
Your result would have a different number of columns with each Hash you provide it.
That's too much work if you ask me.
If you just want to present a readable result, your best and easiest option is to convert the Hash to YAML which is created for readability:
require 'yaml'
hash = {.....}
puts hash.to_yaml
employee:
- name: Claude
lastname: David
profile:
- age: '43'
jobs:
- name: Ingeneer
year: '5'
graduate:
- place: Oxford
year: '1990'
kids:
- name: Viktor
age: '18'
If you want to convert the hash to a CSV file or record, you'll need to get a 'flat' representation of your keys and values. Something like the following:
h = {
a: 1,
b: {
c: 3,
d: 4,
e: {
f: 5
},
g: 6
}
}
def flat_keys(h)
h.keys.reject{|k| h[k].is_a?(Hash)} + h.values.select{|v| v.is_a?(Hash)}.flat_map{|v| flat_keys(v)}
end
flat_keys(h)
# [:a, :c, :d, :g, :f]
def flat_values(h)
h.values.flat_map{|v| v.is_a?(Hash) ? flat_values(v) : v}
end
flat_values(h)
# [1, 3, 4, 5, 6]
Then you can apply that to create a CSV output.
It depends on how those fields are represented in the database.
For example, your jobs has a hash with name key and your kids also has a hash with name key, so you can't just 'flatten' them, because keys have to be unique.
jobs is probably another model (database table), so you probably would have to (depending on the database) write it separately, including things like the id of the related object and so on.
Are you sure you're not in over your head? Judging from your last question and because you seem to treat csv's as simple key-values pair omitting all the database representation and relations.

How to convert an array to a hash with array elements as hash keys and all hash values all set to given value

Using Ruby 2.1 (with ActiveSupport 3.x, if that helps), I want to convert an array like this:
[ :apples, :bananas, :strawberries ]
Into a hash like this:
{ :apples => 20, :bananas => 20, :strawberries => 20 }
Technically, this works:
array = [ :apples, :bananas, :strawberries ]
hash = Hash[array.zip(Array.new(array.length, 20))]
# => {:apples=>20, :bananas=>20, :strawberries=>20}
But that seems really clunky, and I feel like there's a more straightforward way to do this. Is there one?
I looked at Enumerable#zip as well as the default value option for Hash#new but didn't see anything providing a simple method for this conversion.
Another answer:
ary = [ :apples, :bananas, :strawberries ]
Hash[[*ary.each_with_object(20)]]
# => {:apples=>20, :bananas=>20, :strawberries=>20}
Alternatively (as pointed out by the OP):
ary.each_with_object(20).to_h
# => {:apples=>20, :bananas=>20, :strawberries=>20}
Basically, calling each_with_object returns an Enumerator object of pairs consisting of each value and the number 20 (i.e. [:apples, 20], ...) which can subsequently be converted to a hash.
Use Hash[]:
Hash[array.map { |f| [f, 20] }]
I think, Array#product will be helpful here :
ary = [ :apples, :bananas, :strawberries ]
Hash[ary.product([20])]
# => {:apples=>20, :bananas=>20, :strawberries=>20}

Mapping two dimensional array to one dimensional array with indexes

I have 2d array like this:
ary = [
["Source", "attribute1", "attribute2"],
["db", "usage", "value"],
["import", "usage", "value"],
["webservice", "usage", "value"]
]
I want to pull out the following in hash:
{1 => "db", 2 => "import", 3 => "webservice"} // keys are indexes or outer 2d array
I know how to get this by looping trough 2d array. But since I'm learning ruby I thought I could do it with something like this
ary.each_with_index.map {|element, index| {index => element[0]}}.reduce(:merge)
This gives me :
{0=> "Source", 1 => "db", 2 => "import", 3 => "webservice"}
How do I get rid of 0 element from my output map?
I'd write:
Hash[ary.drop(1).map.with_index(1) { |xs, idx| [idx, xs.first] }]
#=> {1=>"db", 2=>"import", 3=>"webservice"}
ary.drop(1) drops the first element, returns the rest.
You could build the hash directly without the merge reduction using each_with_object
ary.drop(1)
.each_with_object({})
.with_index(1) { |((source,_,_),memo),i| memo[i] = source }
Or map to tuples and send to the Hash[] constructor.
Hash[ ary.drop(1).map.with_index(1) { |(s,_,_),i| [i, s] } ]

Creating array of hashes in ruby

I want to create an array of hashes in ruby as:
arr[0]
"name": abc
"mobile_num" :9898989898
"email" :abc#xyz.com
arr[1]
"name": xyz
"mobile_num" :9698989898
"email" :abcd#xyz.com
I have seen hash and array documentation. In all I found, I have to do something
like
c = {}
c["name"] = "abc"
c["mobile_num"] = 9898989898
c["email"] = "abc#xyz.com"
arr << c
Iterating as in above statements in loop allows me to fill arr. I actually rowofrows with one row like ["abc",9898989898,"abc#xyz.com"]. Is there any better way to do this?
Assuming what you mean by "rowofrows" is an array of arrays, heres a solution to what I think you're trying to accomplish:
array_of_arrays = [["abc",9898989898,"abc#xyz.com"], ["def",9898989898,"def#xyz.com"]]
array_of_hashes = []
array_of_arrays.each { |record| array_of_hashes << {'name' => record[0], 'number' => record[1].to_i, 'email' => record[2]} }
p array_of_hashes
Will output your array of hashes:
[{"name"=>"abc", "number"=>9898989898, "email"=>"abc#xyz.com"}, {"name"=>"def", "number"=>9898989898, "email"=>"def#xyz.com"}]
you can first define the array as
array = []
then you can define the hashes one by one as following and push them in the array.
hash1 = {:name => "mark" ,:age => 25}
and then do
array.push(hash1)
this will insert the hash into the array . Similarly you can push more hashes to create an array of hashes.
You could also do it directly within the push method like this:
First define your array:
#shopping_list_items = []
And add a new item to your list:
#shopping_list_items.push(description: "Apples", amount: 3)
Which will give you something like this:
=> [{:description=>"Apples", :amount=>3}]

Resources