How can I double loop using each if I have a structure like this:
Termin 1
[ [ 1][2 ][3 ] ]
Termin 2
[ [1 ] [2 ] [3] ]
Termin.each(){
println("first");
it.each(){
println("second"); // 1 2 3
}
}
it is used when you don't define the attribute name. You can just change the name:
def nested = [[1],[2],[3]]
nested.each { n ->
n.each { s ->
print "Nested: $s \n"
}
}
UPDATE
it is implicit to the wrapped closure, so if you are fluent with Groovy semantics, you can also use
def nested = [[1],[2],[3]]
nested.each {
// `it` is meant for the nested.each{}
it.each {
// `it` is meant for the it.each{}
print "Nested: $it \n"
}
}
Both of the approach yield the same result.
Related
I have an array of hashes:
[
{
"June" => { "A" => { 3 => 48.4 } }
},
{
"January" => { "C" => { 2 => 88.0} }
},
{
"January"=> { "B" => { 2 => 44.0} }
},
{
"January"=> { "C" => { 4 => 48.8} }
}
]
I need to group each similar hash key into an array of the subsequent values like the following:
{
"June" => [{ "A" => [{ 3 => 48.4 }]] },
"January" => [
{ "B" => [{ 2 => 44.0}],
{ "C" => [{ 2 => 88.0}, { 4 => 48.8}],
] }
}
I am looking for an efficient method of grouping these elements. Can anyone help me master this hash of hashes?
I am trying to avoid looping through the base array and grouping manually. I was hoping that map (or some other enumerable method) might give what I want. When I used reduce(Hash.new, :merge), it came close but it used the last hash for each month key instead of adding it to an array.
Note: I added the following after gaining a clearer understanding of the question. My original answer is below.
Here is the OP's array of hashes, modified slightly.
arr = [{ "June" =>{ "A"=>{ 3=>48.4 } } },
{ "January"=>{ "C"=>{ 2=>88.0 } } },
{ "January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } } },
{ "January"=>{ "C"=>{ 2=>10.0 } } },
{ "January"=>{ "C"=>{ 4=>48.8 } } }]
The hash to be constructed appears to be the following.
{ "June" =>[{ "A"=>[{ 3=>48.4 }] }],
"January"=>[{ "B"=>[{ "D"=>[{ 2=>44.0 }] }] }],
"C"=>[{ 2=>98.0, 4=>48.8 }] }] }
Note that 88.0 + 10.0 #=> 98.0 in 2=>98.0.
Observe that all the arrays within arr contain a single element, a hash. That being the case, those arrays serve no useful purpose. I therefore suggest the following hash be constructed instead:
{ "June" =>{ "A"=>{ 3=>48.4 } },
"January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } },
"C"=>{ 2=>98.0, 4=>48.8 } } }
This can be produced with the following recursive method.
def recurse(arr)
arr.map(&:flatten).
group_by(&:first).
each_with_object({}) do |(k,v),h|
o = v.map(&:last)
h.update(k=>o.first.is_a?(Hash) ? recurse(o) : o.sum )
end
end
recurse(arr)
#=> {"June"=>{"A"=>{3=>48.4}},
# "January"=>{"C"=>{2=>98.0, 4=>48.8}, "B"=>{"D"=>{2=>44.0}}}}
(Original answer follows)
Here are two ways to obtain the desired hash. I assume that arr is your array of hashes.
#1 Use the form of Hash::new that takes a block
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
k, v = g.to_a.first
h[k] << v
end
# => {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}
#2 Use Enumerable#group_by
arr.map(&:first).
group_by(&:first).
tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
# => {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}
The steps are as follows.
a = arr.map(&:first)
#=> [["June", {"A"=>{3=>48.4}}], ["January", {"C"=>{2=>88.0}}],
# ["January", {"B"=>{2=>44.0}}], ["January", {"C"=>{4=>48.8}}]]
b = a.group_by(&:first)
#=> {"June"=>[["June", {"A"=>{3=>48.4}}]],
# "January"=>[["January", {"C"=>{2=>88.0}}], ["January", {"B"=>{2=>44.0}}],
# ["January", {"C"=>{4=>48.8}}]]}
c = b.tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
#=> {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{=>48.8}}]}
Let me elaborate the last step. Inside tap's block, we compute the following.
h = b
d = h.keys
#=> ["June", "January"]
The first element of d is passed to each's block and the block variable is assigned to that element.
k = d.first
#=> "June"
The block calculation is as follows.
e = h[k]
#=> [["June", {"A"=>{3=>48.4}}]]
f = e.map(&:last)
#=> [{"A"=>{3=>48.4}}]
h[k] = f
#=> [{"A"=>{3=>48.4}}]
b #=> {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[["January", {"C"=>{2=>88.0}}],
# ["January", {"B"=>{2=>44.0}}],
# ["January", {"C"=>{4=>48.8}}]]}
Next, d[1] ("January") is passed to each's block and similar calculations are performed.
Rather than using Object#tap I could have written
h = arr.map(&:first).
group_by(&:first)
h.keys.each { |k| h[k] = h[k].map(&:last) }
h
tap merely avoids the creation of local variable h and the need to have a final line equal to h.
I have two arrays of hashes:
a = [
{
key: 1,
value: "foo"
},
{
key: 2,
value: "baz"
}
]
b = [
{
key: 1,
value: "bar"
},
{
key: 1000,
value: "something"
}
]
I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.
Here's the expected result:
expected = [
{
key: 1,
value: "bar"
},
{
key: 2,
value: "baz"
},
{
key: 1000,
value: "something"
}
]
I got it to work but I was wondering if there's a less wordy way of doing this:
hash_result = {}
a.each do |item|
hash_result[item[:key]] = item[:value]
end
b.each do |item|
hash_result[item[:key]] = item[:value]
end
result = []
hash_result.each do |k,v|
result << {:key => k, :value => v}
end
puts result
puts expected == result # prints true
uniq would work if you concatenate the arrays in reverse order:
(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]
It doesn't however preserve the order.
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
.reduce(&:merge)
.flat_map(&:last)
Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.
I would rebuild your data a bit, since there are redundant keys in hashes:
thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
Then you can use just Hash#merge:
thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}
But, if you want, you can get exactly result as mentioned in question:
result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"},
# {:key=>2, :value=>"baz"},
# {:key=>1000, :value=>"something"}]
using Enumerable#group_by and Enumerable#map
(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:
[a, b].flatten
.compact
.group_by { |v| v[:key] }
.values
.map { |e| e.reduce(&:merge) }
I have a nested field with arrays in array in JSON like the following:
{
"foo": {
"bar": [
[
"a",
"b"
],
[
"c",
"d"
]
]
}
}
The following is my config file:
input {
file {
codec => "json"
path => "pathtofile"
type => "footype"
start_position => "beginning"
}
}
filter {
json {
source => "message"
remove_field => [ "host", "message", "path" ]
}
}
output {
elasticsearch {
action => "index"
index => "bar"
hosts => [ "http://localhost:9200" ]
}
}
I got the following error:
09:40:47.725 [[main]>worker0] WARN logstash.outputs.elasticsearch -
Failed action. {:status=>400, :action=>["index", {:_id=>nil,
:_index=>"bar", :_type=>"footype", :_routing=>nil},
2017-02-13T01:40:30.387Z myconnection %{message}],
:response=>{"index"=>{"_index"=>"bar", "_type"=>"footype",
"_id"=>"AVo1IN0vK2jgwdCXqZ-q", "status"=>400,
"error"=>{"type"=>"illegal_argument_exception", "reason"=>"mapper
[foo.bar] of different type, current_type [long], merged_type
[text]"}}}}
I have a feeling that it's the array problem. I have done some research and know that array is not well supported. But I need to ingest the array in elasticsearch. Is there a way to actually do that?
Any helps will be appreciated.
I solved this by using a ruby filter:
ruby {
code => '
j = 0
for i in event.get("[foo][bar]") do
#i is an array element in the big array
l = 0
for k in i do
event.set("item#" + j.to_s + "#" + l.to_s, k)
l = l + 1
end
j = j + 1
end
'
}
This will eventually produce fields
item#0#0 = "a"
item#0#1 = "b"
item#1#0 = "c"
item#1#1 = "d"
Here's an example of an array I'm working with:
arr = [ "id1", "name1", "id2", "name2", "id3", "name3" ]
I want to change it into a new array that looks like:
new_arr = [ "id1: name1", "id2: name2", "id3: name3" ]
My attempt:
ids = arr.select.with_index { |_, i| i.even? }
names = arr.select.with_index { |_, i| i.odd? }
new_arr = ids.map { |i| i + ": " + names[ids.index(i)] }
Is there a better or more expressive way of doing this (potentially in a one-liner)?
I would use each_slice and string interpolation.
arr.each_slice(2).map { |(a, b)| "#{a}: #{b}" }
#=> ["id1: name1", "id2: name2", "id3: name3"]
Richard Hamilton`s comment made me think about the performance of the different solutions:
require 'benchmark'
arr = [ "id1", "name1", "id2", "name2", "id3", "name3" ]
slices = arr.each_slice(2)
n = 1_000_000
Benchmark.bmbm(15) do |x|
x.report("hashified :") { n.times do; Hash[*arr].map { |e| e.join ': ' } ; end }
x.report("concatenation :") { n.times do; slices.map { |a| a[0] + ": " + a[1] } ; end }
x.report("array join :") { n.times do; slices.map { |a| a.join(': ') } ; end }
x.report("interpolated :") { n.times do; slices.map { |(a, b)| "#{a}: #{b}" } ; end }
end
# Rehearsal ---------------------------------------------------
# hashified : 3.520000 0.030000 3.550000 ( 3.561024)
# concatenation : 2.300000 0.010000 2.310000 ( 2.315952)
# array join : 3.020000 0.010000 3.030000 ( 3.032235)
# interpolated : 1.950000 0.000000 1.950000 ( 1.954937)
# ----------------------------------------- total: 10.840000sec
#
# user system total real
# hashified : 3.430000 0.040000 3.470000 ( 3.473274)
# concatenation : 2.320000 0.010000 2.330000 ( 2.332920)
# array join : 3.070000 0.010000 3.080000 ( 3.081937)
# interpolated : 1.950000 0.000000 1.950000 ( 1.956998)
You can use Enumerable's each_slice method to get an enumeration of 2-element arrays from your arr. You can then simply join these elements:
arr.each_slice(2).map{|a| a.join(': ')}
What happens here is the each_slice returns an Enumerator which yields the 2-element arrays. Since Enumerators also are Enumerable, you can just use map to change these 2-element arrays and join them to a String.
each_slice is silly :)
Hash[ "id1", "name1", "id2", "name2", "id3", "name3" ].
map { |e| e.join ': ' }
#⇒ [ "id1: name1", "id2: name2", "id3: name3" ]
Try using each_slice:
arr.each_slice(2).entries.map { |ar| ar.join(': ') }
#=> ["id1: name1", "id2: name2", "id3: name3"]
You should use each_slice for this
arr.each_slice(2).map { |a| a[0] + ": " + a[1] }
=> ["id1: name1", "id2: name2", "id3: name3"]
inv = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}} is my object
I want
inv = {"C"=>{"CPS"=>{"CP"=>[{"name"=>"a"}]}}}
I tried
inv["C"]["CPS"].inject({}) do |result, (k, v)|
k = Array.wrap(v)
end
=> [{"name"=>"a"}]
but still inv={"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}}
tries map also
Another option is to use tap
inv["C"]["CPS"].tap do |h|
h["CP"] = [h["CP"]] #or Array.wrap(h["CP"]) in rails
end
inv
#=> {"C"=>{"CPS"=>{"CP"=>[{"name"=>"a"}]}}}
tap will yield the current object so you can modify it in place.
Update
Inspired by #CarySwoveland's broader application you could use something like this as well.
class HashWrapper
attr_reader :original_hash
attr_accessor :target_keys
def initialize(h,*target_keys)
#original_hash = h
#target_keys = target_keys
end
def wrapped_hash
#wrapped_hash ||= {}
end
def wrap_me
original_hash.each do |k,v|
value = v.is_a?(Hash) ? HashWrapper.new(v,*target_keys).wrap_me : v
wrapped_hash[k] = wrap(k,value)
end
wrapped_hash
end
private
def wrap(k,v)
target_keys.include?(k) ? [v] : v
end
end
Then implementation is as follows
wrapper = HashWrapper.new(inv,"CP")
wrapper.wrap_me
#=> {"C"=>
{"CPS"=>
{"CP"=>
[
{"name"=>"a"}
]
}
}
}
new_wrapper = HashWrapper.new(inv,"CP","CPS")
new_wrapper.wrap_me
#=> {"C"=>
{"CPS"=>
[
{"CP"=>
[
{"name"=>"a"}
]
}
]
}
}
This assumes unique keys all the way through the hierarchy otherwise nested keys of the same name will be wrapped in the same fashion from the bottom up.
e.g.
inv = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}},"CP" => "higher level"}}
HashWrapper.new(inv,"CP").wrap_me
#=> {"C"=>
{"CPS"=>
{"CP"=>
[
{"name"=>"a"}
]
},
"CP"=>
[
"higher level"
]
}
}
This should do it:
hash = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}}
val = hash["C"]["CPS"]["CP"]
val_as_arr = [val] # can optionally call flatten here
hash["C"]["CPS"]["CP"] = val_as_arr
puts hash
# => {"C"=>{"CPS"=>{"CP"=> [{"name" => "a"}] }}}
basically
get the value
convert to array
set the value
There is no iteration required here i.e. map or reduce
I suggest you use recursion, in the form of a compact and easily readable method that has broader application than solutions that only work with your specific hash.
def wrap_it(h)
h.each { |k,v| h[k] = v.is_a?(Hash) ? wrap_it(v) : [v] }
h
end
h = { "C"=>{ "CPS"=>{ "CP"=>{ "name"=>"a" } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"name"=>["a"]}}}}
h = { "C"=>{ "CPS"=>{ "CP"=>{ "CPPS"=> { "name"=>"cat" } } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"CPPS"=>{"name"=>["cat"]}}}}}
h = { "C"=>{ "CPS"=>{ "CP"=>{ "CPPS"=> { "name"=>"cat" } },
"DP"=>{ "CPPPS"=>"dog" } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"CPPS"=>{"name"=>["cat"]}}, "DP"=>{"CPPPS"=>["dog"]}}}}