Filename | |
---|---|

Makefile | |

compile-all.lisp | |

index.html | |

p37.lisp | |

p70.lisp | |

p70b.lisp | |

p70c.lisp | |

p71.lisp | |

p72.lisp | |

p73.lisp | |

p80.lisp | |

rdp.lisp |

diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9567d4a --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +LISP=clisp -ansi -E utf-8 +all: + $(LISP) compile-all.lisp +clean: + rm -f *.lib *.fas *.fasl *.x86f + diff --git a/compile-all.lisp b/compile-all.lisp index 6fac660..e4c9aca 100644 --- a/compile-all.lisp +++ b/compile-all.lisp @@ -53,5 +53,22 @@ "p56.lisp" "p57.lisp" "draw-tree.lisp" "p58.lisp" + "p61.lisp" + "p61a.lisp" + "p62.lisp" + "p62a.lisp" + "p63.lisp" + "p64.lisp" + "p65.lisp" + "p66.lisp" + "p67.lisp" + "p68.lisp" + "p69.lisp" + "p70b.lisp" + "p70c.lisp" + "p70.lisp" + "p71.lisp" + "p72.lisp" + "p73.lisp" )) (load (compile-file src))) diff --git a/index.html b/index.html index 4bf448b..b341026 100644 --- a/index.html +++ b/index.html @@ -194,143 +194,6 @@ P60 (**) Construct height-balanced binary trees with a given number of nodes Find out how many height-balanced trees exist for N = 15. -Multiway Trees - -A multiway tree is composed of a root element and a (possibly empty) set of successors which are multiway trees themselves. A multiway tree is never empty. The set of successor trees is sometimes called a forest. - -[p70] - -In Prolog we represent a multiway tree by a term t(X,F), where X denotes the root node and F denotes the forest of successor trees (a Prolog list). The example tree depicted opposite is therefore represented by the following Prolog -term: - -T = t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])]) - -P70B (*) Check whether a given term represents a multiway tree - Write a predicate istree/1 which succeeds if and only if its argument is a Prolog term representing a multiway tree. - Example: - * istree(t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])). - Yes - -P70C (*) Count the nodes of a multiway tree - Write a predicate nnodes/1 which counts the nodes of a given multiway tree. - Example: - * nnodes(t(a,[t(f,[])]),N). - N = 2 - - Write another version of the predicate that allows for a flow pattern (o,i). - -P70 (**) Tree construction from a node string - [p70] - We suppose that the nodes of a multiway tree contain single characters. In the depth-first order sequence of its nodes, a special character ^ has been inserted whenever, during the tree traversal, the move is a backtrack to the - previous level. - - By this rule, the tree in the figure opposite is represented as: afg^^c^bd^e^^^ - - Define the syntax of the string and write a predicate tree(String,Tree) to construct the Tree when the String is given. Work with atoms (instead of strings). Make your predicate work in both directions. - -P71 (*) Determine the internal path length of a tree - We define the internal path length of a multiway tree as the total sum of the path lengths from the root to all nodes of the tree. By this definition, the tree in the figure of problem P70 has an internal path length of 9. Write a - predicate ipl(Tree,IPL) for the flow pattern (+,-). - -P72 (*) Construct the bottom-up order sequence of the tree nodes - Write a predicate bottom-up(Tree,Seq) which constructs the bottom-up sequence of the nodes of the multiway tree Tree. Seq should be a Prolog list. What happens if you run your predicate backwords? - -P73 (**) Lisp-like tree representation - There is a particular notation for multiway trees in Lisp. Lisp is a prominent functional programming language, which is used primarily for artificial intelligence problems. As such it is one of the main competitors of Prolog. In - Lisp almost everything is a list, just as in Prolog everything is a term. - - The following pictures show how multiway tree structures are represented in Lisp. - [p73] - Note that in the "lispy" notation a node with successors (children) in the tree is always the first element in a list, followed by its children. The "lispy" representation of a multiway tree is a sequence of atoms and parentheses ' - (' and ')', which we shall collectively call "tokens". We can represent this sequence of tokens as a Prolog list; e.g. the lispy expression (a (b c)) could be represented as the Prolog list ['(', a, '(', b, c, ')', ')']. Write a - predicate tree-ltl(T,LTL) which constructs the "lispy token list" LTL if the tree is given as term T in the usual Prolog notation. - - Example: - * tree-ltl(t(a,[t(b,[]),t(c,[])]),LTL). - LTL = ['(', a, '(', b, c, ')', ')'] - - As a second, even more interesting exercise try to rewrite tree-ltl/2 in a way that the inverse conversion is also possible: Given the list LTL, construct the Prolog tree T. Use difference lists. - -Graphs - -A graph is defined as a set of nodes and a set of edges, where each edge is a pair of nodes. - -There are several ways to represent graphs in Prolog. One method is to represent each edge separately as one clause (fact). In this form, the graph depicted below is represented as the following predicate: -[graph1] - -edge(h,g). -edge(k,f). -edge(f,b). -... - -We call this edge-clause form. Obviously, isolated nodes cannot be represented. Another method is to represent the whole graph as one data object. According to the definition of the graph as a pair of two sets (nodes and edges), we may -use the following Prolog term to represent the example graph: - -graph([b,c,d,f,g,h,k],[e(b,c),e(b,f),e(c,f),e(f,k),e(g,h)]) - -We call this graph-term form. Note, that the lists are kept sorted, they are really sets, without duplicated elements. Each edge appears only once in the edge list; i.e. an edge from a node x to another node y is represented as e(x,y), -the term e(y,x) is not present. The graph-term form is our default representation. In SWI-Prolog there are predefined predicates to work with sets. - -A third representation method is to associate with each node the set of nodes that are adjacent to that node. We call this the adjacency-list form. In our example: - -[n(b,[c,f]), n(c,[b,f]), n(d,[]), n(f,[b,c,k]), ...] - -The representations we introduced so far are Prolog terms and therefore well suited for automated processing, but their syntax is not very user-friendly. Typing the terms by hand is cumbersome and error-prone. We can define a more -compact and "human-friendly" notation as follows: A graph is represented by a list of atoms and terms of the type X-Y (i.e. functor '-' and arity 2). The atoms stand for isolated nodes, the X-Y terms describe edges. If an X appears as -an endpoint of an edge, it is automatically defined as a node. Our example could be written as: - -[b-c, f-c, g-h, d, f-b, k-f, h-g] - -We call this the human-friendly form. As the example shows, the list does not have to be sorted and may even contain the same edge multiple times. Notice the isolated node d. (Actually, isolated nodes do not even have to be atoms in -the Prolog sense, they can be compound terms, as in d(3.75,blue) instead of d in the example). - -[graph2] -When the edges are directed we call them arcs. These are represented by ordered pairs. Such a graph is called directed graph. To represent a directed graph, the forms discussed above are slightly modified. The example graph opposite is -represented as follows: - -Arc-clause form - arc(s,u). - arc(u,r). - ... - -Graph-term form - digraph([r,s,t,u,v],[a(s,r),a(s,u),a(u,r),a(u,s),a(v,u)]) - -Adjacency-list form - [n(r,[]),n(s,[r,u]),n(t,[]),n(u,[r]),n(v,[u])] - Note that the adjacency-list does not have the information on whether it is a graph or a digraph. - -Human-friendly form - [s > r, t, u > r, s > u, u > s, v > u] - -Finally, graphs and digraphs may have additional information attached to nodes and edges (arcs). For the nodes, this is no problem, as we can easily replace the single character identifiers with arbitrary compound terms, such as city -('London',4711). On the other hand, for edges we have to extend our notation. Graphs with additional information attached to edges are called labelled graphs. - -[graph3] - -Arc-clause form - arc(m,q,7). - arc(p,q,9). - arc(p,m,5). - -Graph-term form - digraph([k,m,p,q],[a(m,p,7),a(p,m,5),a(p,q,9)]) - -Adjacency-list form - [n(k,[]),n(m,[q/7]),n(p,[m/5,q/9]),n(q,[])] - Notice how the edge information has been packed into a term with functor '/' and arity 2, together with the corresponding node. - -Human-friendly form - [p>q/9, m>q/7, k, p>m/5] - -The notation for labelled graphs can also be used for so-called multi-graphs, where more than one edge (or arc) are allowed between two given nodes. - -P80 (***) Conversions - Write predicates to convert between the different graph representations. With these predicates, all representations are equivalent; i.e. for the following problems you can always pick freely the most convenient form. The reason - this problem is rated (***) is not because it's particularly difficult, but because it's a lot of work to deal with all the special cases. - -P81 (**) Path from one node to another one - Write a predicate path(G,A,B,P) to find an acyclic path P from node A to node b in the graph G. The predicate should return all paths via backtracking. P82 (*) Cycle from a given node Write a predicate cycle(G,A,P) to find a closed path (cycle) P starting at a given node A in the graph G. The predicate should return all cycles via backtracking. diff --git a/p37.lisp b/p37.lisp index 6b1bc24..52ba793 100644 --- a/p37.lisp +++ b/p37.lisp @@ -14,13 +14,14 @@ P37 (**) Calculate Euler's totient function phi(m) (improved). Note that a ** b stands for the b'th power of a. " +;;; https://secure.wikimedia.org/wikipedia/en/wiki/Euler%27s_totient_function#Computing_Euler.27s_function (defun phi (m) ;; (p1 - 1) * p1 ** (m1 - 1) ;; + (p2 - 1) * p2 ** (m2 - 1) ;; + (p3 - 1) * p3 ** (m3 - 1) ;; + ... - (reduce (function +) + (reduce (function *) (mapcar (lambda (item) (destructuring-bind (p-i m-i) item (* (1- p-i) (expt p-i (1- m-i))))) diff --git a/p70.lisp b/p70.lisp new file mode 100644 index 0000000..546cd3e --- /dev/null +++ b/p70.lisp @@ -0,0 +1,89 @@ +#-(and) " + +P70 (**) Tree construction from a node string + + We suppose that the nodes of a multiway tree contain single + characters. In the depth-first order sequence of its nodes, a + special character ^ has been inserted whenever, during the tree + traversal, the move is a backtrack to the previous level. + + By this rule, the tree in the figure opposite is represented as: + afg^^c^bd^e^^^ + + a + /|\ + / | \ + f c b + | / \ + g d e + + Define the syntax of the string and write a predicate + tree(String,Tree) to construct the Tree when the String is + given. Work with atoms (instead of strings). Make your predicate + work in both directions. +" + +(load "p70c") +(load "rdp") + +;; Solution: in lisp, we write two function, to parse and to generate. +;; Again parsing and generating are trivial, using a parser generator: + +(defgrammar multiway-tree-string + :terminals ((label "[^^]")) ; one char, not ^ + :start tree + :rules ((--> tree + (opt node) + :action (if (null $1) + (make-empty-multiway-tree) + $1)) ; it's identity, but make-empty-multiway-tree + ; could be defined otherwise. + (--> node + label (rep node) "^" + :action (make-multiway-tree :label (read-from-string (second $1)) + :children $2)))) + +;; (PARSE-MULTIWAY-TREE-STRING "afg^^c^bd^e^^^") +;; --> #S(MULTIWAY-TREE +;; :LABEL A +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL F +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL G +;; :CHILDREN NIL))) +;; #S(MULTIWAY-TREE +;; :LABEL C +;; :CHILDREN NIL) +;; #S(MULTIWAY-TREE +;; :LABEL B +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL D +;; :CHILDREN NIL) +;; #S(MULTIWAY-TREE +;; :LABEL E +;; :CHILDREN NIL))))) +;; +;; (PARSE-MULTIWAY-TREE-STRING "") +;; --> NIL + + +(defun multiway-tree-from-string (string) + (PARSE-MULTIWAY-TREE-STRING string)) + + +;; and walking the tree: + +(defun multiway-tree-to-string (tree) + (cond ((empty-multiway-tree-p tree) "") + ((non-empty-multiway-tree-p tree) + (format nil "~A~{~A~}^" + (multiway-tree-label tree) + (mapcar (function multiway-tree-to-string) + (multiway-tree-children tree)))) + (t (error "Not a multiway-tree ~S" tree)))) + + +;; (multiway-tree-to-string #S(MULTIWAY-TREE :LABEL A :CHILDREN (#S(MULTIWAY-TREE :LABEL F :CHILDREN (#S(MULTIWAY-TREE :LABEL G :CHILDREN NIL))) #S(MULTIWAY-TREE :LABEL C :CHILDREN NIL) #S(MULTIWAY-TREE :LABEL B :CHILDREN (#S(MULTIWAY-TREE :LABEL D :CHILDREN NIL) #S(MULTIWAY-TREE :LABEL E :CHILDREN NIL)))))) +;; --> "AFG^^C^BD^E^^^" + +;;;; THE END ;;;; diff --git a/p70b.lisp b/p70b.lisp new file mode 100644 index 0000000..169b998 --- /dev/null +++ b/p70b.lisp @@ -0,0 +1,145 @@ +#-(and) " + +Multiway Trees + +A multiway tree is composed of a root element and a (possibly empty) +set of successors which are multiway trees themselves. A multiway tree +is never empty. The set of successor trees is sometimes called a +forest. + +[p70] + +In Prolog we represent a multiway tree by a term t(X,F), where X +denotes the root node and F denotes the forest of successor trees (a +Prolog list). The example tree depicted opposite is therefore +represented by the following Prolog term: + +T = t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])]) + + +" + +;; In lisp we could represent a multiway tree in multiple ways. +;; Let's just abstract it away using defstruct. + + +(defstruct (multiway-tree + (:predicate non-empty-multiway-tree-p)) + + label + children) + +;; Again, if lists are wanted instead of structures, (:type list) can +;; be used; if vectors, then (:type vector). In both cases, if the +;; list or vector must start with the symbol MULTIWAY-TREE, the :named +;; option can be added. + + +(defun make-empty-multiway-tree () + 'nil) +(defun empty-multiway-tree-p (tree) + (null tree)) + +(defun multiway-tree-p (tree) + (or (empty-multiway-tree-p tree) + (non-empty-multiway-tree-p tree))) + + + +#-(and) " + +P70B (*) Check whether a given term represents a multiway tree + + Write a predicate istree/1 which succeeds if and only if its + argument is a Prolog term representing a multiway tree. + + Example: + * istree(t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])])). + Yes +" + +;; Badass solution: + +(defun istree (tree) + (multiway-tree-p tree)) + + +;; In practice, nothing more than the badass solution is needed. For +;; the exercise, we may check that the children are multiway trees +;; too. + +(defun istree (tree) + (cond + ((empty-multiway-tree-p tree) t) + ((non-empty-multiway-tree-p tree) + (every (function istree) (multiway-tree-children tree))))) + + +;; Actually, in presence of circular structures, the above istree may +;; not terminate. Since those exercices are boring, we'll implement +;; an istree that checks for circular structures too: + + +(defun istree (tree) + (let ((nodes (make-hash-table))) + (labels ((multiway-node-p (node) + (cond + ((empty-multiway-tree-p node) t) + ((not (non-empty-multiway-tree-p node)) + (return-from istree (values nil :non-tree node))) ; short circuit exit + ((gethash node nodes) + (return-from istree (values nil :circular node))) ; short circuit exit + (t + (setf (gethash node nodes) t) + (every (function multiway-node-p) (multiway-tree-children node)))))) + (multiway-node-p tree)))) + + +(let* ((child (make-multiway-tree :label 'child)) + (root (make-multiway-tree :label 'root :children (list child)))) + (setf (multiway-tree-children child) (list root)) + (assert (equal (list nil :circular root) (multiple-value-list (istree root))))) + +(let* ((child (make-multiway-tree :label 'child :children '(a b c))) + (root (make-multiway-tree :label 'root :children (list child)))) + (assert (equal '(nil :non-tree a) (multiple-value-list (istree root))))) + +(let* ((child (make-multiway-tree + :label 'child + :children (list (make-multiway-tree :label 'a) + (make-multiway-tree :label 'b) + (make-multiway-tree :label 'c)))) + (root (make-multiway-tree :label 'root :children (list child)))) + (assert (istree root))) + + +;; Notice that CL provides for each structure a printer function +;; producing a readable form of the structure: +;; +;; (let* ((child (make-multiway-tree +;; :label 'child +;; :children (list (make-multiway-tree :label 'a) +;; (make-multiway-tree :label 'b) +;; (make-multiway-tree :label 'c)))) +;; (root (make-multiway-tree :label 'root :children (list child)))) +;; root) +;; --> #S(MULTIWAY-TREE +;; :LABEL ROOT +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL CHILD +;; :CHILDREN (#S(MULTIWAY-TREE :LABEL A :CHILDREN NIL) +;; #S(MULTIWAY-TREE :LABEL B :CHILDREN NIL) +;; #S(MULTIWAY-TREE :LABEL C :CHILDREN NIL))))) +;; +;; +;; +;; So we can also write literal multiway-trees as: +;; +;; #S(multiway-tree :label example :children (#S(multiway-tree :label a) #S(multiway-tree :label b))) +;; --> #S(MULTIWAY-TREE :LABEL EXAMPLE +;; :CHILDREN (#S(MULTIWAY-TREE :LABEL A :CHILDREN NIL) +;; #S(MULTIWAY-TREE :LABEL B :CHILDREN NIL))) + + +;;;; END ;;;; + diff --git a/p70c.lisp b/p70c.lisp new file mode 100644 index 0000000..e679608 --- /dev/null +++ b/p70c.lisp @@ -0,0 +1,142 @@ +#-(and) " + +P70C (*) Count the nodes of a multiway tree + + Write a predicate nnodes/1 which counts the nodes of a given multiway tree. + Example: + * nnodes(t(a,[t(f,[])]),N). + N = 2 + + Write another version of the predicate that allows for a flow pattern (o,i). + +" +(load "p70b") + + +(defun multiway-tree-count-nodes (tree) + (cond + ((empty-multiway-tree-p tree) + 0) + ((non-empty-multiway-tree-p tree) + (+ 1 (reduce (function multiway-tree-count-nodes) + (multiway-tree-children tree)))) + (t + (error "Not a multiway tree: ~S" tree)))) + + + + +;; The other version of the prolog predicate generates all the trees +;; that have the given number of nodes. + + +(defun change (n) + (cons (list n) + (loop + :for i :from 1 :below n + :for subchanges = (change i) + :nconc (mapcar (lambda (subchange) + (cons (- n i) subchange)) + subchanges)))) + +(defun cross-product (sets) + " +SETS is a list of lists. +Returns a list containing each one element taken from each lists in SETS. +" + (cond + ((endp sets) '()) + ((endp (rest sets)) (mapcar (function list) (first sets))) + (t (mapcan (lambda (crosses) + (mapcan (lambda (item) + (list (cons item crosses))) + (first sets))) + (cross-product (rest sets)))))) + +;; (cross-product '()) +;; (cross-product '((a1 a2) (b1 b2))) +;; (cross-product '((a1 a2) (b1 b2 b3) (c1 c2))) + + +;; Notice that we consider that the order of the children matters, +;; but the identity of the children does not. +;; +;; So a node with two children, the first of 2 nodes, and the other of +;; 1 node, will be different from a node with two children, the first +;; of 1 node and the other of 2 nodes. + +(defun generate-multiway-trees-with-nodes (node-count next-label) + "Return a list of multiway-trees with NODE-COUNT nodes." + (case node-count + ((0) (list (make-empty-multiway-tree))) + ((1) (list (make-multiway-tree :label (funcall next-label)))) + (otherwise + (loop + :with subtrees = (coerce + (loop + :for remaining-count :below node-count + :collect (generate-multiway-trees-with-nodes remaining-count next-label)) + 'vector) + :for change :in (change (1- node-count)) + :nconc (mapcar (lambda (children) + (make-multiway-tree + :label (funcall next-label) + :children children)) + (cross-product (mapcar (lambda (children-count) (aref subtrees children-count)) + change))))))) + + +;; (generate-multiway-trees-with-nodes 4 (let ((n 0)) (lambda () (incf n)))) +;; --> +;; (#S(MULTIWAY-TREE +;; :LABEL 9 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 7 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 6 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 5 +;; :CHILDREN NIL))))))) +;; #S(MULTIWAY-TREE +;; :LABEL 10 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 8 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 4 +;; :CHILDREN NIL) +;; #S(MULTIWAY-TREE +;; :LABEL 4 +;; :CHILDREN NIL))))) +;; #S(MULTIWAY-TREE +;; :LABEL 11 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 3 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 2 +;; :CHILDREN NIL))) +;; #S(MULTIWAY-TREE +;; :LABEL 1 +;; :CHILDREN NIL))) +;; #S(MULTIWAY-TREE +;; :LABEL 12 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 1 +;; :CHILDREN NIL) +;; #S(MULTIWAY-TREE +;; :LABEL 3 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 2 +;; :CHILDREN NIL))))) +;; #S(MULTIWAY-TREE +;; :LABEL 13 +;; :CHILDREN (#S(MULTIWAY-TREE +;; :LABEL 1 +;; :CHILDREN NIL) +;; #S(MULTIWAY-TREE +;; :LABEL 1 +;; :CHILDREN NIL) +;; #S(MULTIWAY-TREE +;; :LABEL 1 +;; :CHILDREN NIL)))) + +;;;; THE END ;;;; diff --git a/p71.lisp b/p71.lisp new file mode 100644 index 0000000..6416477 --- /dev/null +++ b/p71.lisp @@ -0,0 +1,30 @@ +#-(and) " + +P71 (*) Determine the internal path length of a tree + + We define the internal path length of a multiway tree as the total + sum of the path lengths from the root to all nodes of the tree. By + this definition, the tree in the figure of problem P70 has an + internal path length of 9. Write a predicate ipl(Tree,IPL) for the + flow pattern (+,-). + +" + +;; A simple direct recursive solution: + +(defun multiway-tree-total-path-length (tree so-far) + " +SO-FAR is the length of path from the root to TREE. +Returns the total length of path from the root to each nodes of TREE. +" + (reduce (function +) + (multiway-tree-children tree) + :key (lambda (node) (multiway-tree-total-path-length node (1+ so-far))) + :initial-value so-far)) + +(defun ipl (tree) + (multiway-tree-total-path-length tree 0)) + +(assert (= 9 (ipl (parse-multiway-tree-string "AFG^^C^BD^E^^^")))) + +;;;; THE END ;;;; diff --git a/p72.lisp b/p72.lisp new file mode 100644 index 0000000..7698d9d --- /dev/null +++ b/p72.lisp @@ -0,0 +1,39 @@ +#-(and) " + +P72 (*) Construct the bottom-up order sequence of the tree nodes + + Write a predicate bottom-up(Tree,Seq) which constructs the + bottom-up sequence of the nodes of the multiway tree Tree. Seq + should be a Prolog list. What happens if you run your predicate + backwords? +" + +;; "Bottom-up order sequence of tree nodes" is an idiosyncrasy +;; (google for it, there's no definition!). +;; Perhaps it means postfix order. +;; Right, it's the postfix order. The prolog solution gives: +;; ?- bottom_up(t(a,[t(f,[t(g,[])]),t(c,[]),t(b,[t(d,[]),t(e,[])])]),L). +;; L = [g, f, c, d, e, b, a]. + + +(defun multiway-tree-postfix-order (tree) + " +Returns a list of node labels in the postfix order. +" + (reduce (function nconc) + (multiway-tree-children tree) + :key (function multiway-tree-postfix-order) + :initial-value (list (multiway-tree-label tree)) + :from-end t)) +;; :from-end is needed so that the initial-value is placed on the right. +;; It also proves beneficial since then the lists are walked only once per level. + +;; (multiway-tree-postfix-order (parse-multiway-tree-string "AFG^^C^BD^E^^^")) +;; --> (G F C D E B A) + + +(defun bottom-up (tree) + (multiway-tree-postfix-order tree)) + + +;;;; THE END ;;;; diff --git a/p73.lisp b/p73.lisp new file mode 100644 index 0000000..e76c9ea --- /dev/null +++ b/p73.lisp @@ -0,0 +1,249 @@ +#-(and) " + +P73 (**) Lisp-like tree representation + + There is a particular notation for multiway trees in Lisp. Lisp is + a prominent functional programming language, which is used + primarily for artificial intelligence problems. As such it is one + of the main competitors of Prolog. In Lisp almost everything is a + list, just as in Prolog everything is a term. + + The following pictures show how multiway tree structures are + represented in Lisp. + + Note that in the \"lispy\" notation a node with successors (children) + in the tree is always the first element in a list, followed by its + children. The \"lispy\" representation of a multiway tree is a + sequence of atoms and parentheses ' (' and ')', which we shall + collectively call \"tokens\". We can represent this sequence of + tokens as a Prolog list; e.g. the lispy expression (a (b c)) could + be represented as the Prolog list ['(', a, '(', b, c, ')', + ')']. Write a predicate tree-ltl(T,LTL) which constructs the + \"lispy token list\" LTL if the tree is given as term T in the usual + Prolog notation. + + Example: + * tree-ltl(t(a,[t(b,[]),t(c,[])]),LTL). + LTL = ['(', a, '(', b, c, ')', ')'] + + As a second, even more interesting exercise try to rewrite + tree-ltl/2 in a way that the inverse conversion is also possible: + Given the list LTL, construct the Prolog tree T. Use difference + lists. + +" +(load "rdp") +(use-package :com.informatimago.rdp) +(load "p70") + +" +Again there are several problem with the problem statement. + +In lisp, there are no parentheses. Only cons cells and atoms. The +lisp expression (a (b c)) doesn't represent a sequence of symbols and +parentheses, but a structure made of cons cells and symbols: + + +-----------------------------------+ + | (a (b c)) | + | | + | +---+---+ +---+---+ | + | | * | * |-->| * |NIL| | + | +---+---+ +---+---+ | + | | | | + | v v | + | +---+ +---+---+ +---+---+ | + | | A | | * | * |-->| * |NIL| | + | +---+ +---+---+ +---+---+ | + | | | | + | v v | + | +---+ +---+ | + | | B | | C | | + | +---+ +---+ | + +-----------------------------------+ + + +The correct representation of such a structure in prolog would be: + + [a,[b,c]] + +and not the proposed: + + ['(', a, '(', b, c, ')', ')'] + +A textual representation of that structure would be a STRING, not a +list of characters: + + \"(a (b c))\" + +we can build a list of characters as an intermediate representation of +the string, but it is not too useful. It would not be done usually in +lisp programs. + +On the other hand, when writing a parser, it would be possible to +separate the lexer from the parser, having the lexer generate a list +of tokens to be passed to the parser. + + + +Notice also that if the problem was to produce the multiway-tree as a +sexp where each node is represented by a list containing the label as +first element, and a sublist containing the children as second +element, then we would just have to give the (:type list) option to +the defstruct to have it represent the trees that way! But the syntax +defined above specifies the irregularty that leaf nodes are +represented by the mere label of the leaf, instead of a list with the +label and an empty list of children. + +" + +;; Badass solution: + +(defstruct (multiway-tree + (:type list)) + label + children) + +;; (parse-multiway-tree-string "AFG^^C^BD^E^^^") +;; --> (A ((F ((G NIL))) (C NIL) (B ((D NIL) (E NIL))))) + + + +;; Let's generate the lisp sexp with the leaves reprensted by their +;; labels. This doesn't need that the multiway trees be represented a +;; lists, since we keep using the functional abstraction. + +(defun process-leaves (tree) + (cond + ((empty-multiway-tree-p tree) tree) + ((endp (multiway-tree-children tree)) (multiway-tree-label tree)) + (t (cons (multiway-tree-label tree) + (mapcar (function process-leaves) + (multiway-tree-children tree)))))) + +(assert (equal (process-leaves (parse-multiway-tree-string "AFG^^C^BD^E^^^")) + '(A (F G) C (B D E)))) + +;; Badass solution, using lisp sexps to generate first the string, +;; then the wanted list: + + +(defun my-prin1-to-string (object) + (let ((*print-circle* nil) + (*print-case* :upcase) + (*print-readably* nil) + (*print-pretty* nil) + (*print-base* 10.) + (*print-radix* nil) + (*print-level* nil) + (*print-length* nil) + (*print-lines* nil)) + (prin1-to-string object))) + +(defun tree-ltl (tree) + ;; How unfunny is that! + (coerce (remove #\space (my-prin1-to-string (process-leaves tree))) 'list)) + + +(assert (equal (tree-ltl (parse-multiway-tree-string "AFG^^C^BD^E^^^")) + '(#\( #\A #\( #\F #\G #\) #\C #\( #\B #\D #\E #\) #\)))) + + +;; We could also make the non-parenthesis characters back into symbols: + +(defun tree-ltl (tree) + (map 'list + (lambda (ch) + (if (alphanumericp ch) + (intern (string ch)) + ch)) + (remove #\space (my-prin1-to-string (process-leaves tree))))) + +(assert (equal (tree-ltl (parse-multiway-tree-string "AFG^^C^BD^E^^^")) + '(#\( A #\( F G #\) C #\( B D E #\) #\)))) + + +;; Finally we could also repeat again and again the same tree walking +;; and generation of the list: + + +(defun tree-ltl (tree) + (cond + ((empty-multiway-tree-p tree) "") ; should occur only when root is empty. + ((endp (multiway-tree-children tree)) + (list (multiway-tree-label tree))) + (t (nconc + (list #\( (multiway-tree-label tree)) + (mapcan (function tree-ltl) + (multiway-tree-children tree)) + (list #\)))))) + + +(assert (equal (tree-ltl (parse-multiway-tree-string "AFG^^C^BD^E^^^")) + '(#\( A #\( F G #\) C #\( B D E #\) #\)))) + + +;; Now, for the inverse function, parsing the list could be done +;; directly; we'd have to provide a pseudo-lexer to replace the lexer +;; that scans source strings. For a simple solution we will just +;; convert the list into a string and let the generated scanner do its +;; work: + + +(defgrammar multiway-tree-parenthesized-string + :terminals ((label "[^^]")) ; one char, not ^ + :start tree + :rules ((--> tree + (opt (alt par-node leaf)) + :action (if (null $1) + (make-empty-multiway-tree) + $1)) + ; it's identity, but make-empty-multiway-tree + ; could be defined otherwise. + (--> par-node + "(" label (rep tree) ")" + :action (make-multiway-tree :label (read-from-string (second $2)) + :children $3)) + (--> leaf + label + :action (make-multiway-tree :label (read-from-string (second $1)))))) + +(defun ltl-tree (ltl) + (parse-multiway-tree-parenthesized-string + (format nil "~{~A~}" ltl))) + +(assert (equal (ltl-tree '(#\( A #\( F G #\) C #\( B D E #\) #\))) + (parse-multiway-tree-string "AFG^^C^BD^E^^^"))) + + + + +;; Another solution would be to use the lisp reader, since the list +;; should contain well balanced parentheses, and then convert the +;; obtained sexp into a tree. Notice how much simplier this is, to +;; process simple grammar sufficiently similar to sexps: there's no +;; need to involve the complexities of a parser generator. + +(defun multiway-tree-from-sexp (node) + (cond + ((null node) (make-empty-multiway-tree)) + ((atom node) (make-multiway-tree :label node)) ; a leaf + (t (destructuring-bind (label &rest children) node + (make-multiway-tree :label label + :children (mapcar (function multiway-tree-from-sexp) + children)))))) + +(defun ltl-tree (ltl) + (multiway-tree-from-sexp (let ((*read-eval* nil) + (*read-base* 10.) + (*readtable* (copy-readtable nil))) + (read-from-string (format nil "~{ ~A ~}" ltl))) + ;; we get a list such as (A (F G) C (B D E)) + )) + +(assert (equal (ltl-tree '(#\( A #\( F G #\) C #\( B D E #\) #\))) + (parse-multiway-tree-string "AFG^^C^BD^E^^^"))) + + +;;;; THE END ;;;; + + diff --git a/p80.lisp b/p80.lisp new file mode 100644 index 0000000..6bfc8d7 --- /dev/null +++ b/p80.lisp @@ -0,0 +1,595 @@ +#-(and) " + +Graphs + +A graph is defined as a set of nodes and a set of edges, where each +edge is a pair of nodes. + +There are several ways to represent graphs in Prolog. One method is to +represent each edge separately as one clause (fact). In this form, the +graph depicted below is represented as the following predicate: + +[graph1] + +edge(h,g). +edge(k,f). +edge(f,b). +... + +We call this edge-clause form. Obviously, isolated nodes cannot be +represented. Another method is to represent the whole graph as one +data object. According to the definition of the graph as a pair of two +sets (nodes and edges), we may use the following Prolog term to +represent the example graph: + +graph([b,c,d,f,g,h,k],[e(b,c),e(b,f),e(c,f),e(f,k),e(g,h)]) + +We call this graph-term form. Note, that the lists are kept sorted, +they are really sets, without duplicated elements. Each edge appears +only once in the edge list; i.e. an edge from a node x to another node +y is represented as e(x,y), the term e(y,x) is not present. The +graph-term form is our default representation. In SWI-Prolog there are +predefined predicates to work with sets. + +A third representation method is to associate with each node the set +of nodes that are adjacent to that node. We call this the +adjacency-list form. In our example: + +[n(b,[c,f]), n(c,[b,f]), n(d,[]), n(f,[b,c,k]), ...] + +The representations we introduced so far are Prolog terms and +therefore well suited for automated processing, but their syntax is +not very user-friendly. Typing the terms by hand is cumbersome and +error-prone. We can define a more compact and \"human-friendly\" +notation as follows: A graph is represented by a list of atoms and +terms of the type X-Y (i.e. functor '-' and arity 2). The atoms stand +for isolated nodes, the X-Y terms describe edges. If an X appears as +an endpoint of an edge, it is automatically defined as a node. Our +example could be written as: + +[b-c, f-c, g-h, d, f-b, k-f, h-g] + +We call this the human-friendly form. As the example shows, the list +does not have to be sorted and may even contain the same edge multiple +times. Notice the isolated node d. (Actually, isolated nodes do not +even have to be atoms in the Prolog sense, they can be compound terms, +as in d(3.75,blue) instead of d in the example). + +[graph2] + +When the edges are directed we call them arcs. These are represented +by ordered pairs. Such a graph is called directed graph. To represent +a directed graph, the forms discussed above are slightly modified. The +example graph opposite is represented as follows: + +Arc-clause form + arc(s,u). + arc(u,r). + ... + +Graph-term form + digraph([r,s,t,u,v],[a(s,r),a(s,u),a(u,r),a(u,s),a(v,u)]) + +Adjacency-list form + [n(r,[]),n(s,[r,u]),n(t,[]),n(u,[r]),n(v,[u])] + + Note that the adjacency-list does not have the information on + whether it is a graph or a digraph. + +Human-friendly form + + [s > r, t, u > r, s > u, u > s, v > u] + +Finally, graphs and digraphs may have additional information attached +to nodes and edges (arcs). For the nodes, this is no problem, as we +can easily replace the single character identifiers with arbitrary +compound terms, such as city ('London',4711). On the other hand, for +edges we have to extend our notation. Graphs with additional +information attached to edges are called labelled graphs. + +[graph3] + +Arc-clause form + arc(m,q,7). + arc(p,q,9). + arc(p,m,5). + +Graph-term form + digraph([k,m,p,q],[a(m,p,7),a(p,m,5),a(p,q,9)]) + +Adjacency-list form + [n(k,[]),n(m,[q/7]),n(p,[m/5,q/9]),n(q,[])] + + Notice how the edge information has been packed into a term with + functor '/' and arity 2, together with the corresponding node. + +Human-friendly form + [p>q/9, m>q/7, k, p>m/5] + +The notation for labelled graphs can also be used for so-called +multi-graphs, where more than one edge (or arc) are allowed between +two given nodes. +" + + + +#-(and) " + +P80 (***) Conversions + + Write predicates to convert between the different graph + representations. With these predicates, all representations are + equivalent; i.e. for the following problems you can always pick + freely the most convenient form. The reason this problem is rated + (***) is not because it's particularly difficult, but because it's + a lot of work to deal with all the special cases. + +" + + + +#-(and) " + +A similar set of representations are possible in lisp too. As always, +hiding the representation behind an abstraction will allow to +implement generic algorithms, and to change the representation at +will. We may even design an abstraction allowing to change the +representation on the fly, eg. between two phases of a processing, to +provide better algorithmic complexities. + +To easily write and print graphs, we'll use a s-exp which must be a +list containing either isolated nodes (non-cons objects), or lists of +two or more elements (from-node to-node [:key value ...]) representing +each edge or arc. + +We could easily write a function to map more sugary sexp syntax to +this form, or even a reader macro parsing a syntax with all the +intricacy wanted, but it's hardly worth the pain. + +We'll also allow property lists to store any kind of attributes to the +arcs or edges. +" + + +;;; Graph classes + +(defclass graph () + ((representation :initarg :representation + :documentation "The actual representation of the graph.")) + (:documentation " +This abstract class represents a graph, and is the superclass of a +directed-graph and undirected-graph that can be represented in several +ways. +")) + +(defmethod print-object ((self graph) stream) + (print-unreadable-object (self stream :identity t :type t) + (let ((rep-name (class-name (class-of (slot-value self 'representation))))) + (format stream "as a~:[~;n~] ~A" (find (char (string rep-name) 0) "AEIOUY") rep-name)) + (format stream " with ~A node~:*~P and ~A edge~:*~P" + (length (nodes self)) (length (edges self)))) + self) + +(defclass undirected-graph (graph) + () + (:documentation " +Undirected graphs can have only representations with edges. +")) + + +(defclass attributes () + ((property-list :initform '() + :accessor property-list :initarg :property-list + :accessor properties :initarg :properties))) + + +(defclass edge (attributes) + ((nodes :accessor edge-nodes :initarg :nodes)) + (:documentation " +An undirected edge. The order of the two nodes in the edge-nodes list +is irrelevant. +")) + +(defgeneric edges-with-node (graph node) + (:documentation "Returns a list of the edges in GRAPH associating the given NODE.") + (:method ((g graph) node) (edges-with-node (slot-value g 'representation) node))) + + + +(defclass directed-graph (graph) + () + (:documentation " +Undirected graphs can have only representations with arcs. +")) + +(defclass arc (attributes) + ((from :accessor arc-from :initarg :from) + (to :accessor arc-to :initarg :to)) + (:documentation " +A directed arc, from the FROM node to the TO node. +Note: the API allow for unidrected +")) + + +(defgeneric arcs-from-node (graph node) + (:documentation "Returns a list of the arcs in GRAPH from the NODE. +\(the adjacency list).") + (:method ((g graph) node) (arcs-from-node (slot-value g 'representation) node))) + +(defgeneric arcs-to-node (graph node) + (:documentation "Returns a list of the arcs in GRAPH to the NODE.") + (:method ((g graph) node) (arcs-to-node (slot-value g 'representation) node))) + + + + + + + +(defclass graph-representation () + () + (:documentation "An abstract graph representation.")) + +(defclass undirected-graph-representation () + () + (:documentation "An abstract undirected graph representation.")) + +(defclass directed-graph-representation () + () + (:documentation "An abstract directed graph representation.")) + + + +;;; Generic functions +;; We define here the fundamental operations for graph +;; representations, as generic functions. A method is defined on +;; graph, that just forwards the call to the graph representation. + +(defgeneric nodes (gr) + (:documentation "Returns the list of nodes in the graph or graph representation") + (:method ((g graph)) (nodes (slot-value g 'representation)))) + +(defgeneric add-node (gr node) + (:documentation "Adds a new node to the graph or graph representation. +Return NODE.") + (:method ((g graph) node) (add-node (slot-value g 'representation) node))) + +(defgeneric remove-node (gr node) + (:documentation " +If NODE is a node of the graph or graph representation, then remove +it, as well as all arcs connecting it. Return NODE.") + (:method ((g graph) node) (remove-node (slot-value g 'representation) node))) + + + +(defgeneric edges (gr) + (:documentation " +Returns the list of edges in the undirected graph or graph +representation.") + (:method ((g undirected-graph)) (edges (slot-value g 'representation)))) + +(defgeneric add-edge-between-nodes (gr from to &key &allow-other-keys) + ;; Notice we leave ADD-EDGE to name a generic function of two arguments: (gr edge) + ;; Optional key arguments may be defined for additionnal + ;; initializers for edges (such as weights, etc). + (:documentation " +Adds a new edge the graph or graph representation, between the FROM +and the TO node. If the graph or graph representation is undirected, +then two arcs are added, from FROM to TO and from TO to FROM. If +either FROM or TO is not a node of GR, then it's added before. +Return the new EDGE.") + (:method ((g undirected-graph) from to &rest args &key &allow-other-keys) + (apply (function add-edge-between-nodes) (slot-value g 'representation) from to args))) + +(defgeneric remove-edge (gr edge) + (:documentation " +If EDGE is an edge of the graph or graph representation,then remove it. +Return EDGE.") + (:method ((g undirected-graph) edge) (remove-edge (slot-value g 'representation) edge))) + + + + +(defgeneric arcs (gr) + (:documentation "Returns the list of arcs in the graph or graph representation. +If the graph or graph representation is undirected, then each edge produces two arcs.") + (:method ((g directed-graph)) + (arcs (slot-value g 'representation))) + (:method ((g undirected-graph)) + (mapcan (lambda (edge) + (destructuring-bind (left right) (edge-nodes edge) + (list (make-instance 'arc :from left :to right :properties (properties edge)) + (make-instance 'arc :from right :to left :properties (properties edge))))) + (edges (slot-value g 'representation))))) + +(defgeneric add-arc-between-nodes (gr from to &key &allow-other-keys) + ;; Notice we leave ADD-ARC to name a generic function of two arguments: (gr arc) + ;; Optional key arguments may be defined for additionnal + ;; initializers for arcs (such as weights, etc). + (:documentation " +Adds a new arc the graph or graph representation, between the FROM +and the TO node. If either FROM or TO is not a node of GR, +then it's added before. Return the new ARC.") + (:method ((g directed-graph) from to &rest args &key &allow-other-keys) + (apply (function add-arc-between-nodes) (slot-value g 'representation) from to args))) + +(defgeneric remove-arc (gr arc) + (:documentation " +If ARC is an arc of the graph or graph representation,then remove it. +The nodes are not changed. Return ARC.") + (:method ((g directed-graph) arc) (remove-arc (slot-value g 'representation) arc))) + + +(defgeneric to-sexp (object) + (:documentation " +Returns a sexp representing the graph. +The sexp should be accepted by the method FROM-SEXP +of the same graph class. +")) + +(defgeneric from-sexp (object sexp) + (:documentation " +Replaces the graph nodes and edges with the data from the given SEXP. +Returns GR. +")) + + + + +(defun nodes-and-links-to-sexp (nodes links) + (flet ((nodes-from-links (links) + (mapcan (lambda (link) (list (first link) (second link))) links))) + (append (set-difference nodes (nodes-from-links links)) links))) + +(defmethod to-sexp ((self edge)) + (concatenate 'list (edge-nodes self) (properties self))) + +(defmethod to-sexp ((self arc)) + (concatenate 'list (list (arc-from self) (arc-to self)) (properties self))) + +(defmethod to-sexp ((g directed-graph)) + (nodes-and-links-to-sexp (nodes g) (mapcar (function to-sexp) (arcs g)))) + +(defmethod to-sexp ((g undirected-graph)) + (nodes-and-links-to-sexp (nodes g) (mapcar (function to-sexp) (edges g)))) + + + +(defmethod clear-representation ((g graph)) + (setf (slot-value g 'representation) (make-instance (class-of (slot-value g 'representation))))) + +(defmethod parse-graph-sexp ((g graph) sexp add-link) + (let ((rep (clear-representation g))) + (loop + :for item :in sexp + :do (if (consp item) + (apply add-link rep item) + (add-node rep item)) + :finally (return rep)))) + +(defmethod from-sexp ((g undirected-graph) sexp) + (setf (slot-value g 'representation) + (parse-graph-sexp g sexp (function add-edge-between-nodes))) + g) + +(defmethod from-sexp ((g directed-graph) sexp) + (setf (slot-value g 'representation) + (parse-graph-sexp g sexp (function add-arc-between-nodes))) + g) + + + +;; We'd want to +;; (define-modify-macro deletef (element list) delete) +;; but the order of the argument is not consistent. + +(defmacro deletef (item sequence-place &rest args &key key test test-not) + (declare (ignore key test test-not)) + (multiple-value-bind (vars vals store-vars writer-form reader-form) + (get-setf-expansion sequence-place) + (when (cdr store-vars) + (error "Cannot DELETE from a place with multiple values.")) + `(let* (,@(mapcar (function list) vars vals) + (,(car store-vars) ,reader-form)) + (setf ,(car store-vars) (delete ,item ,(car store-vars) ,@args)) + ,writer-form))) + + + + +;;; Edge list representation +;;; In this representation we only keep a list of links. + +(defclass edge-list-representation (undirected-graph-representation) + ((edges :accessor edges :initarg :edges :initform '()))) + +(defmethod add-edge-between-nodes ((gr edge-list-representation) from to &rest properties &key &allow-other-keys) + (let ((edge (make-instance 'edge + :nodes (list from to) + :properties properties))) + (push edge (edges gr)) + edge)) + +(defmethod remove-edge ((gr edge-list-representation) edge) + (deletef edge (edges gr)) + edge) + +(defmethod nodes ((gr edge-list-representation)) + (delete-duplicates (loop + :for edge :in (edges gr) + :for nodes = (edge-nodes edge) + :collect (first nodes) :collect (second nodes)))) + +(defmethod add-node ((gr edge-list-representation) node) + (declare (ignore gr node)) + (error "Cannot add isolated nodes to a graph represented by a list of edges.")) + +(defmethod remove-node ((gr edge-list-representation) node) + (setf (edges gr) (delete-if (lambda (edge) (member node (edge-nodes edge))) (edges gr))) + node) + +(defmethod edges-with-node ((gr edge-list-representation) node) + (remove-if-not (lambda (edge) (member node (edge-nodes edge))) (edges gr))) + + +;;; Edge list and nodes representation +;;; In this representation in addition to the list of edge, we +;;; maintain a list of nodes, so we may have isolated nodes too. + +(defclass edge-and-node-list-representation (edge-list-representation) + ((nodes :accessor nodes :initarg :nodes :initform '()))) + +(defmethod add-edge-between-nodes ((gr edge-and-node-list-representation) from to &key &allow-other-keys) + (add-node gr from) + (add-node gr to) + (call-next-method)) + +(defmethod add-node ((gr edge-and-node-list-representation) node) + (pushnew node (nodes gr)) + node) + +(defmethod remove-node ((gr edge-and-node-list-representation) node) + (remove node (nodes gr)) + (call-next-method)) + + + + +;;; adjacency list representation +;;; In this representation, we have a hash-table mapping from nodes to +;;; lists of attributed links to nodes. This allow for directed graphs. +;;; Notice that each node is present in the hash-table as a key, so +;;; isolated nodes are easily represented. + +(defclass link (attributes) + ((node :accessor link-node :initarg :node))) + +(defclass adjacency-list-representation (directed-graph-representation) + ((adjacency-list :initform (make-hash-table) + :reader adjacency-list))) + + +(defmethod nodes ((gr adjacency-list-representation)) + (let ((nodes '())) + (maphash (lambda (from adjacents) + (declare (ignore adjacents)) + (push from nodes)) + (adjacency-list gr)) + nodes)) + +(defmethod add-node ((gr adjacency-list-representation) node) + (unless (gethash node (adjacency-list gr)) + (setf (gethash node (adjacency-list gr)) '())) + node) + +(defmethod remove-node ((gr adjacency-list-representation) node) + (let ((al (adjacency-list gr))) + (when (remhash node al) + (maphash (lambda (from adjacents) + ;; I assume it's faster to call (setf gethash) + ;; than to call member or find. + (setf (gethash from al) (delete node adjacents :key (function link-node)))) + al))) + node) + + +(defun make-adjacency-list-arcs-from (from) + (lambda (to) + (make-instance 'arc + :from from + :to (link-node to) + :properties (copy-list (properties to))))) + + +(defmethod arcs ((gr adjacency-list-representation)) + (let ((arcs '())) + (maphash (lambda (from adjacents) + (setf arcs (nconc (mapcar (make-adjacency-list-arcs-from from) + adjacents) arcs))) + (adjacency-list gr)) + arcs)) + +(defmethod add-arc-between-nodes ((gr adjacency-list-representation) from to &rest properties &key &allow-other-keys) + (add-node gr from) + (add-node gr to) + (pushnew (make-instance 'link :node to :properties properties) (gethash from (adjacency-list gr))) + (make-instance 'arc + :from from + :to to + :properties (copy-list properties))) + +(defmethod remove-arc ((gr adjacency-list-representation) arc) + (deletef arc (gethash (arc-from arc) (adjacency-list gr))) + arc) + +(defmethod arcs-from-node ((gr adjacency-list-representation) from) + (mapcar (make-adjacency-list-arcs-from from) + (gethash from (adjacency-list gr)))) + +(defmethod arcs-to-node ((gr adjacency-list-representation) to) + (let ((arcs '())) + (maphash (lambda (from adjacents) + (when (member to adjacents) + (setf arcs (nconc (funcall (make-adjacency-list-arcs-from from) to) arcs)))) + (adjacency-list gr)) + arcs)) + +;;; + +(defun make-edge-graph (data) + (from-sexp (make-instance 'undirected-graph + :representation (make-instance 'edge-list-representation)) + data)) + +(defun make-edge-and-node-graph (data) + (from-sexp (make-instance 'undirected-graph + :representation (make-instance 'edge-and-node-list-representation)) + data)) + +(defun make-adjacency-list-graph (data) + (from-sexp (make-instance 'directed-graph + :representation (make-instance 'adjacency-list-representation)) + data)) + + +(defun set-equal-p (a b) + (and (subsetp a b :test (function equal)) + (subsetp b a :test (function equal)))) + +(defun test/to-sexp () + (dolist (test '(() + (a b c) + ((a b) (b c)) + ((b c) (f c) (g h) d (f b) (k f) (h g)) + ((s r) t (u r) (s u) (u s) (v u)) + ((p q :weight 9) (m q :weight 7) k (p m :weight 5)))) + (assert (set-equal-p test (to-sexp (make-edge-and-node-graph test)))) + (assert (set-equal-p test (to-sexp (make-adjacency-list-graph test))))) + (dolist (test '(() + ((a b) (b c)) + ((b c) (f c) (g h) (f b) (k f) (h g)) + ((s r) (u r) (s u) (u s) (v u)) + ((p q :weight 9) (m q :weight 7) (p m :weight 5)))) + (assert (set-equal-p test (to-sexp (make-edge-graph test))))) + :success) + +;; (test/to-sexp) + + +;; Converting from one graph representation to another can be realized with: +;; (make-...-graph (to-sexp original-graph)) +;; or use copy-from to replace the contents of the current graph with +;; those of the other graph: + +(defmethod copy-from ((g graph) (other graph)) + "Make G a graph equal to OTHER" + (clear-representation g) + ;; Just out of lazyness, we go thru sexps. + (from-sexp (slot-value g 'representation) (to-sexp other)) + ;; if a faster conversion is required, we could get (nodes other) + ;; and (edges other) or (arcs other) and loop on them to add them to + ;; the target graph. + g) + + +;;;; THE END ;;;; diff --git a/rdp.lisp b/rdp.lisp index a4c539c..8157d5f 100644 --- a/rdp.lisp +++ b/rdp.lisp @@ -14,6 +14,10 @@ ;;;;AUTHORS ;;;; <PJB> Pascal Bourguignon <pjb@informatimago.com> ;;;;MODIFICATIONS +;;;; 2011-01-12 <PJB> Added grammar parameter to functions +;;;; generating function names so that different +;;;; grammars with non-terminals named the same +;;;; don't collide. ;;;; 2006-09-09 <PJB> Created ;;;;BUGS ;;;; @@ -32,7 +36,7 @@ ;;;;LEGAL ;;;; GPL ;;;; -;;;; Copyright Pascal Bourguignon 2006 - 2006 +;;;; Copyright Pascal Bourguignon 2006 - 2011 ;;;; ;;;; This program is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU General Public License @@ -181,7 +185,7 @@ TODO: We could also flatten sequences without action, or even sequences with (compute-all-non-terminals ,g) ,g)))) - ,(gen-boilerplate target-language) + ,(gen-boilerplate target-language grammar) ,(generate-scanner target-language grammar) ,@(mapcar (lambda (non-terminal) (generate-nt-parser target-language grammar non-terminal)) @@ -199,7 +203,7 @@ TODO: We could also flatten sequences without action, or even sequences with (compute-all-terminals grammar) (compute-all-non-terminals grammar) (eval `(progn - ,(gen-boilerplate target-language) + ,(gen-boilerplate target-language grammar) ,(generate-scanner target-language grammar) ,@(mapcar (lambda (non-terminal) (generate-nt-parser target-language grammar non-terminal)) @@ -380,7 +384,8 @@ in the grammar." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Generator -- LISP -(defmethod gen-boilerplate ((target (eql :lisp))) +(defmethod gen-boilerplate ((target (eql :lisp)) (grammar grammar)) + (declare (ignore grammar)) `(progn (defstruct scanner @@ -409,8 +414,8 @@ in the grammar." (format nil "^[~{~C~}]\\+" '(#\space #\newline #\tab))))) -(defmethod gen-scanner-function-name ((target (eql :lisp)) grammar-name) - (intern (format nil "SCAN-~A" grammar-name))) +(defmethod gen-scanner-function-name ((target (eql :lisp)) (grammar grammar)) + (intern (format nil "~:@(SCAN-~A~)" (grammar-name grammar)))) (defmethod generate-scanner ((target (eql :lisp)) grammar) #-clisp (error "This generator uses the clisp specific package REGEXP @@ -428,7 +433,7 @@ Please, update it use whatever regexp package is available in ~A" (grammar-all-terminals grammar)) (function >) :key (function length)))))) `(defun - ,(gen-scanner-function-name target (grammar-name grammar)) + ,(gen-scanner-function-name target grammar) (scanner) (let ((match (regexp:match *spaces* (scanner-source scanner) @@ -462,8 +467,8 @@ Please, update it use whatever regexp package is available in ~A" (scanner-position scanner)))))))) -(defmethod gen-parse-function-name ((target (eql :lisp)) non-terminal) - (intern (format nil "PARSE-~A" non-terminal))) +(defmethod gen-parse-function-name ((target (eql :lisp)) (grammar grammar) non-terminal) + (intern (format nil "~:@(~A/PARSE-~A~)" (grammar-name grammar) non-terminal))) (defmethod gen-in-firsts ((target (eql :lisp)) firsts) (if (null (cdr firsts)) @@ -471,14 +476,14 @@ Please, update it use whatever regexp package is available in ~A" `(member (scanner-current-token scanner) ',firsts :test (function word-equal)))) -(defmethod gen-parsing-statement ((target (eql :lisp)) grammar item) +(defmethod gen-parsing-statement ((target (eql :lisp)) (grammar grammar) item) (if (atom item) (if (terminalp grammar item) `(accept scanner ',item) (let* ((firsts (first-rhs grammar item)) (emptyp (member nil firsts))) `(,(if emptyp 'when 'if) ,(gen-in-firsts target (remove nil firsts)) - (,(gen-parse-function-name target item) scanner) + (,(gen-parse-function-name target grammar item) scanner) ,@(unless emptyp '((error "Unexpected token ~S" (scanner-current-token scanner))))))) @@ -509,21 +514,22 @@ Please, update it use whatever regexp package is available in ~A" (cdr item))))))) -(defmethod generate-nt-parser ((target (eql :lisp)) grammar non-terminal) - `(defun ,(gen-parse-function-name target non-terminal) (scanner) +(defmethod generate-nt-parser ((target (eql :lisp)) (grammar grammar) non-terminal) + `(defun ,(gen-parse-function-name target grammar non-terminal) (scanner) ,(gen-parsing-statement target grammar (find-rule grammar non-terminal)))) (defmethod generate-parser ((target (eql :lisp)) grammar) (let ((scanner-function - (gen-scanner-function-name target (grammar-name grammar)))) - `(defun ,(gen-parse-function-name target (grammar-name grammar)) + (gen-scanner-function-name target grammar))) + `(defun ; ,(gen-parse-function-name target grammar (grammar-name grammar)) + ,(intern (format nil "~:@(PARSE-~A~)" (grammar-name grammar))) (source) (let ((scanner (make-scanner :source source :function (function ,scanner-function)))) (,scanner-function scanner) - (prog1 (,(gen-parse-function-name target - (grammar-start grammar)) scanner) + (prog1 (,(gen-parse-function-name target grammar (grammar-start grammar)) + scanner) (unless (scanner-end-of-source scanner) (error "End of source NOT reached.")))))))