Diferença entre map, collect, select e each no Ruby
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.