Що робить метод "map" у Ruby?


250

Я новачок у програмуванні. Чи може хтось пояснити, що .mapробити в:

params = (0...param_count).map

9
Задайте по одному запитання. mapє загальним "функціональним" методом, знайденим на безлічі об'єктів, що використовуються для перетворення значень у послідовності (з особливими міркуваннями). ..і ...є способами створення діапазонів. Крім того, ознайомтеся з REPL, де ви можете спробувати цей матеріал самостійно! :)

5
REPL для рубіна - це irb, для 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
Ах, я розумію. Отже.
bigpotato

24
Для випадкових читачів небезпечно, що вступне речення описує mapтак, ніби воно булоmap!
калейдик

12
щоб побачити різницю між картою та кожною, відкрийте вікно IRB і подивіться на результати для y та z у наступному коді: y = [1,2,3] .each {| x | х + 1}; z = [1,2,3] .мапа {| x | x + 1}
davej

7
@ Inquisitive: 'every' повертає масив, який викликає його (у прикладі [1,2,3]), коли блок подається, 'map' повертає новий масив, заповнений значеннями, обчисленими блоком. Це може допомогти: встановити змінну ary = [1,2,3] та перевірити, чи це object_id. Потім запустіть y = ary.each {| x | х + 1}; z = ary.map {| x | x + 1}. Тепер перевірте об'єкт_id 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. Щоб більш просто використовувати техніку швидкого доступу ampersand, давайте скористаємося іншим прикладом:

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

Трансформація даних у Ruby часто включає каскад mapоперацій. Вивчення map& select, вони - одні з найкорисніших методів Ruby в первинній бібліотеці. Вони так само важливі, як іeach .

( mapтакож псевдонім для collect. Використовуйте те, що найкраще підходить для вас концептуально.)

Більш корисна інформація:

Якщо об'єкт Enumerable, на якому ви працюєте eachабо mapна якому, містить набір перелічених елементів (хеші, масиви), ви можете оголосити кожен із цих елементів у ваших блокових трубах так:

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

Це не працює для мене в irb. Я отримую NoMethodError: private method 'plusone' called for 1:Fixnumв ruby ​​2 та "неправильну кількість args" у 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 є спосіб зробити це звичайним методом - як, а не методом класу?
tekknolagi

6

Використовуючи рубін 2.4, ви можете зробити те ж саме, використовуючи transform_valuesцю функцію, витягнуту з рейок на рубін.

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

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

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.


Це дивно, але Range#mapнасправді перетворює його на масив.
Педро Насіменто

1
@PedroNascimento: Так ... це я сказав?
Ри-

Вибачте, я не знав, що названа сама по собі карта не повертає таку 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}

Map забезпечує значення ітерації через масив, який повертається за допомогою параметрів блоку.


карта точно така ж, як і збирати.
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()}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.