grouping/sorting [newbie question]

B

bdarcus

I'm trying to do something fairly simple that I understand how to do in
XSLT, but not in Ruby.

I have a list of film recommendations, with comments, in a yaml file
that looks like this:

-
title: Lost in translation
setting: Tokyo
comments: Some comments..
-
title: Chungking Express
setting: Hong Kong
comments: >
Some comments.

Since a film may have more than one entry, I want to create a list that
groups the comments by film. So I'd like output like:

Some Film
=======
Comment 1.

Comment 2.

However, while I figured out how to sort the list correctly, I don't
really understand how to do the grouping. Could someone please explain?

Current code:

require 'yaml'

films = YAML::load(File.open("film.yaml"))

sorted = films.sort_by{|film| film["title"]}

sorted.each do |item|
# I want to put the title for a group here,
# then list the comments.
if item["comments"] : puts item["comments"]
else ''
end
end

Bruce
 
R

Ryan Leavengood

However, while I figured out how to sort the list correctly, I don't
really understand how to do the grouping. Could someone please explain?

Inject is your friend:

require 'yaml'

films = YAML::load(File.open("film.yaml"))

grouped_comments = films.inject({}) do |grouped, film|
(grouped[film['title']] ||= []) << film['comments']
grouped
end

grouped_comments.keys.sort.each do |title|
puts "Comments for #{title}:"
grouped_comments[title].each do |comment|
puts "\t#{comment}"
end
end

Ryan
 
B

bdarcus

Thanks Ryan. Certainly less verbose than xslt!

So when you;'re using inject there, you're just creating a hash whose
key is the title?

What is the "||=[]" bit doing?

Bruce
 
R

Ryan Leavengood

Thanks Ryan. Certainly less verbose than xslt!

Generally so. Here is an article by Martin Fowler about his switch from
XSLT to Ruby for processing XML:

http://www.martinfowler.com/bliki/MovingAwayFromXslt.html
So when you;'re using inject there, you're just creating a hash whose
key is the title?

Correct. You give inject the initial value, which it injects into the
block along with each value in the array. So we start with an empty hash
and use the film titles as keys. The return value from the block is used
to re-inject into the block (that is why I have the grouped hash at the
bottom of the block.)
What is the "||=[]" bit doing?

That is a bit of a Ruby idiom (that comes from Perl I think.) It is
equivalent to var = var || []. If var is initially nil, it becomes a new
array, and whenever the above code is called again, var is just
re-assigned to itself. It just makes the code shorter. The verbose
version from the code I sent is this:

grouped_comments = films.inject({}) do |grouped, film|
if grouped[film['title']].nil?
grouped[film['title']] = []
end
grouped[film['title']] << film['comments']
grouped
end

As you can see there is a lot of repetition, so it is a good idea to
become familiar with this idiom.

Ryan
 
B

bdarcus

Ryan said:
Generally so. Here is an article by Martin Fowler about his switch from
XSLT to Ruby for processing XML:

http://www.martinfowler.com/bliki/MovingAwayFromXslt.html

I saw that, though it'd be nice to have a fully functional example to
try out. I couldn't manage how to get it working.

Also, while Fowler's comments may apply well to XSLT 1.0, 2.0 adds a
lot of power to the language. Still, it only goes so far of course,
which is why I'm interested in Ruby and Python.
What is the "||=[]" bit doing?

That is a bit of a Ruby idiom (that comes from Perl I think.) It is
equivalent to var = var || []. If var is initially nil, it becomes a new
array, and whenever the above code is called again, var is just
re-assigned to itself.

E.g. it only creates a new "group" if one doesn't already exist? Was
wondering how to do that, so thanks for this!

Bruce
 
R

Ryan Leavengood

E.g. it only creates a new "group" if one doesn't already exist? Was
wondering how to do that, so thanks for this!

That is one way. Hashes can also take a block in their constructors
which can be used for default values:

h = Hash.new {|h,k| h[k] = 'default'}
h['foo'] = 'bar'
p h['foo'] # => "bar"
p h['baz'] # => "default"

For the code I wrote before, it would be like this:

grouped_comments = films.inject(Hash.new {|h,k| h[k] = []}) do |grouped,
film|
grouped[film['title']] << film['comments']
grouped
end

But in this case I prefer the ||= syntax.

Ryan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top