我正在开发一个使用简单堆栈的项目,以及对所述堆栈的操作。
操作包括:
NoOp # Do nothing, leave stack as-is
Push(i) # Push value i onto stack
Pop(i) # Pop top value of stack, raise if popped value != i or stack is empty
我有一个函数接受这些指令的序列,并按顺序执行它们,从空堆栈开始。然后返回最后一个堆栈。在我的例子中,返回空堆栈的序列被认为是格式良好的。
良好形成的序列的例子:
() # The empty sequence
NoOp
Push(x), Pop(x)
Push(x), NoOp, NoOp, NoOp, Pop(x), NoOp # NoOps are OK sprinkled anywhere
Push(x), Push(y), Pop(y), Pop(x) # Nesting is OK
不格式良好的序列的示例:
Push(x) # Does not leave an empty stack
Pop(x) # Attempt to pop from empty stack
Push(x), Push(y), Pop(x), Pop(y) # Improper nesting, Pop(x) will get y and error
出于测试目的,我希望能够为给定的最大长度N生成所有格式良好的指令序列。有没有办法可以使用itertools完成此操作而不生成全部序列的排列和过滤掉无效的序列?
答案 0 :(得分:1)
好的,我们可以通过递归来做到这一点。
基本情况是没有元素。
如果我们查看可能的模式,我们可以看到所有情况都是以NoOps或Push开始的。
从这些想法中,我们可以提出以下算法。
x = 0
def combinations(N):
global x
result = [] # A list of all the combinations of length N
# Base case
if N == 0:
return [""]
# Cases with NoOps followed by some sequence
last_part=combinations(N-1)
for i in last_part:
result.append("NoOp, " + i)
# Cases with Push(x) - some sequence - Pop(x) - some sequence
if N > 1:
for i in range(1, N):
part1 = combinations(i-1)
part2 = combinations(N-i-1)
for j in part1:
for k in part2:
result.append("Push(" + str(x) + "), " + j + "Pop("+ str(x) + "), " + k)
x += 1
return result
# This is just to test. Change N to whatever it needs to be.
result = combinations(4)
for line in result:
print(line)
对于N = 4,它将返回:
NoOp,NoOp,NoOp,NoOp,
NoOp,NoOp,Push(0),Pop(0),
NoOp,Push(1),Pop(1),NoOp,
NoOp,Push(2),NoOp,Pop(2),
Push(4),Pop(4),NoOp,NoOp,
Push(5),Pop(5),Push(3),Pop(3),
推(6),NoOp,Pop(6),NoOp,
推(8),NoOp,NoOp,Pop(8),
按(9),按(7),弹出(7),弹出(9),
编辑 - 获取0 ... N
的所有结果我认为有人试图做出的评论是,您可能不仅要求N返回的结果,而且要求0 ... N.添加以下行:
result += combinations(N-1)
返回声明前的。对于N == 2它将返回:
NoOp,NoOp,
NoOp,
按(0),弹出(0),
NoOp,
“”
答案 1 :(得分:0)
所以,多亏了Stef的回答,我能够构建一个完美运行的生成器方法。
def yield_sum_splits(n: int) -> typ.Iterable[typ.Tuple[int, int]]:
"""Given an integer, returns all possible sum splits.
For any given split (a, b), a + b will always equal n.
For example:
5 -> (0, 5), (1, 4), (2, 3), (3, 2), (4, 1), (5, 0)
-5 -> (0, -5), (-1, -4), (-2, -3), (-3, -2), (-4, -1), (-5, 0)
"""
sign = int(math.copysign(1, n))
an = abs(n)
for i in range(an + 1):
yield (sign * i, sign * (an - i))
def yield_valid_stack_cmd_seqs(length: int) -> typ.Iterable[str]:
"""Lazily yields valid stack command sequences.
Valid stack command sequences are properly nested and balanced.
"""
counter = itertools.count(start=0)
def helper(n: int) -> typ.Iterable[str]:
if n <= 0:
# Exactly one sequence of length 0.
yield ()
return
# Generate sequences of NoOp + Subcombinations(n - 1).
subcombinations = helper(n - 1)
for subcombination in subcombinations:
yield ('NoOp', *subcombination)
# Generate sequences of the form:
# Push(x) + Subcombinations(a) + Pop(x) + Subcombinations(b),
# where a >= 0, b >= 0, and a + b = (length - 2).
if n >= 2:
for a, b in yield_sum_splits(n - 2):
a_sub = helper(a)
b_sub = helper(b)
# Use itertools.product when using yield.
# Nested for loops would omit some outputs, strangely.
for a_part, b_part in itertools.product(a_sub, b_sub):
i = next(counter)
pu_op = f'Push({i})'
po_op = f'Pop({i})'
yield (pu_op, *a_part, po_op, *b_part)
yield from helper(length)