python 中的展开函数

unfold function in python

我最近在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 函数工具或标准库中的任何地方找到 unfold 的定义。

unfold 不是 python 函数...这是一个递归解决方案...

def to_digits(x):
    if not x: return []
    return to_digits(x//10) + [x%10]

to_digits(12345)

这不使用 lambda,它使用列表理解:

def to_digits(x):
    return [int(i) for i in str(x)]

str(x) 是一个带有 x 字符串化版本的可迭代对象,列表推导式使其成为一个整数列表(因此 int(i))。

出于函数式编程的目的,可以在python中使用map。 一个例子是: map(lambda a, b: a+b, [1,2,3], [2,3,4]) 这将 return [3, 5, 7].

对于你的情况,你可以这样做:

def toDigits(num):
    return map(int, str(num))

标准库中没有unfold实现。不过,在more_itertools模块中也有类似的功能。您可以使用 more_itertools.iterate 生成数组。 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.accumulate,它可以被认为是 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]]