生成格式良好的嵌套堆栈推送和弹出操作

时间:2017-06-01 01:05:34

标签: python combinatorics

我正在开发一个使用简单堆栈的项目,以及对所述堆栈的操作。

操作包括:

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完成此操作而不生成全部序列的排列和过滤掉无效的序列?

2 个答案:

答案 0 :(得分:1)

好的,我们可以通过递归来做到这一点。

基本情况是没有元素。

如果我们查看可能的模式,我们可以看到所有情况都是以NoOps或Push开始的。

  1. NoOps - >然后是N-1
  2. 的序列
  3. 推。推送是不同的,因为它必须后跟一个pop。但是你注意到你可以注意到唯一的序列是push(x) - 一些序列 - pop(x) - 一些序列。请注意,某些序列可能为空或可能具有N-2个元素
  4. 从这些想法中,我们可以提出以下算法。

    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)