如何克隆可迭代并递归替换特定值?

时间:2018-11-04 20:46:47

标签: python python-3.x recursion replace clone

我正在编写一个小部件,可以指定其各个部分的排列方式。

为此,我使用的是模块化原理
“构件块”用于指定任何顺序。

这些“块”被实现为枚举值,其中每个值代表一个单独的组成部分。

import enum

# The 'blocks'
class E(enum.Enum):
    A = 1
    B = 2
    C = 3

class Test():
    def __init__(self, arrangement):
        # The passed 'arrangement' is translated into the real arrangement.
        real_arrangement = []

        for a in arrangement:
            if a == E.A:
                real_arrangement.append("a_component")
            elif a == E.B:
                real_arrangement.append("b_component")
            elif a == E.C:
                real_arrangement.append("c_component")

        print(real_arrangement)


# The user can specify an arrangement...
arrangement = (E.A, E.C, E.B)

# ... and pass it to the constructor.
Test(arrangement)

# 'real_arrangement' = ("a_component", "c_component", "b_component")

请注意,占位符已替换,但结构相同。


但是,我也想给元素一些属性一些自由。 因此,除了纯枚举值之外,还可以传递迭代器,该迭代器包含枚举值和其他参数

# the elements are iterables themself.
arrangement = ((10, E.A),
               (20, E.C),
               (5, E.B))

# real_arrangement = ((10, "a_component"), (20, "c_component"), (5, "b_component"))

请注意,结构保持不变。


因此,我基本上是尝试克隆一个可迭代的并且递归地替换特定值。

我想到的任何方法都难以理解。
也许已经有我可以使用的解决方案了?


上面的代码是在Python 3.5.2。上运行的。

2 个答案:

答案 0 :(得分:1)

一个选择是检查arrangement is iterable的元素,并根据结果使用适当的列表理解。因此,通过一些重构,您可以执行以下操作:

import enum
import collections

# ...
class Test():
    def __init__(self, arrangement):
        def replace(a):
            if a == E.A:
                return "a_component"
            elif a == E.B:
                return "b_component"
            elif a == E.C:
                return "c_component"
            return a

        real_arrangement = [tuple(replace(e) for e in a) if isinstance(a, collections.Iterable) else replace(a) for a in arrangement]
        print(real_arrangement)

    #...

这将使您在问题中发布的两种arrangement列表(或“混合”列表)都能正常工作。

没有可迭代的元素:

arrangement = (E.A, E.C, E.B)
Test(arrangement)
# ['a_component', 'c_component', 'b_component']

具有所有可迭代元素:

arrangement = ((10, E.A), (20, E.C), (5, E.B), (1, E.A))
Test(arrangement)
# [(10, 'a_component'), (20, 'c_component'), (5, 'b_component'), (1, 'a_component')]

具有一些可迭代的元素:

arrangement = (E.A, (20, E.C), (5, E.B), (1, E.A))
Test(arrangement)
# ['a_component', (20, 'c_component'), (5, 'b_component'), (1, 'a_component')]

答案 1 :(得分:0)

这种方法应该适用于常见的(容器)类。

recursively_replace函数的参数:

  • 原始 –应该在其上执行递归替换的对象。
  • 替换dict,其中包含以下形式的对:value_to_replace : replacement
  • include_original_keys -bool-确定在originaldict的情况下是否也应替换密钥。 (默认值为False。)

该函数尝试使用与原始容器相同的容器。 (不是相同的容器对象。)

def recursively_replace(original, replacements, include_original_keys=False):
    """Clones an iterable and recursively replaces specific values."""

    # If this function would be called recursively, the parameters 'replacements' and 'include_original_keys'
    # would have to be passed each time. Therefore, a helper function with a reduced parameter list is used
    # for the recursion, which nevertheless can access the said parameters.

    def _recursion_helper(obj):
        #Determine if the object should be replaced. If it is not hashable, the search will throw a TypeError.
        try: 
            if obj in replacements:
                return replacements[obj]
        except TypeError:
            pass

        # An iterable is recursively processed depending on its class.
        if hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
            if isinstance(obj, dict):
                contents = {}

                for key, val in obj.items():
                    new_key = _recursion_helper(key) if include_original_keys else key
                    new_val = _recursion_helper(val)

                    contents[new_key] = new_val

            else:
                contents = []

                for element in obj:
                    new_element = _recursion_helper(element)

                    contents.append(new_element)

            # Use the same class as the original.
            return obj.__class__(contents)

        # If it is not replaced and it is not an iterable, return it.
        return obj

    return _recursion_helper(original)


# Demonstration
if __name__ == "__main__":

    import enum

    # Define an enumeration whose values should be replaced later.
    class E(enum.Enum):
        A = 1
        B = 2
        C = 3

    # Map the values to be replaced with their respective replacements.
    dict_with_replacements = {E.A : "a_replacement",
                              E.B : "b_replacement",
                              E.C : "c_replacement"}

    ### example 1 ###
    test = (E.A, E.C, E.B)

    result = recursively_replace(test, dict_with_replacements)

    print(result)       # ('a_component', 'c_component', 'b_component')


    ### example 2 ###
    test = ((10, E.A), (20, E.C), (5, E.B))

    result = recursively_replace(test, dict_with_replacements)

    print(result)       # ((10, 'a_component'), (20, 'c_component'), (5, 'b_component'))


    ### example 3 ###
    test = (E.A, (20, E.C), (5, E.B))

    result = recursively_replace(test, dict_with_replacements)

    print(result)       # ('a_component', (20, 'c_component'), (5, 'b_component'))


    ### example 4 & 5 ###
    test = (E.A, {20:E.C, E.B:5})

    result = recursively_replace(test, dict_with_replacements) 

    print(result)       # ('a_component', {<E.B: 2>: 5, 20: 'c_component'})

    result = recursively_replace(test, dict_with_replacements, True)

    print(result)       # ('a_component', {'b_component': 5, 20: 'c_component'})