如何使用 Python 归约或列表理解将函数列表按顺序应用于字符串?

问题描述 投票:0回答:4

问题陈述

我想将函数列表

fs = [ f, g, h ]
按顺序应用于字符串
text='  abCdEf '

类似

f( g( h( text) ) )

这可以通过以下代码轻松完成:

# initial text
text = '  abCDef   '

# list of functions to apply sequentially
fs = [str.rstrip, str.lstrip, str.lower]

for f in fs:
    text = f(text)

# expected result is 'abcdef' with spaces stripped, and all lowercase
print(text)

使用 functools.reduce

似乎

functools.reduce
应该在这里完成这项工作,因为它在每次迭代时“消耗”函数列表。

from functools import reduce

# I know `reduce` requires two arguments, but I don't even know
# which one to chose as text of function from the list
reduce(f(text), fs)

# first interaction should call
y = str.rstrip('   abCDef   ')  --> '    abCDef' 

# next iterations fails, because tries to call '   abCDef'() -- as a function 

不幸的是,这段代码不起作用,因为每次迭代都会返回一个 string 而不是函数,并且失败并显示

TypeError
:
'str' object is not callable
.

问题:有没有使用
map
reduce
list comprehension
来解决此问题的方法?

python list-comprehension reduce fold functools
4个回答
7
投票

reduce
可以采用三个参数:

reduce(function, iterable, initializer)

这三个论点一般来说是什么?

  • function
    是两个参数的函数。我们将这两个参数称为
    t
    f
  • 第一个参数
    t
    将以
    initializer
    开头;然后将继续作为上次调用
    function
    的返回值。
  • 第二个参数
    f
    ,取自
    iterable

在我们的案例中,这三个论点是什么?

  • 可迭代对象是你的函数列表;
  • 第二个参数
    f
    将成为函数之一;
  • 第一个参数
    t
    必须是文本;
  • 初始化程序必须是初始文本;
  • function
    的返回必须是结果文本;
  • function(t, f)
    必须是
    f(t)

最后:

from functools import reduce

# initial text
text = '  abCDef   '

# list of functions to apply sequentially
fs = [str.rstrip, str.lstrip, str.lower]

result = reduce(lambda t,f: f(t), fs, text)

print(repr(result))
# 'abcdef'

6
投票

这是一个替代解决方案,它允许您组合任意数量的函数并保存组合的函数以供重用

又快又脏

import functools as ft

def compose(*funcs):
    return ft.reduce(lambda f, g: lambda x: f(g(x)), funcs)

用途:

In [4]: strip_and_lower = compose(str.rstrip, str.lstrip, str.lower)

In [5]: strip_and_lower('  abCDef   ')
Out[5]: 'abcdef'

In [6]: strip_and_lower("  AJWEGIAJWGIAWJWGIWAJ   ")
Out[6]: 'ajwegiajwgiawjwgiwaj'

In [7]: strip_lower_title = compose(str.title, str.lower, str.strip)

In [8]: strip_lower_title("     hello world  ")
Out[8]: 'Hello World'

请注意,函数的顺序很重要;这就像数学函数组合一样,即

(f . g . h)(x) = f(g(h(x))
,因此函数从右到左应用。

带有类型注释

如果您希望静态分析起作用,您需要添加类型注释。我们可以使用泛型和闭包来代替不可类型化的 lambda:

import functools as ft
from typing import Callable, TypeVar


T = TypeVar("T")

Composable = Callable[[T], T]


def compose(*funcs: Composable[T]) -> Composable[T]:
    def compose(f: Composable[T], g: Composable[T]) -> Composable[T]:
        def f_of_g(x: T) -> T:
            return f(g(x))

        return f_of_g

    return ft.reduce(compose, funcs)


strip_and_lower = compose(str.rstrip, str.lstrip, str.lower)

vscode 中的结果:


2
投票

你可以试试这个:

import functools
text = '  abCDef   '
fs = [str.rstrip, str.lstrip, str.lower]
text = functools.reduce(lambda store, func: func(store), fs, text)
print(text)

我认为您误解了reduce 的工作原理。减少将可迭代减少为单个值。回调函数可以接受两个参数,一个存储和一个元素。

reduce函数首先创建一个store变量。然后,循环遍历可迭代对象,使用存储变量和当前元素调用函数,并将存储更新为返回值。最后,该函数返回存储值。最后一个参数是 store 变量的开头。

因此在代码片段中,它循环遍历函数数组,并调用其上的相应函数。然后 lambda 将返回处理后的值,更新存储。


2
投票

既然您还要求

map
解决方案,这里就是一个。我的
values
包含单元素迭代,
values[0]
具有原始值,
values[i]
具有应用第一个
i
函数后的值。

text = '  abCDef   '
fs = [str.rstrip, str.lstrip, str.lower]

values = [[text]]
values += map(map, fs, values)
result = next(values[-1])

print(repr(result))  # prints 'abcdef'

但我不推荐这个。我主要好奇我是否能做到。现在我将尝试考虑如何避免构建该辅助列表。

好吧,我找到了一种没有辅助列表的方法,只占用 O(1) 内存。它将带有 tee 和 chain 的映射与自馈迭代器纠缠在一起,并使用双端队列来驱动它并获取最终元素。显然只是为了好玩。这是交替运行上述解决方案和新解决方案各三次的输出。它显示结果和峰值内存使用情况。我重复该列表 10000 次以提高内存使用量。

result = 'abcdef'   3,126,760 bytes   map_with_black_magic
result = 'abcdef'      12,972 bytes   map_with_blacker_magic
result = 'abcdef'   3,031,048 bytes   map_with_black_magic
result = 'abcdef'      12,476 bytes   map_with_blacker_magic
result = 'abcdef'   3,031,048 bytes   map_with_black_magic
result = 'abcdef'       8,052 bytes   map_with_blacker_magic

代码(在线尝试!):

def map_with_black_magic(text, fs):
    values = [[text]]
    values += map(map, fs, values)
    return next(values[-1])

def map_with_blacker_magic(text, fs):
    parts = [[[text]]]
    values = chain.from_iterable(parts)
    it1, it2 = tee(values)
    parts.append(map(list, map(map, fs, it1)))
    return deque(it2, 1)[0][0]

from itertools import tee, chain
from collections import deque
import tracemalloc as tm

text = '  abCDef   '
fs = [str.rstrip, str.lstrip, str.lower] * 10000

for func in [map_with_black_magic, map_with_blacker_magic] * 3:
    tm.start()
    result = func(text, fs)
    memory = tm.get_traced_memory()[1]
    tm.stop()
    print(f'{result = !r}{memory:12,} bytes  ', func.__name__) 
© www.soinside.com 2019 - 2024. All rights reserved.