Nesting a given array into children, based on condition [closed] - ruby

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I have an array like this:
tweets = [
{
:user_id => 234567,
:username => "A",
:created_at => "2012-10-12 10:20:30"
},
{
:user_id => 234568,
:username => "B",
:created_at => "2012-10-12 10:20:34"
},
{
:user_id => 234569,
:username => "C",
:created_at => "2012-10-12 10:20:35"
},
{
:user_id => 234570,
:username => "D",
:created_at => "2012-10-12 10:20:40"
}
]
and another array, like this:
followers = [
{
:user_id => 234567,
:follower_ids => [234568, 56654]
},
{
:user_id => 234568,
:follower_ids => [234569, 454445]
},
{
:user_id => 234569,
:follower_ids => [234570, 56333]
},
{
:user_id => 234570,
:follower_ids => [45566, 61145]
}
]
I want to nest it into a deep structure, where one is made into children of another. For making the children, the condition to be satisfied is:
any other tweet which has a greater created_at than the other, and
whose user_id is included in the follower_ids list if that tweet in
the followers array is considered to be a child
and the expected output for the given data is like this:
arranged_tweets = [
{
:user_id => 234567,
:username => "A",
:created_at => "2012-10-12 10:20:30",
:children => [
{
:user_id => 234568,
:username => "B",
:created_at => "2012-10-12 10:20:34",
:children => [
{
:user_id => 234569,
:username => "C",
:created_at => "2012-10-12 10:20:35",
:children => [
{
:user_id => 234570,
:username => "D",
:created_at => "2012-10-12 10:20:40"
}
]
}
]
}
]
}
]

Untested, but should give you the idea:
arranged_tweets = tweets.collect do |tweet|
arranged_tweet(tweet, tweets - [tweet])
end
def arranged_tweet(tweet, other_tweets)
{ :user_id => tweet[:user_id], ...
:children => children(tweet, other_tweets) }
end
def children(tweet, other_tweets)
other_tweets.find_all { |other| is_child?(other, tweet) }.collect do |other|
arranged_tweet(other, other_tweets - [other])
end
end
def is_child?(tweet, parent_tweet)
parent_tweet[:created_at] > tweet[:created_at] &&
is_follower?(tweet[:user_id], parent_tweet[:user_id])
end
def is_follower?(user_id, other_user_id)
followers[other_user_id][:follower_ids].include?(user_id)
end

Related

Ruby/Highrise API - Not saving phone number?

So I've been using this same block of code for about 9 months and suddenly it has stopped saving a phone number into Highrise when a new Lead is generated... Any idea where things are going sideways? I never throws an error and happily saves the contact with everything except for the telephone number.
# create a contact Highrise from supplied information
#person = Highrise::Person.new(first_name: #lead.first_name.humanize,
last_name: #lead.last_name.humanize,
background: "automatically created by LSAL app",
contact_data: { email_addresses: [ { address: #lead.email, location: "Home" } ],
phones: [ { number: #lead.phone, location: "Mobile" } ] } )
#person.save # save contact
#person = Highrise::Person.create(:company_name => account.company, :first_name => account.first_name, :last_name => account.last_name, :contact_data => { :email_addresses => [{ :email_address => {:address => account.email, } }], :addresses => [{:address => {:city => account.city, :location =>"Work", :state => account.state, :country => account.country,:street => "#{account.address1} #{account.address2}" , :zip => account.zip} }], :phone_numbers => [{ :phone_number => { :number => number, :location =>"Work"}}] } )
Hope its helpful

How to count values in a array of hashes

I have an array of hashes
[ {:name => "bob", :type => "some", :product => "apples"},
{:name => "ted", :type => "other", :product => "apples"},....
{:name => "Will", :type => "none", :product => "oranges"} ]
and was wondering if there is a simple way to count the number of product's and store the count as well as the value in an array or hash.
I want the result to be something like:
#products = [{"apples" => 2, "oranges => 1", ...}]
You can do as
array = [
{:name => "bob", :type => "some", :product => "apples"},
{:name => "ted", :type => "other", :product => "apples"},
{:name => "Will", :type => "none", :product => "oranges"}
]
array.each_with_object(Hash.new(0)) { |h1, h2| h2[h1[:product]] += 1 }
# => {"apples"=>2, "oranges"=>1}
You can use Enumerable#group_by and Enumerable#map
array.group_by{|h| h[:product]}.map{|k,v| [k, v.size]}.to_h
# => {"apples"=>2, "oranges"=>1}
While not exactly what the OP was looking for, this may be helpful to many. If you're just looking for the count of a specific product, you could do this:
array = [
{:name => "bob", :type => "some", :product => "apples"},
{:name => "ted", :type => "other", :product => "apples"},
{:name => "Will", :type => "none", :product => "oranges"}
]
array.count { |h| h[:product] == 'apples' }
# => 2
You could count:
hashes = [
{:name => "bob", :type => "some", :product => "apples"},
{:name => "ted", :type => "other", :product => "apples"},
{:name => "Will", :type => "none", :product => "oranges"}
]
hashes.inject(Hash.new(0)) { |h,o| h[o[:product]] += 1; h }
Or maybe...
hashes.instance_eval { Hash[keys.map { |k| [k,count(k)] }] }
I do not know which is the more performant, the latter seims weird to read though.
I would do:
items =[ {:name => "bob", :type => "some", :product => "apples"},
{:name => "ted", :type => "other", :product => "apples"},
{:name => "Will", :type => "none", :product => "oranges"} ]
counts = items.group_by{|x|x[:product]}.map{|x,y|[x,y.count]}
p counts #=> [["apples", 2], ["oranges", 1]]
Then if you need it as a Hash just do:
Hash[counts]

Nested hash iteration: How to iterate a merge over an ( (array of hashes) within a hash )

I'm trying to do as the title says. Here is my code:
school.each { |x| school[:students][x].merge!(semester:"Summer") }
I think I pinpointed the problem to the "[x]" above. If I substitute an array position such as "[2]" it works fine. How can make the iteration work?
If the info above is not enough or you'd like to offer a better solution, please see the details below. Thanks!
The error message I get:
file.rb:31:in []': no implicit conversion of Array into Integer (TypeError)
from file.rb:31:inblock in '
from file.rb:31:in each'
from file.rb:31:in'
The nested hash below before alteration:
school = {
:name => "Happy Funtime School",
:location => "NYC",
:instructors => [
{:name=>"Blake", :subject=>"being awesome" },
{:name=>"Ashley", :subject=>"being better than blake"},
{:name=>"Jeff", :subject=>"karaoke"}
],
:students => [
{:name => "Marissa", :grade => "B"},
{:name=>"Billy", :grade => "F"},
{:name => "Frank", :grade => "A"},
{:name => "Sophie", :grade => "C"}
]
}
I'm trying to append :semester=>"Summer" to each of the last four hashes. Here is what I'm trying to go for:
# ...preceding code is the same. Changed code below...
:students => [
{:name => "Marissa", :grade => "B", :semester => "Summer"},
{:name=>"Billy", :grade => "F", :semester => "Summer"},
{:name => "Frank", :grade => "A", :semester => "Summer"},
{:name => "Sophie", :grade => "C", :semester => "Summer"}
]
}
Just iterate over the students:
school[:students].each { |student| student[:semester] = "Summer" }
Or, using merge:
school[:students].each { |student| student.merge!(semester: "Summer") }
The issue is that when you do array.each {|x| do something}, x actually refers to each element in the array.
For example, in the first iteration of the loop,
x = {:name => "Marissa", :grade => "B"}
So what you are really doing is trying to reference:
school[:student][{:name => "Marissa", :grade => "B"}]
Which will not work
What you could do instead is create a for loop to track the index.
for i in 0 ... school[:student].count
school[:students][i].merge!(semester:"Summer")
end
Edit: Stefan's solution is much better than mine, but I will leave this up to show where you went wrong.
I would do as below using Hash#store :
require 'awesome_print'
school = {
:name => "Happy Funtime School",
:location => "NYC",
:instructors => [
{
:name => "Blake",
:subject => "being awesome"
},
{
:name => "Ashley",
:subject => "being better than blake"
},
{
:name => "Jeff",
:subject => "karaoke"
}
],
:students => [
{
:name => "Marissa",
:grade => "B"
},
{
:name => "Billy",
:grade => "F"
},
{
:name => "Frank",
:grade => "A"
},
{
:name => "Sophie",
:grade => "C"
}
]
}
school[:students].each{|h| h.store(:semester ,"Summer")}
ap school,:index => false,:indent => 10
output
{
:name => "Happy Funtime School",
:location => "NYC",
:instructors => [
{
:name => "Blake",
:subject => "being awesome"
},
{
:name => "Ashley",
:subject => "being better than blake"
},
{
:name => "Jeff",
:subject => "karaoke"
}
],
:students => [
{
:name => "Marissa",
:grade => "B",
:semester => "Summer"
},
{
:name => "Billy",
:grade => "F",
:semester => "Summer"
},
{
:name => "Frank",
:grade => "A",
:semester => "Summer"
},
{
:name => "Sophie",
:grade => "C",
:semester => "Summer"
}
]
}

turn list of depth first traversal nodes back into tree structure in Ruby

Given the following input (from a CSV file):
input = [
{ :level => 0, :value => "a" },
{ :level => 1, :value => "1" },
{ :level => 1, :value => "2" },
{ :level => 2, :value => "I" },
{ :level => 2, :value => "II" },
{ :level => 2, :value => "III" },
{ :level => 0, :value => "b" },
{ :level => 0, :value => "c" },
{ :level => 0, :value => "d" },
{ :level => 1, :value => "3" },
{ :level => 1, :value => "4" },
]
How can I convert this to the following in "The Ruby Way":
expected = [
{ :value => "a", :children => [ { :value => 1, :children => nil },
{ :value => 2, :children => [ { :value => "I", :children => nil },
{ :value => "II", :children => nil },
{ :value => "III", :children => nil } ] } ] },
{ :value => "b", :children => nil },
{ :value => "c", :children => nil },
{ :value => "d", :children => [ { :value => 3, :children => nil },
{ :value => 4, :children => nil } ] },
]
?
Edited:
My solution to this was to sidestep the problem, transform it and get someone else to solve it:
require 'yaml'
def linear_to_tree(a)
yaml_lines = []
a.each do |el|
indent = " " * 4 * el[:level]
yaml_lines << "#{indent}-"
yaml_lines << "#{indent} :value: #{(el[:value])}"
yaml_lines << "#{indent} :children:"
end
yaml_lines << "" # without this, YAML.load complains
yaml = yaml_lines.join("\n")
# open("test_yaml.txt", "w"){|f| f.write(yaml)}
YAML.load(yaml)
end
But there must be a more elegant way to solve this.
P.S. I'd also like to see a one-liner for this transformation, just to see if it's possible.
You should use an empty array for nodes that have no children, an empty array is the null object for a collection. Otherwise you have to dance around both when you assign it, and when you use it.
def transform(inputs)
transform! inputs.dup
end
def transform!(inputs, output=[], current_level=0)
while inputs.any?
input = inputs.shift
level, value = input.values_at :level, :value
value = value.to_i if value =~ /\A\d+\z/
if level < current_level
inputs.unshift input
break
elsif level == current_level
next_children = []
output << {value: value, children: next_children}
transform! inputs, next_children, current_level.next
else
raise "presumably should not have gotten here"
end
end
output
end

ruby language - merge an array into another by finding same element

A = [
{ :id => 1, :name => 'good', :link => nil },
{ :id => 2, :name => 'bad', :link => nil }
]
B = [
{ :id => 3, :name => 'good' },
{ :id => 4, :name => 'good' },
{ :id => 5, :name => 'bad' }
]
I need to merge array B into A so that :link in array A includes the entry in array B if :name is the same value in each array.
For example, after processing array A should be:
A = [
{ :id => 1, :name => 'good', :link => [{ :id => 3, :name => 'good' }, { :id => 4, :name => 'good' }] },
{ :id => 2, :name => 'bad', :link => [{ :id => 5, :name => 'bad' }] }
]
thanks.
The short version;
a.each { | item | item[:link] = b.find_all { | x | x[:name] == item[:name] } }
Demo here.
In ruby the constants begin with an uppercase letter, so you should use lowercase letter:
A => a, B => b
a.each do |ha|
b.each do |hb|
if ha[:name] == hb[:name]
ha[:link] |= []
ha[:link] << hb
end
end
end
Functional approach:
B_grouped = B.group_by { |h| h[:name] }
A2 = A.map { |h| h.merge(:link => B_grouped[h[:name]]) }
#=> [{:link=>[{:name=>"good", :id=>3}, {:name=>"good", :id=>4}], :name=>"good", :id=>1},
# {:link=>[{:name=>"bad", :id=>5}], :name=>"bad", :id=>2}]

Resources