Войти на сайт

Авторизация, ждите ...
×

ТЕМА: array tricks

array tricks 9 года 8 мес. назад #72989

  • Iren_Rin
  • Iren_Rin аватар
  • Вне сайта
  • Мастер
  • Сообщений: 247
  • Спасибо получено: 537
  • Программист RubyУчительПроект года 1 местоКоммерсантПроект месяца 1 место
На мой взгляд работа с массивами - одна из самых сильных сторон руби. С ними можно cделать вообще все что пожелаешь. Тут я хочу привести пример тех полезных методов и трюков, которые я использую чуть ли не каждый день. Я опущу совсем уж банальщину типа <<. Прошу опытный рубистов не кидать в меня помидорами, тема больше для неопытных:)

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

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

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

Работа с индексами не такое уж и тривиальное занятие.
Получить подмасив:
[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
То что совсем не очевидно на первый взгляд, но очень полезно - замена при помощи индексов
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 - рабочая лошадка, когда из одного массива нужно получить другой массив, связанный с первым.
numbers = [18, 13, 14, 15]
numbers.map { |i| "i + 3 = #{i + 3}" } #map выполняет блок для каждого элемента массива, результат становится элементом нового масива
Array#select, Array#reject - исполняют блок для каждого элемента массива, если блок возвращает истину то первый оставляет элемент в новом массиве, второй - наоборот выкидывает. Оба имеют bang аналоги, используйте с умом :)
[1, 2, 3, 4].select { |i| i > 2 } #=> [3, 4]
[1, 2, 3, 4].reject { |i| i > 2 } #=> [1, 2]
Array#inject - очень мощная вещь, очень часто использую. Позволяет пройтись по массиву, с каждым элементом выполнить блок и сохранить результат по всему стеку.
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 из массива.
  [1, nil, nil, 2, 3, nil].compact # => [1, 2, 3]
  # недавно игрался с мейкером - 
  $game_party.line_set.slots.map(&:buttler).compact # тут с map используется краткий синтаксис блоков, могу и про это рассказать как-нибудь.

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

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

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

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

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

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

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

И последний который я сегодня упомяну - sort. Без блока он просто сортирует массив. С блоком его используют, когда нужно как то по сложному отсортировать массив. В блок передаются по два элемента. Метод ожидает от блока один из трех вариантов = -1 когда левый элемент меньше правого, 0 - когда элементы равны, +1 - когда левый элемент больше правого.
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]

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

array tricks 9 года 8 мес. назад #73011

  • Iren_Rin
  • Iren_Rin аватар
  • Вне сайта
  • Мастер
  • Сообщений: 247
  • Спасибо получено: 537
  • Программист RubyУчительПроект года 1 местоКоммерсантПроект месяца 1 место
Приведу пример использования inject

Допустим есть такой массив
arr = [['EUR', 100], ['USD', 50], ['EUR', 200], ['USD', 150]]
Тут первый элемент - валюта, второй - количество денег в этой валюте. Из этого массива мы хотим получить хэш, где ключ будет - валюта, значение - сумма всех денег по этой валюте.
Можно сделать так:
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 }

А можно сделать так:
#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

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

array tricks 9 года 8 мес. назад #73015

  • Amphilohiy
  • Amphilohiy аватар
  • Вне сайта
  • Светлый дракон
  • Сообщений: 547
  • Спасибо получено: 666
  • Победитель Сбитой кодировкиОраторУчитель2 место ГотвПрограммист Ruby
Спасибо,
ВНИМАНИЕ: Спойлер! [ Нажмите, чтобы развернуть ]
Напомнило мноструозные итераторы ЛУА, в которых еще инвариантные состояния есть... Он по сути и тут есть, но это сам массив :)
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Администратор запретил публиковать записи гостям.
Время создания страницы: 0.976 секунд