我最近开始用python练习函数式编程。
假设我定义了一个函数,它获取一个数字数组并将其连接起来:
In [1]: def fromDigits(digits):
...: return reduce(lambda x,y: 10*x+y,digits,0)
...:
In [2]: fromDigits([1,2,3,4,5])
Out[2]: 12345
现在我想实现反向函数:
def toDigits(num):
return unfold(lambda m,digits: (m/10,digits+[m % 10]) if m>0 else None, num, [])
但是我在python
functools或标准库中找不到unfold
的定义
展开不是一个python函数…这是一个递归解…
def to_digits(x):
if not x: return []
return to_digits(x//10) + [x%10]
to_digits(12345)
对于函数式编程的目的,可以在python中使用map
。一个例子是:map(lambda a, b: a+b, [1,2,3], [2,3,4])
返回[3, 5, 7]
.
在你的情况下,你可以这样做:
def toDigits(num):
return map(int, str(num))
标准库中没有unfold
实现。然而,在more_itertools
模块中有一个类似的功能。您可以使用more_itertools。迭代生成数组。可以将more_itertools.iterate
视为unfold
的非终止版本,因此需要使用itertools.takewhile
来终止迭代器。
from more_itertools import iterate, always_reversible
from itertools import takewhile
def to_digits(num):
return always_reversible(
map(
lambda x: x % 10,
takewhile(
lambda x: x > 0,
iterate(lambda x: x // 10, num)
)
)
)
print(*to_digits(12345))
如果你喜欢标准库中的函数,你可能想使用itertools。它可以被认为是more_itertools.iterate
的一个更强大的版本,因为itertools.accumulate
接受一个额外的可迭代形参。由于您不需要额外的可迭代对象来生成数字,因此只需传递repeat(None)
以返回到more_itertools.iterate
行为。
from itertools import takewhile, accumulate, repeat
def to_digits(num):
return reversed(
tuple(
map(
lambda x: x % 10,
takewhile(
lambda x: x > 0,
accumulate(repeat(None), lambda x, _: x // 10, initial=num)
)
)
)
)
print(*to_digits(12345))
unfoldr
解决方案
# Python 3.8+
from collections import deque
def unfold(s, f):
state, elements = s, deque()
while (next_step := f(state)) is not None:
state, element = next_step
elements.appendleft(element)
return list(elements)
def to_digits(num):
return unfold(num, lambda n: (n // 10, n % 10) if n > 0 else None)
>>> to_digits(12345)
[1, 2, 3, 4, 5]
选自问题
问题中提出的逻辑的不同部分是:
def toDigits(num):
return unfold(lambda m,digits: (m/10,digits+[m % 10]) if m>0 else None, num, [])
# +--+ +----+ +-+
# compute the next state <--------+ | |
# | |
# compute the element <------------------------+ |
# |
# predicate to terminate `unfold` <------------------------+
然而,我认为以下这些部分不应该属于传递函数,而是属于unfold
逻辑本身:
def toDigits(num):
return unfold(lambda m,digits: (m/10,digits+[m % 10]) if m^0 else None, num, [])
# +----+ +------+ ++ ++
新函数
传递给unfold
的函数可以简单地为:
lambda n: (n // 10, n % 10) if n > 0 else None
#+------+ ++
# | +---> floor division operator
# |
# +---------> input just the number
# output the next state and the element "unfolded"
# (or None)
类型签名是:
S = TypeVar('S') # state
A = TypeVar('A') # element to unfold
f: Callable[[S], Optional[Tuple[S, A]]] = lambda n: (n // 10, n % 10) if n > 0 else None
# +-+ +--+
# | |
# input <-+ |
# |
# output <---------------------+
展开(r)
看看问题中提出的逻辑,我们可以假设期望的unfold
实际上是"右手口味"。unfoldr
。一个可能的实现(Python 3.8+)可能是:
from collections import deque # O(1) `appendleft`
def unfoldr(s, f):
state, elements = s, deque()
while (next_step := f(state)) is not None:
state, element = next_step
elements.appendleft(element)
return list(elements)
其中签名为:
def unfoldr(state: S, f: Callable[[S], Optional[Tuple[S, A]]]) -> List[A]:
如果你喜欢用递归实现创建堆栈帧:
def unfoldr(state, f):
return [] if (next_step := f(state)) is None else unfoldr(next_step[0], f) + [next_step[1]]
不使用lambda
,它使用列表推导式:
def to_digits(x):
return [int(i) for i in str(x)]
str(x)
是一个可迭代对象,具有x
的字符串化版本,列表推导式使其成为一个整数列表(因此是int(i)
)。