Collect objects from an array based on one unique parameter

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

···

--
Posted via http://www.ruby-forum.com/.

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 = names.uniq # or

  names.uniq! # I'm using the same variable name used in your
                        # example code

···

On Thursday 09 August 2007 02:24:46 am Milo Thurston wrote:

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

--
Konrad Meyer <konrad@tylerc.org> http://konrad.sobertillnoon.com/

Konrad Meyer wrote:

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.

···

--
Posted via http://www.ruby-forum.com/\.

Why don't you use #select?

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

Did I misunderstand your requirement?

Kind regards

robert

···

2007/8/9, Milo Thurston <knirirr@gmail.com>:

Konrad Meyer wrote:
> 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.

Robert Klemme wrote:

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.

···

--
Posted via http://www.ruby-forum.com/\.

Hi --

Robert Klemme wrote:

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

···

On Thu, 9 Aug 2007, Milo Thurston wrote:

--
* 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)

Robert Klemme wrote:
> 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

···

2007/8/9, Milo Thurston <knirirr@gmail.com>:

Hi --

# 2. alternative impl.
selection.things.

I think you mean selection = things :slight_smile:

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

···

On Thu, 9 Aug 2007, Robert Klemme wrote:

--
* 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)

Robert Klemme wrote:

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. :wink:

···

--
Posted via http://www.ruby-forum.com/\.

Hi --

···

On Thu, 9 Aug 2007, dblack@rubypal.com wrote:

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)

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

robert

···

2007/8/9, dblack@rubypal.com <dblack@rubypal.com>:

Hi --

On Thu, 9 Aug 2007, Robert Klemme wrote:

> # 2. alternative impl.
> selection.things.

I think you mean selection = things :slight_smile:

> 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, ...].

Sorry -- s/Miles/Milo/

David

···

On Thu, 9 Aug 2007, dblack@rubypal.com wrote:

Hi --

On Thu, 9 Aug 2007, dblack@rubypal.com wrote:

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.

--
* 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)

unknown wrote:

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.

···

--
Posted via http://www.ruby-forum.com/\.