!x.include? y

basically, I need to filter list A to match list B. list A is a list
of objects with a particular field, and list B is a list of acceptable
values for that field.

what I have works but it reads sort of clumsily. I changed the ! to a
not and got something slightly more readable:

list.reject! {|thing| not list_of_acceptable_values.include?
thing.attribute}

is there a nicer way to do it?

Giles B. wrote:

list.reject! {|thing| not list_of_acceptable_values.include?
thing.attribute}

is there a nicer way to do it?

list.select {|thing| list_of_acceptable_values.include? thing.attribute
} ??

But then you need to reassign list, since I don’t know of any select!

Vince

Hi –

On Mon, 30 Oct 2006, Vincent F. wrote:

But then you need to reassign list, since I don’t know of any select!
This would be a nice use for delete_unless.

David

On 29/10/06, Giles B. [email protected] wrote:


list.reject! {|thing| not list_of_acceptable_values.include? thing.attribute}

is there a nicer way to do it?

You could modify the Array class to include an accept! method that
does the opposite of reject! (I’m sure someone else will chime in if
this is wrong or bad practise!)

class Array
def accept!
delete_if {|item| not yield item}
end
end

def accept!
delete_if {|item| not yield item}
end
end

this is a very tempting solution. a filter method could be cool too.

On 10/29/06, [email protected] [email protected] wrote:

list.select {|thing| list_of_acceptable_values.include? thing.attribute
} ??

But then you need to reassign list, since I don’t know of any select!

This would be a nice use for delete_unless.

Cheers, I didn’t know about delete_unless. (I did find delete_if.)

Giles B. wrote:

basically, I need to filter list A to match list B. list A is a list
of objects with a particular field, and list B is a list of acceptable
values for that field.

Interesting; you’re basically talking about set intersection:
irb(main):001:0> requested = %w|a b c x z|
=> [“a”, “b”, “c”, “x”, “z”]
irb(main):002:0> legal = %w|a b c d e f g|
=> [“a”, “b”, “c”, “d”, “e”, “f”, “g”]
irb(main):003:0> requested & legal
=> [“a”, “b”, “c”]

Except that you’re talking about calling considering an object
equivalent to the value of one of its ‘fields’. I had hoped that the
following would work, but no luck:
class Object
def equiv; self; end
end

class Equiv
def self.[]( data, equiv )
self.new( data, equiv )
end
attr_accessor :data, :equiv
def initialize( data, equiv )
@data, @equiv = data, equiv
end
def <=>( o2 ); @equiv <=> o2.equiv; end
def == ( o2 ); @equiv == o2.equiv; end
def ===( o2 ); @equiv === o2.equiv; end
def eq?( o2 ); @equiv.eq?( o2.equiv ); end
def hash; @equiv.hash end
end

possible = [
Equiv[ 1001, ‘a’ ],
Equiv[ 1002, ‘b’ ],
Equiv[ 1003, ‘c’ ],
Equiv[ 2098, ‘x’ ],
Equiv[ 3143, ‘z’ ]
]
legal = %w|a b c d e f g|

p possible & legal
#=> []

Looking at the source code for Array#&, I see that it uses the hash
value of at least one of the arrays. (And I can see that my custom hash
method above is getting called.) But that’s not enough. Apparently I
can’t even read C code any more, much less write it.

Can someone who is more C savvy than I am tell me what the following
code is doing (and thus what might be done, if anything, to allow the
built-in Array#& to work with custom classes pretending to be something
they aren’t)?

static VALUE
rb_ary_and(ary1, ary2)
VALUE ary1, ary2;
{
VALUE hash, ary3, v, vv;
long i;

ary2 = to_ary(ary2);
ary3 = rb_ary_new2(RARRAY(ary1)->len < RARRAY(ary2)->len ?
    RARRAY(ary1)->len : RARRAY(ary2)->len);
hash = ary_make_hash(ary2, 0);

for (i=0; i<RARRAY(ary1)->len; i++) {
v = vv = rb_ary_elt(ary1, i);
if (st_delete(RHASH(hash)->tbl, (st_data_t*)&vv, 0)) {
    rb_ary_push(ary3, v);
}
}

return ary3;

}

I like to modifiy Proc for making definining methods like this easier:

class Proc
  def inverse
    self.class.new {|*args| !self.call(*args)}
  end
end

Then of course you could continue down the path Marcel was suggesting:

class Array
  def select!(&block)
    delete_if &block.inverse
  end
  alias_method :find_all!, :select!
end

class Hash
  def select!(&block)
    delete_if &block.inverse
  end
  alias_method :find_all!, :select!
end

Actually, now this makes me want to think about what other kind of
higher order goodies could be added to Proc =)

Phrogz wrote:

I had hoped that the following would work, but no luck:
[snip]

The reason that what I posted didn’t work is because I mispelled ‘eql?’
as ‘eq?’. Oops. Following is code that does actually work:
class Object
def equiv; self; end
end

class Equiv
def self.[]( data, equiv )
self.new( data, equiv )
end
attr_accessor :data, :equiv
def initialize( data, equiv )
@data, @equiv = data, equiv
end
def <=>( o2 ); @equiv <=> o2.equiv end
def == ( o2 ); @equiv == o2.equiv end
def ===( o2 ); @equiv === o2.equiv end
def eql?( o2 ); @equiv.eql?( o2.equiv ) end
def hash; @equiv.hash end
end

possible = [
Equiv[ 1001, ‘a’ ],
Equiv[ 1002, ‘b’ ],
Equiv[ 1003, ‘c’ ],
Equiv[ 2098, ‘x’ ],
Equiv[ 3143, ‘z’ ]
]
legal = %w|a b c d e f g|

p possible & legal
#=> [#<Equiv:0x283203c @equiv=“a”, @data=1001>, #<Equiv:0x2832000
@equiv=“b”, @data=1002>, #<Equiv:0x2831fc4 @equiv=“c”, @data=1003>]

So, going back to the original problem (which has long since been
solved), we can do this instead:

require ‘equiv’
objs_as_attributes = list.map{ |obj| Equiv[ obj, obj.attribute ] }
acceptable_obj_wrappers = objs_as_attributes &
list_of_acceptable_values
acceptable_objs = acceptable_obj_wrappers.map{ |eq| eq.data }

Obviously that’s too much work. I smell an interesting approach for
automatically treating one type of object as another:

module Enumerable
def treat_as( method )
result = yield map{|obj| Equiv[obj, obj.send(method)]}
if result.is_a? Equiv
result.data
else
result.map{ |obj| obj.is_a?( Equiv ) ? obj.data : obj }
end
end
end

Person = Struct.new( :name, :age )
people = [
Person.new( ‘Gavin’, 33 ),
Person.new( ‘Bob’, 35 ),
Person.new( ‘Jim’, 40 ),
Person.new( ‘Lisa’, 32 ),
Person.new( ‘Sam’, 30 )
]

legal_ages = [ 32, 33 ]
legal_people = people.treat_as( :age ){ |people_as_ages|
people_as_ages & legal_ages
}
p legal_people
#=> [#<struct Person name=“Gavin”, age=33>, #<struct Person
name=“Lisa”, age=32>]

oldest = people.treat_as( :age ){ |people_as_ages| people_as_ages.max
}
p oldest
#=> #<struct Person name=“Jim”, age=40>

This smells wrong applying only to Enumerables, but I’m too busy to
really think this through. Just thought I’d share the idea.