Python: сопоставление функции с рекурсивными итерациями

у меня есть произвольно вложенная итерация, например:

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

и я хотел бы отобразить функцию над ней без изменения структуры. Например, я мог бы преобразовать все числа в строки, чтобы получить

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

есть ли хороший способ сделать это? Я могу написать свой собственный метод, чтобы вручную пересечь numbers, но я хотел бы знать, есть ли общий способ отображения рекурсивных итераций.

кроме того, в моем примере, это нормально, если strings дает me вложенные списки (или некоторые итерационные), а не вложенные кортежи.

4 ответов


мы сканируем каждый элемент в последовательности и переходим к более глубокой рекурсии, если текущий элемент является подпоследовательностью или дает его отображение, если мы достигли типа данных без последовательности (может быть 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)

демо:

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

def recursive_map(f, it):
    return (recursive_map(f, x) if isinstance(x, tuple) else f(x) for x in it)

если вы хотите расширить свой результат dict, set и другие, вы можете использовать ответ Уриэля:

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}']}

я расширил понятие рекурсивной карты для работы со стандартными коллекциями 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