{"id":274,"date":"2010-04-05T15:11:02","date_gmt":"2010-04-05T15:11:02","guid":{"rendered":"http:\/\/yuguangzhang.com\/blog\/?p=274"},"modified":"2010-04-05T15:11:02","modified_gmt":"2010-04-05T15:11:02","slug":"python-iteration-recursion","status":"publish","type":"post","link":"http:\/\/yuguangzhang.com\/blog\/python-iteration-recursion\/","title":{"rendered":"Coursetree Prerequisites"},"content":{"rendered":"<p>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:<br \/>\n[cc lang=&#8221;python&#8221;][u&#8217;SE 112&#8242;, [u&#8217;MATH 135&#8242;, []]][\/cc]<br \/>\nI wanted to turn it into a diagram of course prerequisites and decided 2 flat lists could retain the data:<\/p>\n<ul>\n<li>a list for the levels<\/li>\n<li>a list for connections between courses<\/li>\n<\/ul>\n<p>For example,<\/p>\n<p><a href=\"http:\/\/yuguangzhang.com\/blog\/wp-content\/uploads\/2010\/04\/tree.PNG\"><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-medium wp-image-275\" title=\"tree\" src=\"http:\/\/yuguangzhang.com\/blog\/wp-content\/uploads\/2010\/04\/tree-300x240.PNG\" alt=\"tree\" width=\"300\" height=\"240\" srcset=\"http:\/\/yuguangzhang.com\/blog\/wp-content\/uploads\/2010\/04\/tree-300x240.PNG 300w, http:\/\/yuguangzhang.com\/blog\/wp-content\/uploads\/2010\/04\/tree.PNG 640w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\nwould translate to<br \/>\n[cc lang=&#8221;python&#8221;][[1],[2,6],[3,5,7],[4]][\/cc]<br \/>\nand<br \/>\n[cc lang=&#8221;python&#8221;][(1,2),(2,3),(2,5),(3,4),(1,6),(6,7)][\/cc]<\/p>\n<h4>Separating the Levels<\/h4>\n<p>\nWe start off with a recursive list representing the tree structure:<br \/>\n[cc lang=&#8221;python&#8221;][1,[2,6,[3,5,[4,[]],[]],[7,[]]]][\/cc]<br \/>\nTo tackle the problem, I first solved a similar one: flattening a list.<br \/>\nThis can easily be done in Scheme, as syntax and type declarations do not get in the way.<br \/>\n[cc lang=&#8221;scheme&#8221;]<br \/>\n(define (flatten sequence)<br \/>\n  (cond ((null? sequence) &#8216;()) ; simplest case: ()<br \/>\n        ((list? (car sequence)) ; a list in front: ((1) &#8230;)<br \/>\n         (append (flatten (car sequence))<br \/>\n                 (flatten (cdr sequence))))<br \/>\n        (else (cons (car sequence) ; an atom in front: (1 &#8230;)<br \/>\n                    (flatten (cdr sequence))))))<\/p>\n<p>> (flatten &#8216;(1 (2 3)))<br \/>\n(list 1 2 3)<br \/>\n[\/cc]<br \/>\nHere&#8217;s the same code translated into Python:<br \/>\n[cc lang=&#8221;python&#8221;]<br \/>\ncar = lambda lst: lst[0]<br \/>\ncdr = lambda lst: lst[1:]<br \/>\ndef flatten(seq):<br \/>\n\tif not seq:<br \/>\n\t\treturn list()<br \/>\n\telif isinstance(car(seq), list):<br \/>\n\t\treturn flatten(car(seq)).extend(flatten(cdr(seq)))<br \/>\n\telse:<br \/>\n\t\treturn [car(seq), flatten(cdr(seq))]<br \/>\n[\/cc]<br \/>\nUnfortunately, 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)).<br \/>\n[cc lang=&#8221;python&#8221;]<br \/>\n>>> flatten([1,2,3])<br \/>\n[1, [2, [3, []]]]<br \/>\n[\/cc]<br \/>\nThe more serious problem is that it exposes a quirk in Python:<br \/>\n[cc lang=&#8221;python&#8221;]<br \/>\n>>> [1].extend([])<br \/>\n>>><br \/>\n[\/cc]<br \/>\nYes, that means the list disappears after being extended with an empty list . . . one of those unpleasant surprises.<br \/>\nSo let&#8217;s redefine a flat list in Python:<br \/>\n[cc lang=&#8221;python&#8221;]<br \/>\ndef flatten(tree):<br \/>\n        result = []<br \/>\n        for node in tree:<br \/>\n                if isinstance(node, list):<br \/>\n                        result.extend(flatten(node))<br \/>\n                else:<br \/>\n                        result.append(node)<br \/>\n        return result<\/p>\n<p>>>> flatten([1,2,3])<br \/>\n[1,2,3]<br \/>\n>>> flatten([1,[2,3]])<br \/>\n[1,2,3]<br \/>\n[\/cc]<br \/>\nThere 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:<br \/>\n[cc lang=&#8221;python&#8221;]<br \/>\ncar = lambda lst: lst[0]<br \/>\ncdr = lambda lst: lst[1:]<\/p>\n<p>&#8221;&#8217;<br \/>\ndepth: returns the maximum nesting level of a list<\/p>\n<p>Given:<br \/>\n    ls, a list<\/p>\n<p>Result:<br \/>\n    an integer<br \/>\n&#8221;&#8217;<br \/>\ndef depth(ls):<br \/>\n    if not ls:<br \/>\n        return 0<br \/>\n    elif isinstance(car(ls),list):<br \/>\n        return max(depth(car(ls))+1,depth(cdr(ls)))<br \/>\n    else:<br \/>\n        return max(1,depth(cdr(ls)))<\/p>\n<p>&#8221;&#8217;<br \/>\nstrip: returns the list elements of a list<\/p>\n<p>Given:<br \/>\n    ls, a list<\/p>\n<p>Result:<br \/>\n    ls, the modified list<br \/>\n&#8221;&#8217;<br \/>\ndef strip(ls, top):<br \/>\n    if top:<br \/>\n        for item in top:<br \/>\n            if item in ls:<br \/>\n                ls.remove(item)<br \/>\n    elif cdr(ls):<br \/>\n        ls = car(ls) + strip(cdr(ls), top) # case like [[1], &#8230;]<br \/>\n    else:<br \/>\n        ls = car(ls)  # case like [[1]]<br \/>\n    return ls<\/p>\n<p>&#8221;&#8217;<br \/>\nlevel: returns the top level elements of a list<\/p>\n<p>Given:<br \/>\n    ls, a list<\/p>\n<p>Result:<br \/>\n    a new list<br \/>\n&#8221;&#8217;<br \/>\ndef level(ls):<br \/>\n    if not ls:<br \/>\n        return []<br \/>\n    elif not isinstance(car(ls),list):<br \/>\n        return [car(ls)] + level(cdr(ls))<br \/>\n    else:<br \/>\n        return level(cdr(ls))<\/p>\n<p>&#8221;&#8217;<br \/>\nlevelize: returns a list of lists, each list is contains the items of a level<\/p>\n<p>Given:<br \/>\n    ls, a list<\/p>\n<p>Result:<br \/>\n    a new list<br \/>\n&#8221;&#8217;<br \/>\ndef levelize(ls):<br \/>\n    result = []<br \/>\n    a = list(ls)<br \/>\n    for i in range(2*depth(ls)):<br \/>\n        if not i%2:<br \/>\n            result.append(level(a))<br \/>\n        a = strip(a, level(a))<br \/>\n    return result<\/p>\n<p>>>> levelize([1,[2,6,[3,5,[4,[]],[]],[7,[]]]])<br \/>\n[[1], [2, 6], [3, 5, 7], [4]]<br \/>\n[\/cc]<\/p>\n<h4>Connecting the Nodes<\/h4>\n<p>\nWe start off with a recursive list representing the tree structure, slightly different from the list for separating the levels:<br \/>\n[cc lang=&#8221;python&#8221;][1,[2,[3,[4],5],6,[7]]][\/cc]<br \/>\nAgain, a mix of recursion and iteration easily solves the problem:<br \/>\n[cc lang=&#8221;python&#8221;]<br \/>\n&#8221;&#8217;<br \/>\npair: returns a list of lists, each list has an odd and even pair<\/p>\n<p>Given:<br \/>\n        ls, a list<\/p>\n<p>Result:<br \/>\n        a list<br \/>\n&#8221;&#8217;<br \/>\ndef pair(ls):<br \/>\n    result = []<br \/>\n    while ls:<br \/>\n        result.append(ls[0:2])<br \/>\n        ls = ls[2:]<br \/>\n    return result<\/p>\n<p>&#8221;&#8217;<br \/>\nconnect: returns a list of tuples, each tuple represents an edge of the graph<\/p>\n<p>Given:<br \/>\n        ls, a list<\/p>\n<p>Result:<br \/>\n        a list of tuples<br \/>\n&#8221;&#8217;<br \/>\ndef connect(ls):<br \/>\n    result = []<br \/>\n    if cdr(ls):<br \/>\n        if cdr(cdr(ls)):<br \/>\n            for item in pair(ls):<br \/>\n                result.extend(connect(item))<br \/>\n        else:<br \/>\n            second = car(cdr(ls))<br \/>\n            for item in level(second):<br \/>\n                result.append((car(ls),item))<br \/>\n            result.extend(connect(second))<br \/>\n    return result<\/p>\n<p>>>> connect([1,[2,[3,[4],5],6,[7]]])<br \/>\n[(1, 2), (1, 6), (2, 3), (2, 5), (3, 4), (6, 7)]<br \/>\n[\/cc]<br \/>\nHopefully, you&#8217;ve had fun reading this article and meanwhile came up with a better way to represent the tree as a flat structure.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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: [cc lang=&#8221;python&#8221;][u&#8217;SE 112&#8242;, [u&#8217;MATH 135&#8242;, []]][\/cc] I wanted to turn it into a diagram of course prerequisites and decided [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","footnotes":""},"categories":[29,23],"tags":[25,28,27,26,22,24],"class_list":["post-274","post","type-post","status-publish","format-standard","hentry","category-coursetree","category-programming","tag-deep-recursion","tag-depth","tag-flatten","tag-iteration","tag-python","tag-scheme"],"aioseo_notices":[],"_links":{"self":[{"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/posts\/274","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/comments?post=274"}],"version-history":[{"count":0,"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/posts\/274\/revisions"}],"wp:attachment":[{"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/media?parent=274"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/categories?post=274"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/yuguangzhang.com\/blog\/wp-json\/wp\/v2\/tags?post=274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}