Kari Python dan parsial
Saat melakukan latihan pemrograman di codewars.com, saya menemukan latihan tentang fungsi kari dan parsial.
Menjadi seorang pemula dalam pemrograman dan baru pada topik tersebut, saya mencari di internet untuk informasi tentang topik tersebut dan cukup jauh untuk menyelesaikan latihan. Namun sekarang saya telah menemukan rintangan yang sepertinya tidak dapat saya atasi dan saya di sini mencari dorongan ke arah yang benar.
Latihannya agak sederhana: tulis sebuah fungsi yang dapat mengkurasi dan / atau memihak fungsi masukan apa pun dan mengevaluasi fungsi masukan tersebut setelah cukup banyak parameter masukan yang disediakan. Fungsi masukan dapat menerima sejumlah parameter masukan. Juga fungsi kari / parsial harus sangat fleksibel dalam cara memanggilnya, mampu menangani banyak, banyak cara berbeda untuk memanggil fungsi tersebut. Selain itu, fungsi kari / parsial diperbolehkan untuk dipanggil dengan lebih banyak input daripada yang dibutuhkan oleh fungsi input, dalam hal ini semua kelebihan input harus diabaikan.
Mengikuti tautan latihan, semua kasus pengujian dapat ditemukan yang harus dapat ditangani oleh fungsi tersebut.
Kode yang saya buat adalah sebagai berikut:
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
Sekarang kode ini gagal ketika tes berikut dijalankan:
test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)
di mana tambahkan = a + b + c (fungsi yang didefinisikan dengan benar), a = 1, b = 2, c = 3, dan jumlah = 6.
Alasan ini gagal adalah karena curry_partial(add, a)
mengembalikan pegangan fungsi ke fungsi tersebut g
. Pada panggilan kedua,, curry_partial(<function_handle to g>, b)
kalkulasi minArgs = len(signature(func).parameters)
tidak bekerja seperti yang saya inginkan, karena sekarang akan menghitung berapa banyak fungsi argumen masukan yang g
dibutuhkan ( 1
yaitu: yaitu *myArgs
), dan bukan berapa banyak yang asli func
masih memerlukan. Jadi pertanyaannya adalah, bagaimana saya bisa menulis kode saya sedemikian rupa sehingga saya dapat melacak berapa banyak argumen input yang func
masih dibutuhkan asli saya (mengurangi angka itu setiap kali saya melakukan partialling fungsi dengan argumen awal yang diberikan).
Saya masih harus banyak belajar tentang pemrograman dan kari / parsial, jadi kemungkinan besar saya belum memilih pendekatan yang paling nyaman. Tapi saya ingin belajar. Kesulitan dalam latihan ini bagi saya adalah kombinasi parsial dan kari, yaitu melakukan loop kari sambil berpartisi setiap argumen awal yang ditemukan.
Jawaban
Coba ini.
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
Ini memang tidak terlalu bagus secara desain. Sebagai gantinya Anda harus menggunakan class
, untuk melakukan semua pekerjaan internal seperti, misalnya, flip-flop (jangan khawatir kami tidak memerlukan flip-flop di sana ;-)).
Setiap kali ada fungsi yang mengambil argumen arbitrer, Anda selalu dapat membuat instance kelas itu dengan meneruskan fungsi tersebut. Tapi kali ini bagaimanapun, saya serahkan pada Anda.
Saya tidak yakin tentang kari , tetapi jika Anda memerlukan generator fungsi parsial sederhana, Anda dapat mencoba sesuatu seperti ini:
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))
Bahkan jika itu tidak menyelesaikan masalah asli Anda, lihat apakah Anda dapat menggunakan kode di atas untuk mengurangi pengulangan kode (saya tidak yakin, tapi saya pikir ada beberapa pengulangan kode di fungsi bagian dalam?); yang akan membuat masalah selanjutnya lebih mudah dipecahkan.
Mungkin ada fungsi di pustaka standar yang sudah menyelesaikan masalah ini. Banyak bahasa fungsional murni seperti Haskell memiliki fitur ini dibangun ke dalam bahasa.