Что делает метод «карты» в Ruby?

250

Я новичок в программировании. Может кто-нибудь объяснить, что .mapбудет делать в:

params = (0...param_count).map
bigpotato
источник
9
Задайте один вопрос за один раз. mapявляется общим «функциональным» методом, который можно найти в перечисляемых объектах, используемых для преобразования значений в последовательности (с особыми соображениями). ..и ...способы создания диапазонов. Кроме того, ознакомьтесь с REPL, где вы можете попробовать этот материал самостоятельно! :)
5
REPL для ruby ​​- это irb, для Rails - это rails c. REPL позволяет вам тестировать код непосредственно в самой языковой оболочке.
Гэри

Ответы:

431

mapМетод принимает перечислимый объект и блок, и запускает блок для каждого элемента, каждый вывод возвращаемого значения из блока (исходный объект не изменяется , если не используется map!):

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

Arrayи Rangeперечислимые типы. mapс блоком возвращает массив. map!мутирует исходный массив.

Где это полезно, и в чем разница между map!и each? Вот пример:

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

Выход:

Danil is a programmer
Edmund is a programmer
Данил Сперанский
источник
3
спасибо Сперанский за пример. тогда чем .map отличается от .each?
bigpotato
2
Аааа, я понял. Таким образом, .map фактически изменяет массив, в то время как .each просто перебирает массив, чтобы получить доступ к значениям, оставляя исходный массив нетронутым?
bigpotato
24
Для случайных читателей опасно, что вступительное предложение описывает, mapкак если бы это былоmap!
kaleidic
12
чтобы увидеть разницу между картой и каждым, откройте окно IRB и посмотрите на результаты для y и z в следующем коде: y = [1,2,3] .each {| x | х + 1}; z = [1,2,3] .map {| x | x + 1}
davej
7
@Inquisitive: 'each' возвращает массив, который его вызывает (в примере, [1,2,3]), когда предоставляется блок, 'map' возвращает новый массив, заполненный значениями, вычисленными блоком. Это может помочь: установите переменную ary = [1,2,3] и проверьте ее object_id. Затем запустите y = ary.each {| x | х + 1}; z = ary.map {| x | х + 1}. Теперь проверьте object_id's y и z. y имеет тот же object_id, что и ary (потому что каждый возвращает ary), но z имеет другой object_id, потому что map вернула новый массив
davej
66

mapвместе с selectи eachявляется одной из рабочих лошадок Руби в моем коде.

Это позволяет вам выполнить операцию над каждым из объектов вашего массива и вернуть их все в одном месте. Примером может быть увеличение массива чисел на единицу:

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

Если вы можете запустить один метод для элементов вашего массива, вы можете сделать это в стиле сокращения следующим образом:

  1. Чтобы сделать это с приведенным выше примером, вы должны сделать что-то вроде этого

    class Numeric
      def plusone
        self + 1
      end
    end
    [1,2,3].map(&:plusone)
    #=> [2,3,4]
  2. Чтобы проще использовать технику быстрого доступа, давайте рассмотрим другой пример:

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

Преобразование данных в Ruby часто включает каскад mapопераций. Изучите map& select, они являются одними из самых полезных методов Ruby в основной библиотеке. Они так же важны, как и each.

( mapтакже является псевдонимом для collect. Используйте то, что лучше всего подходит для вас концептуально.)

Более полезная информация:

Если объект Enumerable, на котором вы работаете eachили mapна котором находится, содержит набор элементов Enumerable (хэши, массивы), вы можете объявить каждый из этих элементов внутри ваших блочных каналов следующим образом:

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

В случае Hash (также Enumerableобъекта, Hash - это просто массив кортежей со специальными инструкциями для интерпретатора). Первый «параметр трубы» - это ключ, второй - значение.

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

Чтобы ответить на актуальный вопрос:

Предполагая, что paramsэто хеш, это будет лучший способ отобразить его: используйте два параметра блока вместо одного, чтобы захватить пару ключ-значение для каждого интерпретируемого кортежа в хэше.

params = {"one" => 1, "two" => 2, "three" => 3}
params.each do |k,v|
  puts "#{k}=#{v}"
end
# one=1
# two=2
# three=3
boulder_ruby
источник
Это не работает для меня в IRB. Я получаю NoMethodError: private method 'plusone' called for 1:Fixnumв ruby ​​2 и «неправильное количество аргументов» в ruby ​​1.9 / 1.8. Во всяком случае, я использовал лямбда: plusone = ->(x) { x + 1 }затем вынуть символ спецификатора: [1,2,3].map(&plusone).
tjmcewan
1
хммм звучит так, как будто вы объявили privateвнутри класса, где вы помещаете свой метод перед тем, как поместить свой метод
boulder_ruby
Да, это так. Кроме этого не было. :( Во-первых, это был прямой сценарий без классов, во-вторых, обычный irb. Вот моя копия / вставка вашего кода: gist.github.com/tjmcewan/a7e4feb2976a93a5eef9
tjmcewan
Да, я просто поместил плохой пример в свой код, извини. Попробуйте модифицированный код. Это работает сейчас ...
boulder_ruby
1
@boulder_ruby Есть ли способ сделать это с помощью обычного метода - например, не метод класса?
Текнолаги
6

Используя ruby ​​2.4, вы можете сделать то же самое, используя transform_valuesэту функцию, извлеченную из рельсов в ruby.

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

h.transform_values { |v| v * 10 }
 #=> {a: 10, b: 20, c: 30}
tokhi
источник
4

0..param_count означает "до и включая param_count". 0...param_countозначает "до, но не включая param_count".

Range#mapне возвращает Enumerable, фактически отображает его в массив. Это так же, как Range#to_a.

Педро Наскименто
источник
3

Он «сопоставляет» функцию с каждым элементом в Enumerable- в данном случае, диапазоне. Таким образом, он будет вызывать блок, переданный один раз для каждого целого числа от 0 доparam_count (исключая - вы правы насчет точек), и возвращать массив, содержащий каждое возвращаемое значение.

Вот документация для Enumerable#map. У него также есть псевдоним collect.

Ry-
источник
Это странно, но на Range#mapсамом деле преобразует его в массив.
Педро Насименто
1
@PedroNascimento: Да ... это то, что я сказал?
Ry-
Извините, я не знал, что карта, названная сама по себе, не вернула Enumerable, как каждая. Я думал, что это сделал.
Педро Насименто
2

Карта является частью перечислимого модуля. Очень похоже на «собирать» Например:

  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}

Карта предоставляет значения, повторяющиеся в массиве, которые возвращаются параметрами блока.

gkstr1
источник
Карта точно так же, как собирать.
BKSpurgeon
0

#each

#eachзапускает функцию для каждого элемента в массиве. Следующие два фрагмента кода эквивалентны:

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

#mapприменяет функцию к каждому элементу массива, возвращая полученный массив. Следующее эквивалентно:

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

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

#map!

#map!это как #map, но изменяет массив на месте. Следующее эквивалентно:

array = ["zero", "one", "two"]
array.map!{|element| element.capitalize()}
array = ["zero", "one", "two"]
array = array.map{|element| element.capitalize()}
Дживан Пал
источник