Python:将函数映射到递归可重用项上

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') 有什么好办法吗?我可以编写自己的方法来手动遍历数字,但我想知道是否有一种通用的方法来映射递归可重用项 另外,

我有一个任意嵌套的iterable,如下所示:

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