ฉันเพิ่งเริ่ม Python และฉันก็ไม่รู้ว่าการบันทึกคืออะไรและใช้อย่างไร นอกจากนี้ฉันขอยกตัวอย่างแบบง่าย ๆ ได้ไหม
ฉันเพิ่งเริ่ม Python และฉันก็ไม่รู้ว่าการบันทึกคืออะไรและใช้อย่างไร นอกจากนี้ฉันขอยกตัวอย่างแบบง่าย ๆ ได้ไหม
คำตอบ:
การบันทึกอย่างมีประสิทธิภาพหมายถึงการจดจำ ("การบันทึกความจำ" → "บันทึกความจำ" →ที่ต้องจดจำ) ผลลัพธ์ของการเรียกใช้เมธอดตามวิธีการอินพุตจากนั้นส่งคืนผลลัพธ์ที่จำได้มากกว่าการคำนวณผลลัพธ์อีกครั้ง คุณสามารถคิดว่ามันเป็นแคชสำหรับผลลัพธ์ของวิธีการ สำหรับรายละเอียดเพิ่มเติมดูหน้า 387 สำหรับคำจำกัดความในบทนำสู่อัลกอริทึม (3e), Cormen และคณะ
ตัวอย่างง่ายๆสำหรับการคำนวณแฟคทอเรียลโดยใช้การบันทึกใน Python จะเป็นดังนี้:
factorial_memo = {}
def factorial(k):
if k < 2: return 1
if k not in factorial_memo:
factorial_memo[k] = k * factorial(k-1)
return factorial_memo[k]
คุณสามารถเพิ่มความซับซ้อนและห่อหุ้มกระบวนการบันทึกความจำลงในคลาสได้:
class Memoize:
def __init__(self, f):
self.f = f
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.f(*args)
#Warning: You may wish to do a deepcopy here if returning objects
return self.memo[args]
แล้ว:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
มีการเพิ่มฟีเจอร์ที่เรียกว่า " decorators " ใน Python 2.4 ซึ่งช่วยให้คุณสามารถเขียนสิ่งต่อไปนี้เพื่อทำสิ่งเดียวกันให้สำเร็จ:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
มัณฑนากรห้องสมุดหลามมีมัณฑนากรที่คล้ายกันเรียกmemoized
ว่าเป็นเพียงเล็กน้อยที่แข็งแกร่งมากขึ้นกว่าMemoize
ระดับที่แสดงที่นี่
factorial_memo
เพราะfactorial
ภายในdef factorial
ยังคงเรียก factorial
unmemoize
if k not in factorial_memo:
if not k in factorial_memo:
args
เป็นสิ่งอันดับ def some_function(*args)
ทำให้สิ่ง tuple
ใหม่เพื่อหลาม 3.2 functools.lru_cache
คือ โดยค่าเริ่มต้นเพียงแคช 128 สายส่วนใหญ่ใช้ในเร็ว ๆ นี้ แต่คุณสามารถตั้งค่าmaxsize
ที่จะNone
ชี้ให้เห็นว่าแคชไม่ควรหมดอายุ:
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
ฟังก์ชั่นนี้ช้ามาก ๆ ลองfib(36)
แล้วคุณจะต้องรอประมาณสิบวินาที
การเพิ่มlru_cache
คำอธิบายประกอบช่วยให้มั่นใจว่าหากมีการเรียกใช้ฟังก์ชั่นเมื่อเร็ว ๆ นี้สำหรับค่าใดค่าหนึ่งมันจะไม่คำนวณค่านั้นซ้ำ แต่ใช้ผลลัพธ์ที่แคชไว้ก่อนหน้านี้ ในกรณีนี้มันนำไปสู่การปรับปรุงความเร็วอย่างมากในขณะที่รหัสไม่รกด้วยรายละเอียดของแคช
fib
มีการเรียกมันจะต้องเกิดขึ้นอีกครั้งในกรณีพื้นฐานก่อนที่การบันทึกจะเกิดขึ้นได้ ดังนั้นพฤติกรรมของคุณเป็นไปตามคาด
คำตอบอื่น ๆ ครอบคลุมถึงสิ่งที่ค่อนข้างดี ฉันไม่ได้ทำซ้ำ เพียงบางจุดที่อาจเป็นประโยชน์กับคุณ
โดยปกติ memoisation เป็นการดำเนินการที่คุณสามารถนำไปใช้กับฟังก์ชันใด ๆ ที่คำนวณบางสิ่ง (แพง) และส่งกลับค่า ด้วยเหตุนี้มันมักจะนำมาใช้เป็นมัณฑนากร การนำไปปฏิบัตินั้นตรงไปตรงมาและมันจะเป็นอย่างนี้
memoised_function = memoise(actual_function)
หรือแสดงเป็นมัณฑนากร
@memoise
def actual_function(arg1, arg2):
#body
การบันทึกคือการเก็บผลลัพธ์ของการคำนวณที่มีราคาแพงและส่งคืนผลลัพธ์ที่แคชไว้แทนที่จะคำนวณใหม่อย่างต่อเนื่อง
นี่คือตัวอย่าง:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
คำอธิบายที่สมบูรณ์มากขึ้นสามารถพบได้ในรายการวิกิพีเดีย memoization
if input not in self.cache
และ self.cache[input]
( has_key
ล้าสมัยตั้งแต่ ... ในช่วงต้นของซีรีส์ 2.x ถ้าไม่ใช่ 2.0 self.cache(index)
ไม่เคยถูกแก้ไข IIRC)
อย่าลืมhasattr
ฟังก์ชั่นในตัวสำหรับผู้ที่ต้องการส่งงานฝีมือ ด้วยวิธีนี้คุณสามารถเก็บแคช mem ไว้ในนิยามของฟังก์ชัน (ตรงข้ามกับส่วนกลาง)
def fact(n):
if not hasattr(fact, 'mem'):
fact.mem = {1: 1}
if not n in fact.mem:
fact.mem[n] = n * fact(n - 1)
return fact.mem[n]
ฉันพบว่ามีประโยชน์มาก
def memoize(function):
from functools import wraps
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
functools.wraps
สำหรับเหตุผลที่ควรใช้
memo
เพื่อให้หน่วยความจำว่างหรือไม่?
การบันทึกนั้นเป็นการบันทึกผลลัพธ์ของการดำเนินการในอดีตที่ทำด้วยอัลกอริทึมแบบเรียกซ้ำเพื่อลดความจำเป็นในการสำรวจทรีการสอบถามซ้ำหากต้องการการคำนวณแบบเดียวกันในระยะต่อมา
ดูhttp://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
ตัวอย่างการจำ Fibonacci ใน Python:
fibcache = {}
def fib(num):
if num in fibcache:
return fibcache[num]
else:
fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
return fibcache[num]
การบันทึกคือการแปลงฟังก์ชั่นไปเป็นโครงสร้างข้อมูล โดยปกติแล้วหนึ่งต้องการให้เกิดการแปลงเพิ่มขึ้นและขี้เกียจ (ตามความต้องการขององค์ประกอบโดเมนที่กำหนด - หรือ "คีย์") ในภาษาที่ใช้งานได้อย่างขี้เกียจการแปลงแบบสันหลังยาวนี้สามารถเกิดขึ้นได้โดยอัตโนมัติดังนั้นการบันทึกช่วยจำสามารถนำไปใช้โดยไม่มีผลข้างเคียง (ชัดเจน)
ฉันควรตอบส่วนแรกก่อน: การบันทึกความจำคืออะไร?
มันเป็นเพียงวิธีการในการแลกเปลี่ยนความทรงจำในเวลา คิดว่าตารางการคูณ
การใช้วัตถุที่ไม่แน่นอนเป็นค่าเริ่มต้นใน Python มักจะถือว่าไม่ดี memoization
แต่ถ้าใช้มันอย่างชาญฉลาดก็จริงจะมีประโยชน์ในการดำเนินการ
นี่คือตัวอย่างที่ดัดแปลงมาจากhttp://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects
การใช้ความไม่แน่นอนdict
ในการกำหนดฟังก์ชั่นผลลัพธ์ที่คำนวณได้กลางสามารถแคช (เช่นเมื่อคำนวณfactorial(10)
หลังจากการคำนวณfactorial(9)
เราสามารถนำผลกลางทั้งหมดกลับมาใช้ใหม่ได้)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
นี่เป็นวิธีการแก้ปัญหาที่จะทำงานกับรายการหรือข้อโต้แย้งประเภท dict โดยไม่ต้องเสียงหอน:
def memoize(fn):
"""returns a memoized version of any function that can be called
with the same list of arguments.
Usage: foo = memoize(foo)"""
def handle_item(x):
if isinstance(x, dict):
return make_tuple(sorted(x.items()))
elif hasattr(x, '__iter__'):
return make_tuple(x)
else:
return x
def make_tuple(L):
return tuple(handle_item(x) for x in L)
def foo(*args, **kwargs):
items_cache = make_tuple(sorted(kwargs.items()))
args_cache = make_tuple(args)
if (args_cache, items_cache) not in foo.past_calls:
foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
return foo.past_calls[(args_cache, items_cache)]
foo.past_calls = {}
foo.__name__ = 'memoized_' + fn.__name__
return foo
โปรดทราบว่าวิธีนี้สามารถขยายไปยังวัตถุใด ๆ โดยธรรมชาติโดยการใช้ฟังก์ชั่นแฮชของคุณเองเป็นกรณีพิเศษใน handle_item ตัวอย่างเช่นในการทำให้วิธีการนี้ใช้งานได้กับฟังก์ชั่นที่รับชุดเป็นอาร์กิวเมนต์อินพุตคุณสามารถเพิ่มไปยัง handle_item:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
ข้อโต้แย้งของ[1, 2, 3]
สามารถผิดพลาดได้รับการพิจารณาเช่นเดียวกับที่แตกต่างกันทะเลาะกับค่าของset
นอกจากนี้ชุดมีการเรียงลำดับเช่นพจนานุกรมเพื่อให้พวกเขายังจะต้อง{1, 2, 3}
sorted()
โปรดสังเกตว่าอาร์กิวเมนต์โครงสร้างข้อมูลแบบเรียกซ้ำจะทำให้เกิดการวนซ้ำไม่สิ้นสุด
list
s และset
s นั้น "tupleized" เป็นสิ่งเดียวกันและดังนั้นจึงแยกไม่ออกจากกัน ตัวอย่างโค้ดสำหรับเพิ่มการสนับสนุนตามที่sets
อธิบายไว้ในการอัปเดตล่าสุดของคุณไม่ได้หลีกเลี่ยงที่ฉันกลัว สิ่งนี้สามารถมองเห็นได้อย่างง่ายดายโดยผ่านการแยก[1,2,3]
และ{1,2,3}
เป็นอาร์กิวเมนต์ของฟังก์ชันทดสอบ "memoize" และดูว่ามันถูกเรียกสองครั้งตามที่ควรหรือไม่
list
s และdict
s เพราะเป็นไปได้ที่list
จะมีสิ่งเดียวกันในนั้นซึ่งเป็นผลมาจากการเรียกmake_tuple(sorted(x.items()))
พจนานุกรม วิธีแก้ปัญหาอย่างง่ายสำหรับทั้งสองกรณีคือการรวมtype()
ค่าไว้ใน tuple ที่สร้างขึ้น ฉันสามารถคิดถึงวิธีที่ง่ายกว่าในการจัดการset
s แต่มันไม่ได้พูดถึง
โซลูชันที่ทำงานกับทั้งอาร์กิวเมนต์ตำแหน่งและคำหลักโดยไม่ขึ้นกับลำดับที่ args คำหลักถูกส่งผ่าน (ใช้inspect.getargspec ):
import inspect
import functools
def memoize(fn):
cache = fn.cache = {}
@functools.wraps(fn)
def memoizer(*args, **kwargs):
kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
if key not in cache:
cache[key] = fn(**kwargs)
return cache[key]
return memoizer
คำถามที่คล้ายกัน: การระบุฟังก์ชัน varargs ที่เทียบเท่ากันจะเรียกใช้การบันทึกความจำใน Python
cache = {}
def fib(n):
if n <= 1:
return n
else:
if n not in cache:
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
if n not in cache
แทน การใช้cache.keys
จะสร้างรายการที่ไม่จำเป็นใน python 2
แค่อยากจะเพิ่มคำตอบที่มีให้อยู่แล้วห้องสมุดมัณฑนากรหลามมีบางอย่างง่าย ๆ การใช้งานที่มีประโยชน์ที่ยังสามารถ memoize "ประเภท unhashable" functools.lru_cache
ซึ่งแตกต่างจาก