[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

grouping/sorting [newbie question]

bdarcus

6/26/2005 2:27:00 PM

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

6 Answers

Ryan Leavengood

6/26/2005 2:54:00 PM

0

bdarcus@gmail.com wrote:
>
> 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


bdarcus

6/26/2005 4:19:00 PM

0

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

Ryan Leavengood

6/26/2005 5:40:00 PM

0

bdarcus@gmail.com wrote:
> 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/MovingAwayFro...

> 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


bdarcus

6/26/2005 6:32:00 PM

0


Ryan Leavengood wrote:
> bdarcus@gmail.com wrote:
> > 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/MovingAwayFro...

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

Ryan Leavengood

6/26/2005 7:35:00 PM

0

bdarcus@gmail.com wrote:
>
> 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


Florian Groß

6/27/2005 1:08:00 AM

0