array tricks

Программист Ruby Коммерсант Проект года 1 место Учитель Проект месяца 1 место
Больше
11 года 8 мес. назад - 11 года 8 мес. назад #72989 от Iren_Rin
Iren_Rin создал тему: array tricks
На мой взгляд работа с массивами - одна из самых сильных сторон руби. С ними можно cделать вообще все что пожелаешь. Тут я хочу привести пример тех полезных методов и трюков, которые я использую чуть ли не каждый день. Я опущу совсем уж банальщину типа <<. Прошу опытный рубистов не кидать в меня помидорами, тема больше для неопытных:)

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

Операции над массивами, они очень быстрые и главное мощные:
Про сложение знают наверное все
Code:
[1, 2] + [3, 4] #=> [1, 2, 3, 4]
Вычитание тоже очень полезно (например для валидации с запрещенными элементами).
Code:
[3, 4, 5] - [4, 5] #=> [3] Удаляет из первого массива все элементы второго массива. (Не изменяет изначальный массив, возвращает копию)
Пересечение массивов - когда есть массив с разрешенными элементами, и массив, который нужно очистить. Очень часто использую.
Code:
[1, 2, 3] & [2, 3] # => [2, 3] Вернет новый массив с элементами которые встречаются в обоих массивах.
Объединение. Работает примерно так - складывает два массива, потом идет по новому массиву и выкидывает все элементы, которые встречал раньше.
Code:
[1, 2, 3, 3] | [2, 4, 5] # => [1, 2, 3, 4, 5]

C умножением будьте осторожны, на самом деле элементы не множатся а множится ссылка на них.
Code:
[ Object.new ] * 4 #Создаст массив с 4 ссылками на один объект Array.new(4) { Object.new } # Чаще всего когда умножают массив - хотят именно такой результат. # 4 раза выполни блок, результат сохрани в новый массив.

Работа с индексами не такое уж и тривиальное занятие.
Получить подмасив:
Code:
[1, 2, 3, 4, 5, 6][2 .. 4] #=> [3, 4, 5] Две точки включают последний элемент [1, 2, 3, 4, 5, 6][2 ... 3] #=> [3, 4] Три точки - не включают, кстати 2 .. 3 - это Range, а не магия :) [1, 2, 3, 4, 5][3 .. 1] #А это не сработает, когда хотите перевернутую часть массива делайте так: [1, 2, 3, 4, 5][1 .. 3].reverse
То что совсем не очевидно на первый взгляд, но очень полезно - замена при помощи индексов
Code:
arr = [1, 2, 3, 4, 5] arr[2 .. 3] = 'x' #Возьми элементы со второго по третий и замени их на x arr # => [1, 2, 'x', 5] arr[0 .. 1] = ['y', 'y', 'y'] #Возьми элементы с первого по второй и замени их на новый массив ( при этом, как ни странно, новый массив не будет одним элементом, он вставится и развернется) arr # => ['y', 'y', 'y', 'x', 5] arr[1 ... 2] = ['z', 'z'] #Возьми элементы со второго по третий (не включая, т.е. только второй) И замени их на новый массив. arr # => ['y', 'z', 'z', 'y', 'x', 5] arr[0 .. 1] = [] #Удали элементы 0 и 1 arr # => ['z', 'y', 'x', 5] arr[1 ... 1] = ['i', 'i'] #Вставь после первого элемента новый массив. arr # => ['z', 'i', 'i', 'y', 'x', 5]

Array#map - рабочая лошадка, когда из одного массива нужно получить другой массив, связанный с первым.
Code:
numbers = [18, 13, 14, 15] numbers.map { |i| "i + 3 = #{i + 3}" } #map выполняет блок для каждого элемента массива, результат становится элементом нового масива
Array#select, Array#reject - исполняют блок для каждого элемента массива, если блок возвращает истину то первый оставляет элемент в новом массиве, второй - наоборот выкидывает. Оба имеют bang аналоги, используйте с умом :)
Code:
[1, 2, 3, 4].select { |i| i > 2 } #=> [3, 4] [1, 2, 3, 4].reject { |i| i > 2 } #=> [1, 2]
Array#inject - очень мощная вещь, очень часто использую. Позволяет пройтись по массиву, с каждым элементом выполнить блок и сохранить результат по всему стеку.
Code:
arr = [1, 2, 3, 4, 5, 6, 7] #Метод принимает один аргумент, который будет начальным значением arr.inject 0 do |sum, element| #В блок передается результат по стеку и текущий элемент if element % 2 == 0 sum + element #Вы можете не изменять значение sum, все равно в следующий блок else #передастся результат исполнения предыдущего sum #(последнее выражение в предыдущем блоке) end end # => В итоге получим сумму всех четных элементов.
Если вы завели переменную-массив, и заполняете ее во время перебора, то скорее всего вам нужен inject.

Array#compact - приятный метод, который просто удаляет все nil из массива.
Code:
[1, nil, nil, 2, 3, nil].compact # => [1, 2, 3] # недавно игрался с мейкером - $game_party.line_set.slots.map(&:buttler).compact # тут с map используется краткий синтаксис блоков, могу и про это рассказать как-нибудь.

Array#flatten - мой любимец :) Разворачивает массив любой вложенности в простой массив:
Code:
[1, [2, [3]], 4].flatten #[1, 2, 3, 4]
Я сам удивляюсь на сколько часто я использую flatten. Он более полезный, чем может показаться.

Array#empty? - возвращает true когда в массиве ничего нет
Array#any? - возвращает true когда в массиве что нибудь есть.
Code:
[1, 2, 3, 4, 5, 6].any? { |i| i > 2 } #Такой синтаксис any? применяет к каждому элементу блок, #пока блок не вернет истину (не nil или false). #Тогда и any? вернет true

Array#find - применяет блок к каждому элементу пока он не вернет истину. Когда истина получена - возвращает текущий элемент
Code:
[1, 2, 3, 4, 5, 6].find { |i| i > 3 } # => 4. До 5 и 6 он не доберется.

#with_index - позволяет к любому итератору подмешать индекс текущего элемента:
Code:
#Это можно использовать к примеру в параллельном переборе arr1 = [2, 3] arr2 = [4, 5] arr1.each.with_index do |element, index| puts element puts arr2[index] end

Array#sample возвращает случайный элемент массива, или несколько уникальных случайных элементов, если передали параметр
Code:
arr = [1, 2, 3, 4, 5] arr.sample # => 3 arr.sample(3) # => [1,3,4]

Array#uniq - если хотите получить массив уникальных значений

Array#join приводит каждый элемент в строку, потом соединяет полученные строки через строку-аргумент.
Code:
arr = [1, 2, 3] "#{arr.join(' + ')} = #{arr.inject(0) { |s, i| s + i }}" #=> "1 + 2 + 3 = 6"

И последний который я сегодня упомяну - sort. Без блока он просто сортирует массив. С блоком его используют, когда нужно как то по сложному отсортировать массив. В блок передаются по два элемента. Метод ожидает от блока один из трех вариантов = -1 когда левый элемент меньше правого, 0 - когда элементы равны, +1 - когда левый элемент больше правого.
Code:
arr = [1, 2, 3, 4, 5, 6, 7, 8] # отсортируем его так - сначала четные, потом нечетные arr.sort { left, right| left % 2 - right % 2 } #[8, 2, 6, 4, 5, 3, 7, 1]

Получилось многовато, надеюсь эта стена текста была полезна кому нибудь. Удачи!
Последнее редактирование: 11 года 8 мес. назад пользователем Iren_Rin.
Спасибо сказали: Agckuu_Coceg, strelokhalfer, Lipton, Amphilohiy, yuryol, MaltonTheWarrior

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Программист Ruby Коммерсант Проект года 1 место Учитель Проект месяца 1 место
Больше
11 года 8 мес. назад #73011 от Iren_Rin
Iren_Rin ответил в теме array tricks
Приведу пример использования inject

Допустим есть такой массив
Code:
arr = [['EUR', 100], ['USD', 50], ['EUR', 200], ['USD', 150]]
Тут первый элемент - валюта, второй - количество денег в этой валюте. Из этого массива мы хотим получить хэш, где ключ будет - валюта, значение - сумма всех денег по этой валюте.
Можно сделать так:
Code:
result = {} arr.each do |sub_arr| currency, amount = sub_arr[0], sub_arr[1] result[currency] ||= 0 #мы проверяем есть ли в хэше значение с таким ключем, если нет - инициализируем этот ключ со нулевым значением result[currency] += amount end result #=> { 'EUR' => 300, 'USD' => 200 }

А можно сделать так:
Code:
#result - результат выполнение предыдущего блока #sub_arr - текущий элемент массива #в метод inject аргументом передаем то значение, которые передасться как result для самого первого блока, ведь для него не было предыдущего. По умолчанию - nil, в нашем случае - {} arr.inject({}) do |result, sub_arr| #в следующий блок в переменную result передасться последнее выражение в этом блоке currency, amount = sub_arr[0], sub_arr[1] result[currency] ||= 0 result[currency] += amount result end

Простой пример - сумма всех элементов в массиве
Code:
[1, 2, 3, 4, 5].inject(0) { |result, element| result + element }
Спасибо сказали: Agckuu_Coceg, strelokhalfer, Amphilohiy, yuryol

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Программист Ruby 2 место Готв Победитель Сбитой кодировки Учитель Оратор
Больше
11 года 8 мес. назад #73015 от Amphilohiy
Amphilohiy ответил в теме array tricks
Спасибо,
ВНИМАНИЕ: Спойлер!
Напомнило мноструозные итераторы ЛУА, в которых еще инвариантные состояния есть... Он по сути и тут есть, но это сам массив :)

Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Время создания страницы: 0.093 секунд
Работает на Kunena форум