Първите ви точки:
(ако сте записали курса)
Каква е разликата между тези имена?
foo = 1
Bar = 2
$baz = 3
foo
е локална променлива, Bar
е константа, $baz
е глобална променлива
Кои от следните неща се оценяват до неистина?
0 [] nil {} ""
Само false
и nil
са неистина.
Какво прави <=>
Връща положително число (1), ако първия елемент е по-голям, нула, ако са равни и отрицателно (-1), ако първия е по-малък.
Помнете го като "минус".
Каква е разликата между единични и двойки кавички?
В единичните няма интерполация, в двойните - има.
В единичните могат да се екранират само \\
и \'
. В двойните има ред екранирания и специални символи.
Какво извежда следният код и защо?
puts 'foo' == 'foo', 'foo'.equal? 'foo'
puts [] == [], [].equal? []
puts 42 == 42, 42.equal? 42
puts :foo == :foo, :foo.equal? :foo
==
сравнява по стойност, а equal?
по идентитет.
Числата и символите се интернират.
Колко от вас са запознати със следните неща:
map
и filter
Очакваният отговор: всички
Един обект се обръща до низ с #to_s
. Има и друг вариант, #inspect
,
който го обръща до текстов низ, изглеждащ като ruby код. Целта е инспектиране.
puts(something)
извежда something.to_s
p(something)
извежда something.inspect
#
е коментар#puts
, Array#inject
C#
Array
. Разбира се, има литерален синтаксис[1, 2, 3, 4]
[18, :female, 'Burgas']
numbers = [:zero, :one, :two]
numbers[1] # :one
numbers[10] # nil
numbers[-1] # :two
numbers[5] = :five
numbers[5] # :five
numbers # [:zero, :one, :two, nil, nil, :five]
Array#fetch
хвърля грешка или връща друга стойност, при индексиране извън обема на списъка:
numbers = [:zero, :one, :two]
numbers.fetch(1) # :one
numbers.fetch(10, :dunno) # :dunno
numbers.fetch(10) # error: IndexError
numbers[10] || :dunno # като предното, ама не точно
Ползва се по-рядко, отколкото си мислите.
numbers = [3, 1, 2, 4]
numbers.length # 4
numbers.size # 4
numbers.sort # [1, 2, 3, 4]
numbers.reverse # [4, 2, 1, 3]
numbers[1..2] # [1, 2]
sort
и reverse
връщат нов списък, без да променят numbers
.
#include?
ви казва дали списък съдържа даден елемент.
prime_digits = [2, 3, 5, 7]
prime_digits.include? 2 # true
prime_digits.include? 4 # false
Внимание: линейно търсене. Подходящо за малки списъци.
[:a, :b, :c] + [:d, :e] # [:a, :b, :c, :d, :e]
[:a, :b, :c, :b, :a] - [:b, :c, :d] # [:a, :a]
[:a, :b, :c] & [:b, :c, :d] # [:b, :c]
[:a, :b, :c] | [:b, :c, :d] # [:a, :b, :c, :d]
&
и |
конкатенират списъците и премахват повторенията.
В Ruby има множества, които са по-удачни в повечето случаи.
numbers = [1, 2, 3]
numbers << 4
p numbers # [1, 2, 3, 4]
numbers.insert 0, :zero
p numbers # [:zero, 1, 2, 3, 4]
result = numbers.delete_at(0)
p result # :zero
p numbers # [1, 2, 3, 4]
stack = [1, 2, 3]
stack.push 4
p stack # [1, 2, 3, 4]
top = stack.pop
p stack # [1, 2, 3]
p top # 4
#shift
и #unshift
са аналогични, но работят с началото на списъка.
[1, 2, 3].join("-") # "1-2-3"
[1, 2, 3].permutation # сещате се какво връща
[1, 2].product([3, 4]) # [[1, 3], [1, 4], [2, 3], [2, 4]]
[[1, 2], [3, 4]].transpose # [[1, 3], [2, 4]]
[1, 2, 3, 4].shuffle # разбърква списъка произволно
Има ред такива методи, които може да намерите в APIDock.
Може да оставите запетая след последния елемент на списъка. Така редовете се разместват по-лесно. Важи и за хешове.
songs = [
'My Favorite Things',
'Alabama',
'A Love Supreme',
]
Има специален синтаксис за списък от думи.
%w(chunky bacon) == ['chunky', 'bacon']
%w[a b c] == ['a', 'b', 'c']
%w{cool stuff} == ['cool', 'stuff']
%w<coffee tea water> == ['coffee', 'tea', 'water']
%w|foo bar| == ['foo', 'bar']
Може да използвате различни видове символи, които да ограждат списъка:
! @ # $ * - _
Този списък е непълен.
slice
(документация)[]
е всъщност метод на Array
, който е синоним на slice
[]=
от своя страна позволява и да променяте въпросните отрязъци от списъкаnumbers = [1, 2, 3, 4, 5, 6]
numbers[0..2] # [1, 2, 3]
numbers[-3..-1] # [4, 5, 6]
numbers[1, 1] # [2]
numbers[0..2] = [:wat]
numbers # [:wat, 4, 5, 6]
Итерира се с #each
, както всичко останало в Ruby:
primes = [2, 3, 5, 7, 11]
primes.each { |n| puts n }
primes.each do |n|
puts n
end
for
#each
do
/end
се ползва, когато блокът е няколко редаnext
прескача изпълнението до края на блока и продължава
със следващия елемент, ала continue
в C/Java
.
break
прекъсва итерацията и продължава изпълнението след блока.
numbers.sort.each do |number|
next if number.odd?
break if number > 100
puts number
end
Hash
, но има и литерал{1 => :one, 2 => :two}
numbers = {:one => :eins, :two => :zwei}
numbers[:one] # :eins
numbers[:three] # nil
numbers[:three] = :drei
numbers[:three] # :drei
numbers.fetch(:four, :keine_ahnung) # :keine_ahnung
numbers.fetch(:four) # error: KeyError
numbers = {:one => :eins, :two => :zwei}
numbers.keys # [:one, :two]
numbers.values # [:eins, :zwei]
numbers.each { |pair| puts pair }
numbers.each { |key, value| puts key, value }
numbers = {1 => 2, 3 => 4}
numbers.has_key?(:three) # false
numbers.size # 2
numbers.invert # {2=>1, 4=>3}
numbers.merge({5 => 6}) # {1=>2, 3=>4, 5=>6}
numbers.to_a # [[1, 2], [3, 4]]
Hash[1, 2, 3, 4] # {1=>2, 3=>4}
Долните два реда произвеждат еднакви хешове. Второто е 1.9 синтаксис:
{:one => 1, :two => 2}
{one: 1, two: 2}
Има интересна врътка при извикването на методи, за която ще говорим по-натам.
Дефинирането става с ключовата дума def
. Резултатът от функцията е
последният оценен израз, ако няма return
някъде.
def factorial(n)
unless n == 1
factorial(n - 1) * n
else
1
end
end
В Ruby няма tail recursion оптимизация. Този код яде стек.
def
винаги дефинира метод в някакъв класdef
не е в дефиниция на клас, отива като private
метод на Object
puts
е пример за нещо такова, както и методите, които дефинирате в irbObject
е удачно само за кратки скриптовеObject
е ужасно лош стилЗа да добавите метод в съществуващ клас, например Array
, просто "отваряте" класа и дефинирате метода:
class Array
def fourty_second
self[41]
end
end
list = []
list[41] = 'The Universe'
list.fourty_second # "The Universe"
Можете да излезете от функция с return
:
def includes?(array, element)
array.each do |item|
return true if item == element
end
false
end
Разбира се, такава функция е излишна.
Може да ползвате array.include?(element)
.
Параметрите в Ruby могат да имат стойности по подразбиране:
def order(drink, size = 'large')
puts "A #{size} #{drink}, please!"
end
order 'tea'
order 'coffee', 'small'
Методите в ruby могат да вземат променлив брой аргументи. Параметърът се означава със
*
и при извикване на функцията съдържа списък от аргументите.
def say_hi(name, *drinks)
puts "Hi, I am #{name} and I enjoy: #{drinks.join(',')}"
end
say_hi 'Stefan', 'coffee', 'tea', 'water'
Параметърът за променлив брой аргументи може да е на всяка позиция в дефиницията:
def something(*a, b, c)
end
def something(a, *b, c)
end
Очевидно, може да има само един такъв параметър във функция.
Когато последния аргумент е хеш, може да изтървете фигурните скобо около него. Долните редове правят едно и също:
def order(drink, preferences)
end
order 'Latte', {:size => 'grande', :syrup => 'hazelnut'}
order 'Latte', :size => 'grande', :syrup => 'hazelnut'
order 'Latte', size: 'grande', syrup: 'hazelnut'
Така ruby симулира извикане на функция с наименовани аргументи.
Често ще видите код в този вид:
def order(drink, preferences = {})
end
order 'Latte'
order 'Latte', size: 'grande', syrup: 'hazelnut'
Така preferences
е незадължителен и няма нужда да го подавате, ако
нямате предпочитания.
Името на метод може да завършва на ?
. Това се ползва, за методи,
които връщат лъжа или истина (предикати):
def even?(n)
n % 2 == 0
end
even? 2
even? 3
Това е само конвенция.
Името на метод може да завършва на !
.
Това се ползва, когато методът има две версии с различно поведение:
numbers = [4, 1, 3, 2, 5, 0]
numbers.sort # връща нов списък
numbers.sort! # променя списъка на място
В случая, "по-опасният" метод завършва на удивителна.
Анонимни функции в Ruby се дефинират с lambda
. Имат три начина на извикване:
pow = lambda { |a, b| a ** b }
pow.call 2, 3
pow[2, 3]
pow.(2, 3)
За нещастие, не може да извиквате така: double(2)
. Това е несъвместимо с
изтърваването на скобите при извикването на метод.
Може и така:
double = lambda do |x|
x * 2
end
Важи стандартната конвенция за { }
и do
/end
.
От 1.9 има по-симпатичен синтаксис за ламбди:
say_hi = lambda { puts 'Hi there!' }
double = lambda { |x| x * 2 }
divide = lambda { |a, b| a / b }
say_hi = -> { puts 'Hi there' }
double = ->(x) { x * 2 }
divide = ->(a, b) { a / b }
Всеки метод в може да приеме допълнителен аргумент, който е "анонимна функция". Може да
го извикате от метода с yield
:
def twice
yield
yield
end
twice { puts 'Ruby rocks!' }
Блокът може да приема аргументи:
def sequence(first, last, step)
current = first
while current < last
yield current
current += step
end
end
sequence(1, 10, 2) { |n| puts n }
# Извежда 1, 3, 5, 7, 9
yield
се оценява до стойността на блока:
def calculate
result = yield(2)
puts "The result for 2 is #{result}"
end
calculate { |x| x ** 2 }
# The result for 2 is 4
Колко от вас знаят какво прави filter
?
Или как можем да напишем filter
:
def filter(array)
result = []
array.each do |item|
result << item if yield item
end
result
end
filter([1, 2, 3, 4, 5]) { |n| n.odd? }
block_given?
ще ви каже дали методът е извикан с блок:
def i_can_haz_block
if block_given?
puts 'yes'
else
puts 'no'
end
end
i_can_haz_block # no
i_can_haz_block { 'something' } # yes
Ако имате ламбда, която искате да подадете като блок, може да ползвате &
:
is_odd = lambda { |n| n.odd? }
filter([1, 2, 3, 4, 5], &is_odd)
filter([1, 2, 3, 4, 5]) { |n| n.odd? }
Горните са (почти) еквиваленти. Има малка разлика в някои други случаи.
Ако искате да вземете блока като обект, има начин:
def invoke_with(*args, &block)
block.(*args)
end
invoke_with(1, 2) { |a, b| puts a + b }
Може и така:
def make_block(&block)
block
end
doubler = make_block { |n| n * 2 }
doubler.(2) # 4
В Ruby има два вида анонимни функции. Другият е Proc.
double = Proc.new { |x| x * 2 }
double.call(2)
double[2]
double.(2)
Дотук е същото, но има разлики при извикване.
f = | Proc.new { |x, y| p x, y } | lambda { |x, y| p x, y } |
---|---|---|
f.call(1) | 1 nil | ArgumentError |
f.call(1, 2) | 1 2 | 1 2 |
f.call(1, 2, 3) | 1 2 | ArgumentError |
f.call([1, 2]) | 1 2 | ArgumentError |
f.call(*[1, 2]) | 1 2 | 1 2 |
yield
ползва семантиката на Proc.new
lambda
Ако първия аргумент на функция е хеш, трябва да изтървете скобите на хеша, ако изтървете скобите на метода.
# Валиден код
order drink: 'latte', size: 'grande'
order({drink: 'latte', size: 'grande'})
# Невалиден код
order {drink: 'latte', size: 'grande'}
Във втория случай, Ruby си мисли, че му подавате блок.
Стандартните функционални неща:
numbers = [-9, -4, -1, 0, 1, 4, 9]
positive = numbers.select { |n| n >= 0 }
even = numbers.reject { |n| n.odd? }
squares = numbers.collect { |n| n ** 2 }
roots = numbers.select { |n| n > 0 }.collect { |n| n ** 0.5 }
#select
и #collect
имат синоними
#find_all
и #map
:
numbers = [-9, -4, -1, 0, 1, 4, 9]
squares = numbers.map { |n| n ** 2 }
positive = numbers.find_all { |n| n >= 0 }
В Ruby подобни синоними се срещат често.
#inject
свежда списък до единична стойно с някаква операция:
numbers = [1, 2, 3, 4, 5]
numbers.inject(0) { |a, b| a + b }
numbers.inject(1) { |a, b| a * b }
numbers.inject { |a, b| a + b }
numbers.inject { |a, b| "#{a}, #{b}" }
Имаме списък с думи. Искаме да получим хеш от вида {дума => дължина на думата}
:
words = %w[chunky bacon is awesome]
words.inject({}) { |hash, word| hash[word] = word.length; hash }
Това е неидиоматично използване на #inject
, но е интересен пример.
def inject(array, initial = nil)
remaining = array.dup
buffer = initial || remaining.shift
until remaining.empty?
buffer = yield buffer, remaining.shift
end
buffer
end
inject([1, 2, 3, 4]) { |a, b| a + b }
inject([1, 2, 3, 4], 0) { |a, b| a + b }
За забавлението. Неяснотите — следващия път.
class Array
def inject(initial = nil)
remaining = dup
buffer = initial || remaining.shift
until remaining.empty?
buffer = yield buffer, remaining.shift
end
buffer
end
end