嵌套列表的非递归遍历-在Python中构建类似的嵌套列表

时间:2019-03-15 05:52:09

标签: python recursion iteration

我需要遍历一个嵌套列表,用str()处理每个非列表项,并返回类似的列表以保持结构。使用递归将非常容易,但是我需要采用这种迭代方式。以下是我尝试的while循环:

def myiter(e):
    a = [e] # initial list
    c = [[]] # final result
    get_last = lambda x: x[len(x)-1] # get ref to the final sublist
    l = get_last(c)
    while a:
        b = a.pop(0)
        if isinstance(b, list):
            # if there are more items to process in the original list
            if a:
                a = b + a
            # else extend original list to process sublists
            else:
                a.extend(b)
            # make a new sublist ref
            l = get_last(c)
            c.append([])
        else:
             # walk and process every item in the nested list
             l.append(str(b))
    return c

这有几个问题,因为输出将显示:

myiter([1, [2, [3, 4], 5]]) # [['1'], ['2'], ['3', '4', '5'], []]

期望的结果是:

['1', ['2', ['3', '4'], '5']]

在Python中有简单的迭代方法来完成任务吗?

2 个答案:

答案 0 :(得分:3)

这似乎可行:

def stringify(a):
    a = a[:]                           # Make copy of what was passed in.
    res = []                           # Initialize result list.
    my_stack = []                      # Initialize our own LIFO stack.
    while (a or my_stack):                            # While a or my_stack is non-empty
        if (a):
            elem = a.pop(0)
            if (not isinstance(elem, list)):          # If popped elem is not a list
                res.append(str(elem))                 # Append stringified elem to res
            else:
                my_stack.append((a, res))           # Push some stuff, to resume working upon later.
                a = elem                            # Let's start iterating on this inner list
                res = []                            # This inner list needs a clean res, to start with.
        else:                                       # my_stack is non-empty
            a, res_prev = my_stack.pop()   # Pop some stuff, to resume, work on outer list
            res_prev.append(res)           # First, append our just-completed inner list.
            res = res_prev
    return res

输出:

a = [1, [2, [3, 4], 5]]
stringify(a)
['1', ['2', ['3', '4'], '5']]

通过了以下测试案例:

a = [1, [[[2]]]]
a = [[[1]], 2]
a = [1, [[2]]]
a = [1, [2, [3, 4], 5], [6, [7, [8]]], 9]
a = [1, [2, [3, 4], 5]]
a = [1, 2, 3, 4, 5]

有关其工作原理的一些说明:

  1. 如果列表pop上的a产生一个整数,我们只需将字符串化的整数附加到res
  2. 如果列表pop上的a产生了一个内部列表,则在处理该内部列表之后出现的元素之前,我们需要开始处理该内部列表。处理完内部列表之后,我们将不得不回到a的其余未弹出元素。
  3. 只要我们检测到当前列表a为空,我们的res就会指向等效的字符串化列表,现在是时候将res附加到任何内容了可能是其(字符串化的)外部列表
  4. 要处理每个遇到的内部列表,我们将该内部列表用作新的a(分配a = elem),并将一个空列表用作新的res({{1} }。在此之前,我们需要将当前res = []和当前a
  5. 推入堆栈
  6. 为什么选择LIFO?好吧,这样看:将最后推到res上的任何内容都代表我们当前正在处理的任何列表(my_stack)的直接外部列表。

答案 1 :(得分:2)

为什么不递归?使用递归过程处理递归数据结构是自然而直接的。将递归过程转换为迭代过程不必涉及克隆输入,创建stack或其他中间值。您的大脑可以摆脱如此严重的复杂性-

def first (a = []):
  return a[0]

def rest (a = []):
  return a[1:]

def myiter (a = []):
  # base: empty a
  if not a:
    return []
  # inductive: non-empty a, first elem is list
  elif isinstance(first(a), list):
    return [ myiter(first(a)) ] + myiter(rest(a))
  # inductive: non-empty a, first elem is non-list
  else:
    return [ str(first(a)) ] + myiter(rest(a))     

print(myiter([1, [2, [3, 4], 5]]))

当然,将str设为函数f的参数-

def myiter (f, a = []):
  # base: empty a
  if not a:
    return []
  # inductive: non-empty a, first elem is list
  elif isinstance(first(a), list):
    return [ myiter(f, first(a)) ] + myiter(f, rest(a))
  # inductive: non-empty a, first elem is non-list
  else:
  return [ f(first(a)) ] + myiter(f, rest(a))

现在,您可以根据需要深度映射str-

print(myiter(str, [1, [2, [3, 4], 5]]))
# ['1', ['2', ['3', '4'], '5']]

或使用您选择的任何功能-

def square (x):
  return x * x

print(myiter(square, [1, [2, [3, 4], 5]]))
# [1, [4, [9, 16], 25]]

您是否由于堆栈限制而试图避免递归?如果您将其设为尾递归-

def identity (x):
  return x

def myiter (f, init = []):
  def run (a = init, then = identity):
    if not a:
      return \
        then([])
    # inductive: non-empty a, first elem is list
    elif isinstance(first(a), list):
      return \
        recur(first(a), lambda l: \
          recur(rest(a), lambda r: \
            then([ l ] + r)))
    # inductive: non-empty a, first elem is non-list
    else:
      return \
        recur(rest(a), lambda r: \
          then([ f(first(a)) ] + r))
  # loop inner function
  return loop (run)

然后实现一个通用的loop,它将递归调用栈转换为迭代序列-

def recur (*values):
  return (recur, values)

def loop (f):
  acc = f ()
  while type(acc) is tuple and acc[0] is recur:
    acc = f(*acc[1])
  return acc

输出是相同的,但是现在myiter可以接受 any 嵌套限制的数组。递归无限制;多么美丽的东西-

print(myiter(str, [1, [2, [3, 4], 5]]))
# ['1', ['2', ['3', '4'], '5']]

print(myiter(square, [1, [2, [3, 4], 5]]))
# [1, [4, [9, 16], 25]]

查看此程序并使用repl.it在您自己的浏览器中验证结果。


  

为什么不能使用递归? -戳刺

     

@Jab,三个原因:第一个最大递归限制经常在我的一个应用程序上遇到,第二个性能问题虽然映射可能与迭代方法并驾齐驱,其次是为了训练和研究这两种不同的编码风格。 – MarkokraM

因此您尚未达到递归限制,但您担心程序会这样做吗?最好了解实际的限制,而不是围绕幽灵编写程序。在使用生成器的简化实现中,请注意,递归仅在遇到嵌套级别时发生。即使您直接保留此实现,它也可以支持任何长度和嵌套级别的列表,直到堆栈限制为止,默认情况下,该列表可能约为1,000。这意味着唯一会破坏堆栈的数据输入是嵌套1000次或更多的数据输入。在达到 actual 限制之前,可以将该程序保留为安全状态。

def square (x):
  return x * x

def myiter (f, init = []):
  def gen (a):
    for x in a:
      if isinstance(x, list):
        yield list(gen(x)) # recursion
      else:
        yield f(x)
  return list(gen(init))

print(myiter(str, [1, [2, [3, 4], 5]]))
# ['1', ['2', ['3', '4'], '5']]

print(myiter(square, [1, [2, [3, 4], 5]]))
# [1, [4, [9, 16], 25]]