mirror of
https://github.com/mfocko/blog.git
synced 2024-11-13 01:30:29 +01:00
1 line
No EOL
16 KiB
JavaScript
1 line
No EOL
16 KiB
JavaScript
"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[115],{78416:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>d});var r=t(85893),s=t(11151);const i={id:"solution",slug:"/recursion/karel/solution",title:"Solution to the problem",description:"Solving the problem introduced in the previous post.\n",tags:["python","karel","recursion","backtracking","solution"],last_update:{date:new Date("2023-12-24T00:00:00.000Z")}},o="Solving the maze problem",l={id:"recursion/2022-11-29-karel/solution",title:"Solution to the problem",description:"Solving the problem introduced in the previous post.\n",source:"@site/algorithms/04-recursion/2022-11-29-karel/2023-12-24-solution.md",sourceDirName:"04-recursion/2022-11-29-karel",slug:"/recursion/karel/solution",permalink:"/algorithms/recursion/karel/solution",draft:!1,unlisted:!1,editUrl:"https://github.com/mfocko/blog/tree/main/algorithms/04-recursion/2022-11-29-karel/2023-12-24-solution.md",tags:[{label:"python",permalink:"/algorithms/tags/python"},{label:"karel",permalink:"/algorithms/tags/karel"},{label:"recursion",permalink:"/algorithms/tags/recursion"},{label:"backtracking",permalink:"/algorithms/tags/backtracking"},{label:"solution",permalink:"/algorithms/tags/solution"}],version:"current",lastUpdatedAt:1703376e3,formattedLastUpdatedAt:"Dec 24, 2023",frontMatter:{id:"solution",slug:"/recursion/karel/solution",title:"Solution to the problem",description:"Solving the problem introduced in the previous post.\n",tags:["python","karel","recursion","backtracking","solution"],last_update:{date:"2023-12-24T00:00:00.000Z"}},sidebar:"autogeneratedBar",previous:{title:"Recursion and backtracking with Robot Karel",permalink:"/algorithms/recursion/karel"},next:{title:"Introduction to dynamic programming",permalink:"/algorithms/recursion/pyramid-slide-down"}},a={},d=[{value:"Summary of the problem",id:"summary-of-the-problem",level:2},{value:"Brainstorming the idea",id:"brainstorming-the-idea",level:2},{value:"\xbbRough\xab pseudocode",id:"rough-pseudocode",level:2},{value:"\xbbProper\xab pseudocode",id:"proper-pseudocode",level:2},{value:"Actual implementation",id:"actual-implementation",level:2},{value:"Testing",id:"testing",level:2},{value:"Fixing the issue",id:"fixing-the-issue",level:2}];function u(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",ol:"ol",p:"p",pre:"pre",section:"section",strong:"strong",sup:"sup",ul:"ul",...(0,s.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.h1,{id:"solving-the-maze-problem",children:"Solving the maze problem"}),"\n",(0,r.jsx)(n.p,{children:"We will go through the given problem the same way as I have suggested in the\nprevious post."}),"\n",(0,r.jsx)(n.h2,{id:"summary-of-the-problem",children:"Summary of the problem"}),"\n",(0,r.jsxs)(n.p,{children:["We have a robot in some kind of a maze and we have to find our way out that is\nmarked with a so-called \u201cbeeper\u201d. We've been given a restriction ",(0,r.jsx)(n.strong,{children:"not to"})," use\nany variables, we can use just backtracking and recursion."]}),"\n",(0,r.jsx)(n.h2,{id:"brainstorming-the-idea",children:"Brainstorming the idea"}),"\n",(0,r.jsx)(n.p,{children:"Let's start with some brainstorming of the solution."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["How will I know what I've checked without any variables?","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"answer"}),": recursion will need to take care of that, cause I'm not allowed\nanything else"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["How will I pass around the fact I've found the exit?","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"answer"}),": I can return values from helper functions, so I should be able to\nindicate ",(0,r.jsx)(n.em,{children:"found"}),"/",(0,r.jsx)(n.em,{children:"not found"})]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["How is the exit marked?","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"answer"}),": there is one \u201cbeeper\u201d as a mark"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Can I reduce my problem somehow?","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"answer"}),": I could check each possible direction as a reduced search space"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"rough-pseudocode",children:"\xbbRough\xab pseudocode"}),"\n",(0,r.jsxs)(n.p,{children:["We should be able to construct a ",(0,r.jsx)(n.em,{children:"skeleton"})," of our solution at least. Pseudocode\nfollows:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ruby",children:"def find_exit\n if found the exit then\n signal others\n terminate\n end\n\n check left\n check front\n check right\nend\n"})}),"\n",(0,r.jsx)(n.p,{children:"As you can see, we only mention what we want to do very roughly, technical\ndetails are left out, except for the early return (which is the base of our\nrecursive function)."}),"\n",(0,r.jsx)(n.h2,{id:"proper-pseudocode",children:"\xbbProper\xab pseudocode"}),"\n",(0,r.jsx)(n.p,{children:"In the proper pseudocode we will need to dive into the technical details like\nthe way we check for exit, move around, etc."}),"\n",(0,r.jsx)(n.p,{children:"We can start by cleaning up and decomposing the function written above:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ruby",children:"def find_exit\n # BASE: found exit\n if found_exit() then\n return true\n end\n\n # check left\n if left_is_clear() then\n turn_left()\n step()\n if find_exit() then\n return true\n end\n\n turn_around()\n step()\n turn_left()\n end\n\n # check front\n if front_is_clear() then\n step()\n if find_exit() then\n return true\n end\n\n turn_around()\n step()\n turn_around()\n end\n\n # check right\n if right_is_clear() then\n turn_right()\n step()\n if find_exit() then\n return true\n end\n\n turn_around()\n step()\n turn_right()\n end\n\n return false\nend\n"})}),"\n",(0,r.jsx)(n.p,{children:"We are missing few of the functions that we use in our pseudocode above:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"found_exit()"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"turn_around()"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.code,{children:"turn_right()"})}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"We can implement those easily:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ruby",children:"def found_exit\n if not beepers_present() then\n return false\n end\n\n pick_beeper()\n if beepers_present() then\n put_beeper()\n return false\n end\n\n put_beeper()\n return true\nend\n\ndef turn_around\n turn_left()\n turn_left()\nend\n\ndef turn_right\n turn_around()\n turn_left()\nend\n"})}),"\n",(0,r.jsx)(n.p,{children:"Now we have everything ready for implementing it in Python."}),"\n",(0,r.jsx)(n.h2,{id:"actual-implementation",children:"Actual implementation"}),"\n",(0,r.jsxs)(n.p,{children:["It's just a matter of rewriting the pseudocode into Python",(0,r.jsx)(n.sup,{children:(0,r.jsx)(n.a,{href:"#user-content-fn-1",id:"user-content-fnref-1","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"1"})}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-py",children:"class SuperKarel(Karel):\n # you can define your own helper functions on Karel here, if you wish to\n\n def found_exit(self) -> bool:\n if not self.beepers_present():\n return False\n\n self.pick_beeper()\n if self.beepers_present():\n self.put_beeper()\n return False\n\n self.put_beeper()\n return True\n\n def turn_around(self):\n for _ in range(2):\n self.turn_left()\n\n def turn_right(self):\n for _ in range(3):\n self.turn_left()\n\n def find_exit(self) -> bool:\n if self.found_exit():\n return True\n\n # check left\n if self.left_is_clear():\n self.turn_left()\n self.step()\n if self.find_exit():\n return True\n\n self.turn_around()\n self.step()\n self.turn_left()\n\n # check front\n if self.front_is_clear():\n self.step()\n if self.find_exit():\n return True\n\n self.turn_around()\n self.step()\n self.turn_around()\n\n # check right\n if self.right_is_clear():\n self.turn_right()\n self.step()\n if self.find_exit():\n return True\n\n self.turn_around()\n self.step()\n self.turn_right()\n\n return False\n\n def run(self):\n self.find_exit()\n"})}),"\n",(0,r.jsx)(n.p,{children:"We have relatively repetitive code for checking each of the directions, I would\npropose to refactor a bit, in a fashion of checkin just forward, so it's more\nreadable:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-py",children:"def find_exit(self) -> bool:\n if self.found_exit():\n return True\n\n self.turn_left()\n for _ in range(3):\n if self.front_is_blocked():\n self.turn_right()\n continue\n\n self.step()\n if self.find_exit():\n return True\n\n self.step()\n self.turn_around()\n self.turn_right()\n\n return False\n"})}),"\n",(0,r.jsxs)(n.p,{children:["We can also notice that turning around takes 2 left turns and turning to right\ndoes 3. We get 5 left turns in total when we turn around and right afterwards\u2026\nTaking 4 left turns just rotates us back to our initial direction, therefore it\nis sufficient to do just one left turn (",(0,r.jsx)(n.code,{children:"5 % 4 == 1"}),"). That way we get:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-py",children:"def find_exit(self) -> bool:\n if self.found_exit():\n return True\n\n self.turn_left()\n for _ in range(3):\n if self.front_is_blocked():\n self.turn_right()\n continue\n\n self.step()\n if self.find_exit():\n return True\n\n self.step()\n # turning around and right is same as one turn to the left\n self.turn_left()\n\n return False\n"})}),"\n",(0,r.jsx)(n.h2,{id:"testing",children:"Testing"}),"\n",(0,r.jsx)(n.p,{children:"In the skeleton, with the previous post, I have included multiple mazes that can\nbe tested. I have tried this solution with all of the given mazes, and it was\nsuccessful in finding the exit. However there is one precondition of our\nsolution that we haven't spoken about."}),"\n",(0,r.jsxs)(n.p,{children:["We are silently expecting the maze ",(0,r.jsx)(n.strong,{children:"not to"})," have any loops. Example of such\nmaze can be the ",(0,r.jsx)(n.code,{children:"maze666.kw"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2510\n\u2502. . . . .\u2502.\u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\n\u2502.\u2502. . .\u2502.\u2502.\u2502\n\u2502 \u2502 \u2502 \u2502 \u2502 \u2502\n\u2502.\u2502.\u2502. . .\u2502.\u2502\n\u2502 \u2502 \u2502 \u2514\u2500\u2524\n\u2502.\u2502.\u2502. . . 1\u2502\n\u2502 \u2502 \u2502 \u2502 \u250c\u2500\u2524\n\u2502.\u2502. . .\u2502.\u2502.\u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\n\u2502. > . . .\u2502.\u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2518\n"})}),"\n",(0,r.jsx)(n.p,{children:"If you try running our solution on this map, Karel just loops and never finds\nthe solution. Let's have a look at the loop he gets stuck in:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2510\n\u2502* * * * *\u2502.\u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\n\u2502*\u2502* * *\u2502*\u2502.\u2502\n\u2502 \u2502 \u2502 \u2502 \u2502 \u2502\n\u2502*\u2502*\u2502. * *\u2502.\u2502\n\u2502 \u2502 \u2502 \u2514\u2500\u2524\n\u2502*\u2502*\u2502. * * 1\u2502\n\u2502 \u2502 \u2502 \u2502 \u250c\u2500\u2524\n\u2502*\u2502* * *\u2502*\u2502.\u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\n\u2502* * * * *\u2502.\u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2518\n"})}),"\n",(0,r.jsx)(n.p,{children:"He walks past the exit, but can't see it, cause there's always a feasible path\nthat is worth trying."}),"\n",(0,r.jsx)(n.admonition,{title:"Algorithm",type:"tip",children:(0,r.jsx)(n.p,{children:"The algorithm we have written to find the exit is a depth-first search (DFS).\nHowever, as opposed to the usual implementation, we have no notion of paths that\nare being (or have already been) explored."})}),"\n",(0,r.jsx)(n.h2,{id:"fixing-the-issue",children:"Fixing the issue"}),"\n",(0,r.jsxs)(n.p,{children:["Since we are not allowed to use variables, the only way to resolve this issue is\nto mark the \u201ccells\u201d that we have tried. We can easily use beepers for this, but\nwe need to be careful ",(0,r.jsx)(n.strong,{children:"not to"})," confuse the exit with already visited cell."]}),"\n",(0,r.jsxs)(n.p,{children:["To do that we'll use ",(0,r.jsx)(n.strong,{children:"2"})," beepers instead of the one. Implementation follows:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-py",children:'def visited(self) -> bool:\n if not self.beepers_present():\n return False\n\n self.pick_beeper()\n if not self.beepers_present():\n self.put_beeper()\n return False\n\n self.pick_beeper()\n if self.beepers_present():\n assert False, "no cell shall be marked with 3 beepers"\n\n self.put_beeper()\n self.put_beeper()\n return True\n\n\ndef find_exit(self) -> bool:\n # BASE: already tried\n if self.visited():\n self.turn_around()\n return False\n\n # BASE\n if self.found_exit():\n return True\n\n # mark the cell as visited\n for _ in range(2):\n self.put_beeper()\n\n self.turn_left()\n for _ in range(3):\n if self.front_is_blocked():\n self.turn_right()\n continue\n\n self.step()\n if self.find_exit():\n return True\n\n self.step()\n # turning around and right is same as one turn to the left\n self.turn_left()\n\n return False\n'})}),"\n",(0,r.jsx)(n.p,{children:"Now our solution works also for mazes that have loops."}),"\n",(0,r.jsxs)(n.section,{"data-footnotes":!0,className:"footnotes",children:[(0,r.jsx)(n.h2,{className:"sr-only",id:"footnote-label",children:"Footnotes"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{id:"user-content-fn-1",children:["\n",(0,r.jsxs)(n.p,{children:["which is usually very easy matter ",(0,r.jsx)(n.a,{href:"#user-content-fnref-1","data-footnote-backref":"","aria-label":"Back to reference 1",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n"]}),"\n"]})]})}function c(e={}){const{wrapper:n}={...(0,s.a)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(u,{...e})}):u(e)}},11151:(e,n,t)=>{t.d(n,{Z:()=>l,a:()=>o});var r=t(67294);const s={},i=r.createContext(s);function o(e){const n=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),r.createElement(i.Provider,{value:n},e.children)}}}]); |