Python:在递归迭代上映射函数

时间:2017-02-07 16:44:07

标签: python dictionary recursion

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

numbers = (1, 2, (3, (4, 5)), 7)

我想在不改变结构的情况下在其上映射函数。例如,我可能想将所有数字转换为字符串以获取

strings = recursive_map(str, numbers)
assert strings == ('1', '2', ('3', ('4', '5')), '7')

有一个很好的方法吗?我可以用自己的方法编写自己的方法来手动遍历numbers,但我想知道是否有一种通用的方法来映射递归的迭代。

另外,在我的例子中,如果strings给我嵌套列表(或一些可迭代的)而不是嵌套的元组,也没关系。

5 个答案:

答案 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)

如果您想将结果扩展到dictset和其他人,可以使用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))附加到条件列表中。以及其他各种曲折的话题。