【问题标题】:Python: Map a function over recursive iterablesPython:在递归迭代上映射一个函数
【发布时间】:2017-06-25 00:35:12
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

    标签: python dictionary recursion


    【解决方案1】:

    我们扫描序列中的每个元素,如果当前项是子序列,则进行更深层次的递归,如果我们达到非序列数据类型(可能是intstr、或任何复杂的类)。

    我们使用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')
    

    或者更复杂的例子:

    >>> 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')
    

    【讨论】:

    • 我相信你只是泛化到 sequences。某物是否是可迭代的不是类型问题,而是遵循协议的问题。 OP 可能意味着序列,但这不会迭代集合、管道等。某物是否可迭代由它是否实现可迭代协议来定义
    【解决方案2】:
    def recursive_map(f, it):
        return (recursive_map(f, x) if isinstance(x, tuple) else f(x) for x in it)
    

    【讨论】:

      【解决方案3】:

      如果您想将结果扩展到dictset 等,您可以使用 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}']}
      

      【讨论】:

      • 不适用于套装。将 Sequence 更改为 Collection 以使其工作。
      • @fjsj 谢谢。我相应地改变了它。
      【解决方案4】:

      我扩展了递归映射的概念,使其适用于标准的 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
      

      【讨论】:

        【解决方案5】:

        之前每个人都提到过对于任何风格的 flatten 函数可能需要的东西的数量,但是我一直在玩一些东西作为学习语言的练习(所以 Python 新手警报)我没有看到这里完全放在一起。基本上我希望我的flatten 能够处理任何长度的Iterables,并以最有效(时间和空间)的方式进行嵌套。这将我引向了生成器模式,我对函数提出的第一个要求是在它出现之前没有任何东西可以创建。

        我的另一个要求是没有任何 显式 循环(for/while),因为为什么不这样做:至少自从在 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)) 附加到条件。以及其他各种会影响重点的花里胡哨。

        【讨论】:

          猜你喜欢
          • 2012-10-17
          • 1970-01-01
          • 2021-08-13
          • 2021-03-03
          • 2019-03-01
          • 2018-08-29
          • 1970-01-01
          • 2019-04-03
          • 2016-03-01
          相关资源
          最近更新 更多