From: Robert Klemme Date: 2007-06-01T20:25:20+09:00 Subject: Re: Set intersection with a specific criteria: a better idiom? On 31.05.2007 19:21, Joel VanderWerf wrote: > 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? Basically you want to do the intersection based on a specific criterion. My first choice in such a scenario would be a Hash based solution, i.e. you create an index based on what you consider the key field. See attachment for a sample implementation. Regarding your question whether to include your code in std lib or facets lib: I vote "no". Here's why: a set intersection will leave you with a single uniform set of items, namely all items present in all sets that take part in the intersection. Your solution is not directly an intersection but it creates a copy of one set with all elements from the copy removed that do not fit a certain criterion. More specifically, you kind of reversed the logic because the resulting set has only elements from the /parameter/ (i.e. the right hand side of the operator) and not from the left hand side. So while for intersection order of arguments does not matter, it does for substraction: irb(main):001:0> require 'set' => true irb(main):002:0> s1 = (1..10).to_set => # irb(main):003:0> s2 = (5..15).to_set => # irb(main):004:0> s1 - s2 => # irb(main):005:0> s2 - s1 => # I would rewrite your original code like this: require 'set' ... # use a set for efficiency reasons keys = event_errors.map {|x| file_name x}.to_set # or, more efficient: keys = event_errors.inject(Set.new) {|s,x| s << file_name(x)} # create the result result = socket_channel_errors.select {|x| keys.include? file_name(x)} Since this is basically a two liner I don't see any necessity to change std libs. Kind regards robert