Skip to content

Commit 7d5611d

Browse files
committed
Added functions Curry and Uncurry
1 parent f06b0f5 commit 7d5611d

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
def curry(func, arity):
2+
"""
3+
Каррирование функции с заданной арностью.
4+
5+
Args: func - исходная функция,
6+
arity - арность функции (кол-во ожид. аргументов).
7+
8+
return: рекурсивно с пустого кортежа собираем все аргументы
9+
и вызываем для них исходную функцию. Её результат возвращаем.
10+
11+
raises: ValueError - если арность отрицательная.
12+
"""
13+
14+
if arity < 0:
15+
raise ValueError("Арность не должна быть <0")
16+
17+
def curried(args_received):
18+
"""
19+
Рекурсивная функция, которая копит аргументы.
20+
21+
args_received: type - tuple, уже полученные аргументы
22+
23+
return: если все аргументы получены - результат данной функции,
24+
иначе - новая функция, ждущая след. аргумент.
25+
"""
26+
if len(args_received) == arity:
27+
return func(*args_received)
28+
29+
def next_arg(arg):
30+
"""
31+
Функция, в которой мы получаем новый аргумент
32+
и добавлем его к кортежу полученных аргументов.
33+
34+
return: curried(аргументы + новый) и проверяем,
35+
достаточно ли аргументов для подсчета результата.
36+
"""
37+
new_args = args_received + (arg,)
38+
return curried(new_args)
39+
40+
return next_arg
41+
42+
if arity == 0:
43+
return lambda: func()
44+
45+
return curried(())
46+
47+
48+
def uncurry(curried_func, arity):
49+
"""
50+
Функция декаррирования каррированной функции с заданной арностью.
51+
52+
Args: curried_func - каррированная функция,
53+
arity - арность функции.
54+
55+
return: внутренняя декаррирующая функция, принимающая все аргументы в виде кортежа.
56+
57+
raise: ValueError - если передана отрицательная арность.
58+
"""
59+
if arity < 0:
60+
raise ValueError("Арность не должна быть <0")
61+
62+
def uncurried(*args):
63+
"""
64+
Функция, принимающая все аргументы сразу.
65+
66+
*args - все аргументы функции.
67+
68+
return: последовательно применяем все аргументы к каррированной
69+
функции и возвращаем результат.
70+
71+
raise: TypeError при неверном кол-ве аргументов.
72+
"""
73+
if len(args) != arity:
74+
raise TypeError(f"Функция ожидает {arity} аргументов, получено {len(args)}")
75+
76+
result = curried_func
77+
for arg in args:
78+
result = result(arg)
79+
80+
if arity == 0:
81+
return result()
82+
83+
return result
84+
85+
return uncurried

tests/test_curry.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import pytest
2+
from hypothesis import given, strategies as st
3+
4+
from functions_curry_7.curry_uncurry import curry, uncurry
5+
6+
7+
# unit тесты
8+
9+
10+
def test_curry_basic():
11+
"""Тест каррирования функции с 3 аргументами."""
12+
13+
def sum_3(a, b, c):
14+
return a + b + c
15+
16+
curried = curry(sum_3, 3)
17+
result = curried(1)(2)(3)
18+
assert result == 6, f"Ожидалось 6, получено {result}"
19+
20+
21+
def test_curry_partial_application():
22+
"""Тест частичного применения каррированной функции."""
23+
24+
def multiply(a, b):
25+
return a * b
26+
27+
curried = curry(multiply, 2)
28+
double = curried(2)
29+
30+
assert double(5) == 10
31+
assert double(6) == 12
32+
assert callable(double), "Частично примененная функция должна быть вызываемой"
33+
34+
35+
def test_uncurry_basic():
36+
"""Тест обратимости"""
37+
38+
def add_4(a, b, c, d):
39+
return a + b + c + d
40+
41+
curried = curry(add_4, 4)
42+
uncurried = uncurry(curried, 4)
43+
re_curried = curry(uncurried, 4)
44+
45+
assert curried(1)(2)(3)(4) == 10
46+
assert uncurried(1, 2, 3, 4) == 10
47+
assert re_curried(1)(2)(3)(4) == 10
48+
49+
50+
def test_zero_arity():
51+
"""Тестирование функций с нулевой арностью"""
52+
53+
def constant():
54+
return 42
55+
56+
curried_const = curry(constant, 0)
57+
assert curried_const() == 42
58+
59+
uncurried_const = uncurry(curried_const, 0)
60+
assert uncurried_const() == 42
61+
62+
63+
def test_single_argument():
64+
"""Тестирование функций с одним аргументом"""
65+
66+
def identity(x):
67+
return x
68+
69+
curried_id = curry(identity, 1)
70+
assert curried_id(5) == 5
71+
72+
uncurried_id = uncurry(curried_id, 1)
73+
assert uncurried_id(10) == 10
74+
75+
76+
# тесты ошибок
77+
78+
79+
def test_curry_negative_arity():
80+
"""Тест ошибки при отрицательной арности в curry."""
81+
82+
def func(a):
83+
return a
84+
85+
with pytest.raises(ValueError, match="Арность не должна быть <0"):
86+
curry(func, -1)
87+
88+
with pytest.raises(ValueError, match="Арность не должна быть <0"):
89+
curry(func, -100)
90+
91+
92+
def test_uncurry_negative_arity():
93+
"""Тест ошибки при отрицательной арности в uncurry."""
94+
95+
def func(a):
96+
return a
97+
98+
curried = curry(func, 1)
99+
100+
with pytest.raises(ValueError, match="Арность не должна быть <0"):
101+
uncurry(curried, -1)
102+
103+
104+
def test_uncurry_wrong_number_of_args():
105+
"""Тест ошибки при передаче неправильного количества аргументов в uncurry."""
106+
107+
def sum_3(a, b, c):
108+
return a + b + c
109+
110+
curried = curry(sum_3, 3)
111+
uncurried = uncurry(curried, 3)
112+
113+
with pytest.raises(TypeError, match="Функция ожидает 3 аргументов"):
114+
uncurried(1, 2)
115+
116+
with pytest.raises(TypeError, match="Функция ожидает 3 аргументов"):
117+
uncurried(1, 2, 3, 4)
118+
119+
120+
# Property-based тесты с hypothesis
121+
122+
integers = st.integers(min_value=-100, max_value=100)
123+
small_lists = st.lists(integers, min_size=0, max_size=5)
124+
125+
126+
@given(a=integers, b=integers, c=integers)
127+
def test_curry_uncurry_inverse(a: int, b: int, c: int):
128+
"""
129+
Property-based тест: curry и uncurry обратны друг другу.
130+
Для любых a, b, c результаты должны совпадать.
131+
"""
132+
133+
def sum_3(x, y, z):
134+
return x + y + z
135+
136+
curried = curry(sum_3, 3)
137+
uncurried = uncurry(curried, 3)
138+
139+
assert curried(a)(b)(c) == uncurried(a, b, c)
140+
assert curried(a)(b)(c) == a + b + c
141+
142+
143+
@given(args=st.tuples(integers, integers, integers))
144+
def test_curry_preserves_behavior(args):
145+
"""
146+
Property-based тест: каррированная функция ведет себя так же,
147+
как исходная.
148+
"""
149+
150+
def sum_3(x, y, z):
151+
return x + y + z
152+
153+
curried = curry(sum_3, 3)
154+
a, b, c = args
155+
156+
direct_result = sum_3(a, b, c)
157+
curried_result = curried(a)(b)(c)
158+
159+
assert direct_result == curried_result
160+
161+
162+
@given(x=integers, y=integers, z=integers, w=integers)
163+
def test_partial_application_property(x, y, z, w):
164+
"""
165+
Property-based тест: частичное применение работает корректно.
166+
"""
167+
168+
def add4(a, b, c, d):
169+
return a + b + c + d
170+
171+
curried_add4 = curry(add4, 4)
172+
partially_applied = curried_add4(x)(y)
173+
174+
assert callable(partially_applied)
175+
176+
result = partially_applied(z)(w)
177+
178+
assert result == x + y + z + w
179+
assert result == add4(x, y, z, w)

0 commit comments

Comments
 (0)