我有一个任意嵌套的迭代,如下所示:
numbers = (1, 2, (3, (4, 5)), 7)
我想在不改变结构的情况下在其上映射函数。例如,我可能想将所有数字转换为字符串以获取
strings = recursive_map(str, numbers)
assert strings == ('1', '2', ('3', ('4', '5')), '7')
有一个很好的方法吗?我可以用自己的方法编写自己的方法来手动遍历numbers
,但我想知道是否有一种通用的方法来映射递归的迭代。
另外,在我的例子中,如果strings
给我嵌套列表(或一些可迭代的)而不是嵌套的元组,也没关系。
答案 0 :(得分:15)
我们扫描序列中的每个元素,如果当前项是子序列,则进行更深的递归,或者如果我们到达非序列数据类型,则产生它的映射(可能是{{1} },int
或任何复杂的类。)
我们使用str
来概括每个序列的想法,而不仅仅是元组或列表,以及collections.Sequence
对yield的概念,以确保我们返回的子序列保持与它们相同的类型
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)
或更复杂的例子:
>>> numbers = (1, 2, (3, (4, 5)), 7)
>>> mapped = recursive_map(numbers, str)
>>> tuple(mapped)
('1', '2', ('3', ('4', '5')), '7')
答案 1 :(得分:6)
def recursive_map(f, it):
return (recursive_map(f, x) if isinstance(x, tuple) else f(x) for x in it)
答案 2 :(得分:2)
如果您想将结果扩展到dict
,set
和其他人,可以使用Uriel的答案:
from collections import Sequence, 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, Sequence):
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}']}
答案 3 :(得分:1)
我扩展了递归映射的概念,以处理标准的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
答案 4 :(得分:0)
以前,每个人都提到了flatten
函数的任何形式可能需要的内容,但是作为学习语言的练习,我一直在玩一些东西(因此Python noob alert ),我在这里看不到什么地方。基本上,我希望我的flatten
能够处理任何长度和嵌套的Iterable
,并且以最有效的方式(时间和空间)进行。这导致我进入生成器模式,而我对函数提出的第一个要求是在函数生成之前就什么都不能创建。
我的另一个要求是(对于/同时)不存在任何 explicit 循环,因为为什么不这样做:至少由于Python 3.3中yield from
的有用添加,我非常确定可能。当然,它必须是递归的,但是事实证明,将其提供给适当的“平坦”生成器比我想象的要难。因此,这是我的2p,说明了奇妙的chain
,而且我怀疑它是为以下情况而设计的(当然要稍微抽象一些):
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)))
不幸的是,对于我的免费野心勃勃的项目(和自我),根据一些相当粗略的基准测试,这比使用for
的版本慢30%:
def flatten(items):
for item in items:
if isinstance(item,Iterable):
yield from flatten(item)
else:
yield item
Uriel已经给出了一个变体。但我希望它能很好地说明以准功能方式使用Python的灵活性和功能,尤其是对于刚接触该语言的其他人。
编辑:为避免在单个列表项中分割字符串,可以将and not isinstance(item,(str,bytes))
附加到条件列表中。以及其他各种曲折的话题。