Поиск по сайту:

Как использовать методы массива в Ruby


Введение

строку, преобразовать один массив данных в другой и свернуть массив в одно значение.

В этом руководстве вы изучите некоторые из наиболее практичных методов, которые Ruby предоставляет для работы с данными, хранящимися в массивах.

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

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

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

Давайте начнем изучение методов массива с рассмотрения нескольких способов доступа к элементам.

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

Если вы уже ознакомились с учебным пособием «Как работать с массивами в Ruby», вы знаете, что можете получить доступ к отдельному элементу, используя его индекс, который начинается с нуля, например:

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)
Output
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, то есть Great White, и указываем, что нам нужны элементы 2, поэтому мы получаем новый массив, содержащий Великая белая и Голова-молота.

Вы можете использовать метод 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:

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

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

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
sample = sharks.sample(2)
print sample
Output
["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
Output
Hammerhead

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

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

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

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

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
results = sharks.reject {|item| item.include?("a")}
print results
Output
["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
Output
["Tiger", "Hammerhead", "Great White", "Angel"]

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

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

Для простых массивов строк или чисел метод sort эффективен и даст вам нужные результаты:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сортировать с помощью sort не так просто. Вызов sort для массива завершается ошибкой:

sharks.sort
Output
ArgumentError: comparison of Hash with Hash failed

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

sorted_sharks.sort{|a, b| a[:name] <=> b[:name]}
print sorted_sharks
Output
[{: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
Output
[{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}]

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

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

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

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

Иногда вы будете получать списки данных, которые имеют некоторое дублирование. Вы можете пройтись по массиву и отфильтровать дубликаты, но метод Ruby 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 каждая акула заключена в HTML-тег :

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

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

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

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

Все объекты в Ruby имеют метод to_s, который преобразует объект в строку. Это то, что использует оператор print. Учитывая наш массив акул:

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
Output
Hammerhead Great White Tiger Whale

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

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

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

sharks = ["Hammerhead", "Great White", "Tiger", "Whale"]
result = sharks.join
print result
Output
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
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
Output
6

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

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

Поскольку мы хотим суммировать массив, мы инициализируем результат значением 0, а затем добавляем текущее значение к результату в блоке:

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

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

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

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

Когда вы пишете 2 + 2 в Ruby, вы фактически вызываете метод + для целого числа 2:

2.+(2) # 4

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

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

output = [1, 2, 3].reduce(:+)
print output
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
Output
[1,2,3]

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

Заключение

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

Обязательно ознакомьтесь с этими связанными руководствами, чтобы продолжить изучение работы с данными в Ruby:

  • Как работать со строками в Ruby
  • Как работать с массивами в Ruby
  • Понимание типов данных в Ruby