6

I need to print the different variations of printing valid tags "<" and ">" given the number of times the tags should appear and below is the solution in python using recursion.

def genBrackets(c):
    def genBracketsHelper(r,l,currentString):
        if l > r or r == -1 or l == -1:
            return
        if r == l and r == 0:
            print currentString
        genBracketsHelper(r,l-1, currentString + '<')
        genBracketsHelper(r-1,l, currentString + '>')
        return
    genBracketsHelper(c, c, '')

#display options with 4 tags     
genBrackets(4)

I am having a hard time really understanding this and want to try to convert this into a iterative version but I haven't had any success.

As per this thread: Can every recursion be converted into iteration? - it looks like it should be possible and the only exception appears to be the Ackermann function.

If anyone has any tips on how to see the "stack" maintained in Eclipse - that would also be appreciated.

PS. This is not a homework question - I am just trying to understand recursion-to-iteration conversion better.

Edit by Matthieu M. an example of output for better visualization:

>>> genBrackets(3)
<<<>>>
<<><>>
<<>><>
<><<>>
<><><>
Community
  • 1
  • 1
  • Ackermann can be converted into an iterative structure, just like every other recursive function, assuming you are working in a Turing-complete language. – Jeremiah Willcock Feb 23 '11 at 06:44
  • You can achieve such algorithm using dynamic programming. Wouldn't know how to implement it in python, so if you're interested in a C style version, tell me and I'll post some code. All possible combinations of N pair of brackets, is basically al possible binary trees with N nodes. To build all those trees, you first must be able to build all possible trees with 0..N-1 nodes, and then take one of those trees with X nodes (where X is between 0..N-1) and another tree with Y nodes (where Y equals N-1-X) and add them a common root. – Fede Feb 23 '11 at 09:44
  • Thanks to all for the answers - all were very helpful and the hardest part is having to select "one" correct answer. I am going with @tangentstorm's answer since it was the easiest "iterative" version for me to understand. – AlgoLearner Feb 24 '11 at 04:27

4 Answers4

4

I tried to keep basically the same structure as your code, but using an explicit stack rather than function calls to genBracketsHelper:

def genBrackets(c=1):
    # genBracketsStack is a list of tuples, each of which
    # represents the arguments to a call of genBracketsHelper
    # Push the initial call onto the stack:
    genBracketsStack = [(c, c, '')]
    # This loop replaces genBracketsHelper itself
    while genBracketsStack != []:
        # Get the current arguments (now from the stack)
        (r, l, currentString) = genBracketsStack.pop()
        # Basically same logic as before
        if l > r or r == -1 or l == -1:
            continue # Acts like return
        if r == l and r == 0:
            print currentString
        # Recursive calls are now pushes onto the stack
        genBracketsStack.append((r-1,l, currentString + '>'))
        genBracketsStack.append((r,l-1, currentString + '<'))
        # This is kept explicit since you had an explicit return before
        continue

genBrackets(4)

Note that the conversion I am using relies on all of the recursive calls being at the end of the function; the code would be more complicated if that wasn't the case.

Jeremiah Willcock
  • 30,161
  • 7
  • 76
  • 78
  • Excellent, thank you - this definitely helps. I am also curious if there is a method that does not use a stack. – AlgoLearner Feb 23 '11 at 06:44
  • @AlgoLearner: You can probably use a set of global variables to represent the parameters and use a counter to represent the recursion depth, but that would be more complicated and less general to other recursive functions. – Jeremiah Willcock Feb 23 '11 at 06:46
  • Actually, after a push you also need to jump to the 'start'. So two recursive calls one after the other need not translate to two consecutive pushes... –  Feb 23 '11 at 06:53
  • @Moron: If you did that (an immediate jump to the start), you would need to push a continuation to know to run the second call when the first one completes. I was thinking about the thing about not using a stack, and (unless you used something stack-like such as a continuation), you need at least one bit of data per level of recursion (to give the current path down the tree of recursive calls). – Jeremiah Willcock Feb 23 '11 at 06:56
  • @Jeremiah: I was talking about correctness! You translation is incorrect, IMO. I guess the best way to check would be to just run both and compare the output. –  Feb 23 '11 at 06:58
  • @Moron: I did run both. Looking at it again, I got my pushes in the wrong order -- that's fixed now. – Jeremiah Willcock Feb 23 '11 at 07:08
  • All this recursion is making my brain stack overflow. I need to sleep on this. Like @moron said - it is still confusing to me as to how the consecutive calls to the "push" still manage to produce the right result. – AlgoLearner Feb 23 '11 at 07:14
  • @Jere: It _might_ work in this case, but I don't think it works in general (inorder for instance). Anyway, I haven't tried proving/disproving :-) –  Feb 23 '11 at 07:15
  • @Moron: That's true; that's why there is the disclaimer at the bottom of the answer. The technique will not work for the in-order case; you would probably need a continuation or something equivalent to one for that case. – Jeremiah Willcock Feb 23 '11 at 07:17
  • @jer: Ah, I missed the last sentence. +1 then :-) –  Feb 23 '11 at 07:18
  • @Jeremiah, @Moron: actually, after playing at it a bit, I don't find the "in-order" solution to be that complicated. I've posted the code below and though the structure is slightly more complicated (I keep a list of children for each node), the manipulation is very similar I think. – Matthieu M. Feb 23 '11 at 13:45
  • @Matt: Yes (+1), but the general case could still get complicated I suppose. For instance, if the second recursive call used arguments which were generated by the first call. –  Feb 23 '11 at 15:40
  • @Moron: Well, using references to a common variable to pass the state would sidestep the issue I guess. But I agree that recursion allows to express the ideas more neatly, that's for sure! – Matthieu M. Feb 23 '11 at 15:41
  • @Matt: Yeah, I suppose that can work :-) You are right though, the recursive calls can be viewed as a depth first search and the dfs can be done by pushing all the children first. Of course, we can complicate matters further by introducing branching etc... –  Feb 23 '11 at 15:43
  • Thanks @Matt and @Jere for the answers and @Moron for the feedback - tracing through both helped me understand how recursion works better - as well as the "stack" conversion to an iterative solution. I accepted @tangentstorm's answer as the solution although the ones you have both listed work too. Sorry, I couldn’t select them all as the right answer. – AlgoLearner Feb 24 '11 at 04:29
  • @AlgoLearner: no worry, the question is its own reward as far as I am concerned. – Matthieu M. Feb 24 '11 at 07:09
2

You asked about doing this without a stack.

This algorithm walks the entire solution space, so it does a bit more work than the original versions, but it's basically the same concept:

  • each string has a tree of possible suffixes in your grammar
  • since there are only two tokens, it's a binary tree
  • the depth of the tree will always be c*2, so...
  • there must be 2**(c*2) paths through the tree

Since each path is a sequence of binary decisions, the paths correspond to the binary representations of the integers between 0 and 2**(c*2)-1.

So: just loop through those numbers and see if the binary representation corresponds to a balanced string. :)

def isValid(string):
    """
    True if and only if the string is balanced.
    """
    count = { '<': 0, '>':0 }
    for char in string:
        count[char] += 1
        if count['>'] > count['<']:
            return False # premature closure

    if count['<'] != count['>']:
        return False # unbalanced
    else:
        return True


def genBrackets(c):
    """
    Generate every possible combination and test each one.
    """
    for i in range(0, 2**(c*2)):
        candidate = bin(i)[2:].zfill(8).replace('0','<').replace('1','>')
        if isValid(candidate):
            print candidate
tangentstorm
  • 7,183
  • 2
  • 29
  • 38
  • Nice approach - this seems so straightforward - it's kinda sad that it wasn't more obvious (granted I need to lookup the bin and zfill functions) – AlgoLearner Feb 24 '11 at 04:30
1

In general, a recursion creates a Tree of calls, the root being the original call, and the leaves being the calls that do not recurse.

A degenerate case is when a each call only perform one other call, in this case the tree degenerates into a simple list. The transformation into an iteration is then simply achieved by using a stack, as demonstrated by @Jeremiah.

In the more general case, as here, when each call perform more (strictly) than one call. You obtain a real tree, and there are, therefore, several ways to traverse it.

If you use a queue, instead of a stack, you are performing a breadth-first traversal. @Jeremiah presented a traversal for which I know no name. The typical "recursion" order is normally a depth-first traversal.

The main advantage of the typical recursion is that the length of the stack does not grow as much, so you should aim for depth-first in general... if the complexity does not overwhelm you :)

I suggest beginning by writing a depth first traversal of a tree, once this is done adapting it to your algorithm should be fairly simple.

EDIT: Since I had some time, I wrote the Python Tree Traversal, it's the canonical example:

class Node:
  def __init__(self, el, children):
    self.element = el
    self.children = children

  def __repr__(self):
    return 'Node(' + str(self.element) + ', ' + str(self.children) + ')'

def depthFirstRec(node):
  print node.element

  for c in node.children: depthFirstRec(c)

def depthFirstIter(node):
  stack = [([node,], 0), ]

  while stack != []:
    children, index = stack.pop()

    if index >= len(children): continue
    node = children[index]

    print node.element

    stack.append((children, index+1))
    stack.append((node.children, 0))

Note that the stack management is slightly complicated by the need to remember the index of the child we were currently visiting.

And the adaptation of the algorithm following the depth-first order:

def generateBrackets(c):
  # stack is a list of pairs children/index
  stack = [([(c,c,''),], 0), ]

  while stack != []:
    children, index = stack.pop()

    if index >= len(children): continue  # no more child to visit at this level
    stack.append((children, index+1))    # register next child at this level

    l, r, current = children[index]

    if r == 0 and l == 0: print current

    # create the list of children of this node
    # (bypass if we are already unbalanced)
    if l > r: continue

    newChildren = []
    if l != 0: newChildren.append((l-1, r, current + '<'))
    if r != 0: newChildren.append((l, r-1, current + '>'))
    stack.append((newChildren, 0))

I just realized that storing the index is a bit "too" complicated, since I never visit back. The simple solution thus consists in removing the list elements I don't need any longer, treating the list as a queue (in fact, a stack could be sufficient)!

This applies with minimum transformation.

def generateBrackets2(c):
  # stack is a list of queues of children
  stack = [[(c,c,''),], ]

  while stack != []:
    children = stack.pop()

    if children == []: continue  # no more child to visit at this level
    stack.append(children[1:])   # register next child at this level

    l, r, current = children[0]

    if r == 0 and l == 0: print current

    # create the list of children of this node
    # (bypass if we are already unbalanced)
    if l > r: continue

    newChildren = []
    if l != 0: newChildren.append((l-1, r, current + '<'))
    if r != 0: newChildren.append((l, r-1, current + '>'))
    stack.append(newChildren)
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

Yes.

def genBrackets(c):
    stack = [(c, c, '')]
    while stack:
        right, left, currentString = stack.pop()
        if left > right or right == -1 or left == -1:
            pass
        elif right == left and right == 0:
            print currentString
        else:
            stack.append((right, left-1, currentString + '<'))
            stack.append((right-1, left, currentString + '>'))

The output order is different, but the results should be the same.

tangentstorm
  • 7,183
  • 2
  • 29
  • 38
  • Guess I should have refreshed before I posted. :) Looks like my conversion was pretty much the same as Jeremiah's. Fun question! – tangentstorm Feb 23 '11 at 07:04