Map_if, collect_if?


#1

Hi,

I have been through the pickaxe book, looking at the array and
enumerable
built-in class and module and can’t find a way of achieving a map_if or
collect_if.
From pickaxe, collect “Returns a new array containing the results of
running
block once for every element in enum”. Of course map is an alias to
collect.
There is find_all but it returns an array containing the elements of
enum,
not the results of running the block, so I have written one.
Is there a better way that already exists?
thanks,
Bruce.

module Enumerable

returns an array containing the results of running block once for

every
element in enum, for which block

is not false.

def map_if
rt, r = [], nil
self.each { |e| (r = yield(e)) and rt << r }
rt
end
alias :collect_if :map_if

end

a = [ 1,2,3,4,5 ]

b = a.map_if { |i|
(i % 2 != 0) and (i * 10)
}

p b ## [10, 30, 50]


#2

Try using the ‘select’ method…

irb(main):002:0* a = [ 1,2,3,4,5 ]
irb(main):003:0> b = a.select { |i|
irb(main):004:1* (i % 2 != 0) and (i * 10)
irb(main):005:1> }
=> [1, 3, 5]


#3

On Apr 12, 2006, at 9:04 PM, Mark Van H. wrote:

Try using the ‘select’ method…

irb(main):002:0* a = [ 1,2,3,4,5 ]
irb(main):003:0> b = a.select { |i|
irb(main):004:1* (i % 2 != 0) and (i * 10)
irb(main):005:1> }
=> [1, 3, 5]

Not the effect he was looking for:

b = a.map_if { |i| (i % 2 != 0) and (i * 10) }
p b
[10, 30, 50]

Personally I think his solution is a little icky, what if you wanted
to do

if x
then map false

a.map_if { |i| (i % 2 != 0) and false }
would break down and you’d get an empty array

I would just do

a.select { |i| (i % 2 != 0) }.map { |i| i * 10 }

if I needed map-if style functionality


#4

On Thu, 13 Apr 2006, Bruce W. wrote:

a = [ 1,2,3,4,5 ]

b = a.map_if { |i|
(i % 2 != 0) and (i * 10)
}

p b ## [10, 30, 50]

inject is very powerfull:

harp:~ > cat a.rb
a = 1 … 5
b = a.inject([]){|a,i| i[0] == 1 ? a.push(i * 10) : a}
p b

harp:~ > ruby a.rb
[10, 30, 50]

regards.

-a


#5

Ara,

Thanks for your reply. I have been using inject but shyed away in this
case
because for the ‘real’ code that I have been working on I am actually
already using an accumualtor.

So I had;

acc = []
array.each { |v|
condition_expression and acc << v.attribute
}

Using inject seems to be roughly equivalent. In the example above v is a
instance of the Struct class.

cheers,
Bruce.


#6

On 4/13/06, Bruce W. removed_email_address@domain.invalid wrote:

There is find_all but it returns an array containing the elements of enum,

is not false.

b = a.map_if { |i|
(i % 2 != 0) and (i * 10)
}

p b ## [10, 30, 50]

module Enumerable
def map_if( &block )
find_all(&block).map(&block)
end
end

this for sure is shorter, had no time to test performance though. ( I
yield
twice, you yield only once, which might be costy!)

Cheers
Robert


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

#7

harp:~ > cat a.rb
a = 1 … 5
b = a.inject([]){|a,i| i[0] == 1 ? a.push(i * 10) : a}
p b

harp:~ > ruby a.rb
[10, 30, 50]

I don’t get it: why is i[0] == 1 returning true in case i is an integer
with an odd value?

Thanks & cheers,

Steph.


#8

Robert and Logan,

Thanks for your replies. That was good thinking.

On 4/13/06, Robert D. removed_email_address@domain.invalid wrote:

On 4/13/06, Bruce W. removed_email_address@domain.invalid wrote:

module Enumerable
def map_if( &block )
find_all(&block).map(&block)
end
end

I particularly like the use of calling block twice. At first I thought
that
this couldn’t work but of course once the block has been successful for
the
call to find_all, it must be successful for the call to map – very
nice.

I will file away the find_all {}.map {} idiom for future use but for
this
case I will either use a manual accumulator or inject. The only reason
is
performance and performance only matters in this case because the code
is
likely to be get call many hundreds of times in a loop and I will be
impatiently waiting for it finish.

WRT to performance, not only is the block being called twice but an
extra
array is being created and then freed. In the past I have had issues
ruby
processes using a lot of memory and spending time with the garbage
collector. I don’t want to start any kind of anti-ruby flame war here;
Ruby
is my first choice. Manually stopping the GC at the top of the loop and
starting the GC at the bottom of the loop, solved the problem but I just
want to minimse memory use where I can, just for this code.

thanks again.
Bruce.


#9

On Apr 14, 2006, at 11:07 AM, Stephan M. wrote:

I don’t get it: why is i[0] == 1 returning true in case i is an
integer
with an odd value?

Thanks & cheers,

Steph.

For integers [] gives you the value of the bit at the given index.
All odd integers will have an LSB of 1
i.e.
1 -> 0b001
2 -> 0b010 # even
3 -> 0b011 #odd
4 -> 0b100 #even
5 -> 0b101 #odd


#10

For integers [] gives you the value of the bit at the given index. All odd integers will have an LSB of 1
i.e.

Ah, I see. Thanks for your help!

Cheers,

Steph.