Cà ri trăn và một phần
Trong khi thực hiện các bài tập lập trình trên codewars.com, tôi đã gặp một bài tập về hàm currying và một phần.
Là một người mới học lập trình và mới làm quen với chủ đề này, tôi đã tìm kiếm thông tin về chủ đề này trên mạng internet và khá sâu vào việc giải bài tập. Tuy nhiên, bây giờ tôi đã vấp phải một chướng ngại vật mà dường như tôi không thể vượt qua và đang ở đây để tìm kiếm một cú huých đúng hướng.
Bài tập khá đơn giản: viết một hàm có thể xử lý và / hoặc một phần bất kỳ hàm đầu vào nào và đánh giá hàm đầu vào khi đã cung cấp đủ các tham số đầu vào. Hàm đầu vào có thể chấp nhận bất kỳ số lượng tham số đầu vào nào. Ngoài ra, hàm curry / part nên rất linh hoạt trong cách gọi của nó, có thể xử lý nhiều, nhiều cách gọi hàm khác nhau. Ngoài ra, hàm curry / một phần được phép gọi với nhiều đầu vào hơn so với yêu cầu của hàm đầu vào, trong trường hợp đó, tất cả các đầu vào thừa cần được bỏ qua.
Theo liên kết bài tập, tất cả các trường hợp kiểm thử có thể được tìm thấy mà hàm cần có khả năng xử lý.
Mã tôi nghĩ ra như sau:
from functools import partial
from inspect import signature
def curry_partial(func, *initial_args):
""" Generates a 'curried' version of a function. """
# Process any initial arguments that where given. If the number of arguments that are given exceeds
# minArgs (the number of input arguments that func needs), func is evaluated
minArgs = len(signature(func).parameters)
if initial_args:
if len(initial_args) >= minArgs:
return func(*initial_args[:minArgs])
func = partial(func, *initial_args)
minArgs = len(signature(func).parameters)
# Do the currying
def g(*myArgs):
nonlocal minArgs
# Evaluate function if we have the necessary amount of input arguments
if minArgs is not None and minArgs <= len(myArgs):
return func(*myArgs[:minArgs])
def f(*args):
nonlocal minArgs
newArgs = myArgs + args if args else myArgs
if minArgs is not None and minArgs <= len(newArgs):
return func(*newArgs[:minArgs])
else:
return g(*newArgs)
return f
return g
Bây giờ mã này không thành công khi kiểm tra sau được thực hiện:
test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)
trong đó add = a + b + c (hàm được xác định đúng), a = 1, b = 2, c = 3 và sum = 6.
Lý do điều này không thành công là vì curry_partial(add, a)trả về một hàm xử lý cho hàm g. Trong lần gọi thứ hai, curry_partial(<function_handle to g>, b)phép tính minArgs = len(signature(func).parameters)không hoạt động như tôi muốn, vì bây giờ nó sẽ tính toán hàm lượng đối số đầu vào gyêu cầu (nghĩa là 1: tức là *myArgs), chứ không phải bao nhiêu đối số ban đầu funcvẫn yêu cầu. Vì vậy, câu hỏi là, làm thế nào tôi có thể viết mã của mình để có thể theo dõi bao nhiêu đối số đầu vào mà ban đầu của tôi funcvẫn cần (giảm số đó mỗi khi tôi chia nhỏ hàm với bất kỳ đối số ban đầu nào đã cho).
Tôi vẫn còn nhiều điều để tìm hiểu về lập trình và sơ chế / từng phần, vì vậy rất có thể tôi đã không chọn cách tiếp cận thuận tiện nhất. Nhưng tôi muốn học. Khó khăn trong bài tập này đối với tôi là sự kết hợp của một phần và curry, tức là thực hiện một vòng lặp curry trong khi phân tách bất kỳ đối số ban đầu nào gặp phải.
Trả lời
Thử thứ này đi.
from inspect import signature
# Here `is_set` acts like a flip-flop
is_set = False
params = 0
def curry_partial(func, *partial_args):
"""
Required argument: func
Optional argument: partial_args
Return:
1) Result of the `func` if
`partial_args` contains
required number of items.
2) Function `wrapper` if `partial_args`
contains less than the required
number of items.
"""
global is_set, params
if not is_set:
is_set = True
# if func is already a value
# we should return it
try: params = len(signature(func).parameters)
except: return func
try:
is_set = False
return func(*partial_args[:params])
except:
is_set = True
def wrapper(*extra_args):
"""
Optional argument: extra_args
Return:
1) Result of the `func` if `args`
contains required number of
items.
2) Result of `curry_partial` if
`args` contains less than the
required number of items.
"""
args = (partial_args + extra_args)
try:
is_set = False
return func(*args[:params])
except:
is_set = True
return curry_partial(func, *args)
return wrapper
Điều này thực sự không tốt lắm về mặt thiết kế. Thay vào đó, bạn nên sử dụng class, để thực hiện tất cả các công việc bên trong như flip-flop chẳng hạn (đừng lo lắng chúng tôi không cần flip-flop nào ở đó ;-)).
Bất cứ khi nào có một hàm nhận đối số tùy ý, bạn luôn có thể khởi tạo lớp đó truyền hàm. Nhưng lần này, tôi để đó cho bạn.
Tôi không chắc chắn về cách nấu cà ri , nhưng nếu bạn cần một trình tạo hàm một phần đơn giản, bạn có thể thử một cái gì đó như sau:
from functools import partial
from inspect import signature
def execute_or_partial(f, *args):
max = len(signature(f).parameters)
if len(args) >= max:
return f(*args[:max])
else:
return partial(f, *args)
s = lambda x, y, z: x + y + z
t = execute_or_partial(s, 1)
u = execute_or_partial(t, 2)
v = execute_or_partial(u, 3)
print(v)
or
print(execute_or_partial(execute_or_partial(execute_or_partial(s, 1), 2), 3))
Ngay cả khi nó không giải quyết được vấn đề ban đầu của bạn, hãy xem liệu bạn có thể sử dụng đoạn mã trên để giảm sự lặp lại mã hay không (tôi không chắc chắn, nhưng tôi nghĩ rằng có một số sự lặp lại mã trong hàm bên trong?); điều đó sẽ làm cho các vấn đề tiếp theo được giải quyết dễ dàng hơn.
Có thể có các hàm trong thư viện chuẩn đã giải quyết vấn đề này. Nhiều ngôn ngữ chức năng thuần túy như Haskell có tính năng này được tích hợp sẵn trong ngôn ngữ.