Que fait la méthode «map» dans Ruby?

250

Je suis nouveau en programmation. Quelqu'un peut-il expliquer ce .mapqui ferait:

params = (0...param_count).map
grosse Pomme de terre
la source
9
Posez une question à la fois. mapest une méthode "fonctionnelle" courante trouvée sur les objets Enumerable utilisés pour transformer les valeurs dans une séquence (avec des considérations spéciales). ..et ...sont des moyens de créer des plages. De plus, familiarisez-vous avec le REPL, où vous pouvez essayer ce genre de choses vous-même! :)
5
REPL pour ruby ​​is irb, pour Rails it is rails c. REPL vous permet de tester le code directement sur le shell de langage lui-même.
Gary

Réponses:

431

La mapméthode prend un objet énumérable et un bloc, et exécute le bloc pour chaque élément, produisant chaque valeur retournée à partir du bloc (l'objet d'origine est inchangé sauf si vous utilisez map!):

[1, 2, 3].map { |n| n * n } #=> [1, 4, 9]

Arrayet Rangesont des types énumérables. mapavec un bloc renvoie un tableau. map!mute le tableau d'origine.

Où est-ce utile et quelle est la différence entre map!et each? Voici un exemple:

names = ['danil', 'edmund']

# here we map one array to another, convert each element by some rule
names.map! {|name| name.capitalize } # now names contains ['Danil', 'Edmund']

names.each { |name| puts name + ' is a programmer' } # here we just do something with each element

Le résultat:

Danil is a programmer
Edmund is a programmer
Danil Speransky
la source
3
merci speransky pour l'exemple. alors comment est .map différent de .each?
bigpotato
2
Ahhh je comprends. Donc .map mute en fait le tableau tandis que .each fait simplement une boucle dans le tableau pour accéder aux valeurs tout en laissant le tableau d'origine intact?
bigpotato
24
Il est dangereux pour les lecteurs occasionnels que la phrase d'ouverture décrit mapcomme si elle étaitmap!
kaléidique
12
pour voir la différence entre la carte et chacune, ouvrez une fenêtre IRB et regardez les résultats pour y et z dans le code suivant: y = [1,2,3] .each {| x | x + 1}; z = [1,2,3] .map {| x | x + 1}
davej
7
@Inquisitive: 'each' renvoie le tableau qui l'appelle (dans l'exemple, [1,2,3]) lorsqu'un bloc est fourni, 'map' renvoie un nouveau tableau rempli avec les valeurs calculées par le bloc. Cela pourrait vous aider: définissez la variable ary = [1,2,3] et vérifiez son object_id. Exécutez ensuite y = ary.each {| x | x + 1}; z = ary.map {| x | x + 1}. Vérifiez maintenant les object_id de y et z. y a le même object_id que ary (parce que chacun a retourné ary), mais z a un object_id différent, car map a renvoyé un nouveau tableau.
davej
66

map, avec selectet eachest l'un des chevaux de bataille de Ruby dans mon code.

Il vous permet d'exécuter une opération sur chacun des objets de votre tableau et de les renvoyer tous au même endroit. Un exemple serait d'incrémenter un tableau de nombres de un:

[1,2,3].map {|x| x + 1 }
#=> [2,3,4]

Si vous pouvez exécuter une seule méthode sur les éléments de votre tableau, vous pouvez le faire dans un style abrégé comme ceci:

  1. Pour ce faire avec l'exemple ci-dessus, vous devez faire quelque chose comme ça

    class Numeric
      def plusone
        self + 1
      end
    end
    [1,2,3].map(&:plusone)
    #=> [2,3,4]
  2. Pour utiliser plus simplement la technique du raccourci esperluette, utilisons un autre exemple:

    ["vanessa", "david", "thomas"].map(&:upcase)
    #=> ["VANESSA", "DAVID", "THOMAS"]

La transformation de données dans Ruby implique souvent une cascade d' mapopérations. Etudiez map& select, ce sont quelques-unes des méthodes Ruby les plus utiles de la bibliothèque principale. Ils sont tout aussi importants que each.

( mapest également un alias pour collect. Utilisez ce qui vous convient le mieux conceptuellement.)

Plus d'informations utiles:

Si l' objet Enumerable que vous exécutez eachou mapsur contient un ensemble d'éléments Enumerable (hachages, tableaux), vous pouvez déclarer chacun de ces éléments à l'intérieur de vos canaux de blocs comme suit:

[["audi", "black", 2008], ["bmw", "red", 2014]].each do |make, color, year|
  puts "make: #{make}, color: #{color}, year: #{year}"
end
# Output:
# make: audi, color: black, year: 2008
# make: bmw, color: red, year: 2014

Dans le cas d'un Hash (également un Enumerableobjet, un Hash est simplement un tableau de tuples avec des instructions spéciales pour l'interpréteur). Le premier "paramètre de tuyau" est la clé, le second est la valeur.

{:make => "audi", :color => "black", :year => 2008}.each do |k,v|
    puts "#{k} is #{v}"
end
#make is audi
#color is black
#year is 2008

Pour répondre à la vraie question:

En supposant qu'il paramss'agit d'un hachage, ce serait la meilleure façon de le mapper: utilisez deux paramètres de bloc au lieu d'un pour capturer la paire clé / valeur pour chaque tuple interprété dans le hachage.

params = {"one" => 1, "two" => 2, "three" => 3}
params.each do |k,v|
  puts "#{k}=#{v}"
end
# one=1
# two=2
# three=3
boulder_ruby
la source
Cela ne fonctionne pas pour moi dans irb. Je reçois NoMethodError: private method 'plusone' called for 1:Fixnumen rubis 2 et «mauvais nombre d'arguments» en rubis 1.9 / 1.8. Quoi qu'il en soit, j'ai utilisé un lambda: plusone = ->(x) { x + 1 }prenez le spécificateur symbole: [1,2,3].map(&plusone).
tjmcewan
1
hmm sonne comme si vous aviez déclaré à l' privateintérieur de la classe où vous avez mis votre méthode avant de mettre votre méthode
boulder_ruby
Ouais, ça le fait totalement. Sauf que ce n'était pas le cas. :( Tout d'abord, c'était dans un script droit sans classes, ensuite dans plain irb. Voici mon copier / coller de votre code: gist.github.com/tjmcewan/a7e4feb2976a93a5eef9
tjmcewan
Ouais, je viens de mettre un mauvais exemple dans mon code, je suis désolé. Essayez le code modifié. Ça marche maintenant ...
boulder_ruby
1
@boulder_ruby existe-t-il un moyen de le faire avec une méthode normale - comme dans, pas une méthode de classe?
tekknolagi
6

En utilisant ruby ​​2.4, vous pouvez faire la même chose en utilisant transform_valuescette fonctionnalité extraite des rails vers ruby.

h = {a: 1, b: 2, c: 3}

h.transform_values { |v| v * 10 }
 #=> {a: 10, b: 20, c: 30}
tokhi
la source
4

0..param_countsignifie "jusqu'à et y compris param_count". 0...param_countsignifie "jusqu'à, mais sans inclure param_count".

Range#mapne renvoie pas un Enumerable, il le mappe en fait sur un tableau. C'est la même chose que Range#to_a.

Pedro Nascimento
la source
3

Il "mappe" une fonction à chaque élément d'une Enumerable- dans ce cas, une plage. Ainsi, il appellerait le bloc passé une fois pour chaque entier de 0 à param_count(exclusif - vous avez raison sur les points) et retournerait un tableau contenant chaque valeur de retour.

Voici la documentation de Enumerable#map. Il a également un alias, collect.

Ry-
la source
C'est bizarre, mais le Range#mapconvertit en fait en tableau.
Pedro Nascimento
1
@PedroNascimento: Ouais ... c'est ce que j'ai dit?
Ry-
Désolé, je ne savais pas que la carte appelée par elle-même ne renvoyait pas un Enumerable, comme chacun. Je pensais que oui.
Pedro Nascimento
2

La carte fait partie du module énumérable. Très similaire à "collecter" Par exemple:

  Class Car

    attr_accessor :name, :model, :year

    Def initialize (make, model, year)
      @make, @model, @year = make, model, year
    end

  end

  list = []
  list << Car.new("Honda", "Accord", 2016)
  list << Car.new("Toyota", "Camry", 2015)
  list << Car.new("Nissan", "Altima", 2014)

  p list.map {|p| p.model}

La carte fournit des valeurs itératives à travers un tableau qui sont retournées par les paramètres de bloc.

gkstr1
la source
la carte est exactement la même que collecter.
BKSpurgeon
0

#each

#eachexécute une fonction pour chaque élément d'un tableau. Les deux extraits de code suivants sont équivalents:

x = 10
["zero", "one", "two"].each{|element|
    x++
    puts element
}
x = 10
array = ["zero", "one", "two"]

for i in 0..2
    x++
    puts array[i]
end

#map

#mapapplique une fonction à chaque élément d'un tableau, renvoyant le tableau résultant. Les éléments suivants sont équivalents:

array = ["zero", "one", "two"]
newArray = array.map{|element| element.capitalize()}
array = ["zero", "one", "two"]

newArray = []
array.each{|element|
    newArray << element.capitalize()
}

#map!

#map!est comme #map, mais modifie le tableau en place. Les éléments suivants sont équivalents:

array = ["zero", "one", "two"]
array.map!{|element| element.capitalize()}
array = ["zero", "one", "two"]
array = array.map{|element| element.capitalize()}
Jivan Pal
la source