Eric Torreborre wrote: > Hi, > > I have been trying to intersect 2 sets of objects that where I want the "intersected" objects to share a common property. > > This is what I came up with: > > Here, I want to get all the Errors having the same file name: > > def intersection_set > Set.new(event_errors).&(Set.new(socket_channel_errors)) {|x| file_name(x)} > end > > To allow that I extended the Set class with: > > def &(enum, &property) > n = self.class.new > if block_given? > mapped = self.map{|e| property.call(e)} > enum.each { |o| n.add(o) if mapped.include?(property.call(o)) } > else > enum.each { |o| n.add(o) if include?(o) } > end > n > end > > I have met this requirement quite some times while scripting and I may miss something here. > Do you know a more idiomatic, Ruby-way of doing that? Hm, it's like Schwartzian transform: intersect_by instead of sort_by. Your implementation looks good, but might be more efficient with: mapped = self.class.new(map{|e| property.call(e)}) which would make mapped a set, for faster lookups. Also, using yield will probably be more efficient than instantiating a proc and calling it: def &(enum) n = self.class.new if block_given? mapped = self.class.new(map{|e| yield e}) enum.each { |o| n.add(o) if mapped.include?(yield o) } else enum.each { |o| n.add(o) if include?(o) } end n end It would be more elegant to use select than to iterate through enum using #each and #add to the set, but Set#select returns an Array. At the cost of using two iterations instead of once, you could do this: require 'set' class Set def intersect_by(other) props = self.class.new(other.map {|t| yield t}) self.class.new(select {|t| props.include? t.prop}) end # Actually the following is closer to the origial: # def intersect_by(other) # props = self.class.new(map {|t| yield t}) # self.class.new(other.select {|t| props.include? t.prop}) # end alias old_intersect & def &(other) if block_given? intersect_by(other) {|t| yield t} else old_intersect(other) {|t| yield t} end end end class Test attr_accessor :prop def initialize prop @prop = prop end end s1 = Set.new([ Test.new(1), Test.new(2), Test.new(3) ]) s2 = Set.new([ Test.new(2), Test.new(3), Test.new(4) ]) p(s1.intersect_by(s2) {|t| t.prop}) p(Set[1,2,3]&Set[2,3,4]) __END__ Output: #<Set: {#<Test:0xb79f73e4 @prop=2>, #<Test:0xb79f73d0 @prop=3>}> #<Set: {2, 3}> -- vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407