【问题标题】:Python - loop through lists with recursionPython - 使用递归循环遍历列表
【发布时间】:2021-10-11 22:15:02
【问题描述】:

我有一个关于递归的问题。我想要一个函数,它将 my_input 中的一个特定字母作为输入,并且 创建名为 my_output 的列表。该函数循环遍历 my_input-list 及其子列表并添加每个列表 以 my_output 的每个子列表中的一个字母开头。

my_input = [
    ["A", "B", "C"],
    ["D", "E", "F"],
    ["B", "G", "H"],
    ["H", "I", "J"],
]

my_output = [
    ["A", "B", "C"],
    ["B", "G", "H"],
    ["H", "I", "J"],
]

例如:起始字母是“A”。该函数查找以“A”开头的子列表并添加 它到 my_output。比它取“B”(此子列表中的第二个字母)并查看是否存在子列表,即 以“B”开头,这是真的,并将这个添加到 my_output。它对“C”做同样的事情(但这里是 没有以“C”开头的子列表)。现在它对 my_output 中的第二个列表执行相同的操作并检查 字母“G”和“H”(“G”没有结果,但“H”有一个结果 -> 将以“H”开头的子列表添加到 my_output。 它最后检查字母“I”和“J”,但没有找到任何结果。该函数返回 my_output。

通过那个问题,我希望学习如何创建递归函数(至少我希望这是正确的/最好的 解决这个问题的方法),但我不明白。

提前致谢。

顺便说一句:这可行,但我认为它不是很好,一定有更好的方法吗?

def get_my_output(letter):
    checked_letters = []
    my_output = []
    for i in my_input:
        if i[0] == letter:
            checked_letters.append(i[0])
            my_output.append(i)
    for j in my_output:
        for k in j:
            if k not in checked_letters:
                for l in my_input:
                    if l[0] == k:
                        if l not in my_output:
                            my_output.append(l)
            else:
                continue
    return my_output

【问题讨论】:

    标签: python list loops recursion


    【解决方案1】:

    递归

    通过那个问题,我希望学习如何创建递归函数(至少我希望这是解决这个问题的正确/最佳方法),但我不明白。

    在递归中,您总是在“遍历”某些数据结构,通常将更深的递归层的输出添加到调用者的输出中。通常,这会产生一个函数,该函数在末尾尾递归)调用自己。

    重要的是,您总是需要某种停止条件(递归锚),它可以防止一次又一次地无限调用您的函数。

    解决方案

    出于演示目的,我将这段代码编写得非常冗长,并在其中添加了大量的 cmets。由于您没有指定递归锚点,因此我将从输入中删除已添加到输出中的所有列表:

    from typing import List
    
    def recurse_list(input_list: List[List[str]], starting_letter: str) -> List[List[str]]:
        result = []
        # get lists starting with desired letter
        current_lists = [l for l in input_list if l[0] == starting_letter]
        result.extend(current_lists)
        for current_list in current_lists:
            # recursively append lists starting with the latter letters in the current list
            for letter in current_list[1:]:
                # remove all lists from ``input_list`` that are already added to the result
                # this acts as a recursion anchor to stop execution at some point
                rest = [l for l in input_list if l not in result]
                result.extend(recurse_list(rest, letter))
        return result
    
    my_output = recurse_list(my_input, "A")
    print(my_output)
    

    结果:

    [['A', 'B', 'C'], ['B', 'G', 'H'], ['H', 'I', 'J']]
    

    您的解决方案

    顺便说一句:这可行,但我认为它不是很好,必须有一个 更好的方法?

    def get_my_output(letter):
        checked_letters = []
        my_output = []
        for i in my_input:
            if i[0] == letter:
                checked_letters.append(i[0])
                my_output.append(i)
        for j in my_output:
            for k in j:
                if k not in checked_letters:
                    for l in my_input:
                        if l[0] == k:
                            if l not in my_output:
                                my_output.append(l)
                else:
                    continue
        return my_output
    

    这里,if k not in checked_letters 充当递归锚。总的来说,您使用了正确的结构。但是,您不调用get_my_output(),因此这是一个迭代而不是递归的解决方案。

    此外,您可以通过使用 python 的列表推导 ([x for x in my_list if <condition>]) 而不是嵌套的 for 循环和 if 条件来最大程度地减少代码行和认知负担。而且您不需要 else: continue 案例,它什么都不做。

    此外,您的方法使用第二个聚合器变量 (checked_letters),您必须将其提供给递归调用,从而引入函数不应具有的参数。但是,您仍然可以使用默认参数添加它,因此第一个调用可以在没有它的情况下工作,例如 def get_my_output(letter, checked_letters=()) - 我仍然建议将 my_input 添加为参数,以使该函数可重用于其他输入。这里,默认参数() 是一个元组,因为它是不可变的,因此如果没有显式设置,则保持为空。 Python 中的可变默认参数(例如 [] 列表)将保留上次调用的值,从而污染未来的调用并可能泄漏到不相关的执行中。

    【讨论】:

      【解决方案2】:

      您可以使用list comprehension 来适应递归。您的递归函数将使用输入字母和输入列表“my_input”开始处理。

      您需要保留一个array 来跟踪您是否有visitedinput_letter 的递归函数。对于未访问的字母,我们可以从my_input 中找出有效行的列表,然后扩展我们的本地输出数组。 然后每个字母列表中的每个字母列表中的每个字母,我们都会调用递归函数得到结果。

      如果从my_input 中找不到有效的字母列表,则基本条件将返回空数组

      上述整个程序如下:

      my_input = [
          ["A", "B", "C"],
          ["D", "E", "F"],
          ["B", "G", "H"],
          ["H", "I", "J"],
      ]
      
      visited = []
      
      def list_with_recursion(letter_input, input_matrix):
          output = []
          if letter_input not in visited:
              visited.append(letter_input)
          else:
              return output
      
          input_letter_list = [x for x in input_matrix if x[0] == letter_input]
          if len(input_letter_list) == 0:
              return output
          output.extend(input_letter_list)
      
          func = lambda letter: list_with_recursion(letter, input_matrix)
          res = [func(x) for letter_list in input_letter_list for x in letter_list]
          output.extend(ele_list for lst in res for ele_list in lst if lst != [])
          return output
      
      
      my_output = list_with_recursion('A', my_input)
      print(my_output)
      

      给出:

      [['A', 'B', 'C'], ['B', 'G', 'H'], ['H', 'I', 'J']]
      

      【讨论】:

        【解决方案3】:

        发电机

        这是生成器的一个很好的用例。我们可以写traverse 来接受graphquery,例如my_input"A" 在您的问题中。我们使用附加参数visited 来跟踪图中已经访问过的节点 -

        def traverse(graph, query, visited = {}):
          if query in visited: return
          for nodes in graph:
            if nodes[0] == query:
              yield nodes
              for node in nodes:
                yield from traverse(graph, node, { *visited, query })
        
        my_input = [
            ["A", "B", "C"],
            ["D", "E", "F"],
            ["B", "G", "H"],
            ["H", "I", "J"],
        ]
        
        print(list(traverse(my_input, "A")))
        
        [
          ['A', 'B', 'C'],
          ['B', 'G', 'H'],
          ['H', 'I', 'J']
        ]
        

        上面我们使用list(...) 将生成器输出捕获到列表[...] 中。有时您可能希望跳过此步骤并直接在每个元素上执行效果。生成器是可迭代的,这意味着我们可以使用 for 或推导式一步一步完成 -

        for x in traverse(my_input, "A"):
          print(x)
        
        ['A', 'B', 'C']
        ['B', 'G', 'H']
        ['H', 'I', 'J']
        

        当我们以 query = "B" 开头时,我们看到的输出略有不同 -

        for x in traverse(my_input, "B"):
          print(x)
        
        ['B', 'G', 'H']
        ['H', 'I', 'J']
        

        我们只看到一行query = "D"

        for x in traverse(my_input, "D"):
          print(x)
        
        ['D', 'E', 'F']
        

        对于query = "Z",我们得到一个空输出 -

        print(list(traverse(my_input, "Z")))
        
        []
        

        替代实施

        我们可以通过多种方式实现traverse。我们可以假设子列表的头部是唯一的,而不是像上面那样使用visited,而不是稍后在图中重复 -

        def traverse(graph, query):
          for nodes in graph:
            if nodes[0] == query:
              yield nodes
              for node in nodes[1:]:
                yield from traverse(graph, node)
        

        nodes 解构为first*rest 是另一种选择-

        def traverse(graph, query):
          for nodes in graph:
            [first, *rest] = nodes
            if first == query:
              yield nodes
              for node in rest:
                yield from traverse(graph, node)
        

        这些更简单的替代方法的一个缺点是像 ["A", "B", "C", "A"] 这样的子列表,其中“A”在单个子列表中重复多次,或者 "A" 出现在多个子列表中,例如 [["A","B","C"],["C","A","F"],...] 会导致堆栈溢出。这篇文章中的第一个实现不会遇到这个问题。

        【讨论】: