Collect objects from an array based on one unique parameter

M

Milo Thurston

If one has an array of objects, each containing various values, what
means are there to find all the objects where one of these particular
values is unique?
I have done this using a hash to record values I've seen before (code
attached), but wondered if there might be another way, e.g. using
collect.

Attachments:
http://www.ruby-forum.com/attachment/76/modeltest.rb
 
K

Konrad Meyer

--nextPart1342838.3hG2f52WpC
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

If one has an array of objects, each containing various values, what
means are there to find all the objects where one of these particular
values is unique?

And only those unique values?

names =3D names.uniq # or

names.uniq! # I'm using the same variable name used in your
# example code
I have done this using a hash to record values I've seen before (code
attached), but wondered if there might be another way, e.g. using
collect.
=20
Attachments:
http://www.ruby-forum.com/attachment/76/modeltest.rb

=2D-=20
Konrad Meyer <[email protected]> http://konrad.sobertillnoon.com/

--nextPart1342838.3hG2f52WpC
Content-Type: application/pgp-signature; name=signature.asc
Content-Description: This is a digitally signed message part.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (GNU/Linux)

iD8DBQBGut67CHB0oCiR2cwRAl+VAJ9G5muoF+gS4+kM8dQPdLwtdZNx1wCgybR3
MNkVFUaAWPQr49ERbCfAGBA=
=5zwL
-----END PGP SIGNATURE-----

--nextPart1342838.3hG2f52WpC--
 
M

Milo Thurston

Konrad said:
And only those unique values?

Not only those, but the entire objects.
I could use something like

names = things.collect {|x| x.name}.uniq

to just get the names, but I need the rest of the data in the object
also.
The code I posted works, but I'm not sure that it's the best solution.
 
R

Robert Klemme

2007/8/9 said:
Not only those, but the entire objects.
I could use something like

names = things.collect {|x| x.name}.uniq

to just get the names, but I need the rest of the data in the object
also.
The code I posted works, but I'm not sure that it's the best solution.

Why don't you use #select?

selection = things.select {|x| x.name = "foo"}

Did I misunderstand your requirement?

Kind regards

robert
 
M

Milo Thurston

Robert said:
selection = things.select {|x| x.name = "foo"}

That is closer, but still not quite it.
A slight change of my previously posted code may make it clearer. In
this case I start with an array of "Thing" objects called "things", and
would like an array called "uniquely_named_things" containing Things
where the name is unique.


check = Hash.new
uniquely_named_things = Array.new
things.each do |s|
if check[s.name].nil?
uniquely_named_things << s
end
check[s.name] = 1
end

Basically, I wonder if anything that does the same as this has already
been included in Ruby.
 
D

dblack

Hi --

Robert said:
selection = things.select {|x| x.name = "foo"}

That is closer, but still not quite it.
A slight change of my previously posted code may make it clearer. In
this case I start with an array of "Thing" objects called "things", and
would like an array called "uniquely_named_things" containing Things
where the name is unique.


check = Hash.new
uniquely_named_things = Array.new
things.each do |s|
if check[s.name].nil?
uniquely_named_things << s
end
check[s.name] = 1
end

Basically, I wonder if anything that does the same as this has already
been included in Ruby.

I don't think so. It might be handy to generalize it:

module UniqBy
def uniq_by
res = []
count = Hash.new(0)
each do |item|
y = yield(item)
if y
count[y] += 1
if count[y] == 1
res << item
else
res.delete(item)
end
end
end
res
end
end

Thing = Struct.new:)name)

a = Thing.new("David")
b = Thing.new("John")
c = Thing.new("David")
d = Thing.new("Mary")
e = Thing.new("Joe")

things = [a,b,c,d,e].extend(UniqBy)

p things.uniq_by {|thing| thing.name }


Another way to do this, which I imagine is much slower, is:

things.select do |thing|
things.select {|other| other.name == thing.name }.size == 1
end


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
R

Robert Klemme

2007/8/9 said:
Robert said:
selection = things.select {|x| x.name = "foo"}

That is closer, but still not quite it.
A slight change of my previously posted code may make it clearer. In
this case I start with an array of "Thing" objects called "things", and
would like an array called "uniquely_named_things" containing Things
where the name is unique.


check = Hash.new
uniquely_named_things = Array.new
things.each do |s|
if check[s.name].nil?
uniquely_named_things << s
end
check[s.name] = 1
end

Your code seems to implement something different from your wording.
Your wording says "keep only things whose name is not used by another
thing". Your code does "keep one thing per unique name".
Basically, I wonder if anything that does the same as this has already
been included in Ruby.

There are numerous ways to achieve that. As I am a big fan of #inject,
this is probably my first choice:

# 1. keep only one thing per name
selection = things.inject({}) {|h,th| h[th.name] ||= th; h}.values

# 2. keep things whose name is unique
selection = things.
inject(Hash.new {|h,k| h[k]=[]}) {|h,th| h[th.name] << th; h}.
select {|k,v| v.size == 1}.
map {|k,v| v}
# 2. alternative impl.
selection.things.
inject({}) do |h,th|
h[th.name] = h.has_key? th.name ? nil : th
h
end.values.compact

If I am not mistaken David's code implements the second solution
similar to my alternative implementation.

Kind regards

robert
 
D

dblack

Hi --

# 2. alternative impl.
selection.things.

I think you mean selection = things :)
inject({}) do |h,th|
h[th.name] = h.has_key? th.name ? nil : th

When I tried your code I found you need parens:

h[th.name] = h.has_key?(th.name) ? nil : th

Otherwise it's like:

h[th.name] = h.has_key? (th.name ? nil : th)

and you get [false, false, false, ...].


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Milo Thurston

Robert said:
Your code seems to implement something different from your wording.
Your wording says "keep only things whose name is not used by another
thing". Your code does "keep one thing per unique name".

It is my code that gives the correct meaning in this case. Evidently it
is more precise that English. ;-)
 
R

Robert Klemme

2007/8/9 said:
Hi --

# 2. alternative impl.
selection.things.

I think you mean selection = things :)
inject({}) do |h,th|
h[th.name] = h.has_key? th.name ? nil : th

When I tried your code I found you need parens:

h[th.name] = h.has_key?(th.name) ? nil : th

Otherwise it's like:

h[th.name] = h.has_key? (th.name ? nil : th)

and you get [false, false, false, ...].

Good catches! Thanks! /me confesses I did not try - just check for syntax. :-}

robert
 
D

dblack

Hi --

I don't think so. It might be handy to generalize it:

module UniqBy
def uniq_by

Actually what I wrote should probably be called uniq_for or something.
uniq_by would be more like one per unique name (rather than only
unique names), which it sounds like what Miles was actually after.


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
D

dblack

M

Milo Thurston

unknown said:
Sorry -- s/Miles/Milo/

No problem - it happens all the time.

selection = things.inject({}) {|h,th| h[th.name] ||= th; h}.values

...looks like what I was after, thanks, allowing everything to be done
in one line. I had not heard of inject, so that is generally useful to
know.
 

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

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,241
Members
46,831
Latest member
RusselWill

Latest Threads

Top