Map_if, collect_if?

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]

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]

···

On 4/12/06, Bruce Woodward <bruce.woodward@gmail.com> wrote:

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]

--
Mark Van Holstyn
mvette13@gmail.com
http://lotswholetime.com

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

···

On Thu, 13 Apr 2006, Bruce Woodward wrote:

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

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

p b ## [10, 30, 50]

--
be kind whenever possible... it is always possible.
- h.h. the 14th dali lama

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

···

On 4/13/06, Bruce Woodward <bruce.woodward@gmail.com> wrote:

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]

--
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

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

···

On Apr 12, 2006, at 9:04 PM, Mark Van Holstyn 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]

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.

···

On 4/12/06, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote:

On Thu, 13 Apr 2006, Bruce Woodward 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
--
be kind whenever possible... it is always possible.
- h.h. the 14th dali lama

Robert and Logan,

Thanks for your replies. That was good thinking.

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.

···

On 4/13/06, Robert Dober <robert.dober@gmail.com> wrote:

On 4/13/06, Bruce Woodward <bruce.woodward@gmail.com> wrote:

* ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> [060413 06:04]:

  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.

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

···

On Apr 14, 2006, at 11:07 AM, Stephan Mueller 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.

* Logan Capaldo <logancapaldo@gmail.com> [060414 17:34]:

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.