Cà ri trăn và một phần

Aug 19 2020

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

1 Ava Aug 20 2020 at 13:27

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.

Nishant Aug 21 2020 at 02:46

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ữ.