П'ять методів Ruby, які вам слід використовувати

8 хв. читання

Ruby має певний шарм, як мова програмування. Хтось влучно підкреслив, що «Ruby навчить вас висловлювати свої ідеї засобами комп'ютера».

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

Розглянемо деякі методи Ruby, які добре вирішують специфічні проблеми.

1. Object#tap

Можливо, ви потрапляли у ситуацію, коли викликали метод об'єкту, а повернене значення не було таким, як ви хотіли. Ви сподівалися отримати об'єкт, але замість цього отримували щось інше. Або, хотіли додати випадкове число до набору параметрів збережених у хеші, тож використовували Hash.[], але назад отримували bar замість хешу параметрів і вам доводилося повертати його явно.

  def update_params(params)
  params[:foo] = 'bar'
  params
end

Рядок params у прикладі здається зайвим, тож ми можемо використати Object#tap.

Викликайте Object#tap на об'єкті, а потім передайте йому блок коду, який бажаєте запустити. Об'єкт буде передано блоку, а потім повернено.

Приклад:

def update_params(params)
  params.tap {|p| p[:foo] = 'bar' }
end

Існує велика кількість чудових нагод, щоб використати Object#tap, наприклад випадки, в яких викликаний метод не повертає об'єкт, а ви потребуєте саме цього.

2. Array#bsearch

Якщо вам доведеться зіткнутися з великою кількістю даних, то Ruby пропонує вам механізм перерахувань, це спрощує пошук даних. select, reject, та find – важливий набір інструментів, які я використовую щоденно. Коли даних забагато, вам варто починати хвилюватися про час, який буде займати пошук.

Якщо ви використовуєте **ActiveRecord ** і маєте справу с SQL, то для вас велика кількість речей залишається «за кулісами». І слід впевнитися, що ваші пошукові запити проводяться з найменшою алгоритмічною складністю. Іноді слід отримати всі дані з БД до того, як ви почнете з нею працювати. Наприклад, якщо записи захищено, то ви не зможете запросити їх з SQL.

У такі моменти варто задуматися про те, як відсіювати дані за допомогою алгоритму, який має у крайньому випадку складність О, якщо це можливо.

Суть полягає у часі, який займають алгоритми залежно від їх складності:

  • O(1)
  • O(log n)
  • O(n)
  • O(n log(n))
  • O(n^2)
  • O(2^n)
  • O(n!)

Складність вашого алгоритму пошуку має бути десь на початку цього списку.

Якщо потрібно виконати пошук по масиву з Ruby, спершу на думку спадає Enumerable#find, також відомий як detect. У будь-якому випадку, цей метод буде перебирати значення списку, доки не знайде шукане. При великій кількості даних це допустимо тільки, якщо шукане значення знаходиться на початку списку. У такому випадку складність буде O(n).

Array#bsearch може знайти необхідне значення зі складністю O(log n).

Різниця у часі між алгоритмами пошуку проілюстрована нижче, на прикладі їх роботи над масивом з 50 000 000 чисел:

require 'benchmark'

data = (0..50_000_000)

Benchmark.bm do |x|
  x.report(:find) { data.find {|number| number > 40_000_000 } }
  x.report(:bsearch) { data.bsearch {|number| number > 40_000_000 } }
end

Час пошуку для двох функцій find & bsearch:

         user       system     total       real
find     3.020000   0.010000   3.030000   (3.028417)
bsearch  0.000000   0.000000   0.000000   (0.000006)

Як ви можете бачити bsearch набагато швидший. Однак, є невелика хитрість, пов'язана з використанням bsearch: масив має бути відсортований. Це дещо обмежує його корисність, проте його досі слід тримати про запас.

3. Enumerable#flat_map

Робота з реляційними даними іноді потребує зібрати до купи незв'язні атрибути й передати їх до не вкладеного масиву. Припустимо, у вас додаток для блогу, і ви хочете знайти авторів коментарів, залишених у минулому місяці.

Ви можете зробити щось подібне:

module CommentFinder
  def self.find_for_users(user_ids)
    users = User.where(id: user_ids)
    users.map do |user|
      user.posts.map do |post|
        post.comments.map |comment|
          comment.author.username
        end
      end
    end
  end
end

Результат може бути схожим на цей:

[[['Ben', 'Sam', 'David'], ['Keith']], [[], [nil]], [['Chris'], []]]

Або, можете використати flatten.

module CommentFinder
  def self.find_for_users(user_ids)
    users = User.where(id: user_ids)
    users.map { |user|
      user.posts.map { |post|
        post.comments.map { |comment|
          comment.author.username
        }.flatten
      }.flatten
    }.flatten
  end
end

Інший спосіб – це flat_map.

module CommentFinder
  def self.find_for_users(user_ids)
    users = User.where(id: user_ids)
    users.flat_map { |user|
      user.posts.flat_map { |post|
        post.comments.flat_map { |comment|
          comment.author.username
        }
      }
    }
  end
end

Різниця між ними не суттєва, проте у більшості випадків ліпше використати flatten.

4. Array.new з Block

Показовим прикладом може стати гра «Морський бій», це чудова вправа для ООП. Потрібно створити правила, гравців та кораблі.

Реалізація і представлення поля - це доволі цікава річ. Після декількох спроб, ви з'ясуєте, що найпростіший шлях – створити сітку 8х8, яка виглядає приблизно так:

class Board
  def board
    @board ||= Array.new(8) { Array.new(8) { '0' } }
  end
end

Виклик Array.new(x) створює масив розмірністю х елементів.

Array.new(8)
#=> [nil, nil, nil, nil, nil, nil, nil, nil]

Коли ви передаєте йому блок, він заповнює кожен елемент масиву результатом оцінки цього блоку:

Array.new(8) { 'O' }
#=> ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']

Якщо передати масив з 8 як блок, то буде створено 8 елементів з величиною 0, і в результаті ви матимете масив 8х8, заповнений нулями.

Використовуючи Array#new з блоком, ви створите масиви з даними за замовчуванням.

5. <=>

Оператор сортування <=> (Зореліт), зазвичай можна зустріти у більшості вбудованих у Ruby класів, а його ефективність стає очевидною під час роботи з перерахуваннями.

Проілюструвати його роботу можна на прикладі поведінки для Fixnums. a <=> b Якщо a = b, він поверне 0. Якщо a < b, отримаєте -1.
У випадку a > b, буде повернено 1.

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

Прикладом влучного використання може стати реалізація годинника, коли вам необхідно встановити години і хвилини використовуючи власноруч створені методи + & -. Труднощі починаються тоді, коли ви намагаєтеся додати більше за 60 хвилин, тому що, це зробить вашу змінну хвилин недійсною. Тож вам доведеться впоратися, просто збільшуючи показник години й віднімати 60 хвилин з нього.

Приклад реалізації з використанням <=>:

  def fix_minutes
    until (0...60).member? minutes
      @hours -= 60 <=> minutes
      @minutes += 60 * (60 <=> minutes)
    end
    @hours %= 24
    self
  end

Працює це так: поки хвилини не пройшли від 0 до 60, він віднімає 1 або -1 з годин, залежно від того, буде кількість хвилин більша за 60. Потім він коректує хвилини, додаючи або -60, або 60 в залежності він порядку сортування.

Отже, <=> чудово підходить для визначення власних алгоритмів сортування та арифметичних операцій, проте пам'ятайте, що він повертає 1 з 3 значень Fixnum.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація