[ACCEPTED]-Map an array modifying only elements matching a certain condition-collect

Accepted answer
Score: 25

Because arrays are pointers, this also works:

a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }

puts a.inspect
# => ["hello", "to!", "you!", "dude"]

In 6 the loop, make sure you use a method that 5 alters the object rather than creating a 4 new object. E.g. upcase! compared to upcase.

The exact 3 procedure depends on what exactly you are 2 trying to achieve. It's hard to nail a definite 1 answer with foo-bar examples.

Score: 9
old_a.map! { |a| a == "b" ? a + "!" : a }

gives

=> ["a", "b!", "c"]

map! modifies the receiver in place, so 1 old_a is now that returned array.

Score: 6

I agree that the map statement is good as 3 it is. It's clear and simple,, and would 2 easy for anyone to maintain.

If you want 1 something more complex, how about this?

module Enumerable
  def enum_filter(&filter)
    FilteredEnumerator.new(self, &filter)
  end
  alias :on :enum_filter
  class FilteredEnumerator
    include Enumerable
    def initialize(enum, &filter)
      @enum, @filter = enum, filter
      if enum.respond_to?(:map!)
        def self.map!
          @enum.map! { |elt| @filter[elt] ? yield(elt) : elt }
        end
      end
    end
    def each
      @enum.each { |elt| yield(elt) if @filter[elt] }
    end
    def each_with_index
      @enum.each_with_index { |elt,index| yield(elt, index) if @filter[elt] } 
    end
    def map
      @enum.map { |elt| @filter[elt] ? yield(elt) : elt }
    end
    alias :and :enum_filter
    def or
      FilteredEnumerator.new(@enum) { |elt| @filter[elt] || yield(elt) }
    end
  end
end

%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ]

require 'set'
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}>

('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy"
Score: 4

Your map solution is the best one. I'm not 3 sure why you think map_modifying_only_elements_where 2 is somehow better. Using map is cleaner, more 1 concise, and doesn't require multiple blocks.

Score: 3

One liner:

["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }

In the code above, you start 17 with [] "cumulative". As you enumerate through 16 an Enumerator (in our case the array, ["a", "b", "c"]), cumulative 15 as well as "the current" item get passed 14 to our block (|cumulative, i|) and the result 13 of our block's execution is assigned to 12 cumulative. What I do above is keep cumulative 11 unchanged when the item isn't "b" and append 10 "b!" to cumulative array and return it when 9 it is a b.

There is an answer above that 8 uses select, which is the easiest way to do (and 7 remember) it.

You can combine select with map in order 6 to achieve what you're looking for:

 arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
 => ["b!"]

Inside 5 the select block, you specify the conditions for 4 an element to be "selected". This will return 3 an array. You can call "map" on the resulting 2 array to append the exclamation mark to 1 it.

Score: 2

Ruby 2.7+

As of 2.7 there's a definitive answer.

Ruby 2.7 is introducing filter_map for this exact 4 purpose. It's idiomatic and performant, and 3 I'd expect it to become the norm very soon.

For 2 example:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

Here's a good read on the subject.

Hope that's useful to 1 someone!

Score: 1

If you don't need the old array, I prefer 3 map! in this case because you can use the 2 ! method to represent you are changing the 1 array in place.

self.answers.map!{ |x| (x=="b" ? x+"!" : x) }

I prefer this over:

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
Score: 1

It's a few lines long, but here's an alternative 2 for the hell of it:

oa = %w| a b c |
na = oa.partition { |a| a == 'b' }
na.first.collect! { |a| a+'!' }
na.flatten! #Add .sort! here if you wish
p na
# >> ["b!", "a", "c"]

The collect with ternary 1 seems best in my opinion.

Score: 1

I've found that the best way to accomplish 1 this is by using tap

arr = [1,2,3,4,5,6]
[].tap do |a|
  arr.each { |x| a << x if x%2==0 }
end

More Related questions