แกงหลามและบางส่วน
ขณะทำแบบฝึกหัดการเขียนโปรแกรมบน codewars.com ฉันพบแบบฝึกหัดเกี่ยวกับการกะหรี่และฟังก์ชันบางส่วน
ด้วยความที่เป็นมือใหม่ในการเขียนโปรแกรมและยังใหม่กับหัวข้อนี้ฉันจึงค้นหาข้อมูลเกี่ยวกับหัวข้อนี้บนอินเทอร์เน็ตและมีวิธีแก้ปัญหาแบบฝึกหัดค่อนข้างไกล อย่างไรก็ตามตอนนี้ฉันได้พบกับอุปสรรคที่ดูเหมือนจะเอาชนะไม่ได้และกำลังมองหาการเขยิบไปในทิศทางที่ถูกต้อง
แบบฝึกหัดค่อนข้างง่าย: เขียนฟังก์ชันที่สามารถแกงและ / หรือฟังก์ชั่นอินพุตใด ๆ บางส่วนและประเมินฟังก์ชันอินพุตเมื่อมีพารามิเตอร์อินพุตเพียงพอ ฟังก์ชันอินพุตสามารถรับพารามิเตอร์อินพุตจำนวนเท่าใดก็ได้ นอกจากนี้ฟังก์ชันแกง / ฟังก์ชันบางส่วนควรมีความยืดหยุ่นในการเรียกใช้สามารถจัดการกับการเรียกฟังก์ชันได้หลายวิธี นอกจากนี้ยังอนุญาตให้เรียกฟังก์ชัน curry / บางส่วนด้วยอินพุตมากกว่าที่ฟังก์ชันอินพุตต้องการในกรณีนี้จำเป็นต้องละเว้นอินพุตส่วนเกินทั้งหมด
ตามลิงค์แบบฝึกหัดคุณจะพบกรณีการทดสอบทั้งหมดที่ฟังก์ชันต้องสามารถจัดการได้
รหัสที่ฉันสร้างขึ้นมีดังต่อไปนี้:
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
ตอนนี้รหัสนี้ล้มเหลวเมื่อดำเนินการทดสอบต่อไปนี้:
test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)
โดยที่ add = a + b + c (ฟังก์ชันที่กำหนดอย่างถูกต้อง), a = 1, b = 2, c = 3 และ sum = 6
ด้วยเหตุนี้ล้มเหลวเป็นเพราะผลตอบแทนที่จับฟังก์ชั่นการทำงานcurry_partial(add, a)
g
ในการเรียกครั้งที่สองcurry_partial(<function_handle to g>, b)
การคำนวณminArgs = len(signature(func).parameters)
ไม่ได้ผลอย่างที่ฉันต้องการเพราะตอนนี้จะคำนวณจำนวนฟังก์ชันอาร์กิวเมนต์อินพุตที่g
ต้องการ (ซึ่งก็คือ1
: ie *myArgs
) และไม่ใช่จำนวนต้นฉบับที่func
ยังต้องการ คำถามคือฉันจะเขียนโค้ดของฉันได้อย่างไรเพื่อให้ฉันสามารถติดตามจำนวนอาร์กิวเมนต์อินพุตที่ฉันfunc
ต้องการได้ (ลดจำนวนนั้นทุกครั้งที่ฉันเข้าร่วมฟังก์ชันด้วยอาร์กิวเมนต์เริ่มต้นที่กำหนด)
ฉันยังต้องเรียนรู้อีกมากเกี่ยวกับการเขียนโปรแกรมและการแกง / บางส่วนดังนั้นฉันจึงไม่ได้เลือกแนวทางที่สะดวกที่สุด แต่ฉันต้องการเรียนรู้ ความยากในแบบฝึกหัดนี้สำหรับฉันคือการรวมกันของบางส่วนและแกงเช่นการทำแกงกะหรี่ในขณะที่การโต้แย้งข้อโต้แย้งเริ่มต้นใด ๆ ที่พบ
คำตอบ
ลองดูสิ
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
นี่ไม่ใช่สิ่งที่ดีมากจากการออกแบบ แต่คุณควรใช้class
เพื่อทำงานภายในทั้งหมดเช่น Flip-Flop (ไม่ต้องกังวลว่าเราไม่จำเป็นต้องมีฟลิปฟล็อปที่นั่น ;-))
เมื่อใดก็ตามที่มีฟังก์ชันที่ใช้อาร์กิวเมนต์โดยพลการคุณสามารถสร้างอินสแตนซ์คลาสนั้นโดยส่งผ่านฟังก์ชันได้ตลอดเวลา แต่คราวนี้ฉันฝากไว้กับคุณ
ฉันไม่แน่ใจเกี่ยวกับแกงกะหรี่แต่ถ้าคุณต้องการเครื่องกำเนิดฟังก์ชันบางส่วนแบบง่ายๆคุณสามารถลองสิ่งนี้:
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))
แม้ว่าจะไม่สามารถแก้ปัญหาเดิมของคุณได้โปรดดูว่าคุณสามารถใช้รหัสด้านบนเพื่อลดการซ้ำรหัสได้หรือไม่ (ฉันไม่แน่ใจ แต่ฉันคิดว่ามีการซ้ำรหัสในฟังก์ชันภายในหรือไม่) ที่จะทำให้ปัญหาที่ตามมาแก้ไขได้ง่ายขึ้น
อาจมีฟังก์ชันในไลบรารีมาตรฐานที่แก้ปัญหานี้ได้แล้ว ภาษาที่ใช้งานได้จริงหลายภาษาเช่น Haskell มีคุณลักษณะนี้อยู่ในภาษา