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.
Related
The following only prints white lines. If this isn't the way to retrieve values from keys in Ruby, what is?
numbers = []
for i in 1..100 do
hash = {
:FizzBuzz => 1,
:Prime => 3,
:Fibonacci => 5
}
numbers << { i => hash }
end
numbers.each do |number|
puts number[:Prime]
end
Note this is a MCVE, in the final application 1, 3 and 5 will be function calls.
Try it
For those wondering what I was trying to do, the final (non-MCVE) result can be found on Code Review.
After the first loop, numbers is an array like this:
[
{ 1 => { :FizzBuzz => 1, :Prime => 3, :Fibonacci => 5 } },
{ 2 => { :FizzBuzz => 1, :Prime => 3, :Fibonacci => 5 } },
...
{ 100 => { :FizzBuzz => 1, :Prime => 3, :Fibonacci => 5 } }
]
number[:Prime] is trying to fetch the :Prime element out of your first array element, but you only have the key 1.
The end result is, you print empty lines, as nothing is found at each iteration. Hash access is okay; it is the logic of your code that is the problem (and you did not explain what you are trying to do precisely).
Each number is a hash of this form:
{1=>{:FizzBuzz=>1, :Prime=>3, :Fibonacci=>5}}
which has a number as the sole key. When you look for a hash key that does not exist using Hash#[], Ruby returns nil. And puts nil prints a blank line.
Indeed Hash#[] is the way to retrieve values from a hash.
The keys of number are all integers (coming from i). The symbol :Prime is not a key. Therefore the results are all nil.
This question already has answers here:
Converting a nested hash into a flat hash
(8 answers)
Closed 8 years ago.
Here is a structure of hash of arrays:
[
{
"key1" => [
"value1",
{"key2" => ["value2"]},
{"key3" => [
"value3",
{
"key4" => "value4"
}
]
}
]
},
{
"anotherKey1" => [],
}
]
I want desired output for that structure like filepaths:
/key1/value1
/key1/key2/value2
/key3/value3
/key3/key4/value4
How can I do that without inventing a wheel? Simple recursion could help, but is there any ready-to-go modules?
I do not think you would be reinventing any wheels to do this. You would like to traverse a nested structure of arrays and hashes and react completely different to the elements depending on whether something is an Array or a Hash. No library function is going to do exactly that for you, as you would need to vary more than one thing with blocks in order to be as flexible as you might like to be.
In short: write your recursive function to do this.
(Btw: The top level of your data structure is an array of hashes, not a hash of arrays …)
I decided to write my own wheel (thanks for Patru, vote up).
And I have this function:
def flat_hash_of_arrays(hash,string = "",delimiter="/",result = [])
# choose delimiter
hash.each do |key,value|
# string dup for avoid string-reference (oh, Ruby)
newString = string + delimiter + key
# if value is array
if value.is_a?(Array)
# if array not empty
value.each do |elementOfArray|
# if a string, I dont need recursion, hah
if elementOfArray.is_a?(String)
resultString = newString + delimiter + elementOfArray
# add new object
result << resultString
end
# if a hash, I need recursion
if elementOfArray.is_a?(Hash)
flat_hash_of_arrays(elementOfArray,newString,delimiter,result)
end
end
end
end
end
and test it:
flatten_hash = {
"key1" => [
"value1",
{"key2" => ["value2"]},
{"key3" => [
"value3",
{
"key4" => "value4"
}
]
},
"value4",
{
"key4" => ["value5"],
}
]
}
result = []
flat_hash_of_arrays(flatten_hash,"","/",result)
puts result
output is:
/key1/value1
/key1/key2/value2
/key1/key3/value3
/key1/value4
/key1/key4/value5
fine!
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] } ]
I have an array of objects that has been sorted according to several properties of these objects. In order of priority, these properties are foo, bar and baz. This means that the objects are first sorted by foo; then subsequences having the same foo value are sorted by bar; and then those with the same foo and bar values are sorted by baz.
I would like to turn this into a nested hash that reflects this grouping. Basically I'm looking for a recursive Enumerable#group_by. The keys would be values of foo, bar, and baz; the values would be either sub-hashes or arrays of the objects. Here is an example:
[obj1, obj2, ... objn].group_by_recursive(:foo, :bar, :baz)
#=> {
foo_val_1 => {
bar_val_1 => {
baz_val_1 => [
obj1,
obj2,
obj3
],
baz_val_2 => [
obj4,
obj5
]
},
bar_val_2 => {
baz_val_1 => [
obj6,
obj7
],
baz_val_2 => [
obj8
]
},
},
foo_val_2 => {
...
},
...
}
Came up with a pretty good solution. Monkey-patch Enumerable like this:
module Enumerable
def group_by_recursive(*props)
groups = group_by(&props.first)
if props.count == 1
groups
else
groups.merge(groups) do |group, elements|
elements.group_by_recursive(*props.drop(1))
end
end
end
end
The properties you pass can be either Procs or Symbols
Similar to Sean's and lacking error handling ...
class Array
def nested_group_by(*keys)
return self if keys.length == 0
groups = group_by(&keys.shift)
Hash[groups.map { | k, v | [k, v.nested_group_by(*keys)] }]
end
end
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