Python:将函数映射到递归可重用项上
我有一个任意嵌套的iterable,如下所示:Python:将函数映射到递归可重用项上,python,dictionary,recursion,Python,Dictionary,Recursion,我有一个任意嵌套的iterable,如下所示: numbers = (1, 2, (3, (4, 5)), 7) 我想在不改变结构的情况下,在上面映射一个函数。例如,我可能希望将所有数字转换为字符串以获得 strings = recursive_map(str, numbers) assert strings == ('1', '2', ('3', ('4', '5')), '7') 有什么好办法吗?我可以编写自己的方法来手动遍历数字,但我想知道是否有一种通用的方法来映射递归可重用项 另外,
numbers = (1, 2, (3, (4, 5)), 7)
我想在不改变结构的情况下,在上面映射一个函数。例如,我可能希望将所有数字转换为字符串以获得
strings = recursive_map(str, numbers)
assert strings == ('1', '2', ('3', ('4', '5')), '7')
有什么好办法吗?我可以编写自己的方法来手动遍历数字
,但我想知道是否有一种通用的方法来映射递归可重用项
另外,在我的示例中,如果
strings
给我的是嵌套列表(或一些iterable)而不是嵌套元组,那也没关系 我们扫描序列中的每个元素,如果当前项是一个子序列,则进行更深层次的递归;如果达到非序列数据类型(可以是int
,str
,或任何复杂类),则生成其映射
我们使用collections.Sequence
来概括每个序列的思想,而不仅仅是元组或列表,并在产生时使用type(item)
,以确保我们得到的子序列仍然保持与它们相同的类型
from collections import Sequence
def recursive_map (seq, func):
for item in seq:
if isinstance(item, Sequence):
yield type(item)(recursive_map(item, func))
else:
yield func(item)
演示:
或者更复杂的例子:
>>> complex_list = (1, 2, [3, (complex('4+2j'), 5)], map(str, (range(7, 10))))
>>> tuple(recursive_map(complex_list, lambda x: x.__class__.__name__))
('int', 'int', ['int', ('complex', 'int')], 'map')
如果您想将结果扩展到
dict
、set
等,可以使用Uriel的答案:
from collections import Collection, Mapping
def recursive_map(data, func):
apply = lambda x: recursive_map(x, func)
if isinstance(data, Mapping):
return type(data)({k: apply(v) for k, v in data.items()})
elif isinstance(data, Collection):
return type(data)(apply(v) for v in data)
else:
return func(data)
测试输入:
recursive_map({0: [1, {2, 2, 3}]}, str)
收益率:
{0: ['1', '{2, 3}']}
我扩展了递归映射的概念,以处理标准python集合:list、dict、set、tuple:
def recursiveMap(something, func):
if isinstance(something, dict):
accumulator = {}
for key, value in something.items():
accumulator[key] = recursiveMap(value, func)
return accumulator
elif isinstance(something, (list, tuple, set)):
accumulator = []
for item in something:
accumulator.append(recursiveMap(item, func))
return type(something)(accumulator)
else:
return func(something)
这通过了以下测试,我将主要包括这些测试作为使用示例:
from hypothesis import given
from hypothesis.strategies import dictionaries, text
from server.utils import recursiveMap
def test_recursiveMap_example_str():
assert recursiveMap({'a': 1}, str) == {'a': '1'}
assert recursiveMap({1: 1}, str) == {1: '1'}
assert recursiveMap({'a': {'a1': 12}, 'b': 2}, str) == {'a': {'a1': '12'}, 'b': '2'}
assert recursiveMap([1, 2, [31, 32], 4], str) == ['1', '2', ['31', '32'], '4']
assert recursiveMap((1, 2, (31, 32), 4), str) == ('1', '2', ('31', '32'), '4')
assert recursiveMap([1, 2, (31, 32), 4], str) == ['1', '2', ('31', '32'), '4']
@given(dictionaries(text(), text()))
def test_recursiveMap_noop(dictionary):
assert recursiveMap(dictionary, lambda x: x) == dictionary
之前每个人都提到过,任何类型的
flatte
函数都可能需要很多东西,但我一直在练习学习该语言(因此Python noob alert),但我在这里没有看到这些东西。基本上,我希望我的plant
能够以最有效的方式(时间和空间)处理任何长度和嵌套的Iterable
s。这就引出了生成器模式,我对函数提出的第一个要求是在它出现之前不创建任何东西
我的另一个要求是没有任何显式循环(for/while),因为为什么不呢:至少因为在Python3.3中添加了有益的yield from
,我非常确信这是可能的。当然,它必须是递归的,但是让它给出一个合适的“平坦”生成器比我想象的要复杂得多。这是我的2p,它展示了奇妙的链
,我猜想,它是为这种情况(当然有点抽象)而设计的:
from itertools import chain
from collections import Iterable
def flatten(items):
if isinstance(items,Iterable):
yield from chain(*map(flatten,items))
else:
yield items
items = [0xf, [11, 22, [23, (33,(4, 5))], 66, [], [77]], [8,8], 99, {42}]
print(list(flatten(items)))
不幸的是,对于我的免费雄心勃勃的项目(和ego),根据一些相当粗略的基准测试,这比使用for
的版本慢约30%:
def flatten(items):
for item in items:
if isinstance(item,Iterable):
yield from flatten(item)
else:
yield item
乌里尔已经给出了一种变体。然而,我希望它能很好地说明Python以准函数方式使用的灵活性和强大功能,特别是对于其他对该语言不熟悉的人
编辑:为了避免拆分单个列表项中的字符串,可以将
而不是isinstance(item,(str,bytes))
附加到条件中。以及其他各种各样的钟声和口哨声,这些都会影响到这一点。我相信你只是在概括序列。某个东西是否是iterable不是类型问题,而是遵循协议的问题。OP可能意味着序列,但这不会在集合、管道等上迭代。某个东西是否是iterable取决于它是否实现了iterable协议,这对集合不起作用。将序列更改为集合以使其工作。@fjsj谢谢。我相应地改变了它。
from itertools import chain
from collections import Iterable
def flatten(items):
if isinstance(items,Iterable):
yield from chain(*map(flatten,items))
else:
yield items
items = [0xf, [11, 22, [23, (33,(4, 5))], 66, [], [77]], [8,8], 99, {42}]
print(list(flatten(items)))
def flatten(items):
for item in items:
if isinstance(item,Iterable):
yield from flatten(item)
else:
yield item