Coursetree Prerequisites

Apr 05 2010 Published by under CourseTree,Programming

While working on the tree part of the coursetree project, I ran into the question of how to display the course dependencies. I have written a recursive function that returns a recursive list of course prerequisites:

[u'SE 112', [u'MATH 135', []]]

I wanted to turn it into a diagram of course prerequisites and decided 2 flat lists could retain the data:

  • a list for the levels
  • a list for connections between courses

For example,

tree
would translate to

[[1],[2,6],[3,5,7],[4]]

and

[(1,2),(2,3),(2,5),(3,4),(1,6),(6,7)]

Separating the Levels

We start off with a recursive list representing the tree structure:

[1,[2,6,[3,5,[4,[]],[]],[7,[]]]]

To tackle the problem, I first solved a similar one: flattening a list.
This can easily be done in Scheme, as syntax and type declarations do not get in the way.

(define (flatten sequence)
  (cond ((null? sequence) '()) ; simplest case: ()
        ((list? (car sequence)) ; a list in front: ((1) ...)
         (append (flatten (car sequence))
                 (flatten (cdr sequence))))
        (else (cons (car sequence) ; an atom in front: (1 ...)
                    (flatten (cdr sequence))))))

> (flatten '(1 (2 3)))
(list 1 2 3)

Here’s the same code translated into Python:

car = lambda lst: lst[0]
cdr = lambda lst: lst[1:]
def flatten(seq):
    if not seq:
        return list()
    elif isinstance(car(seq), list):
        return flatten(car(seq)).extend(flatten(cdr(seq)))
    else:
        return [car(seq), flatten(cdr(seq))]

Unfortunately, flatten in Python produces a hypernested structure for flat lists, as (1 2 3) in Scheme is equibalent to (cons 1 (cons 2 (cons 3 empty))) or (1 . (2 . 3)).

>>> flatten([1,2,3])
[1, [2, [3, []]]]

The more serious problem is that it exposes a quirk in Python:

>>> [1].extend([])
>>>

Yes, that means the list disappears after being extended with an empty list . . . one of those unpleasant surprises.
So let’s redefine a flat list in Python:

def flatten(tree):
        result = []
        for node in tree:
                if isinstance(node, list):
                        result.extend(flatten(node))
                else:
                        result.append(node)
        return result

>>> flatten([1,2,3])
[1,2,3]
>>> flatten([1,[2,3]])
[1,2,3]

There is a similarity between this approach and the recursive one: different actions are taken for a list node and other nodes. I used a combined functional and imperative approach to solve the problem:

car = lambda lst: lst[0]
cdr = lambda lst: lst[1:]

'''
depth: returns the maximum nesting level of a list

Given:
    ls, a list

Result:
    an integer
'''

def depth(ls):
    if not ls:
        return 0
    elif isinstance(car(ls),list):
        return max(depth(car(ls))+1,depth(cdr(ls)))
    else:
        return max(1,depth(cdr(ls)))


'''
strip: returns the list elements of a list

Given:
    ls, a list

Result:
    ls, the modified list
'''

def strip(ls, top):
    if top:
        for item in top:
            if item in ls:
                ls.remove(item)
    elif cdr(ls):
        ls = car(ls) + strip(cdr(ls), top) # case like [[1], ...]
    else:
        ls = car(ls)  # case like [[1]]
    return ls


'''
level: returns the top level elements of a list

Given:
    ls, a list

Result:
    a new list
'''

def level(ls):
    if not ls:
        return []
    elif not isinstance(car(ls),list):
        return [car(ls)] + level(cdr(ls))
    else:
        return level(cdr(ls))

'''
levelize: returns a list of lists, each list is contains the items of a level

Given:
    ls, a list

Result:
    a new list
'''

def levelize(ls):
    result = []
    a = list(ls)
    for i in range(2*depth(ls)):
        if not i%2:
            result.append(level(a))
        a = strip(a, level(a))
    return result

>>> levelize([1,[2,6,[3,5,[4,[]],[]],[7,[]]]])
[[1], [2, 6], [3, 5, 7], [4]]

Connecting the Nodes

We start off with a recursive list representing the tree structure, slightly different from the list for separating the levels:

[1,[2,[3,[4],5],6,[7]]]

Again, a mix of recursion and iteration easily solves the problem:

'''
pair: returns a list of lists, each list has an odd and even pair

Given:
        ls, a list

Result:
        a list
'''

def pair(ls):
    result = []
    while ls:
        result.append(ls[0:2])
        ls = ls[2:]
    return result

'''
connect: returns a list of tuples, each tuple represents an edge of the graph

Given:
        ls, a list

Result:
        a list of tuples
'''

def connect(ls):
    result = []
    if cdr(ls):
        if cdr(cdr(ls)):
            for item in pair(ls):
                result.extend(connect(item))
        else:
            second = car(cdr(ls))
            for item in level(second):
                result.append((car(ls),item))
            result.extend(connect(second))
    return result

>>> connect([1,[2,[3,[4],5],6,[7]]])
[(1, 2), (1, 6), (2, 3), (2, 5), (3, 4), (6, 7)]

Hopefully, you’ve had fun reading this article and meanwhile came up with a better way to represent the tree as a flat structure.

No responses yet

Linux vs Windows

Feb 19 2009 Published by under Linux,Windows

In the beginning was the command line. Now days, the windows command line has become more advanced as the power shell and the Linux command line ix just more versatile. As far as Linux being GNU and free goes, GNU doesn’t stand for great software and free doesn’t mean freedom. Who would want to spend their time looking around on the Internet to get suspend or hibernate working when it already works? Who would want to invest time and effort into a system that is not guaranteed to satisfy all your needs?

From a moral point of view, Linux makes logical sense, it appeals to reason. Whatever marketing Windows does, it cannot beat the simple message Linux delivers, that it is free and by osmosis, the user is free, too. That is obviously a false assumption. Yes, Linux offers the chance, given the choice, to modify source code. But the choice is not there in the first place. I’ve actually never modified source code yet, and as for compiling, I end up wasting more time on it than I can save. For example, Dr Scheme compiled in Gentoo simply hangs where it doesn’t. As for great software, I think Enterprise Linux might be better than Windows due to the bug fixes and support. I’d rather be free as in having free time, free in my non-obligation to the community, and free as in not having a bag of thoughts surrounding how I should use my computer. These days, I have too much clutter in my head.
Here’s a quote that I think is an appropriate description of a proper relationship to the computer: “Few things about your PC are as boring as things that just work, invisible in the background. ” To a large extent, Linux fits the boring category because it just works. When it doesn’t a few minutes on Google will solve it. On the other hand, Microsoft takes the other end. It gives gamers the best graphics, the desktop never crashes, and there’s a GUI to control everything. It doesn’t makes the PC anymore fun (to a large extent, trying to have fun on Windows often meant wasting time), but it won’t crash the OS. Compared to Linux, my fun using it comes largely from experimentation and getting hardware to work. The first part often crashes the desktop and the latter often takes hours to get done what only takes 5 minutes with the limited options in Windows. Having limited options in the OS does save me time making decisions and trying this or that as in Linux.

No responses yet