Diferença entre map, collect, select e each no Ruby


Enumerable é um iceberg gigante, nesse post vamos aprender a ponta dele.

Quando se está começando a programar Ruby, vejo que muitas pessoas (inclusive eu), tiveram duvidas quando se trata da diferença entre os métodos map, select e each, eu por exemplo sempre usava each para quase tudo e com isso perdia muitas funcionalidades que a linguagem me proporciona.

Each

O each executa para cada elemento a logica passada no bloco.

Se você for iterar um array em Ruby usando um simples for igual as outras linguagens, provavelmente você irá fazer algo parecido com isso:

places = ['restaurant', 'mall', 'park', 'theater'] 
for place in places 
puts place
end

# => restaurant
# => mall
# => park
# => theater

Porém no Ruby, a maneira mais indicada de iterar em uma coleção é usando o each, então ficaria assim:

places.each do |place| 
puts place
end
# => restaurant 
# => mall
# => park
# => theater

Ou até mesmo usando o inline, que você vai obter o mesmo resultado:

places.each { |place| puts place }

Outro exemplo

numbers = [1, 2, 3, 4, 5] 
numbers.each { |number| puts number * 2 } 
# => 2 
# => 4
# => 6
# => 8
# => 10
# => [1, 2, 3, 4, 5]

Vale lembrar que o each não altera o array que foi passado no exemplo:

puts numbers 
# => 1 
# => 2
# => 3
# => 4
# => 5
# => nil

E também vale lembrar que o each não retorna nada se tentarmos salvar o bloco em uma variavel, diferente do map que veremos a seguir.

a = numbers.each { |number| puts number * 2 } 
puts a 
# => [1, 2, 3, 4, 5]

Por baixo dos panos

O each entre todos os métodos que iremos ver a seguir é o único que não pertence ao modulo Enumerable, e o que isso significa?

Enumerable é um modulo no ruby, que traz junto com ele varios métodos, como podemos ver a seguir:

Enumerable.instance_methods 
# => [:to_a, :to_h, :include?, :find, :entries, :sort, :sort_by, :grep, :grep_v, :count, :detect, :find_index, :find_all, :select, :reject, :collect, :map, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :first, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :member?, :each_with_index, :reverse_each, :each_entry, :each_slice, :each_cons, :each_with_object, :zip, :take, :take_while, :drop, :drop_while, :cycle, :chunk, :slice_before, :slice_after, :slice_when, :chunk_while, :lazy]
Enumerable.class 
# => Module

E como é um modulo, eu posso incluir ele em qualquer classe e ganhar todos esses métodos, para isso tudo que minha classe precisa ter é um método each implementado que retorna os itens da coleção. Parece confuso? Com o exemplo a seguir se torna fácil de entender.

class OddNumbers 
include Enumerable

def each
yield 1
yield 3
yield 5
end
end

Apos fazer isso, já podemos ver todos os métodos proporcionados pelo Enumerable na nossa classe, entre eles o map, select, inject entre outros.

OddNumbers.instance_methods 
# => [:each, :to_a, :to_h, :include?, :find, :entries, :sort, :sort_by, :grep, :grep_v, :count, :detect, :find_index, :find_all, :select, :reject, :collect, :map, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :first, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :member?, :each_with_index, :reverse_each, :each_entry, :each_slice, :each_cons, :each_with_object, :zip, :take, :take_while, :drop, :drop_while, :cycle, :chunk, :slice_before, :slice_after, :slice_when, :chunk_while, :lazy, :instance_of?, :public_send, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :instance_variables, :tap, :method, :public_method, :singleton_method, :is_a?, :extend, :define_singleton_method, :to_enum, :enum_for, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :display, :object_id, :send, :to_s, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]

E podemos ver o modulo Enumerable nos módulos inclusos.

OddNumbers.included_modules 
# => [Enumerable, Kernel]

Nota: Podemos notar que se nossa classe não responder a nenhum método each, o modulo Enumerable não ira funcionar, pois para ele fazer esses métodos todos funcionarem ele precisa que a classe já tenha o each implementado.

E o que isso muda? A principio nada, pois a maioria das pessoas sempre utilizam esses métodos com Hash ou Array, que ja tem o método each implementado e consequentemente o Enumerable incluso.

Array.included_modules 
# => [Enumerable, Kernel]
Hash.included_modules
# => [Enumerable, Kernel]

Map

Voltando a explicação dos métodos, com o map as coisas já ficam mais interessantes, ele pode ser aplicado a qualquer classe que tenha o modulo Enumerable incluso.

Diferente do each visto a cima em vários aspectos, o mais visível sem dúvidas é a sua característica de retornar um Array com o resultado do bloco aplicado para cada elemento recebido.

numbers.map { |number| number * 2 } 
# => [2, 4, 6, 8, 10]

Outro exemplo:

places = ['restaurant', 'mall', 'park', 'theater'] 
places_upcase = places.map { |place| place.upcase }
places_upecase 
# => ["RESTAURANT", "MALL", "PARK", "THEATER"]

Collect

O collect é a mesma coisa que o map, sendo apenas um alias.

Select

Conhecido como o filter em outras linguagens, o select executa a expressão passada para cada elemento do array, se a expressão retornar true, ela e adicionada ao array de output.

numbers = [1, 2, 3, 4, 5] numbers.select { |number| number > 3 } 
# => [4, 5]

Podemos ver no exemplo a cima que o retorno do bloco veio apenas com os dois números maiores que 3, respeitando a expressão passada ao bloco.

No exemplo abaixo temos outro exemplo, onde só pegamos os elementos impares de um array, e como nos outros métodos, salvamos a saída em uma variável.

odd_numbers = numbers.select { |number| number.odd? } 
odd_numbers 
# => [1, 3, 5]

Conclusão

Embora muita gente se confunda achando que os dois métodos são a mesma coisa (e isso é normal!!!), eles não são, e cada um pode ser útil da sua forma. No modulo Enumerable ainda existe uma infinidade de métodos muito úteis (e outros nem tanto, vamos ser sinceros), que podem te ajudar no seu dia a dia ou até em situações muito especificas, e eu recomendo que caso tenha interesse estude profundamente o modulo, pois ele pode aumentar muito sua produtividade.