แกงหลามและบางส่วน

Aug 19 2020

ขณะทำแบบฝึกหัดการเขียนโปรแกรมบน 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ต้องการได้ (ลดจำนวนนั้นทุกครั้งที่ฉันเข้าร่วมฟังก์ชันด้วยอาร์กิวเมนต์เริ่มต้นที่กำหนด)

ฉันยังต้องเรียนรู้อีกมากเกี่ยวกับการเขียนโปรแกรมและการแกง / บางส่วนดังนั้นฉันจึงไม่ได้เลือกแนวทางที่สะดวกที่สุด แต่ฉันต้องการเรียนรู้ ความยากในแบบฝึกหัดนี้สำหรับฉันคือการรวมกันของบางส่วนและแกงเช่นการทำแกงกะหรี่ในขณะที่การโต้แย้งข้อโต้แย้งเริ่มต้นใด ๆ ที่พบ

คำตอบ

1 Ava Aug 20 2020 at 13:27

ลองดูสิ

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 (ไม่ต้องกังวลว่าเราไม่จำเป็นต้องมีฟลิปฟล็อปที่นั่น ;-))

เมื่อใดก็ตามที่มีฟังก์ชันที่ใช้อาร์กิวเมนต์โดยพลการคุณสามารถสร้างอินสแตนซ์คลาสนั้นโดยส่งผ่านฟังก์ชันได้ตลอดเวลา แต่คราวนี้ฉันฝากไว้กับคุณ

Nishant Aug 21 2020 at 02:46

ฉันไม่แน่ใจเกี่ยวกับแกงกะหรี่แต่ถ้าคุณต้องการเครื่องกำเนิดฟังก์ชันบางส่วนแบบง่ายๆคุณสามารถลองสิ่งนี้:

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 มีคุณลักษณะนี้อยู่ในภาษา