Методы массивов в Ruby

Массивы позволяют представлять в программах списки данных. Данные в массиве можно сортировать, удалять дубликаты, изменять порядок, также можно извлекать разделы массива или искать в нем определенные данные. Вы также можете преобразовать массив в строку, преобразовать один массив данных в другой и свернуть массив в одно значение.

Читайте также:

Данный мануал познакомит вас с основными методами массивов Ruby.

При работе с мануалом вы заметите, что некоторые методы заканчиваются восклицательным знаком (!). Эти методы имеют побочные эффекты, такие как изменение исходного значения или исключения. Многие методы, перечисленные в этом руководстве, имеют аналог с этим суффиком.

Вы также столкнетесь с методами, которые заканчиваются вопросительным знаком (?). Эти методы возвращают логическое значение.

Это соглашение об именах, используемое в Ruby, не предусмотрено на уровне программы; просто это такой способ определить, чего ожидать от метода.

Доступ к элементам

Если вы уже читали мануал Работа с массивами в Ruby, вы знаете, что можете получить доступ к отдельному элементу, используя его индекс, и что индексация начинается с 0.

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[0]    # "Tiger"
sharks[1]    # "Great White"
sharks[-1]   # "Angel"

Также вы, вероятно, помните методы first и last, которые выводят первый и последний элемент в массиве.

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.first   # "Tiger"
sharks.last    # "Angel"

При запросе несуществующего элемента массива вы получите nil. Чтобы вместо этого получить ошибку, используется метод fetch.

sharks.fetch(42)
IndexError: index 42 outside of array bounds: -4...4

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

sharks.fetch(42, "Nope")     # "Nope"

Извлечение нескольких значений массива

Иногда из массива нужно извлечь не один элемент, а подмножество значений.

Если вы укажете начальный индекс, а затем количество требуемых элементов, вы получите новый массив, содержащий эти значения. Например, чтобы извлечь две средние записи из массива sharks, нужно использовать:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks[1,2]   # ["Great White", "Hammerhead"]

1 – это индекс первого элемента в нужном подмножестве, а 2 – количество требуемых элементов. Потому в результате вы получите новый массив, который включает в себя элементы Great White и Hammerhead.

Метод slice может сделать то же самое:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.slice(1,2)   # ["Great White", "Hammerhead"]

Метод slice также возвращает новый массив, оставляя исходный массив неизменным. Однако, если вы используете метод slice!, он изменит исходный массив.

Метод take позволяет извлечь указанное количество записей с начала массива:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sharks.take(2)  # ["Tiger", "Great White"]

В некоторых ситуациях нужно извлечь не конкретный, а произвольный элемент массива.

Извлечение произвольной записи из массива

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

Чтобы получить случайный элемент из массива, вы можете сгенерировать случайный индекс между 0 и последним индексом массива и использовать его как индекс для извлечения значения, но есть более простой способ – метод sample, который извлекает случайную запись из массива.

Попробуйте использовать его, чтобы получить случайный элемент из массива ответов и создать примитивную версию игры Magic 8-Ball (файл 8ball.rb):

answers = ["Yes", "No", "Maybe", "Ask again later"]
print answers.sample
Maybe

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

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sample = sharks.sample(2)
print sample
["Whale", "Great White"]

Поиск и фильтрация элементов

Чтобы найти конкретные элементы в массиве, можно перебирать элементы, пока не будет найден необходимый. Но массивы Ruby предоставляют несколько методов, специально предназначенных для упрощения процесса поиска по массивам.

Чтобы просто уточнить, есть ли в массиве тот или иной элемент, используйте метод include?. Он возвращает true, если значение есть в массиве.

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sharks.include? "Tiger"      # true
["a", "b", "c"].include? 2   # false

Однако include? выводит только точные совпадения, потому часть слова с его помощью найти не получится.

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sharks.include? "Tiger"      # true
sharks.include? "tiger"      # false
sharks.include? "ti"         # false

Метод find находит и возвращает первый элемент в массиве, который соответствует указанному условию.

Например, чтобы найти первую запись в массиве sharks, которая содержит букву a, вы можете использовать метод each; он сравнит все записи и остановит итерацию при первом совпадении:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = nil
sharks.each do |shark|
if sharks.include? "a"
result = shark
break
end
end

Также вы можете использовать метод find, чтобы сделать то же самое:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.find {|item| item.include?("a")}
print result
Hammerhead

Метод find выполняет предоставленный блок для каждого элемента массива. Если последнее выражение в блоке имеет значение true, метод find возвращает значение и останавливает итерацию. Если он не находит ничего после итерации по всем элементам, он возвращает nil.

Метод select работает аналогичным образом, но он создает новый массив, содержащий все элементы, которые соответствуют условию, вместо того, чтобы просто вернуть одно значение и остановиться.

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.select {|item| item.include?("a")}
print results
["Hammerhead", "Great White", "Whale"]

Метод reject возвращает новый массив, содержащий элементы, которые не соответствуют условию. Вы можете использовать его в качестве фильтра, который удаляет из массива ненужные элементы. Вот пример метода, который удалит все записи, содержащие букву a:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.reject {|item| item.include?("a")}
print results
["Tiger"]

Методы select и reject возвращают новый массив, оставляя исходный массив неизменным. Однако, если вы используете select! и reject!, исходный массив будет изменен.

Метод find_all является псевдонимом для select, но метода find_all!  не существует.

Сортировка массива

Сортировка данных является обычной практикой. Вам может потребоваться перечислить имена или отсортировать номера от самых маленьких до самых больших.

Массивы Ruby предлагают метод reverse, который может изменять порядок элементов в массиве. Если у вас есть список организованных данных, которые уже, reverse быстро отобразит элементы в обратном порядке:

sharks = ["Angel", "Great White", "Hammerhead", "Tiger"]
reversed_sharks = sharks.reverse
print reversed_sharks
["Tiger", "Hammerhead", "Great White", "Angel"]

Метод reverse возвращает новый массив и не изменяет исходный. Используйте reverse!, если вы хотите изменить исходный массив.

Однако изменение порядка элементов на обратный не всегда является наиболее эффективным или практичным способом сортировки данных. Используйте метод sort для сортировки элементов в массиве так, как хотелось бы вам.

Этот метод эффективно работает с простыми массивами строк и чисел.

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort
print sorted_sharks
["Angel", "Great White", "Hammerhead", "Tiger"]

Однако, если вы хотите отсортировать элементы по другому принципу, вам нужно указать способ сортировки. Метод sort принимает блок Ruby, который позволяет сравнить элементы массива.

Для сравнения используется оператор <=>. Этот оператор сравнивает два объекта Ruby и возвращает -1, если объект слева меньше, 0, если объекты одинаковы, и 1, если объект слева больше.

1 <=> 2    # -1
2 <=> 2    #  0
2 <=> 1    #  1

Метод sort принимает такой блок, который затем используется для сортировки значений в массиве.

Вот пример, который явно сравнивает записи в массиве для сортировки в порядке возрастания:

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| a <=> b }
print sorted_sharks

Переменные a и b представляют отдельные элементы в массиве, которые сравниваются. Результат выглядит следующим образом:

["Angel", "Great White", "Hammerhead", "Tiger"]

Чтобы отсортировать этот массив в обратном порядке, измените порядок сравниваемых элементов.

sharks = ["Tiger", "Great White", "Hammerhead", "Angel"]
sorted_sharks = sharks.sort{|a,b| b <=> a }
print sorted_sharks
["Tiger", "Hammerhead", "Great White", "Angel"]

Метод sort отлично подходит для массивов, содержащих простые типы данных, такие как целые числа, числа с плавающей точкой и строки. Но когда массивы содержат более сложные объекты, придется немного поработать.

Вот массив хэшей, где каждый хэш представляет название акулы:

sharks = [
{name: "Hammerhead"},
{name: "Great white"},
{name: "Angel"}
]

Отсортировать его с помощью sort  не так просто. Вызвать метод не получится:

sharks.sort
ArgumentError: comparison of Hash with Hash failed

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

sorted_sharks.sort{|a, b| a[:name] <=> b[:name]}
print sorted_sharks
[{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}]

При работе с более сложными структурами вы можете применить метод sort_by, который использует более эффективный алгоритм сортировки. sort_by принимает блок, для которого требуется только один аргумент, ссылка на текущий элемент в массиве:

sharks = [
{name: "Hammerhead"},
{name: "Great white"},
{name: "Angel"}
]
sorted_sharks = sharks.sort_by{|shark| shark[:name] }
print sorted_sharks
[{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}]

Метод sort_by реализует преобразование Шварца, алгоритм сортировки, наиболее подходящий для сравнения объектов на основе значения конкретного ключа. Метод sort_by эффективно использовать при сравнении коллекций объектов.

Методы sort и sort_by возвращают новые массивы, оставляя исходный массив без изменений. Если вы хотите изменить исходный массив, используйте sort! и sort_by!.

Удаление повторяющихся элементов

Иногда в списках встречаются повторяющиеся элементы. Вы можете перебрать массив и отфильтровать повторения, но метод uniq делает это намного быстрее. Метод uniq возвращает новый массив без повторяющихся значений.

[1,2,3,4,1,5,3].uniq   # [1,2,3,4,5]

Иногда повторы появляются после слияния двух наборов данных. рассмотрим эти два массива:

sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]

Если их объединить, появятся повторы:

sharks + new_sharks
# ["Tiger", "Great White", "Tiger", "Hammerhead"]

С помощью uniq можно удалить повторы. Но вместо того, чтобы складывать массивы, используйте оператор конвейера |, который поможет объединить их:

sharks | new_sharks
# ["Tiger", "Great White", "Hammerhead"]

Массивы Ruby также поддерживают вычитание. Это значит, что вы могли бы вычесть new_sharks из sharks, чтобы получить только новые значения:

sharks = ["Tiger", "Great White"]
new_sharks = ["Tiger", "Hammerhead"]
sharks - new_sharks   # ["Great White"]

Преобразование данных

Метод map и его алиас collect могут преобразовывать содержимое массива, что означает, что он может выполнять операцию над каждым элементом.

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

numbers = [2,4,6,8]
# square each number
squared_numbers = numbers.map {|number| number * number}
print squared_numbers

Переменная squared_numbers представляет собой массив исходных чисел, возведенных в квадрат:

[4, 16, 36, 64]

Метод map часто используется в веб-приложениях для преобразования массива в элементы для раскрывающегося списка HTML. Вот очень упрощенная версия того, как это может выглядеть:

]sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
options = sharks.map {|shark| "<option>#{shark}</option>"}
print options

В массиве options теперь каждый элемент заключен в тег <option> </ option>:

["<option>Hammerhead</option>", "<option>Great White</option>", "<option>Tiger</option>", "<option>Whale</option>"]

map возвращает новый массив, оставляя исходный массив неизменным. Используя map!, чтобы изменить существующий массив. И помните, что у map есть псевдоним collect. При написании кода важно быть последовательными и использовать только тот или иной метод.

Поскольку map возвращает новый массив, он затем может быть преобразован и изменен или даже преобразован в строку.

Преобразование массива в строку

Преобразовать объект Rubyв строку можно с помощью метода to_s, который используется оператором print. Вернемся к массиву sharks:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]

Метод to_s преобразует его в такую строку.

"[\"Hammerhead\", \"Great White\", \"Tiger\", \"Whale\"]"

Это удобно для отладки, но не очень полезно в программах.

Метод join преобразовывает массив в строку, но позволяет управлять организацией элементов.

Метод join в качестве аргумента принимает символ, который нужно использовать в качестве разделителя. Чтобы преобразовать массив в строку, разделенную пробелами, используйте:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(" ")
print result
Hammerhead Great White Tiger Whale

Чтобы разделить элементы массива запятыми, используйте:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join(", ")
print result
Hammerhead, Great White, Tiger, Whale

Если метод join не получит разделитель, он преобразует массив в сплошную строку:

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join
print result
HammerheadGreat WhiteTigerWhale

Метод join вместе с map может быстро преобразовать массив для вывода. Метод map может преобразовать элементы в данные, а метод join – преобразовать полученный результат в строку для отображения. Ранее вы уже видели, как преобразовать массив sharks в массив HTML-элементов. Теперь вы можете использовать join, чтобы преобразовать массив в строку, где разделителем будет символ новой строки.

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
options = sharks.map {|shark| "<option>#{shark}</option>"}
output = options.join("\n")
print output
<option>Hammerhead</option>
<option>Great White</option>
<option>Tiger</option>
<option>Whale</option>

Сведение массива к единому значению

При работе с данными может возникнуть необходимость свести их к единому значению (например, получить сумму).

Для этого можно использовать переменную и метод each.

result = 0
[1, 2, 3].each {|num| result += num}
print result
6

Вместо этого можно использовать метод reduce. Он итерирует массив и получает сумму путем бинарных операций с каждым элементом.

Метод reduce принимает исходное значение и блок с двумя локальными значениями: ссылкой на результат и ссылкой на текущий элемент. Внутри блока определяется логика вычисления конечного результата.

Чтобы получить сумму элементов массива, инициируйте результат 0 и добавьте текущее значение к результату в блоке.

output = [1,2,3].reduce(0) {|result, current| result += current }
print output
6

Если вы инициализируете результат 0, можно пропустить аргумент и просто передать блок. Метод автоматически перейдет к первому элементу массива.

output = [1,2,3].reduce {|result, current| result += current }
print output
6

Метод reduce принимает бинарный метод, или метод объекта, принимающий в качестве аргумента другой объект, который будет применяться к каждой записи массива. Затем метод reduce использует результат, чтобы создать единое значение.

Когда вы пишете в Ruby 2 + 2, вы вызываете метод + на целое число 2.

2.+(2)   # 4

Ruby применяет синтаксический сахар, потому вы можете просто написать 2 + 2.

Метод reduce позволяет указать бинарный метод, передавая его имя как символ. Это значит, вы можете передать методу :+, чтобы получить сумму элементов.

output = [1, 2, 3].reduce(:+)
print output
6

Метод reduce умеет не только складывать числа. Важно помнить, что метод reduce сводит массив к единому значению, которым может быть даже другой массив.

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

Можно использовать метод reject, чтобы отфильтровать нечисловые значения, а затем использовать map, чтобы преобразовать оставшиеся значения в целые числа. Но reduce может сделать это в один шаг.

Используйте пустой массив как значение инициализации. В блоке конвертируйте текущее значение в целое число с помощью метода Integer. Если значение нельзя преобразовать в целое число, Integer делает исключение, и значению можно присвоить nil.

Затем отобранные значения помещаются в новый массив, кроме значений nil.

Код выглядит так:

values = ["1", "2", "a", "3"]
integers = values.reduce([]) do |array, current|
val = Integer(current) rescue nil
array.push(val) unless val.nil?
array
end
print integers
[1,2,3]

Заключение

Теперь вы знакомы с базовыми методами массивов Ruby и умеете извлекать элементы из массива, выполнять поиск по массиву, сортировать элементы и преобразовывать данные, создавая новые массивы и строки.

Эти функции можно применить во многих программах Ruby.

Читайте также:

Tags: