Transform deeply nested ruby hash - ruby

I have a deeply nested Ruby hash which I need to transform into another hash. A Hash might have 0 or more children. Here's the input hash:
"{'id' => 'apple', 'children' => [{'id' => 'ipad', 'children' => [{'id' => 'ipadmini'}]},{'id' => 'ipadmini'}]}"
output
[{"ipad"=>{"id"=>"ipad", "paths"=>[[{"id"=>"apple"}, {"id"=>"ipad"}]]}},
{"ipadmini"=>{"id"=>"ipadmini", "paths"=>[[{"id"=>"ipad"}, {"id"=>"ipadmini"}], [{"id"=>"apple"}, {"id"=>"ipad"}, {"id"=>"ipadmini"}]]}}, {"apple"=>{"id"=>"apple", "paths"=>[[{"id"=>"apple"}]]}}]
my code:
def construct_concept(concept)
h = {}
c = Hash[*concept.to_a.first]
c['paths'] = [[Hash[*concept.to_a.first]]]
h[concept['id']] = c
h
end
def parent_child_concepts(concepts)
pc = {}
pc[:parent] = Hash[*concepts.first]
pc[:children] = concepts.values_at('children').flatten.map {|child| parent_child_concepts(child)} || []
pc
end
def add_parent_child_paths(parent_hash,children_array)
h = {}
parent_hash.each do |parent_key,parent_value|
h[parent_key] = parent_value
children_array.each do |child|
child.each do |k,v|
h[k] = v
h[k]['paths'].map {|path| path.unshift({'id' => parent_key})}
end
end
end
h
end
def build_concept_data(concepts)
#concept {"id"=>"apple", "children"=>[{"id"=>"ipad"}]}
parsed_concepts = parent_child_concepts(concepts)
parent = construct_concept(parsed_concepts[:parent])
children = parsed_concepts[:children].each_with_object([]) {|child,accu| accu << construct_concept(child)}
concept_paths_data = add_parent_child_paths(parent,children)
end

def build_data(node, ctx = [], result = [])
id = node['id']
children = node['children']
branch = result.find{|h| h.has_key?(id)}
if branch.nil?
result.push({
id => {
'id' => id,
'paths' => []
}
})
end
branch = result.find{|h| h.has_key?(id)}
branch[id]['paths'].unshift(
[ctx, id].flatten.map do |ctx_id|
{
'id' => ctx_id
}
end
)
if children.is_a?(Array)
children.each do |node|
new_ctx = ctx.dup
new_ctx.push(id)
nesting(node, new_ctx, result)
end
end
result
end

Related

How can I parse a string into a hash?

I am trying to parse a string into a hash.
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
The result should look similar to this:
pairs = {
'Open' = 1,
'Published' => true
'Body' => {
'Message' => [3455, 2524, 2544, 2452],
'Error' => [2455],
'Currency' => 'EUR',
}
}
Maybe someone can help on how I can make it. The only way I can think as for now is regexp.
something like this with regexp:
require 'pp'
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
pairs = {}
pairs['Body'] = {}
values = []
str.scan(/Body\W+(.+)/).flatten.each do |line|
key = line[/\A\w+/]
value = line[/\w+\z/]
if line[/\A\w+\[\d+\]/] || key == 'Error'
values = [] unless pairs['Body'][key]
values << value
value = values
end
pairs['Body'][key] = value
end
str.scan(/\[0\]\.(?!Body.).*/).each do |line|
key = line[/(?!\A)\.(\w+)/, 1]
value = line[/\w+\z/]
if line[/\A\w+\[\d+\]/]
values = [] unless pairs[key]
values << value
value = values
end
pairs[key] = value
end
PP.pp pairs
-
{"Body"=>
{"Message"=>["3455", "2524", "2544", "2452"],
"Error"=>["2455"],
"Currency"=>"EUR"},
"Open"=>"1",
"Published"=>"true"}
Here it is. This code should work with any structure.
def parse(path, value, hash)
key, rest = path.split('.', 2)
if rest.nil?
hash[key] = value
else
hash[key] ||= {}
parse(rest, value, hash[key])
end
end
def conv_to_array(hash)
if hash.is_a?(Hash)
hash.each do |key, value|
hash[key] = if value.is_a?(Hash) && value.keys.all? { |k| k !~ /\D/ }
arr = []
value.each do |k, v|
arr[k.to_i] = conv_to_array(v)
end
arr
else
conv_to_array(value)
end
end
hash
else
if hash !~ /\D/
hash.to_i
elsif hash == 'true'
true
elsif hash == 'false'
false
else
hash
end
end
end
str = "Notifications[0].Open=1
Notifications[0].Body.Message[0]=3455
Notifications[0].Body.Message[1]=2524
Notifications[0].Body.Message[2]=2544
Notifications[0].Body.Message[3]=2452
Notifications[0].Body.Error[0]=2455
Notifications[0].Body.Currency=EUR
Notifications[0].Published=true"
str = str.tr('[', '.').tr(']', '')
hash = {}
str.split(' ').each do |chunk|
path, value = chunk.split('=')
parse(path.strip, value.strip, hash)
end
hash = conv_to_array(hash)
hash['Notifications'][0]
# => {"Open"=>1, "Body"=>{"Message"=>[3455, 2524, 2544, 2452], "Error"=>[2455], "Currency"=>"EUR"}, "Published"=>true}

Array with hash, how to merge same keys and add its value

I have an array with hashes in it. If they have the same key I just want to add its value.
#receivers << result
#receivers
=> [{:email=>"user_02#yorlook.com", :amount=>10.00}]
result
=> {:email=>"user_02#yorlook.com", :amount=>7.00}
I want the result of above to look like this
[{:email=>"user_02#yorlook.com", :amount=>17.00}]
Does anyone know how to do this?
Here is the the entire method
def receivers
#receivers = []
orders.each do |order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result = { email: product_email, amount: amount(order.total_price) }
else
result = { email: product_email, amount: amount(order.total_price, 0.9),
email: outfit_email, amount: amount(order.total_price, 0.1) }
end
#receivers << result
end
end
Using Enumerable#group_by
#receivers.group_by {|h| h[:email]}.map do |k, v|
{email: k, amount: v.inject(0){|s,h| s + h[:amount] } }
end
# => [{:email=>"user_02#yorlook.com", :amount=>17.0}]
Using Enumerable#each_with_object
#receivers.each_with_object(Hash.new(0)) {|h, nh| nh[h[:email]]+= h[:amount] }.map do |k, v|
{email: k, amount: v}
end
# Output: [{ "em#il.one" => 29.0 }, { "em#il.two" => 39.0 }]
def receivers
return #receivers if #receivers
# Produces: { "em#il.one" => 29.0, "em#il.two" => 39.0 }
partial_result = orders.reduce Hash.new(0.00) do |result, order|
product_email = order.product.user.paypal_email
outfit_email = order.outfit_user.paypal_email
if order.user_owns_outfit?
result[product_email] += amount(order.total_price)
else
result[product_email] += amount(order.total_price, .9)
result[outfit_email] += amount(order.total_price, .1)
end
result
end
#receivers = partial_result.reduce [] do |result, (email, amount)|
result << { email => amount }
end
end
I would just write the code this way:
def add(destination, source)
if destination.nil?
return nil
end
if source.class == Hash
source = [source]
end
for item in source
target = destination.find {|d| d[:email] == item[:email]}
if target.nil?
destination << item
else
target[:amount] += item[:amount]
end
end
destination
end
usage:
#receivers = []
add(#receivers, {:email=>"user_02#yorlook.com", :amount=>10.00})
=> [{:email=>"user_02#yorlook.com", :amount=>10.0}]
add(#receivers, #receivers)
=> [{:email=>"user_02#yorlook.com", :amount=>20.0}]
a = [
{:email=>"user_02#yorlook.com", :amount=>10.0},
{:email=>"user_02#yorlook.com", :amount=>7.0}
]
a.group_by { |v| v.delete :email } # group by emails
.map { |k, v| [k, v.inject(0) { |memo, a| memo + a[:amount] } ] } # sum amounts
.map { |e| %i|email amount|.zip e } # zip to keys
.map &:to_h # convert nested arrays to hashes
From what I understand, you could get away with just .inject:
a = [{:email=>"user_02#yorlook.com", :amount=>10.00}]
b = {:email=>"user_02#yorlook.com", :amount=>7.00}
c = {email: 'user_03#yorlook.com', amount: 10}
[a, b, c].flatten.inject({}) do |a, e|
a[e[:email]] ||= 0
a[e[:email]] += e[:amount]
a
end
=> {
"user_02#yorlook.com" => 17.0,
"user_03#yorlook.com" => 10
}

Which element is giving StaleElementReferenceError?

So my code is giving me [remote server] resource://fxdriver/modules/web-element-cache.js:8325:24:in `fxdriver.cache.getElementAt': Element is no longer attached to the DOM (Selenium::WebDriver::Error::StaleElementReferenceError)
There are several elements being used in the code, and I am trying to see which element is giving me this error so I can make sure there is a wait for it.
EDIT
Here is the code:
path = [".//*[#id='sub_nav_content']/table/tbody/tr[2]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[3]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[4]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[5]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[6]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[7]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[8]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[9]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[10]/td[3]/a", ".//*[#id='sub_nav_content']/table/tbody/tr[11]/td[3]/a"]
path.each do |path|
begin
wait.until {
element = browser.find_element(:xpath => path)
element if element.displayed?
}
browser.find_element(:xpath => path).click
table = wait.until {
element = browser.find_element(id: "possible_matched")
element if element.displayed?
}
if table
puts "Table Found"
else
puts "Table Error"
end
#creates an 2D array containing patient name, admit date and prints to screen
names = browser.find_elements(:xpath => ".//*[#id='possible_matched']/table/tbody/tr/td[1]")
name_array = []
names.each { |name| name_array << name.text}
admits = browser.find_elements(:xpath => ".//*[#id='possible_matched']/table/tbody/tr/td[5]")
admit_array = []
admits.each { |date| admit_array << date.text }
name_admit_array = name_array.zip(admit_array)
name_admit_array.each do |name, date|
puts "#{name}: #{date}"
end
#finds the location of the sub-array containing patient name and collection associated admit date
patient_name = browser.find_element(:xpath => ".//*[#id='dialog-modal-cancel-hl7-preview']/table/tbody/tr[2]/td[1]").text
collected_date = browser.find_element(:xpath => ".//*[#id='dialog-modal-cancel-hl7-preview']/table/tbody/tr[2]/td[4]").text
puts patient_name
puts collected_date
mo, da, yr = collected_date.split('/').map(&:to_i)
cd = [yr, mo, da]
bl = name_admit_array.each_with_index.select { |(name, date), i|
m, d, y = date.split('/').map(&:to_i)
dt = [y, m, d]
name.downcase == patient_name.downcase and (dt <=> cd)<0
}.map {|x, i| i }
blf = name_admit_array.values_at(*bl)
if bl.any?
bf = blf.rindex(blf.max) + 2
wait.until {
element = browser.find_element(:xpath => ".//*[#id='possible_matched']/table/tbody/tr[#{bf}]/td[6]/div/a")
element if element.displayed?
}
browser.find_element(:xpath => ".//*[#id='possible_matched']/table/tbody/tr[#{bf}]/td[6]/div/a").click
else
browser.find_element(:xpath => "html/body/div[6]/div[1]/a/span").click
end
end while bl.any?
end
Check the complete stack trace of the error, it would point to the exact line which threw the exception.

Category Hierarchy in Jekyll

I've been trying to use Jekyll categories in a hierarchical fashion, i.e.
A: ['class', 'topic', 'foo']
AA: ['class', 'topic', 'foo', 'bar']
AB: ['class', 'topic', 'foo', 'baz']
AAA: ['class', 'topic', 'foo', 'bar', 'qux']
I'm trying to create a listing of all immediate subdirectories programmatically. That is, on a page with categories (A), I wish to be able to list the posts with categories (AA) and (AB), but not (AAA). Is this possible with Jekyll's vanilla structure, or should I consider using a plugin?
You need to use a plugin.
I've managed to make most of what you describe happen, but I'm not using categories at all, just representing a directory tree.
require 'digest/md5'
require 'open-uri'
module Jekyll
# Add accessor for directory
class Page
attr_reader :dir
end
class NavTree < Liquid::Tag
def render(context)
site = context.registers[:site]
#page_url = context.environments.first["page"]["url"]
#folder_weights = site.data['folder_weights']
#folder_icons = site.data['folder_icons']["icons"]
#nodes = {}
tree = {}
sorted_tree = {}
site.pages.each do |page|
# exclude all pages that are hidden in front-matter
if page.data["navigation"]["show"] != false
path = page.url
path = path.index('/')==0 ? path[1..-1] : path
#nodes[path] = page.data
end
end
#let's sort the pages by weight
array = []
#nodes.each do |path, data|
array.push(:path => path, :weight => data["weight"], :title => data["title"])
end
sorted_nodes = array.sort_by {|h| [-(h[:weight]||0), h[:path] ]}
sorted_nodes.each do |node|
current = tree
node[:path].split("/").inject("") do |sub_path,dir|
sub_path = File.join(sub_path, dir)
current[sub_path] ||= {}
current = current[sub_path]
sub_path
end
end
tree.each do |base, subtree|
folder_weight = #folder_weights[base]? #folder_weights[base] : 0
tree[base] = {"weight" => folder_weight, "subtree" => subtree}
end
tree_array = []
tree.each do |key, value|
tree_array.push(:base => key, :weight => value["weight"], :subtree => value["subtree"])
end
sorted_tree = tree_array.sort_by {|node| [ -(node[:weight]), node[:base] ]}
puts "generating nav tree for #{#page_url}"
files_first_traverse "", sorted_tree, 0
end
def files_first_traverse(prefix, nodes = [], depth=0)
output = ""
if depth == 0
id = 'id="nav-menu"'
end
output += "#{prefix}<ul #{id} class=\"nav nav-list\">"
nodes.each do |node|
base = node[:base]
subtree = node[:subtree]
name = base[1..-1]
if name.index('.') != nil
icon_name = #nodes[name]["icon"]
name = #nodes[name]["title"]
end
li_class = ""
if base == #page_url
li_class = "active"
if icon_name
icon_name = icon_name + " icon-white"
end
end
icon_html = "<span class=\"#{icon_name}\"></span>" unless icon_name.nil?
output += "#{prefix}<li class=#{li_class}>#{icon_html}#{name}</li>" if subtree.empty?
end
nodes.each do |node|
base = node[:base]
subtree = node[:subtree]
next if subtree.empty?
href = base
name = base[1..-1]
if name.index('.') != nil
is_parent = false
name = #nodes[name]["title"]
else
is_parent = true
href = base + '/index.html'
if name.index('/')
name = name[name.rindex('/')+1..-1]
end
end
name.gsub!(/_/,' ')
li_class = ""
if #page_url.index(base)
list_class = "collapsibleListOpen"
else
list_class = "collapsibleListClosed"
end
if href == #page_url
li_class = "active"
end
if is_parent
id = Digest::MD5.hexdigest(base)
icon_name = #folder_icons[base]
icon_html = icon_name.nil? ? "" : "<span class=\"#{icon_name}\"></span>"
li = "<li id=\"node-#{id}\" class=\"parent #{list_class}\"><div class=\"subtree-name\">#{icon_html}#{name}</div>"
else
icon_name = #nodes[name]["icon"]
if icon_name && li_class=="active"
icon_name = icon_name + " icon-white"
end
icon_html = icon_name.nil? ? "<i class=\"#{icon_name}\"></i>" : ""
li = "<li class=\"#{li_class}\">#{icon_html}#{name}</li>"
end
output += "#{prefix} #{li}"
subtree_array = []
subtree.each do |base, subtree|
subtree_array.push(:base => base, :subtree => subtree)
end
depth = depth + 1
output += files_first_traverse(prefix + ' ', subtree_array, depth)
if is_parent
output+= "</li>"
end
end
output += "#{prefix} </ul>"
output
end
end
end
Liquid::Template.register_tag("navigation", Jekyll::NavTree)
It is ugly code, but it's a start. You can see it in action on https://sendgrid.com/docs

Rally Ruby toolkit: how to get URL of Portfolio Item's state?

Is there an example in Ruby using rally_api how to set State of a feature as mentioned here?
Specifically, is there a way to query the ObjectID or the fully qualified path of state to use
"State" => "Developing"
instead of
"State" => "/state/<ObjectID>"
It is possible to query State, create a hash, and populate the hash with the query results, where State Name is the key and State _ref is the value:
state_results.each do |s|
s.read
state_hash[s["Name"]] = s["_ref"]
end
Then we can update a State:
features.each do |f|
field_updates={"State" => state_hash["Developing"]}
f.update(field_updates)
end
Here is a code example:
#rally = RallyAPI::RallyRestJson.new(config)
queryState = RallyAPI::RallyQuery.new()
queryState.type = :state
queryState.fetch = "true"
queryState.workspace = {"_ref" => "https://rally1.rallydev.com/slm/webservice/v2.0/workspace/11111" }
queryState.project = {"_ref" => "https://rally1.rallydev.com/slm/webservice/v2.0/project/22222" } #use valid OIDs
queryState.query_string = "(TypeDef.Name = \"Feature\")"
state_hash = Hash.new
state_results = #rally.find(queryState)
state_results.each do |s|
s.read
#puts "Ref: #{s["_ref"]}, Name: #{s["Name"] }, TypeDef: #{s["TypeDef"]}"
state_hash[s["Name"]] = s["_ref"]
end
query = RallyAPI::RallyQuery.new()
query.type = "portfolioitem/feature"
query.fetch = "Name,FormattedID"
query.workspace = {"_ref" => "https://rally1.rallydev.com/slm/webservice/v2.0/workspace/1111" }
query.project = {"_ref" => "https://rally1.rallydev.com/slm/webservice/v2.0/project/22222" } #use valid OIDs
query.query_string = "(Name = \"my feature\")"
results = #rally.find(query)
features = [];
results.each do |f|
f.read
puts "Current state of Feature #{f["FormattedID"]}: #{f["State"].to_s}"
features << f
end
features.each do |f|
field_updates={"State" => state_hash["Developing"]}
f.update(field_updates)
puts "Feature #{f["FormattedID"]} is now in State: #{f["State"].to_s}"
end

Resources