From 870bdff6cb4b12ca5131e0b644c806f9fef43042 Mon Sep 17 00:00:00 2001 From: Michael Leuschel <leuschel@uni-duesseldorf.de> Date: Tue, 15 Oct 2024 13:36:51 +0200 Subject: [PATCH] add more examples and an image --- logic_programming/2_IntroProlog.ipynb | 567 ++++++++++++++++++-------- logic_programming/img/simple_tree.png | Bin 0 -> 17748 bytes 2 files changed, 395 insertions(+), 172 deletions(-) create mode 100644 logic_programming/img/simple_tree.png diff --git a/logic_programming/2_IntroProlog.ipynb b/logic_programming/2_IntroProlog.ipynb index ffca183..06d4722 100644 --- a/logic_programming/2_IntroProlog.ipynb +++ b/logic_programming/2_IntroProlog.ipynb @@ -786,6 +786,15 @@ "?- edge(A,B,C)." ] }, + { + "cell_type": "markdown", + "id": "c137912e", + "metadata": {}, + "source": [ + "By calling jupyter:print_transition_graph(PredSpec, FromIdx, ToIdx, LabelIdx),\n", + "a transition graph can be created (FromIdx is the number of argument specifying the origin, ToIdx the destination and LabelIdx the label)." + ] + }, { "cell_type": "code", "execution_count": 68, @@ -1213,64 +1222,14 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 44, "id": "12f78859", "metadata": { "vscode": { "languageId": "prolog" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " <style>\n", - " details {\n", - " font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; font-size: 13px;\n", - " }\n", - "\n", - " details > summary {\n", - " cursor: pointer;\n", - " }\n", - " </style>\n", - " <details><summary>Previously defined clauses of user:edge/2 were retracted (click to expand)</summary><pre>:- dynamic edge/2.\n", - "\n", - "edge(a, b).\n", - "edge(a, c).\n", - "edge(b, d).\n", - "edge(c, d).\n", - "edge(d, e).\n", - "edge(f, g).\n", - "</pre></details>" - ], - "text/plain": [ - "Previously defined clauses of user:edge/2 were retracted:\n", - ":- dynamic edge/2.\n", - "\n", - "edge(a, b).\n", - "edge(a, c).\n", - "edge(b, d).\n", - "edge(c, d).\n", - "edge(d, e).\n", - "edge(f, g).\n" - ] - }, - "metadata": { - "application/json": {} - }, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "% Asserting clauses for user:edge/2\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "edge(a,b). edge(a,c).\n", "edge(b,d). edge(c,d).\n", @@ -1288,7 +1247,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 45, "id": "99bfae95", "metadata": { "vscode": { @@ -1302,7 +1261,7 @@ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n", "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n", " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n", - "<!-- Generated by graphviz version 6.0.1 (20220911.1526)\n", + "<!-- Generated by graphviz version 10.0.1 (20240210.2158)\n", " -->\n", "<!-- Pages: 1 -->\n", "<svg width=\"206pt\" height=\"260pt\"\n", @@ -1313,79 +1272,79 @@ "<g id=\"node1\" class=\"node\">\n", "<title>a</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-234\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"63\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">a</text>\n", + "<text text-anchor=\"middle\" x=\"63\" y=\"-228.95\" font-family=\"Times,serif\" font-size=\"14.00\">a</text>\n", "</g>\n", "<!-- b -->\n", "<g id=\"node2\" class=\"node\">\n", "<title>b</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"27\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">b</text>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">b</text>\n", "</g>\n", "<!-- a->b -->\n", "<g id=\"edge1\" class=\"edge\">\n", "<title>a->b</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M54.65,-216.76C50.29,-208.28 44.85,-197.71 39.96,-188.2\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"42.99,-186.44 35.3,-179.15 36.77,-189.64 42.99,-186.44\"/>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M54.65,-216.76C50.42,-208.55 45.19,-198.37 40.42,-189.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"43.68,-187.79 36,-180.49 37.46,-190.99 43.68,-187.79\"/>\n", "</g>\n", "<!-- c -->\n", "<g id=\"node3\" class=\"node\">\n", "<title>c</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"99\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">c</text>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">c</text>\n", "</g>\n", "<!-- a->c -->\n", "<g id=\"edge2\" class=\"edge\">\n", "<title>a->c</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M71.35,-216.76C75.71,-208.28 81.15,-197.71 86.04,-188.2\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"89.23,-189.64 90.7,-179.15 83.01,-186.44 89.23,-189.64\"/>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M71.35,-216.76C75.58,-208.55 80.81,-198.37 85.58,-189.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"88.54,-190.99 90,-180.49 82.32,-187.79 88.54,-190.99\"/>\n", "</g>\n", "<!-- d -->\n", "<g id=\"node4\" class=\"node\">\n", "<title>d</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"63\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">d</text>\n", + "<text text-anchor=\"middle\" x=\"63\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">d</text>\n", "</g>\n", "<!-- b->d -->\n", "<g id=\"edge3\" class=\"edge\">\n", "<title>b->d</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M35.35,-144.76C39.71,-136.28 45.15,-125.71 50.04,-116.2\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"53.23,-117.64 54.7,-107.15 47.01,-114.44 53.23,-117.64\"/>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M35.35,-144.76C39.58,-136.55 44.81,-126.37 49.58,-117.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"52.54,-118.99 54,-108.49 46.32,-115.79 52.54,-118.99\"/>\n", "</g>\n", "<!-- c->d -->\n", "<g id=\"edge4\" class=\"edge\">\n", "<title>c->d</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M90.65,-144.76C86.29,-136.28 80.85,-125.71 75.96,-116.2\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"78.99,-114.44 71.3,-107.15 72.77,-117.64 78.99,-114.44\"/>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M90.65,-144.76C86.42,-136.55 81.19,-126.37 76.42,-117.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"79.68,-115.79 72,-108.49 73.46,-118.99 79.68,-115.79\"/>\n", "</g>\n", "<!-- e -->\n", "<g id=\"node5\" class=\"node\">\n", "<title>e</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"63\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">e</text>\n", + "<text text-anchor=\"middle\" x=\"63\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">e</text>\n", "</g>\n", "<!-- d->e -->\n", "<g id=\"edge5\" class=\"edge\">\n", "<title>d->e</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M63,-71.7C63,-63.98 63,-54.71 63,-46.11\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"66.5,-46.1 63,-36.1 59.5,-46.1 66.5,-46.1\"/>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M63,-71.7C63,-64.41 63,-55.73 63,-47.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"66.5,-47.62 63,-37.62 59.5,-47.62 66.5,-47.62\"/>\n", "</g>\n", "<!-- f -->\n", "<g id=\"node6\" class=\"node\">\n", "<title>f</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-234\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"171\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">f</text>\n", + "<text text-anchor=\"middle\" x=\"171\" y=\"-228.95\" font-family=\"Times,serif\" font-size=\"14.00\">f</text>\n", "</g>\n", "<!-- g -->\n", "<g id=\"node7\" class=\"node\">\n", "<title>g</title>\n", "<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">g</text>\n", + "<text text-anchor=\"middle\" x=\"171\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">g</text>\n", "</g>\n", "<!-- f->g -->\n", "<g id=\"edge6\" class=\"edge\">\n", "<title>f->g</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M171,-215.7C171,-207.98 171,-198.71 171,-190.11\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"174.5,-190.1 171,-180.1 167.5,-190.1 174.5,-190.1\"/>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M171,-215.7C171,-208.41 171,-199.73 171,-191.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"174.5,-191.62 171,-181.62 167.5,-191.62 174.5,-191.62\"/>\n", "</g>\n", "</g>\n", "</svg>\n" @@ -1418,6 +1377,28 @@ "jupyter:print_transition_graph(edge/2, 1, 2,0)." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "b400c8fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\u001b[1;31mERROR: call/1: Unknown procedure: jupyter:print_transition_graph/1\n", + "ERROR: However, there are definitions for:\n", + "ERROR: jupyter:print_transition_graph/4\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "jupyter:print_transition_graph(edge/2)." + ] + }, { "cell_type": "code", "execution_count": 75, @@ -2191,126 +2172,321 @@ }, { "cell_type": "code", - "execution_count": 86, + "execution_count": 13, "id": "d4664fd8", "metadata": { "vscode": { "languageId": "prolog" } }, + "outputs": [], + "source": [ + "\n", + "construct(Name,Department,employe(Name,Department)).\n", + "\n", + "get_name(employe(Name,_),Name).\n", + "get_dept(employe(_,Dept),Dept)." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "08715fa2", + "metadata": { + "vscode": { + "languageId": "prolog" + } + }, "outputs": [ { "data": { - "text/html": [ - "\n", - " <style>\n", - " details {\n", - " font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; font-size: 13px;\n", - " }\n", - "\n", - " details > summary {\n", - " cursor: pointer;\n", - " }\n", - " </style>\n", - " <details><summary>Previously defined clauses of user:construct/3 were retracted (click to expand)</summary><pre>:- dynamic construct/3.\n", - "\n", - "construct(A, B, employe(A, B)).\n", - "</pre></details>" - ], "text/plain": [ - "Previously defined clauses of user:construct/3 were retracted:\n", - ":- dynamic construct/3.\n", - "\n", - "construct(A, B, employe(A, B)).\n" + "\u001b[1mE1 = employe(peter,cs),\n", + "E2 = employe(mary,cs),\n", + "N1 = peter,\n", + "D2 = cs" ] }, - "metadata": { - "application/json": {} - }, + "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "?- construct(peter,cs,E1), construct(mary,cs,E2), get_name(E1,N1), get_dept(E2,D2)." + ] + }, + { + "cell_type": "markdown", + "id": "43452d97", + "metadata": {}, + "source": [ + "Let us work out a small database example using such compound terms for data abstraction:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "e9ff8860", + "metadata": {}, + "outputs": [], + "source": [ + "% Facts for courses with time slot and location:\n", + "course(logic_programming,time(tuesday,1430,1600),location(25,'5F')).\n", + "course(sks,time(monday,1430,1600),location(25,'5G')).\n", + "course(drones,time(thursday,1430,1600),location(25,'12.O2.55')).\n", + "course(navigating_tomorrow,time(thursday,1430,1600),location(25,'12.O2.21')).\n", + "% separate facts about which programme the courses belong to (can be multiple):\n", + "programme(logic_programming,bachelor_cs).\n", + "programme(logic_programming,master_dsai).\n", + "programme(sks,master_cs).\n", + "programme(navigating_tomorrow,bachelor_cs).\n" + ] + }, + { + "cell_type": "markdown", + "id": "3dc100cb", + "metadata": {}, + "source": [ + "Let us try and write a predicate for:\n", + "- which courses take place on a particular day\n", + "- which courses take place in a particular building (e.g., 25)\n", + "- which courses are available for multiple programmes\n", + "- which courses are in conflict (time-wise)\n", + "- on which days there are courses a given programme (e.g., bachelor_cs)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fbed4b05", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f506936", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c34b9d8c", + "metadata": {}, + "source": [ + "Here is a solution. You will see that the compound term encoding of times and buildings\n", + "has some advantages and drawbacks over a flatter representation such as course/6 with 6 arguments:\n", + "```\n", + "course(logic_programming,tuesday,1430,1600,25,'5F').\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "74a9e660", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "day(Lect,Day) :- course(Lect,time(Day,_,_),_).\n", + "building(Lect,Bldg) :- course(Lect,_,location(Bldg,_)).\n", + "multi(Lect) :- course(Lect,_,_), programme(Lect,P1), programme(Lect,P2), P1\\=P2.\n", + "conflict(L1,L2) :- course(L1,T,_), course(L2,T,_), L1\\=L2.\n", + "lecture_day(Day,Progr) :- course(L1,time(Day,_,_),_), programme(L1,Progr)." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3cddfd08", + "metadata": {}, + "outputs": [ { "data": { "text/plain": [ - "% Asserting clauses for user:construct/3\n" + "\u001b[1mL = logic_programming" ] }, "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "?- multi(L)." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "9271f0d7", + "metadata": {}, + "outputs": [ { "data": { - "text/html": [ - "\n", - " <style>\n", - " details {\n", - " font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; font-size: 13px;\n", - " }\n", - "\n", - " details > summary {\n", - " cursor: pointer;\n", - " }\n", - " </style>\n", - " <details><summary>Previously defined clauses of user:get_name/2 were retracted (click to expand)</summary><pre>:- dynamic get_name/2.\n", - "\n", - "get_name(employe(A, _), A).\n", - "</pre></details>" - ], "text/plain": [ - "Previously defined clauses of user:get_name/2 were retracted:\n", - ":- dynamic get_name/2.\n", - "\n", - "get_name(employe(A, _), A).\n" + "\u001b[1mX = drones,\n", + "Y = navigating_tomorrow" ] }, - "metadata": { - "application/json": {} - }, + "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "?- conflict(X,Y)." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d7b8619e", + "metadata": {}, + "outputs": [ { "data": { "text/plain": [ - "% Asserting clauses for user:get_name/2\n" + "\u001b[1mL = [tuesday,thursday]" ] }, "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "?- findall(D,lecture_day(D,bachelor_cs),L)." + ] + }, + { + "cell_type": "markdown", + "id": "e2ed5de7", + "metadata": {}, + "source": [ + "## Peano Arithmetic\n", + "\n", + "The arguments to a functor can in term also make use of a functor.\n", + "\n", + "We can also use functors of arity 1. (Functors of arity 0 are simply constants, i.e., atoms in Prolog terminology.)\n", + "This can be used to represent natural numbers:\n", + "- we can represent zero using the atom `zero/0`\n", + "- we can represent the successor of a number using a functor `s/1` \n", + "\n", + "Thus the number 1 is represented by the term `s(zero)` and the number 2 by `s(s(zero))`.\n", + "\n", + "How could one write predicates to check if a term is a valid natural number in that representation?\n", + "E.g., `s(zero)` is valid, `s(a)` is not.\n", + "How could one write addition and multiplication predicate to add or multiply two numbers?\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d1a7bf2", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1735fe4c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "dbc89f40", + "metadata": {}, + "source": [ + "A possible solution is this one.\n", + "This is also called Peano arithmetic. Note, that contrary to built-in arithmetic (using is/2), these predicates are reversible" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d44c70af", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "\n", + "\n", + "nat(zero).\n", + "nat(s(X)) :- nat(X).\n", + "\n", + "plus(zero,X,X).\n", + "plus(s(X),Y,s(Z)) :- plus(X,Y,Z).\n", + "\n", + "mult(zero,_,zero).\n", + "mult(s(X),Y,Z) :- mult(X,Y,XY), plus(Y,XY,Z). % (x+1)*y = x*y + y\n", + "\n", + "exp(zero,s(_),zero).\n", + "exp(s(_),zero,s(zero)).\n", + "exp(Y,s(X),Z) :- exp(Y,X,YX), mult(YX,Y,Z)." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "6e96b7aa", + "metadata": {}, + "outputs": [ { "data": { - "text/html": [ - "\n", - " <style>\n", - " details {\n", - " font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; font-size: 13px;\n", - " }\n", - "\n", - " details > summary {\n", - " cursor: pointer;\n", - " }\n", - " </style>\n", - " <details><summary>Previously defined clauses of user:get_dept/2 were retracted (click to expand)</summary><pre>:- dynamic get_dept/2.\n", - "\n", - "get_dept(employe(_, A), A).\n", - "</pre></details>" - ], "text/plain": [ - "Previously defined clauses of user:get_dept/2 were retracted:\n", - ":- dynamic get_dept/2.\n", - "\n", - "get_dept(employe(_, A), A).\n" + "\u001b[1mR = s(s(s(zero)))" ] }, - "metadata": { - "application/json": {} + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "?-plus(s(s(zero)),s(zero),R)." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "d119214f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\u001b[1mR = s(s(s(s(s(s(zero))))))" + ] }, + "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "?-mult(s(s(zero)),s(s(s(zero))),R)." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "b3c9b644", + "metadata": {}, + "outputs": [ { "data": { "text/plain": [ - "% Asserting clauses for user:get_dept/2\n" + "\u001b[1mR = s(s(s(s(s(s(s(s(zero))))))))" ] }, "metadata": {}, @@ -2318,30 +2494,39 @@ } ], "source": [ - "\n", - "construct(Name,Department,employe(Name,Department)).\n", - "\n", - "get_name(employe(Name,_),Name).\n", - "get_dept(employe(_,Dept),Dept)." + "?-exp(s(s(zero)),s(s(s(zero))),R)." ] }, { "cell_type": "code", - "execution_count": 87, - "id": "08715fa2", - "metadata": { - "vscode": { - "languageId": "prolog" + "execution_count": 42, + "id": "e61cc47b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\u001b[1mX = s(zero)" + ] + }, + "metadata": {}, + "output_type": "display_data" } - }, + ], + "source": [ + "?-plus(X,s(zero),s(s(zero)))." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "747d23d1", + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[1mE1 = employe(a,cs),\n", - "E2 = employe(b,cs),\n", - "N1 = a,\n", - "D2 = cs" + "\u001b[1mX = s(s(zero))" ] }, "metadata": {}, @@ -2349,21 +2534,24 @@ } ], "source": [ - "?- construct(a,cs,E1), construct(b,cs,E2), get_name(E1,N1), get_dept(E2,D2)." + "?-mult(X,s(zero),s(s(zero)))." ] }, { "cell_type": "markdown", - "id": "e2ed5de7", + "id": "15942ef6", "metadata": {}, "source": [ - "The arguments to a functor can in term also make use of a functor.\n", + "## Recursive Data Structures\n", + "\n", + "Compound terms can be used the represent complex data structures, as will see below.\n", + "First, think on how one could represent a list of objects, say a list of length 2 containing `a` and `b`.\n", "\n", - "One could thus for example represent a list in Prolog by using\n", + "One could for example represent a list in Prolog by using\n", "a functor `cons/2` to denote a non-empty list and `nil/0` to denote\n", "an empty list.\n", - "Note that a functor of arity 0 is simply a constant (aka atom in Prolog).\n", - "So a list of length two with a and b as elements is represented as follows:" + "(Remember, a functor of arity 0 is simply a constant (aka atom in Prolog).)\n", + "So a list of length two with a and b as elements can be represented as follows:" ] }, { @@ -2826,6 +3014,30 @@ "?- last0(X,cons(a,cons(b,nil)))." ] }, + { + "cell_type": "markdown", + "id": "34c38129", + "metadata": {}, + "source": [ + "Exercise: write predicates prefix/2 and suffix/2 which is true if the first argument is a prefix or suffix of the second one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a65d023", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df88c4bd", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "5f251e4d", @@ -2839,7 +3051,10 @@ "- the left sub-tree\n", "- the information at the root of the tree\n", "- the right sub-tree\n", - "We also need the empty tree, which we represent by `nil`." + "We also need the empty tree, which we represent by `nil`.\n", + "\n", + "For example, how would you represent this tree:\n", + "" ] }, { @@ -2866,6 +3081,14 @@ "?- Mytree = tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil)))." ] }, + { + "cell_type": "markdown", + "id": "8ac0d99f", + "metadata": {}, + "source": [ + "Let us now try to write a predicate which reverses (mirrors) the tree, swapping left and right children:" + ] + }, { "cell_type": "code", "execution_count": 104, diff --git a/logic_programming/img/simple_tree.png b/logic_programming/img/simple_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..6ae0a1bc2aabb33e824d3b1f27e3cd129f6dda65 GIT binary patch literal 17748 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 literal 0 HcmV?d00001 -- GitLab