Ruby
Шпаргалка по Ruby: переменные, строки и интерполяция, символы, массивы, хеши, диапазоны, условия, итераторы, методы, классы, модули, исключения и идиомы.
Ruby — выразительный язык с динамической типизацией, где «всё является объектом». Эта шпаргалка собирает базовый синтаксис, типы, коллекции, классы и идиомы Ruby с рабочими примерами и результатами в комментариях # =>.
Переменные и типы
Переменные в Ruby не объявляются заранее и не имеют фиксированного типа. Регистр первой буквы определяет область видимости.
name = "Аня" # локальная переменная
age = 25 # Integer
pi = 3.14 # Float
active = true # TrueClass
p name.class # => String
p age.class # => Integer
p pi.class # => Float
p nil.class # => NilClass
Префиксы переменных: @var — переменная экземпляра, @@var — переменная класса, $var — глобальная, CONST — константа (с заглавной буквы).
PI = 3.14159 # константа
$global = "всем" # глобальная переменная
x = 5
y = x # копия значения
puts x + y # => 10
Строки
Строки в двойных кавычках поддерживают интерполяцию #{} и escape-последовательности. В одинарных кавычках интерполяции нет.
name = "Мир"
puts "Привет, #{name}!" # => Привет, Мир!
puts 'Привет, #{name}!' # => Привет, #{name}!
# Интерполяция любого выражения
puts "2 + 2 = #{2 + 2}" # => 2 + 2 = 4
Полезные методы строк
s = "Ruby"
p s.length # => 4
p s.upcase # => "RUBY"
p s.downcase # => "ruby"
p s.reverse # => "ybuR"
p s.include?("ub")# => true
p s * 3 # => "RubyRubyRuby"
p " тест ".strip# => "тест"
p "a,b,c".split(",") # => ["a", "b", "c"]
p ["a","b"].join("-") # => "a-b"
p "hello".chars # => ["h", "e", "l", "l", "o"]
p "Ruby"[0] # => "R"
p "Ruby"[0..1] # => "Ru"
Символы (:sym)
Символ — это неизменяемый именованный идентификатор. Одинаковые символы — это один и тот же объект в памяти, поэтому они эффективны как ключи хешей и имена.
status = :active
p status # => :active
p status.class # => Symbol
# Один и тот же объект
p :foo.object_id == :foo.object_id # => true
# А строки — разные объекты
p "foo".object_id == "foo".object_id # => false
p :hello.to_s # => "hello"
p "world".to_sym # => :world
Числа
Ruby различает Integer и Float. Целочисленное деление возвращает целое; чтобы получить дробь — используйте число с точкой.
p 7 / 2 # => 3 (целочисленное деление)
p 7.0 / 2 # => 3.5
p 7 % 2 # => 1 (остаток)
p 2 ** 10 # => 1024 (степень)
p 10.even? # => true
p 7.odd? # => true
p (-5).abs # => 5
p 3.14.round # => 3
p 3.14.round(1) # => 3.1
p 7.to_f # => 7.0
p "42".to_i # => 42
p 255.to_s(16) # => "ff" (в шестнадцатеричной)
Массивы и их методы
Массив — упорядоченная коллекция произвольных объектов, индексация с нуля. Отрицательные индексы отсчитываются с конца.
arr = [1, 2, 3, 4, 5]
p arr[0] # => 1
p arr[-1] # => 5
p arr[1..3] # => [2, 3, 4]
p arr.first # => 1
p arr.last # => 5
p arr.length # => 5
Изменение и обход
a = [1, 2, 3]
a.push(4) # a => [1, 2, 3, 4]
a << 5 # a => [1, 2, 3, 4, 5]
p a.pop # => 5, a => [1, 2, 3, 4]
p a.shift # => 1, a => [2, 3, 4]
a.unshift(0) # a => [0, 2, 3, 4]
p [3, 1, 2].sort # => [1, 2, 3]
p [1, 2, 2, 3].uniq # => [1, 2, 3]
p [1, 2, 3].reverse # => [3, 2, 1]
p [1, 2, 3].include?(2) # => true
p [1, 2, 3].sum # => 6
p [1, 2, 3, 4].min # => 1
p [1, 2, 3, 4].max # => 4
Хеши
Хеш — это набор пар «ключ — значение». Ключами часто служат символы. Есть два синтаксиса записи.
# Современный синтаксис с символами-ключами
user = { name: "Аня", age: 25 }
p user[:name] # => "Аня"
p user[:age] # => 25
# Классический синтаксис (=>) для любых ключей
prices = { "хлеб" => 50, "молоко" => 80 }
p prices["хлеб"] # => 50
user[:city] = "Москва" # добавление ключа
p user.keys # => [:name, :age, :city]
p user.values # => ["Аня", 25, "Москва"]
p user.key?(:age) # => true
p user.fetch(:name) # => "Аня"
p user[:unknown] # => nil
Обход хеша
scores = { math: 5, history: 4 }
scores.each do |subject, mark|
puts "#{subject}: #{mark}"
end
# => math: 5
# => history: 4
Диапазоны (Range)
Диапазон описывает интервал значений. Две точки .. включают конец, три точки ... исключают его.
p (1..5).to_a # => [1, 2, 3, 4, 5]
p (1...5).to_a # => [1, 2, 3, 4]
p ('a'..'e').to_a # => ["a", "b", "c", "d", "e"]
p (1..10).include?(7) # => true
p (1..100).sum # => 5050
# Диапазоны удобны в case и циклах
(1..3).each { |i| print i } # => 123
Условия (if / unless / case)
В Ruby «ложными» считаются только false и nil; всё остальное (включая 0 и "") — истина.
age = 20
if age >= 18
puts "взрослый"
elsif age >= 14
puts "подросток"
else
puts "ребёнок"
end
# => взрослый
# Постфиксная форма
puts "ok" if age > 0 # => ok
puts "мало" unless age > 100 # => мало
case / when
grade = 4
result = case grade
when 5 then "отлично"
when 4 then "хорошо"
when 3 then "удовлетворительно"
else "неуд"
end
p result # => "хорошо"
# case с диапазонами
score = 87
case score
when 90..100 then puts "A"
when 80..89 then puts "B"
else puts "C"
end # => B
Циклы и итераторы
В Ruby редко пишут классические циклы — чаще используют итераторы с блоками: each, map, select, times, upto.
# each — обход без возврата нового массива
[1, 2, 3].each { |x| print x } # => 123
# map — преобразование в новый массив
p [1, 2, 3].map { |x| x * 2 } # => [2, 4, 6]
# select — фильтрация
p [1, 2, 3, 4].select { |x| x.even? } # => [2, 4]
# reject — обратная фильтрация
p [1, 2, 3, 4].reject { |x| x.even? } # => [1, 3]
# reduce — свёртка
p [1, 2, 3, 4].reduce(0) { |s, x| s + x } # => 10
times, upto, классические циклы
3.times { |i| print i } # => 012
1.upto(3) { |i| print i } # => 123
3.downto(1) { |i| print i } # => 321
i = 0
while i < 3
print i
i += 1
end # => 012
# each_with_index
%w[a b c].each_with_index do |val, idx|
puts "#{idx}: #{val}"
end
# => 0: a
# => 1: b
# => 2: c
Методы
Методы определяются через def. Возвращается значение последнего выражения — return необязателен.
def greet(name)
"Привет, #{name}!"
end
p greet("Аня") # => "Привет, Аня!"
Аргументы по умолчанию и именованные
def power(base, exp = 2)
base ** exp
end
p power(3) # => 9
p power(2, 3) # => 8
# Именованные аргументы
def connect(host:, port: 80)
"#{host}:#{port}"
end
p connect(host: "site.ru") # => "site.ru:80"
p connect(host: "site.ru", port: 443) # => "site.ru:443"
# Произвольное число аргументов
def total(*nums)
nums.sum
end
p total(1, 2, 3, 4) # => 10
Блоки, yield и &block
Блок — это кусок кода, передаваемый методу. Внутри метода его вызывают через yield или принимают явно как &block.
def twice
yield
yield
end
twice { puts "хей" }
# => хей
# => хей
# yield с аргументом
def with_value
yield 42
end
with_value { |v| puts "значение #{v}" } # => значение 42
# block_given? — проверка наличия блока
def maybe
return "нет блока" unless block_given?
yield
end
p maybe # => "нет блока"
# Явный параметр-блок &block
def run(&block)
block.call(10)
end
p run { |x| x + 5 } # => 15
Классы и объекты
Класс описывает шаблон объекта. Метод initialize — это конструктор, вызываемый при .new.
class Animal
attr_accessor :name # геттер + сеттер
attr_reader :legs # только геттер
def initialize(name, legs = 4)
@name = name
@legs = legs
end
def describe
"#{@name} ходит на #{@legs} лапах"
end
end
cat = Animal.new("Кот")
p cat.name # => "Кот"
cat.name = "Барсик" # сеттер от attr_accessor
p cat.describe # => "Барсик ходит на 4 лапах"
p cat.legs # => 4
Наследование
class Dog < Animal
def initialize(name)
super(name, 4) # вызов конструктора предка
end
def speak
"#{@name}: Гав!"
end
end
d = Dog.new("Рекс")
p d.speak # => "Рекс: Гав!"
p d.describe # => "Рекс ходит на 4 лапах" (унаследован)
p d.is_a?(Animal) # => true
Модули и mixin
Модуль — набор методов для подмешивания в классы через include. Так Ruby реализует множественное «наследование» поведения.
module Greetable
def hello
"Привет, я #{name}"
end
end
class Person
include Greetable
attr_reader :name
def initialize(name)
@name = name
end
end
p Person.new("Лена").hello # => "Привет, я Лена"
Символы vs строки
Главное отличие: строки изменяемы и каждая — новый объект; символы неизменяемы и существуют в единственном экземпляре. Символы используют для ключей хешей, имён методов и состояний.
| Свойство | Строка "foo" | Символ :foo |
|---|---|---|
| Изменяемость | изменяема | неизменяем |
| Объект в памяти | новый каждый раз | один и тот же |
| Типичное применение | текст, ввод-вывод | ключи, имена, флаги |
# Как ключи хеша символы предпочтительнее
config = { host: "localhost", port: 3000 }
p config[:port] # => 3000
# Преобразования между ними
p "name".to_sym # => :name
p :name.to_s # => "name"
Обработка исключений
Блок begin/rescue/ensure ловит ошибки. rescue обрабатывает исключение, ensure выполняется всегда, raise бросает ошибку.
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "Ошибка: #{e.message}" # => Ошибка: divided by 0
ensure
puts "Готово" # => Готово (выполнится в любом случае)
end
raise и несколько типов
def divide(a, b)
raise ArgumentError, "ноль!" if b == 0
a / b
end
begin
divide(10, 0)
rescue ArgumentError => e
puts e.message # => ноль!
end
# Несколько типов ошибок
begin
Integer("abc")
rescue TypeError, ArgumentError => e
puts "не число" # => не число
end
Идиомы Ruby
Короткие выразительные конструкции, которые делают код «руби-стайл».
Тернарный оператор
age = 20
status = age >= 18 ? "взрослый" : "ребёнок"
p status # => "взрослый"
||= и &&= (присваивание по условию)
# ||= присваивает, только если переменная nil или false
x = nil
x ||= 10
p x # => 10
x ||= 99
p x # => 10 (уже задано — не меняется)
# &&= присваивает, только если значение «истинно»
y = 5
y &&= y * 2
p y # => 10
z = nil
z &&= 100
p z # => nil (осталось nil)
Безопасная навигация &.
Оператор &. вызывает метод, только если объект не nil, иначе возвращает nil вместо ошибки.
user = nil
p user&.name # => nil (без ошибки NoMethodError)
name = "Аня"
p name&.upcase # => "АНЯ"
# Цепочка вызовов безопасна
profile = nil
p profile&.address&.city # => nil
Прочие удобные идиомы
# Множественное присваивание
a, b = 1, 2
a, b = b, a # обмен значениями
p [a, b] # => [2, 1]
# Значение по умолчанию через fetch
h = { x: 1 }
p h.fetch(:y, 0) # => 0
# Превращение в булево через !!
p !!nil # => false
p !!"текст" # => true
# Однострочный цикл с диапазоном
p (1..5).map { |n| n * n } # => [1, 4, 9, 16, 25]