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.
On Thursday 09 August 2007 02:24:46 am Milo T. wrote:
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
Konrad M. 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 K. 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.
2007/8/9, Milo T. [email protected]:
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
Hi –
On Thu, 9 Aug 2007, Milo T. wrote:
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
endBasically, 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
2007/8/9, Milo T. [email protected]:
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
Hi –
On Thu, 9 Aug 2007, Robert K. wrote:
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
2007/8/9, [email protected] [email protected]:
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
Hi –
On Thu, 9 Aug 2007, [email protected] 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
Robert K. 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.
On Thu, 9 Aug 2007, [email protected] wrote:
uniq_by would be more like one per unique name (rather than only
unique names), which it sounds like what Miles was actually after.
Sorry – s/Miles/Milo/
David
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.