- Сообщений: 247
- Спасибо получено: 537
Мемоизация
11 года 6 мес. назад - 11 года 6 мес. назад #75302
от Iren_Rin
Iren_Rin создал тему: Мемоизация
После непродолжительного отпуска сначала
в горах
, потом
на острове
решил выложить небольшую статью о том, с чем я сталкиваюсь каждый день - о мемоизации.
И так, мемоизация - запоминание (кэширование) результата исполнения какого нибудь куска кода, в нашем случае - метода. Если применять ее умело, то выгода велика, как впрочем и опасность возможных веселых багов. И так, ближе к делу, давайте рассмотрим следующий код.
Метод #bar у нас будет представлять медленный метод, предположим что в нем происходит сложное вычисление, результат которого в принципе будет постоянным для данного объекта, т.е. не поменяется за время его жизни. Каждый раз при вызове #bar мы будем ждать 3 секунды, а в реальности еще будем тратить ресурсы машины. Хотелось бы как нибудь сохранить результат исполнения метода, и при следующем вызове возвращать его, а не исполнять все заново. И вот к нам на помощь приходят инстанс переменные, они имеют значение nil по умолчанию.
Этот некрасивый участок кода уже будет работать, но хотелось бы то нибудь покороче. Упростим:
Вот это уже куда ближе. Когда в @bar ничего нет - выполнится вторая часть, когда там будет строка - метод вернет эту строка и остановится.
В руби есть еще более сокращенная запись - выражение ||=. Используем его, и вместо блока с begin - end вынесем медленное вычисление в отдельный приватный метод
Поговорим теперь о подводных камнях, которые тут легко упустить
1 Приватный метод исполниться только один раз. Не используйте мемоизацию если не уверены что результат исполнения будет постоянным.
2 И менее очевидная вещь - если результатом исполнение медленной части является булево значение ( True или False ) то такая мемоизация сработает только если метод возращает True. Мой прошлый проект жил с такими методами где то с год, и все удивлялись, почему же так медленно)
И так, вызываем первый раз #fast_part и записываем в @result false. Вызываем следующий раз и фактически будет исполнено следующее:
Для мемоизации булевых методов нужно делать так
А что делать если метод принимает аргументы и его результат будет зависеть от их? Воспользуемся хэшами, ведь они тоже возвращают nil для несуществующего ключа (по умолчанию). Так же ключами хэшей могут быть почти все объекты, в том числе и массивы.
Вариант fast_part для булевых методов будет таким
Вот и все, используйте мемоизацию с умом и да ускорятся скрипты ваши.
И так, мемоизация - запоминание (кэширование) результата исполнения какого нибудь куска кода, в нашем случае - метода. Если применять ее умело, то выгода велика, как впрочем и опасность возможных веселых багов. И так, ближе к делу, давайте рассмотрим следующий код.
Code:
class Dog
def bar
sleep 3
'bar!'
end
end
Code:
# тут и дальше представьте что мы пишем внутри класса Bar
def bar
unless @bar
@bar = begin
sleep 3
'bar!' #мы запишем в @bar строку, и в следующий раз руби не пойдет в unless
end
end
@bar
end
Code:
def bar
@bar || @bar = begin
sleep 3
'bar!'
end
end
В руби есть еще более сокращенная запись - выражение ||=. Используем его, и вместо блока с begin - end вынесем медленное вычисление в отдельный приватный метод
Code:
def bar
@bar ||= calculate_bar # ||= следует понимать как “верни мне значение переменной или,
# если там nil или false,- вычисли правую часть, запиши результат в
# переменную, и верни его
end
private
def calculate_bar
sleep 3
'bar!'
end
Поговорим теперь о подводных камнях, которые тут легко упустить
1 Приватный метод исполниться только один раз. Не используйте мемоизацию если не уверены что результат исполнения будет постоянным.
2 И менее очевидная вещь - если результатом исполнение медленной части является булево значение ( True или False ) то такая мемоизация сработает только если метод возращает True. Мой прошлый проект жил с такими методами где то с год, и все удивлялись, почему же так медленно)
Code:
def fast_part
@result ||= slow_part
end
private
def slow_part
sleep 3
false
end
И так, вызываем первый раз #fast_part и записываем в @result false. Вызываем следующий раз и фактически будет исполнено следующее:
Code:
false ||= slow_part # и как результат руби всегда будет исполнять slow_part
# и записывать результат в result
Для мемоизации булевых методов нужно делать так
Code:
def fast_part
@result = slow_part unless defined? @result # defined? уже просто так не обдурить))
@result
end
А что делать если метод принимает аргументы и его результат будет зависеть от их? Воспользуемся хэшами, ведь они тоже возвращают nil для несуществующего ключа (по умолчанию). Так же ключами хэшей могут быть почти все объекты, в том числе и массивы.
Code:
def fast_part(a, b)
@results ||= {} #инициализируем @results пустым хэшем, если нужно
@results[[a, b]] ||= slow_part(a, b) # записываем результат в хэш с ключем - массивом из аргументов
end
private
def slow_part(a, b)
sleep 3
"#{a} #{b}"
end
Вариант fast_part для булевых методов будет таким
Code:
def fast_part(a, b)
@results ||= {}
@results[[a, b]] = slow_part(a, b) unless @results.has_key?([a, b])
@results[[a, b]]
end
Последнее редактирование: 11 года 6 мес. назад пользователем Iren_Rin.
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
11 года 6 мес. назад #75304
от Lekste
Lekste ответил в теме Мемоизация
Очевидно, но полезно.
Как ж я не любил раньше скрипты за сокращения эти...
Как ж я не любил раньше скрипты за сокращения эти...
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
9 года 9 мес. назад #93381
от Amphilohiy
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Amphilohiy ответил в теме Мемоизация
Внезапно наткнулся на мемоизацию в Хэше. По большому счету средствами руби. Надыбал
тут
такой кусок кода:
Изначально идея класть значения по умолчанию в хэшЮ при обращении по некоторому ключу. Однако применение в мемоизации кажется (во всяком случае мне) очевидным. Например рекурсивное вычисление числа фибоначчи:
Значения добавляются только при обращении к хэшу (что полезно знать, если вы решите пробегать по этому хэшу), и только при отсутствии значения. Так значение числа фибоначчи 10 высчитывало значения для 0-10, в то время как следующее вычисление 12 считало только 11-12.
Code:
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]
Code:
cache = Hash.new do |hash, key|
puts "evaluate value for #{key}"
hash[key] = [0, 1].include?(key) ? 1 : hash[key-1] + hash[key-2]
end
puts cache # => {}
# > evaluate value for 10
# > evaluate value for 9
# > evaluate value for 8
# > evaluate value for 7
# > evaluate value for 6
# > evaluate value for 5
# > evaluate value for 4
# > evaluate value for 3
# > evaluate value for 2
# > evaluate value for 1
# > evaluate value for 0
puts cache[10] # => 89
puts cache # => {1=>1, 0=>1, 2=>2, 3=>3, 4=>5, 5=>8, 6=>13, 7=>21, 8=>34, 9=>55, 10=>89}
# > evaluate value for 12
# > evaluate value for 11
puts cache[12] # => 233
puts cache # => {1=>1, 0=>1, 2=>2, 3=>3, 4=>5, 5=>8, 6=>13, 7=>21, 8=>34, 9=>55, 10=>89, 11=>144, 12=>233}
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
Время создания страницы: 0.098 секунд
