From 765e52653458d61f107245e5f3eb1f3ff15f1e51 Mon Sep 17 00:00:00 2001 From: Allen Downey Date: Fri, 26 Apr 2024 14:59:34 -0400 Subject: [PATCH] Update documentation --- ...dd3d5b3af81e1fc228a8aaee3ea80575486ff0.png | Bin 0 -> 11019 bytes ...d9a66f559504d4c1b89fbe148c631e206580b0.png | Bin 0 -> 5390 bytes ...1edfb01810e693b7ebd67c56de713f1d132655.png | Bin 0 -> 7048 bytes ...fa1a65dded15806c5dff8a8854f2f3896703eb.png | Bin 0 -> 6755 bytes ...6fcdd9ee1479e2274f2efebc840e7f3520ce6f.png | Bin 0 -> 2183 bytes ...d2c786600c5a1461f0695f96fd869a561a08c7.png | Bin 0 -> 6053 bytes ...4af2bc442ddaadef59db22add59927b85d1ab2.png | Bin 0 -> 10725 bytes _sources/chap01.ipynb | 17 +- _sources/chap02.ipynb | 180 +- _sources/chap03.ipynb | 40 +- _sources/chap04.ipynb | 37 +- _sources/chap05.ipynb | 268 +- _sources/chap06.ipynb | 510 +-- _sources/chap07.ipynb | 38 +- _sources/chap08.ipynb | 34 +- _sources/chap09.ipynb | 36 +- _sources/chap10.ipynb | 45 +- _sources/chap11.ipynb | 45 +- _sources/chap12.ipynb | 36 +- _sources/chap13.ipynb | 17 +- _sources/chap14.ipynb | 13 + _sources/chap15.ipynb | 13 + _sources/chap16.ipynb | 2735 ++++++++++++++ _sources/chap17.ipynb | 3356 +++++++++++++++++ _sources/chap18.ipynb | 3327 ++++++++++++++++ _sources/index.md | 2 +- chap00.html | 3 + chap01.html | 14 + chap02.html | 23 +- chap03.html | 42 +- chap04.html | 26 +- chap05.html | 49 +- chap06.html | 20 +- chap07.html | 52 +- chap08.html | 13 + chap09.html | 13 + chap10.html | 13 + chap11.html | 29 +- chap12.html | 15 + chap13.html | 17 +- chap14.html | 13 + chap15.html | 23 + chap16.html | 1348 +++++++ chap17.html | 1727 +++++++++ chap18.html | 1724 +++++++++ genindex.html | 3 + index.html | 11 +- objects.inv | Bin 985 -> 1091 bytes search.html | 3 + searchindex.js | 2 +- 50 files changed, 15084 insertions(+), 848 deletions(-) create mode 100644 _images/351c7b94fa9021934acda94ae1dd3d5b3af81e1fc228a8aaee3ea80575486ff0.png create mode 100644 _images/4aaaffd556f4fee05dc8c25d40d9a66f559504d4c1b89fbe148c631e206580b0.png create mode 100644 _images/54319c437cca81caa5fa9ee52a1edfb01810e693b7ebd67c56de713f1d132655.png create mode 100644 _images/5f1d5a265aab792dbe104aaedafa1a65dded15806c5dff8a8854f2f3896703eb.png create mode 100644 _images/6e851969c74483fc4efb36d87b6fcdd9ee1479e2274f2efebc840e7f3520ce6f.png create mode 100644 _images/93ab30dffba5edf8630e6bc3afd2c786600c5a1461f0695f96fd869a561a08c7.png create mode 100644 _images/f3935334fcd59aa65b4e7a8ad44af2bc442ddaadef59db22add59927b85d1ab2.png create mode 100644 _sources/chap16.ipynb create mode 100644 _sources/chap17.ipynb create mode 100644 _sources/chap18.ipynb create mode 100644 chap16.html create mode 100644 chap17.html create mode 100644 chap18.html diff --git a/_images/351c7b94fa9021934acda94ae1dd3d5b3af81e1fc228a8aaee3ea80575486ff0.png b/_images/351c7b94fa9021934acda94ae1dd3d5b3af81e1fc228a8aaee3ea80575486ff0.png new file mode 100644 index 0000000000000000000000000000000000000000..bc1c542e305e9281b578d19b61e4e8f933e3af14 GIT binary patch literal 11019 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 B!4m)g literal 0 HcmV?d00001 diff --git a/_images/4aaaffd556f4fee05dc8c25d40d9a66f559504d4c1b89fbe148c631e206580b0.png b/_images/4aaaffd556f4fee05dc8c25d40d9a66f559504d4c1b89fbe148c631e206580b0.png new file mode 100644 index 0000000000000000000000000000000000000000..c0708ed324dc64c73f3da8898d55e312f5fcc2b1 GIT binary patch literal 5390 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 literal 0 HcmV?d00001 diff --git a/_images/54319c437cca81caa5fa9ee52a1edfb01810e693b7ebd67c56de713f1d132655.png b/_images/54319c437cca81caa5fa9ee52a1edfb01810e693b7ebd67c56de713f1d132655.png new file mode 100644 index 0000000000000000000000000000000000000000..c28ec38a95fa93e96a08ad1a9f3603e6118a22cf GIT binary patch literal 7048 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 E0OBqhO8@`> literal 0 HcmV?d00001 diff --git a/_images/5f1d5a265aab792dbe104aaedafa1a65dded15806c5dff8a8854f2f3896703eb.png b/_images/5f1d5a265aab792dbe104aaedafa1a65dded15806c5dff8a8854f2f3896703eb.png new file mode 100644 index 0000000000000000000000000000000000000000..f761e41efacea889b1144190eaac0cb64c4de030 GIT binary patch literal 6755 zcmb`McT`jDmd1lv5Gf)}qzQt6AXR!*sUo2AAtFK)q=TVJC#WEzGyw&aW}#Oh^eSC? zFQGR*)?N3`o%th_tdn!j`<}g@=lATqzNM{3Nq&wTfk04ds4L%r z-*fOiFBzZ7?{flYE? z$w#ZJ7bL{2f?XU$yL+#FKXv}~*M~Do|ny@fhFrMd(O16J<_QoWg$h#Uo6W*7)b+^b<0zT7e(_H6^ z<@CQJr9zSPzdHcKDsb=HGmJ%tOc_Nt$;?Oz4qo)f>@;U z43PBn^dlaNSp)fK2R2-PAtp7IcVu+5sjZEhpFi*wo5!ycQAJqWdyy*k_QJBR%lU`0 zZmZ{AN1qOer{AyaFvr~1*FW(+_4e1))YRSTpRA6zIql z<^s03Yz_|yk)ffX1wM!9jKaeH()r|=OiE^n!KllZFBeq)o{h1& zQ}rO|ltOxb<9gJWFbr$!2eDUmb;>n0HL2Oz=KH&Av2yO=u;IvjTT{~ularG{XGLCc zYet?veOhTC+o-9j=~34g6^gUM&(MUuwO?ODyL)?Q{IbGUPLYv4oNJFm2s|i$Tj`BI z!@!_=@7~!HR4kS*w^qvzN%gNIlOB+Ab8{!Aq+ncEj2}EW*g>b}JjiBeth`G?LXv4* z@sUm5qZ4DHL&dIe6HX=Vy>~@WP|$gyw}~SsOSRo)d%~A`JrKG7f)VMJYask{Z|>($ zm6frIlEVXhQ)_EtVPT|poQ!m)4{Mg4h0h^=TnHcSaz7iDot;g`z>xQJ@vem3P~BUO z>u$J$fq=k3C-p<}lplKOIXNknmGQ;JQd3h?yh1`L`T6hQA31q>Y4pBZjEfDu_KA#~ z99Nf1=k1`8eyElG2*Shag z;!}^pQ3Gobqeet{s_f2>$AGctFR< zxX~`V(piK;Ub}w%gRE=zS53Qug#~L-F)=JUKb)I{p*dgIhr%{@XWU za&q#emM0^{VbCMO&cAN?Q!=YUpV}WBs+pK@iin6%Fp2u{#JhdL={#W0-?15DpgN3q zY7AlBTpndsRICbQ+t}VVZ~G|j=H}KpIH>OED9W#&%_S~Q|Ln^b&KsJVCn+crOG`hB zm^R$HbxWxVP59B=Jhhlp<>FkDHHE`z6+IcfSM-E6MiMm(8^y)Rc|=mOQ7)KS@=0QL zb_i~`i1e1Bp{BdLd*G0uae4I8l5KyM;lLL$4jGrFAIVxVshOFN2fsZ^OirGfoo&c8 zz__h9@sPMp2TMlFx(cGI2!$0!QG!N4+S<%|lC=_(lGa+qI^IS_H4dYy+V+2MG_|yt zf*{Ck_3D-rwujx^-Pb_>-oAUM4CP^9U;tYvV$sfmmypr*tV*_X(BikWwl*0ncefoY zXKKjHMb4#-)|nP@XTpbMaX+> zeu54xuo=jERC_!;A_Di-zz`nsEdzrlxc6cx7A6rBKhpGy{CDr@5PO^RzA-V(NbfEB zqA?GCI334_4l8N$g9lD6WzuQyLJ(9^dGl9w1#UY%L-^{X#UXCGje?A zxm+>_3rkK-4FDeqi;o{$66=1ctBOu&Gk0=MvvZtmk_$=CvgnA1`i_W>R)6$}8eA*iu>=MQGAy!nPBtGyf)e-qip9E7RVne zZjAT!^;H^L8ZewZdD0e}SAUT6<;x4>3XibZ*w($R#reJrMtBT2qlK$EhNsDJYaa_= z){gsoY%SEo(xXMq$aBiq$;ZaV?nTxcqAKdu*u9lO^XGfhXw^bk9`&W`ZM%-G1cro| zug^3sjh01y`gA@IgOPS!9^sCnq^9PPlN;%ZaSU^sYY&TvxUQ#155@)l)cv_k3F)502wyV;78&P&uWF%Fb90SQ#c1p{s9mg-!&JJ?s=UM0QOOZ|Qpr(g z+DNmo_wS$T5k|asM&$^bJ@4MX$EqcO@PIAXf%F$ohsdhy=vZ`qx=O*WupjOXMex9J z#&MI+6i4`2s8aUF+qk&4nwO`T?~)E+0RW@*Wb#bWp86b~oI4-X{aHa?}Ly{->rME1$BBvl}L z3Ok$nsgfhIhNHx^3;U_ZRT7z;8tffXKZscjRfU&yHUu-zkCb3&FNODaKgsg7Dk}80 zD!Qv2tNj;gv7SCta>SzTeMZ>-9l8EIgZimKTM2x9f?Du76&00wYQd_9)h{HF1FE{F zGSSHmw#fF6qIKpN2Pk9)gwxhz|o&R-O|fayO82* zHDims7!>7}954$GKXShxZ0C1%l1lu>9i zkkN^62jZx#f60`?GMx8!9iU`7(sWb4VQoRSV_wtp91#XhJ3~wB;Oc5Y*xPbm?9b{W zEIxbdJOAt!-m#yA!=lsTe^M>!HBS9c{Dy(*Vt)&~K1U;kklOd}f5fz$KA|f`+11%O zv2>u|vz2n8PQ-BN>bpbTQEn;G7f+DiO=LIrcHt9~(NK8yT4ev#Z zn5JfBRit**zM`gu4^32xaK&aD|lo`&|Dc+BYk3(naZFCQ~n<~lt!RWq01)4n`f_WI46BM*01 zCwKSuj+~~auaB4aT(VO<*fj;QFvvAIM!@e>O?j*U$a7w}fb+g=karZ(lDE5vJZuJy zwz9OGsC!Mjz2!3?C@07AEFgf)(aDLJNDxFv0^&G!o`>fZWC z<%%6+Vp;(@5=zg>%nSn3GIFkpF~sjj;kv$_C@3h%G%AY(JbxP-dsAIqy(}{$iB6km zu{$Y{uo4tR;pOd(Eq50Ki5zhq_ii({U;NX1^z{eKS5_s*y~M`7HYlMMFO$!`v{))D zEgh=GIR3l=nCQMzv9r3qK9v+@%&Dk&7>=uq37@_rZV*`yTl7KBeO+Y{y^xl&yE`4) z)X`x96$O@?fT-#;=YD0j8R93!0@zCow$+uPSibM|ZiIJKUh-puSQG*-`-LU4}9 zW@cgG;WbSWJnoxaYB{CHkw|2Dd3kD1jz#?&dS}4$K0*+i{6}E*Fen%=FR$J0VH6f) zK}~v`;!|E;I5FMdKtE&%ZeuOD2 zE9VcBjh<`IQCebipP?Z}u{ZSmzp$7T@2GD{KQ~rv~RAjFoJjcv7_xm%c!7`~F=iJtG4goZ{HAW1EWup_!Qi zl?Qu^HWkuS%6qNul;S`e(45C9&MLtO0rKe5NJ-Cu8>Exx&z~b8C!RTX?gp^@vyHN5 zuzPz|>=YMyim!^Y@-02Rr%ktkALGA*T|oqJ!40W+c*ww_3KW+oCmpb8I|;+GUjErT zQmmQ!ImSWEk|D5rgQ4yBBkLi0H37iYX-SSMr)bAXInIy~=NGn$P@79b1%VZwJ4c9I zRN^$3U4JovWAg)s^|6Ho#(whJacZ_8Xh1D3EeckdcZ|kv&)23KX<1lWFBgr}tWMSn z4Zo_YQUp#3VwH^qDh9&$%gI?0A0c;;fDun5Ch&AvAW+BOBv4YAi^!H8{q6H-r2X7> z#qnPn|CDR9DtJW$MK_{7VGz)!JyOXgxBlY-RMEuGpY_>BWwAyf4$jTdq|zI6o{-_d zkG|Do@E;zwL|#66^r(G@{LVL%dU6m&*8Uxx^*PU#@(n6bMsA1}u=b(3JCtN(mJ;mk zpRW22=3CJs0ChRIxnIHrWp8Jt17Z!X1*C{*BcRtODJg%}Ga~a9%`w<}`Ia=dZ{OZr z!bX5q84)67l%75nhfo0|-vj2|S=kXQU7M&85fBsv$=DxM39Kd{IJiIvrc`VYZ4pxT z6NsWw7iI)7$x#5#*Mo!itgWqC?i<>$$a}ogM|!{S?(XJSP=m1X=X@tMZ&4?s#=yv^ zZKf&09oQdRV8dA9v1Q#AWDhfena{7-z7!WncX!{papT6l0&99GTB0;9VLI|)m<8jf zrsif7{5Ed85G*7S4h8F8FB>PcPky$fgjz3sy4V1(dwGvvU%y`JFs%ygJ6P#0YgqKe zu8+BEsy>i}m@y3tZL7z}$2r7}f|*g}G@!-AQAc-@rXk&(lKi`I?;X>aCfOjevD-Q= z^zspHwsLQQAMeeh6Q2m7j4d*g~9XV@Oy`j3y*;P}c1a$CAv3oSO$K8t6#4u zbTcq8bOZXB<62r;*4Ean3T5}b84DfYk?CNesT8dsy)mk4KaNBKSxTOW@ctU z7hZxKT~G`8GvXH&CNSWkCMR&XHkja@Rq&FMmX?MH_xbuODUVHF(ARPhq$sEQ@1DGV(FE+pJA~*2b2{TVBD`? zmM?sLaX5g~b})5FnAPT(t|U!Hfb99P3fYxPydYHZ447F;ZZ3_YqT=1mdy|7%&TmUg zWuT_jZ`^Ra$6KXqY^)7F5NQT@t7L6`2@1P4TErLNa-TqenN7JZ9$+UTHa4iNOg2M5 z$7N`)eY!DpV`~5@W;^(e-`^2nt{et%UR!$d*coICsb`@DR7 z9Out}zshY?b%pkYG&&ra6!4>P{70qw9;D2w#{15%Ey!|q=GB!W|iba%z zo}lY{5hL#`(tWn@=;vJ8m@wm&ShbgK(_Oh>|q8K-3+e{%a0LO<#MiR#@ zQcklJP+?{@ekWVwWErJ=aBa=aH?$KJ+F&5{8{2_yP(1wI5olho=(Q`HmX;>BJK-w_ zMA1Gd5W*(k3~ftBMO8VK*v)8sHH&-au$MPho0xfkjP23a7Q}7(J9ACd+GC}!!#t|i zpYp<`OXSZWN3XB1uWoIbL4FW0EDVx%njOSnf{XznpE$}P#%^san4#Kg%pOwfnYiPMJit$sr~dU_F`gMF9u^=85H=D~M-x-bK(vK6*yjlK$E zL>ydzgqVGQMxy9;sG}8=BYN@hYevwA>-tQzVJ=_)BCdb>Ee6_<--P%7yYT2ldJjpq YdGp$Rd5)^V7d{9L6>a70YY$)i2g4Ry%>V!Z literal 0 HcmV?d00001 diff --git a/_images/6e851969c74483fc4efb36d87b6fcdd9ee1479e2274f2efebc840e7f3520ce6f.png b/_images/6e851969c74483fc4efb36d87b6fcdd9ee1479e2274f2efebc840e7f3520ce6f.png new file mode 100644 index 0000000000000000000000000000000000000000..e7463f1bb33b624df9e618f93dcc882447fe1093 GIT binary patch literal 2183 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 literal 0 HcmV?d00001 diff --git a/_sources/chap01.ipynb b/_sources/chap01.ipynb index 1ede4e9..ab9e073 100644 --- a/_sources/chap01.ipynb +++ b/_sources/chap01.ipynb @@ -55,7 +55,7 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py')\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", "\n", "import thinkpython" ] @@ -1437,6 +1437,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "06d3e72c", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "23adf208", @@ -1458,6 +1471,8 @@ "id": "ebf1a451", "metadata": {}, "source": [ + "Here are some topics you could ask a virtual assistant about:\n", + "\n", "* Earlier I mentioned bitwise operators but I didn't explain why the value of `7 ^ 2` is 5. Try asking \"What are the bitwise operators in Python?\" or \"What is the value of `7 XOR 2`?\"\n", "\n", "* I also mentioned the order of operations. For more details, ask \"What is the order of operations in Python?\"\n", diff --git a/_sources/chap02.ipynb b/_sources/chap02.ipynb index ad985d1..fda295c 100644 --- a/_sources/chap02.ipynb +++ b/_sources/chap02.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "1a0a6ff4", "metadata": { "tags": [ @@ -23,22 +23,7 @@ " return filename\n", "\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", - "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "3f2dcab4", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", "import thinkpython" ] @@ -69,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "id": "59f6db42", "metadata": {}, "outputs": [], @@ -89,7 +74,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "id": "1301f6af", "metadata": {}, "outputs": [], @@ -107,7 +92,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "id": "f7adb732", "metadata": {}, "outputs": [], @@ -128,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "id": "6bcc0a66", "metadata": {}, "outputs": [ @@ -138,7 +123,7 @@ "'And now for something completely different'" ] }, - "execution_count": 8, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -157,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "id": "3f11f497", "metadata": {}, "outputs": [ @@ -167,7 +152,7 @@ "42" ] }, - "execution_count": 9, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -178,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "id": "6b2dafea", "metadata": {}, "outputs": [ @@ -188,7 +173,7 @@ "6.283185307179586" ] }, - "execution_count": 10, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -207,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "id": "72c45ac5", "metadata": {}, "outputs": [ @@ -217,7 +202,7 @@ "3" ] }, - "execution_count": 11, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -228,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "id": "6bf81c52", "metadata": {}, "outputs": [ @@ -238,7 +223,7 @@ "42" ] }, - "execution_count": 12, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -260,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "id": "2c25e84e", "metadata": { "tags": [ @@ -282,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "id": "5b27a635", "metadata": { "tags": [ @@ -292,9 +277,9 @@ "outputs": [ { "data": { - "image/png": "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gXx8fLR+/Xq73RBXr15ttC5xd3dXyZIlVb58eeXKlUsZM2ZUeHi4jh07ZjWDkxQ/69Mff/xh93dsuV0vWLCgKlWqpHz58ilLliwKCwvT/v37jRZXZi+99JK+/vprm8sze//99zV16lSrx3x9fVWxYkVlypRJly5dkr+/v9XgztWrV9fatWvtBj0jRozQrFmzlCFDBlWpUkVlypRR9uzZFRISogMHDuj8+fNG2eLFi2vNmjUqWrRoovW0FBMTozJlyhhB0ocffqi3337b6f83u379usqUKSMpvtWXr6+vChQooPv37+vSpUvauXOn1Yx777//vsNWaUWLFjXCeB8fH1WpUkX58+dX9uzZFRUVpQsXLujAgQNWn2f27Nm1dOlS1a9fP8nvAQCAx1l6DSHBGD1weUOHDlVoaKi+/PJLnT592pjC11LBggU1d+5cpwZWRfrKlSuXOnbsqCNHjsjb21tVq1Z9pBvQzJkzG7djY2N19OhRHT161G75HDly6N1339Wrr76apEFWE3PixIkErTgs3blzxypMMXt4ivS0FhYWZrMelmy1orEMlOy5c+eO9uzZoz179th83jxV+ldffWU1q48jQUFBVmOwWKpWrZq+/fZb+fr6JrqM0NBQq9nf7ty5o59//tnpOnTs2DHRoKdGjRpasWKFXn75ZZ09e1YxMTH6999/E5TLkCGDhg8fro8++sip142OjtaBAwcSbYnRvHlzTZs2zWhVZEumTJmM2w8ePEj0MzWXHz58uEaNGuX0bEtXr1616u71MG9vb02YMMGprrgTJ05Uvnz5NHHiRKObZWKfQ+fOnTVlyhSnZnGLjY3V/v37jUGMH9alSxd9/vnniQaRtqxbt84Iedzd3dWrV68k/b8t169f1/r1620+V6BAAX322Wfq3Lmzw+VYfv8hISHavHlzouUbNWqkyZMnP1bjlwEA8KQj6MF/wtixY9WyZUvNmTNHO3bs0LVr15QxY0aVLFlS7du310svvfTIp9xG8pUoUUIlSpRIl9d+8cUX1bhxY23evFl79+7V8ePHdfHiReMKdo4cOVSgQAFVqVJFTZo0UYcOHex2G0HSmE80d+/erb179+rq1asKDQ1VWFiYMmTIIG9vb5UrV0516tRRz549nR4o97nnntPChQuN7/T69eu6efOmPD09lT9/ftWoUUMdO3ZUy5Yt5e7+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WGPGjNHx48eT+lFp06ZNGjhwoCpWrKi8efOqTJkyatWqlX766SfduXNHUvKmzNy8ebNGjBih2rVr66mnnlKePHlUtmxZdezYUT/++KPu3r2b5Loi9Z09e1arV6/Wtm3bdPv27fSujkOWQU779u2VLVs29ezZ03hs6dKlVgcVialcuXKCqT0vX76sCRMmqH79+ipatKgKFCigGjVq6O2339aFCxeSVNetW7dq8ODBqlSpktVva+bMmWk2k1Nav6cLFy5o4sSJatasmUqVKiUfHx+VKlVKzZo10yeffKJLly4l+v+dOnUy6rdhwwa75Sy3OV5eXvrrr7/slv3iiy+MchMmTEjS+5HiB801/795QE9Jatu2rVUdEpvyOiQkRN98841at26tMmXKKE+ePCpevLgaNmyosWPHOrVtPn/+vM3pY/39/TVs2DDVqFFDRYoUkZeXl0aPHp3k92kWGRmpX375RV27dlXFihWVP39+5c6dW0WKFFGNGjXUvXt3ffXVVzp69KjDZd2/f1/z589Xr169jClwCxcurOrVq+u1117Tpk2bnKqTrfX29OnTGjt2rOrUqaMiRYooX758ql+/vr766iubv59Tp07prbfeUt26dVW4cGEVLVpUzZo1008//aS4uLgkfUYnTpzQ+PHj1aRJE2M9L1GihJo2baqJEyfq6tWrDt+L5bbq1VdfTbAuObs/DQ0N1eTJk9W4cWMVL15c+fLl09NPP63XXnvNqe8oJdMk//nnn+revbsqVqyoPHnyqGTJkurQoYMWL14sk8nk8LXNwsLC9MUXX6hx48Z66qmnVLBgQVWvXl3Dhg3Tvn37jHK2jiVTS2BgoD766CM1bdpUpUuXlo+Pj1GPgQMH6tdff9WtW7ccLmf37t166623jGObvHnzqnz58urUqZN+/PFH47gpMba+k3v37mnWrFlq06aNypQpIx8fH5UvX14vv/yyze1HZGSkfvrpJ7Vs2VJlypRR3rx59fTTT+vNN9/U5cuXHdbB1m/u5MmTGj16tGrVqqXChQurcOHCqlevnj7++GNdv37d4TKTIyXH685utx3NGHTnzh39/PPP6t69uypVqqT8+fOrUKFC8vX11dChQ7Vly5YUvceYmBiVKFHCqMuuXbuc/t/WrVsb/zdjxowU1cMstY5NEpte3XIdd7QtXLBggdUsrJbHJVWqVElQ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5zfKv/fee0795jJlymRyd3c3TZo0KcH72r17t6lChQpG2fz589s9JnZmXUvt4/WHfzeOttvmv1WrVpnc3d2N9z9+/HjTtWvXEpTbtm2bqXz58sbyX3jhBZvLW7NmjVGmYcOGDus5ffr0ROsXFhZmKlCggFH+zz//TNHvIS2OTSy3G71797b72kndFlqum4lts83rvHk/4+bmZnr99ddtHiscPHjQVK9ePav9taPvMUOGDCZJpkqVKpn8/f0TlLXcD6f2/iO99gVPwl96+U+P0WPJzc1Nq1atstmEfvjw4UbXpNjYWL333nvKmzev1q5dqypVqliV9fDw0CeffGIkv5GRkVq/fr3N11yyZIlxdbhWrVpau3atqlatmqBclixZ9O677xrjG9y5c0fffvttgnI7duyQFH91dsaMGUbzcFvLa9Wqlc2rhOZlSNL777+faEpaoUIFjR8/XjVr1rT5/Lhx44yxLWxxc3NT69atrbph/fjjjzbLxsXFWfXTHzx4sD755BNlzpzZqlzevHm1aNEiVa9e3e5VLkvTpk0zrs60b99ev//+u0qWLJmgnJeXlyZPnqzevXtLiu/OlZpdE+A8T09P9ejRQ76+vja7k5hb+MyaNUsbN25M1zF8Ll++bNVP3nJcHknGVWcpvjn+77//nqTlx8TE6Ndff1XPnj0TtLaoUKGC1W98xYoVNscZe/DggdUYMX369NFXX32VoGtDgQIF9Ntvv6ly5cqKiYlJUj2TIrXe07hx44z7nTp10vTp0xNsi7y8vPTTTz+pbdu2xmMffPBBgq40GTJkUN26dSXFr1+W20kzy9Y7I0aMMOrhqOyj7rY1btw4o3l2nTp1tGjRogRXCDNnzqwJEyZY9Xv/7LPPHP6W4uLilDVrVq1cudJm99aHt9fO8Pf3N25///33Nq96S/HfUcOGDe12l54+fbrRfNzb21t//vmnqlWrlqBcjx499PPPPxv3//rrL6MbQmJiYmI0f/58m60hunTpor59+xr33333XWXKlEmrVq2yai1mNmzYMOOY48GDB3a7QO/YsUOLFi2SFH8VdsOGDTbXJw8PD7344ovGbycuLk6ff/65w/eUHNHR0frmm280bNiwBN3nihQpolmzZhm/az8/P4ctg5PyuvXr19eSJUsSdG/IkCGDJkyYoOrVq0uSTCaTza5MUvz4HadPn5Yk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XbqkDBkyqECBAqpbt6769OmT5CbxmTNn1uzZs9W3b1/9+uuv2r17t27cuCFvb2+VLFlSXbp0Ud++fZUtWzarfqzmQZftyZ07t+bOnaujR49q6dKl8vPz0/nz5xUaGip3d3flzJlTJUuWVNWqVdW0aVM1bdo03bpeID6cs+zi9DjYsGGDVReY5DZtLV26tKpXr250wVywYMEjCQYyZsyoH374Qb169dLs2bO1a9cuq99Wp06d1K9fv3TpvpMSo0aNUs+ePTV37lxt3LhR58+fN6auL168uJ599lkNGDBARYsWdXqZD38fiX0/OXPmVNWqVY0DucyZM1vNrpNe+vfvr7Zt22rOnDnasGGDgoKCFBYWpuzZs6tIkSJq0qSJ+vXrp/Lly6drPc+dO6ft27dr27ZtCggI0OnTp42pz7NmzapChQrJ19dXHTp0UJs2bRLtdidJtWvX1t69e7VkyRKtXr1ahw8f1o0bN5QxY0bly5dP9erVU9euXZ+oIMPDw0Njx47VkCFDtGjRIv377786ceKEQkJCdP/+feXIkUNPPfWU0XKrVatWiQZYL7zwgipVqqRZs2Zp7969unr1qnHB5r+iadOm2rt3r3766SetWbNG586d0/3791WwYEE1aNBAgwYNUo0aNazG6kvNcW1y586tBQsWaN++ffr999+1bds2Xb58WeHh4fL09FShQoVUpUoVNW/eXB07drS7nOzZs2vOnDl69dVXtXjxYm3btk3Xrl3T3bt35ePjowoVKui5555Tv379HHahfxy98MILql+/vn755Rdt3rzZaPnw1FNPqXXr1nrllVfshvDJkRrH65Y++ugj1a1bVwsWLNCBAwcUHBxsd9gES88884z27t2r1atXa/369dqzZ4+uX7+u27dvK2vWrMqbN6/Kli2rOnXqqEWLFlYzWKWGKlWqqHjx4kYInBatecxc9dhEiv8ct2zZoo0bN2r16tXatWuXrl69qlu3bhnnXKVLl1bNmjXVvHlz1a5dO9UvuqT2/iMttG7dWlu2bNHMmTPl7++vS5cuKTIy8j+1T0ouN1M6fUq3b99Oj5fFf0iLFi2MFkQbN258LE6uAACAa9i0aZMRtDRv3txoOYO0UblyZWNG1cOHDz8x3cxczfnz5/X000/LZDIpW7ZsOnHiRKq1aANcUXr1PGAwZrikCxcuGIPEZcqUKc2alAIAgP8my2Dn4S42gKuaN2+e0ZqiU6dOhDzAY4qgBy7HZDJp9OjRxgCD7dq1o5sVAABINXv27LEaK6J79+7pWBvg0bh3757mzp1r3LecpRDA44WgB0+UiRMnavr06QoJCbH5/Pnz59W7d29jZi4PDw+9/vrrj7KKAADgCXXx4kX1799f/v7+NseAiIuL0+LFi9W5c2dj2vM2bdqobNmyj7qqwCM3YcIEXb9+XVL8LI6PcmBeAEnDYMx4oly6dElffPGFxo4dq0qVKqlMmTLKmTOnIiMjdfLkSR06dMhoySNJ77zzDs2pAQCAUx48eKAVK1ZoxYoVyps3r3x9fVWgQAF5eHgoODhYu3fvtpq2ukCBApoyZUr6VRhIQ+Yp3O/du6d9+/bp0KFDkuJnwh03blz6Vg5Aogh68ESKjY1NdOp3T09Pvf/++7TmAQAAyXLjxg1t2LDB7vPVqlXTvHnzUnVmJ+BxsmfPHs2YMSPB46+//nqSZ84F8GgR9OCJ8tlnn6lRo0baunWrjh8/rps3byokJERxcXHKlSuXypQpo8aNG6t///4ceAEAgCQpVqyYNm3apL/++kt79+7V5cuXFRISolu3bil79uzKmzevateureeff16tW7dO7+oCj0zWrFlVsWJFvfjii+rdu3d6VweAA0yvDgAAAAAAkMqYXh0AAAAAAAApQtADAAAAAADgIgh6AAAAAAAAXARBDwAAAAAAgIsg6AEAAAAAAHARBD0AAAAAAAAugqAHAAAAAADARRD0AAAAAAAAuAiCHgAAAAAAABdB0AMAAAAAAOAiCHoAAAAAAABcBEEPAAAAAACAiyDoAQAAAAAAcBEEPQAAAAAAAC6CoAcAAAAAAMBFEPQAAAAAAAC4CIIeAAAAAAAAF0HQAwAAAAAA4CIIegAAAAAAAFwEQQ8AAAAAAICLIOgBAAAAAABwEQQ9AAAAAAAALoKgBwAAAAAAwEUQ9AAAAAAAALgIgh4AAAAAAAAXQdADAAAAAADgIgh6AAAAAAAAXARBDwAAAAAAgIsg6AEAAAAAAHARbiaTyZTelQAAAAAAAEDK0aIHAAAAAADARRD0AAAAAAAAuAiCHgAAAAAAABdB0AMAAAAAAOAiCHoAAAAAAABcBEEPAAAAAACAiyDoAQAAAAAAcBEEPQAAAAAAAC6CoAcAAAAAAMBFEPQAAAAAAAC4CIIeAAAAAAAAF0HQAwAAAAAA4CIIegAAAAAAAFwEQQ8AAAAAAICLIOgBAAAAAABwEQQ9AAAAAAAALoKgBwAAAAAAwEUQ9AAAAAAAALgIgh4AAAAAAAAXQdADAAAAAADgIgh6AAAAAAAAXARBDwAAAAAAgIsg6AEAAAAAAHARBD0AAAAAAAAugqAHAAAAAADARRD0AAAAAAAAuAiCHgAAAAAAABdB0AMAAAAAAOAiCHoAAAAAAABcBEEPAAAAAACAiyDoAQAAAAAAcBEEPQAAAAAAAC6CoAcAAAAAAMBFEPQAAAAAAAC4CIIeAAAAAAAAF0HQAwAAAAAA4CIIegAAAAAAAFwEQQ8AAAAAAICLIOgBAAAAAABwEQQ9AAAAAAAALoKgBwAAAAAAwEUQ9AAAAAAAALgIgh4AAAAAAAAXQdADAAAAAADgIgh6AAAAAAAAXARBDwAAAAAAgIsg6AEAAAAAAHARBD0AAAAAAAAugqAHAAAAAADARRD0AAAAAAAAuAiCHgAAAAAAABdB0AMAAAAAAOAiCHoAAAAAAABcBEEPAAAAAACAiyDoAQAAAAAAcBEEPQAAAAAAAC6CoAcAAAAAAMBFEPQAAAAAAAC4CIIeAAAAAAAAF0HQAwAAAAAA4CL+DzWa9F4WyVfbAAAAAElFTkSuQmCC", + "image/png": "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Qj+H1/7MnTtXTZgwodo8qqu/Y8aMUZ6enurs2bMqPz9f3XnnnSo0NFSdO3dO5eTkKCcnJ5Wamlrv/OnUqZNKTk5u0PgtW7ZMDRs2rEHnq127dio9PV1pmqamT59e6/maMWOGCgkJUXl5eVXeIzfq2P38/JTZbFbnzp1Tv//+u+rTp49atWpVtfMXERGhli9frjRNU4WFhWrcuHFqzpw51cppmqZWrVqlxo8fr86fP680TVMrVqxQQ4YM0cfv6aefVpqmqUWLFqmHH35Yr+fu7q4SExOrjUF8fLxydXVVv/zyiyosLFTh4eGqU6dOStM09d133yl7e/sa59y1v2dnZ6vevXvr75MzZ86ojh07qh9//FF99913CtCPsSHzYuLEiXo7dnZ26scff6yxD9f701A37dGLDz30EAB9+vShuLhY/71v376cPn0aqFjFnT59mvvvv1+vZ2lpyc8//4zJZOLf//43xcXFmEwmgoKCADCbzUyfPp0RI0YwYMAAfcV47NgxFixYQEFBAVZWVqSnp3P58mUyMjKwsrIiICAAgICAAO68804AiouL2bdvH3l5efr+L168SHp6erXjOXz4ML/99tuNHqZaubi4cP78eRITE9m9ezdhYWE4OzvXWj4+Pp5Bgwbh4OCAg4MDbdu21a+mAO666y48PT0B6N+/P4sWLQJg3759DB48WG/7iSee4N133611P4MGDdKvNPr3709aWhpQcZtp7Nix2NjYABAREcEHH3zA1KlTCQwMZO/eveTn5zNgwABOnjzJ999/z969ewkNDa13LL755ht69uypX26PGjWKl156ST8fXbp0wc/Pr8a6np6eLF26lJkzZ2IymfTVW139BRg2bBj/+Mc/AOjZsycDBw7E2toaa2trXF1dycjIoFOnTg2eP/WN37XqOl+DBg2iXbt2er/feuutGttITEwkOjpaP8Y2bdrcsGN3c3MDYPTo0fq28PBwkpKSCA8Pr9KPhIQEDh8+rF9NXr58mSZNmtTY523btvHtt9/q79erV6/WWG7UqFG8+eabnD17loyMDCwsLPD19a1Wbt++fYwcOVJfNT/++OOkpKTU2GZdUlNTyczM1HOsUnp6Ol26dMHa2prRo0cDDcuVsLAwoOKqvUuXLmRlZdGhQ4fr7tdfddOCv3JSVZ7gyonUpEkT/R6YUoqgoCDWrFlTrf7d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", "text/plain": [ - "
" + "
" ] }, "metadata": {}, @@ -339,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 12, "id": "ac2620ef", "metadata": { "tags": [ @@ -352,7 +337,7 @@ "evalue": "invalid syntax (4061783710.py, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[15], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m million! = 1000000\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + "\u001b[0;36m Cell \u001b[0;32mIn[12], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m million! = 1000000\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], @@ -371,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 13, "id": "1a8b8382", "metadata": { "tags": [ @@ -384,7 +369,7 @@ "evalue": "invalid decimal literal (3636686625.py, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[16], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m 76trombones = 'big parade'\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid decimal literal\n" + "\u001b[0;36m Cell \u001b[0;32mIn[13], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m 76trombones = 'big parade'\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid decimal literal\n" ] } ], @@ -403,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "id": "b6938851", "metadata": { "tags": [ @@ -416,7 +401,7 @@ "evalue": "invalid syntax (3285659805.py, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[17], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m class = 'Self-Defence Against Fresh Fruit'\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + "\u001b[0;36m Cell \u001b[0;32mIn[14], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m class = 'Self-Defence Against Fresh Fruit'\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], @@ -454,7 +439,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 15, "id": "4a8f4b3e", "metadata": { "tags": [ @@ -468,7 +453,7 @@ "35" ] }, - "execution_count": 18, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -502,7 +487,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 16, "id": "98c268e9", "metadata": {}, "outputs": [], @@ -522,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 17, "id": "47bc17c9", "metadata": {}, "outputs": [ @@ -532,7 +517,7 @@ "3.141592653589793" ] }, - "execution_count": 20, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -554,7 +539,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 18, "id": "fd1cec63", "metadata": {}, "outputs": [ @@ -564,7 +549,7 @@ "5.0" ] }, - "execution_count": 21, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -583,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 19, "id": "87316ddd", "metadata": {}, "outputs": [ @@ -593,7 +578,7 @@ "25.0" ] }, - "execution_count": 22, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -627,7 +612,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 20, "id": "7f0b92df", "metadata": {}, "outputs": [ @@ -637,7 +622,7 @@ "42" ] }, - "execution_count": 23, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -658,7 +643,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 21, "id": "b882c340", "metadata": {}, "outputs": [], @@ -676,7 +661,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 22, "id": "299817d8", "metadata": {}, "outputs": [], @@ -705,7 +690,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 23, "id": "805977c6", "metadata": {}, "outputs": [ @@ -715,7 +700,7 @@ "18" ] }, - "execution_count": 26, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -734,7 +719,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 24, "id": "962e08ab", "metadata": {}, "outputs": [ @@ -744,7 +729,7 @@ "20" ] }, - "execution_count": 27, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -764,7 +749,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 25, "id": "a797e44d", "metadata": {}, "outputs": [ @@ -792,7 +777,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 26, "id": "73428520", "metadata": {}, "outputs": [ @@ -820,7 +805,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 27, "id": "9ad5bddd", "metadata": {}, "outputs": [ @@ -859,7 +844,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 28, "id": "060c60cf", "metadata": {}, "outputs": [ @@ -869,7 +854,7 @@ "101" ] }, - "execution_count": 31, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -888,7 +873,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 29, "id": "2875d9e0", "metadata": {}, "outputs": [ @@ -898,7 +883,7 @@ "25.0" ] }, - "execution_count": 32, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -918,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 30, "id": "43b9cf38", "metadata": {}, "outputs": [ @@ -928,7 +913,7 @@ "5" ] }, - "execution_count": 33, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -949,7 +934,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 31, "id": "e8a21d05", "metadata": {}, "outputs": [ @@ -959,7 +944,7 @@ "3.142" ] }, - "execution_count": 34, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -978,7 +963,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 32, "id": "724128f4", "metadata": {}, "outputs": [ @@ -1004,7 +989,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 33, "id": "69295e52", "metadata": { "tags": [ @@ -1036,7 +1021,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 34, "id": "edec7064", "metadata": { "tags": [ @@ -1068,7 +1053,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 35, "id": "f86b2896", "metadata": { "tags": [ @@ -1114,7 +1099,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 36, "id": "607893a6", "metadata": {}, "outputs": [], @@ -1134,7 +1119,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 37, "id": "615a11e7", "metadata": {}, "outputs": [], @@ -1158,7 +1143,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 38, "id": "cc7fe2e6", "metadata": {}, "outputs": [], @@ -1176,7 +1161,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 39, "id": "7c93a00d", "metadata": {}, "outputs": [], @@ -1220,7 +1205,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 40, "id": "86f07f6e", "metadata": { "tags": [ @@ -1233,7 +1218,7 @@ "evalue": "invalid syntax (4061783710.py, line 1)", "output_type": "error", "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[43], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m million! = 1000000\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + "\u001b[0;36m Cell \u001b[0;32mIn[40], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m million! = 1000000\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], @@ -1252,7 +1237,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 41, "id": "682395ea", "metadata": { "tags": [ @@ -1285,7 +1270,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 42, "id": "2ff25bda", "metadata": {}, "outputs": [ @@ -1295,7 +1280,7 @@ "2.5" ] }, - "execution_count": 45, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -1376,6 +1361,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9e6cab4", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "7256a9b2", @@ -1432,7 +1430,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 43, "id": "18de7d96", "metadata": {}, "outputs": [ @@ -1442,7 +1440,7 @@ "523.5987755982989" ] }, - "execution_count": 46, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } @@ -1464,7 +1462,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 44, "id": "de812cff", "metadata": {}, "outputs": [ @@ -1474,7 +1472,7 @@ "1.0" ] }, - "execution_count": 47, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -1500,7 +1498,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 45, "id": "b4ada618", "metadata": {}, "outputs": [ @@ -1510,7 +1508,7 @@ "7.3890560989306495" ] }, - "execution_count": 48, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } @@ -1519,7 +1517,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 46, "id": "4424940f", "metadata": {}, "outputs": [ @@ -1529,7 +1527,7 @@ "7.3890560989306495" ] }, - "execution_count": 49, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -1538,7 +1536,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 47, "id": "50e8393a", "metadata": {}, "outputs": [ @@ -1548,7 +1546,7 @@ "7.38905609893065" ] }, - "execution_count": 50, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } diff --git a/_sources/chap03.ipynb b/_sources/chap03.ipynb index 677e7dd..a5e025f 100644 --- a/_sources/chap03.ipynb +++ b/_sources/chap03.ipynb @@ -4,7 +4,11 @@ "cell_type": "code", "execution_count": null, "id": "103cbe3c", - "metadata": {}, + "metadata": { + "tags": [ + "remove-cell" + ] + }, "outputs": [], "source": [ "from os.path import basename, exists\n", @@ -19,24 +23,9 @@ " return filename\n", "\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", - "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "d4b444ba", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "import thinkpython\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -978,12 +967,25 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f77b428", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "82951027", "metadata": {}, "source": [ - "### Ask an assistant\n", + "### Ask a virtual assistant\n", "\n", "The statements in a function or a `for` loop are indented by four spaces, by convention.\n", "But not everyone agrees with that convention.\n", diff --git a/_sources/chap04.ipynb b/_sources/chap04.ipynb index 36aa9ad..2ea6c6a 100644 --- a/_sources/chap04.ipynb +++ b/_sources/chap04.ipynb @@ -22,8 +22,8 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py')\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py')\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "download('https://github.com/ramalho/jupyturtle/releases/download/2024-03/jupyturtle.py');" ] }, @@ -1427,11 +1427,38 @@ }, { "cell_type": "markdown", - "id": "50ed5c38", + "id": "0bfe2e19", "metadata": {}, "source": [ - "## Exercises\n", + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "9f94061e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exception reporting mode: Verbose\n" + ] + } + ], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", "\n", + "%xmode Verbose" + ] + }, + { + "cell_type": "markdown", + "id": "50ed5c38", + "metadata": {}, + "source": [ "For the exercises below, there are a few more turtle functions you might want to use.\n", "\n", "* `penup` lifts the turtle's imaginary pen so it doesn't leave a trail when it moves.\n", @@ -3945,7 +3972,7 @@ "id": "9d9f35d1", "metadata": {}, "source": [ - "### Ask an assistant\n", + "### Ask a virtual assistant\n", "\n", "There are several modules like `jupyturtle` in Python, and the one we used in this chapter has been customized for this book.\n", "So if you ask a virtual assistant for help, it won't know which module to use.\n", diff --git a/_sources/chap05.ipynb b/_sources/chap05.ipynb index 8639498..1cfc355 100644 --- a/_sources/chap05.ipynb +++ b/_sources/chap05.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 80, "id": "8119ba50", "metadata": { "tags": [ @@ -22,24 +22,11 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py')\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py')\n", - "download('https://github.com/ramalho/jupyturtle/releases/download/2024-03/jupyturtle.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "9798ca97", - "metadata": {}, - "outputs": [], - "source": [ - "import thinkpython\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "download('https://github.com/ramalho/jupyturtle/releases/download/2024-03/jupyturtle.py');\n", "\n", - "thinkpython.traceback('Minimal')\n", - "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -71,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 81, "id": "30bd0ba7", "metadata": {}, "outputs": [ @@ -81,7 +68,7 @@ "1.75" ] }, - "execution_count": 3, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -102,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 82, "id": "451e3198", "metadata": {}, "outputs": [ @@ -112,7 +99,7 @@ "1" ] }, - "execution_count": 4, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -133,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 83, "id": "64b92876", "metadata": {}, "outputs": [ @@ -143,7 +130,7 @@ "45" ] }, - "execution_count": 5, + "execution_count": 83, "metadata": {}, "output_type": "execute_result" } @@ -163,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 84, "id": "0a593844", "metadata": {}, "outputs": [ @@ -173,7 +160,7 @@ "45" ] }, - "execution_count": 6, + "execution_count": 84, "metadata": {}, "output_type": "execute_result" } @@ -201,7 +188,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 85, "id": "db33a44d", "metadata": {}, "outputs": [ @@ -211,7 +198,7 @@ "2" ] }, - "execution_count": 7, + "execution_count": 85, "metadata": {}, "output_type": "execute_result" } @@ -233,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 86, "id": "367fce0c", "metadata": {}, "outputs": [ @@ -243,7 +230,7 @@ "(2, 5)" ] }, - "execution_count": 8, + "execution_count": 86, "metadata": {}, "output_type": "execute_result" } @@ -267,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 87, "id": "85589d38", "metadata": {}, "outputs": [ @@ -277,7 +264,7 @@ "True" ] }, - "execution_count": 9, + "execution_count": 87, "metadata": {}, "output_type": "execute_result" } @@ -288,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 88, "id": "3c9c8f61", "metadata": {}, "outputs": [ @@ -298,7 +285,7 @@ "False" ] }, - "execution_count": 10, + "execution_count": 88, "metadata": {}, "output_type": "execute_result" } @@ -318,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 89, "id": "c0e51bcc", "metadata": {}, "outputs": [], @@ -329,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 90, "id": "a6be44db", "metadata": {}, "outputs": [ @@ -339,7 +326,7 @@ "False" ] }, - "execution_count": 12, + "execution_count": 90, "metadata": {}, "output_type": "execute_result" } @@ -359,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 91, "id": "90fb1c9c", "metadata": {}, "outputs": [ @@ -369,7 +356,7 @@ "bool" ] }, - "execution_count": 13, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } @@ -380,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 92, "id": "c1cae572", "metadata": {}, "outputs": [ @@ -390,7 +377,7 @@ "bool" ] }, - "execution_count": 14, + "execution_count": 92, "metadata": {}, "output_type": "execute_result" } @@ -409,7 +396,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 93, "id": "c901fe2b", "metadata": {}, "outputs": [ @@ -419,7 +406,7 @@ "True" ] }, - "execution_count": 15, + "execution_count": 93, "metadata": {}, "output_type": "execute_result" } @@ -430,7 +417,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 94, "id": "1457949f", "metadata": {}, "outputs": [ @@ -440,7 +427,7 @@ "False" ] }, - "execution_count": 16, + "execution_count": 94, "metadata": {}, "output_type": "execute_result" } @@ -451,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 95, "id": "56bb7eed", "metadata": {}, "outputs": [ @@ -461,7 +448,7 @@ "True" ] }, - "execution_count": 17, + "execution_count": 95, "metadata": {}, "output_type": "execute_result" } @@ -472,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 96, "id": "1cdcc7ab", "metadata": {}, "outputs": [ @@ -482,7 +469,7 @@ "False" ] }, - "execution_count": 18, + "execution_count": 96, "metadata": {}, "output_type": "execute_result" } @@ -493,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 97, "id": "df1a1287", "metadata": {}, "outputs": [ @@ -503,7 +490,7 @@ "True" ] }, - "execution_count": 19, + "execution_count": 97, "metadata": {}, "output_type": "execute_result" } @@ -527,7 +514,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 98, "id": "848c5f2c", "metadata": {}, "outputs": [ @@ -537,7 +524,7 @@ "True" ] }, - "execution_count": 20, + "execution_count": 98, "metadata": {}, "output_type": "execute_result" } @@ -556,7 +543,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 99, "id": "eb66ee6a", "metadata": {}, "outputs": [ @@ -566,7 +553,7 @@ "False" ] }, - "execution_count": 21, + "execution_count": 99, "metadata": {}, "output_type": "execute_result" } @@ -585,7 +572,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 100, "id": "6de8b97c", "metadata": {}, "outputs": [ @@ -595,7 +582,7 @@ "True" ] }, - "execution_count": 22, + "execution_count": 100, "metadata": {}, "output_type": "execute_result" } @@ -615,7 +602,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 101, "id": "add63275", "metadata": {}, "outputs": [ @@ -625,7 +612,7 @@ "True" ] }, - "execution_count": 23, + "execution_count": 101, "metadata": {}, "output_type": "execute_result" } @@ -658,7 +645,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 102, "id": "80937bef", "metadata": {}, "outputs": [ @@ -694,7 +681,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 103, "id": "bc74a318", "metadata": {}, "outputs": [], @@ -724,7 +711,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 104, "id": "d16f49f2", "metadata": {}, "outputs": [ @@ -771,7 +758,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 105, "id": "309fccb8", "metadata": {}, "outputs": [ @@ -821,7 +808,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 106, "id": "d77539cf", "metadata": {}, "outputs": [ @@ -861,7 +848,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 107, "id": "91cac1a0", "metadata": {}, "outputs": [ @@ -889,7 +876,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 108, "id": "f8ba1724", "metadata": {}, "outputs": [ @@ -916,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 109, "id": "014cd6f4", "metadata": {}, "outputs": [ @@ -947,7 +934,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 110, "id": "17904e98", "metadata": {}, "outputs": [], @@ -973,7 +960,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 111, "id": "6c1e32e2", "metadata": {}, "outputs": [ @@ -1027,7 +1014,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 112, "id": "1bb13f8e", "metadata": {}, "outputs": [], @@ -1052,7 +1039,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 113, "id": "e7b68c57", "metadata": {}, "outputs": [ @@ -1093,7 +1080,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 114, "id": "643148da", "metadata": { "tags": [ @@ -1115,7 +1102,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 115, "id": "a8510119", "metadata": { "tags": [ @@ -1156,7 +1143,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 116, "id": "a2a376b3", "metadata": { "tags": [ @@ -1208,7 +1195,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 117, "id": "af487feb", "metadata": {}, "outputs": [], @@ -1229,7 +1216,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 118, "id": "e5d6c732", "metadata": { "tags": [ @@ -1251,7 +1238,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 119, "id": "22454b51", "metadata": { "tags": [ @@ -1266,11 +1253,11 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[41], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "Cell \u001b[0;32mIn[39], line 2\u001b[0m, in \u001b[0;36mrecurse\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrecurse\u001b[39m():\n\u001b[0;32m----> 2\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "Cell \u001b[0;32mIn[39], line 2\u001b[0m, in \u001b[0;36mrecurse\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrecurse\u001b[39m():\n\u001b[0;32m----> 2\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[119], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[117], line 2\u001b[0m, in \u001b[0;36mrecurse\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrecurse\u001b[39m():\n\u001b[0;32m----> 2\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[117], line 2\u001b[0m, in \u001b[0;36mrecurse\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrecurse\u001b[39m():\n\u001b[0;32m----> 2\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", " \u001b[0;31m[... skipping similar frames: recurse at line 2 (2958 times)]\u001b[0m\n", - "Cell \u001b[0;32mIn[39], line 2\u001b[0m, in \u001b[0;36mrecurse\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrecurse\u001b[39m():\n\u001b[0;32m----> 2\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[117], line 2\u001b[0m, in \u001b[0;36mrecurse\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrecurse\u001b[39m():\n\u001b[0;32m----> 2\u001b[0m \u001b[43mrecurse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded" ] } @@ -1307,7 +1294,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 120, "id": "ac0fb4a6", "metadata": { "tags": [ @@ -1319,7 +1306,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 121, "id": "f6a2e4d6", "metadata": {}, "outputs": [], @@ -1338,7 +1325,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 122, "id": "e0600e5e", "metadata": { "tags": [ @@ -1350,7 +1337,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 123, "id": "964346f0", "metadata": {}, "outputs": [ @@ -1368,7 +1355,7 @@ "'It is Arthur, King of the Britons'" ] }, - "execution_count": 45, + "execution_count": 123, "metadata": {}, "output_type": "execute_result" } @@ -1390,7 +1377,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 124, "id": "590983cd", "metadata": { "tags": [ @@ -1403,7 +1390,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 125, "id": "60a484d7", "metadata": {}, "outputs": [ @@ -1421,7 +1408,7 @@ "'What do you mean: an African or European swallow?'" ] }, - "execution_count": 47, + "execution_count": 125, "metadata": {}, "output_type": "execute_result" } @@ -1442,7 +1429,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 126, "id": "8d3d6049", "metadata": { "tags": [ @@ -1464,7 +1451,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 127, "id": "a04e3016", "metadata": { "tags": [ @@ -1516,7 +1503,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 128, "id": "b82642f6", "metadata": { "tags": [ @@ -1529,7 +1516,7 @@ "evalue": "unexpected indent (2365500740.py, line 2)", "output_type": "error", "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[50], line 2\u001b[0;36m\u001b[0m\n\u001b[0;31m y = 6\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mIndentationError\u001b[0m\u001b[0;31m:\u001b[0m unexpected indent\n" + "\u001b[0;36m Cell \u001b[0;32mIn[128], line 2\u001b[0;36m\u001b[0m\n\u001b[0;31m y = 6\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mIndentationError\u001b[0m\u001b[0;31m:\u001b[0m unexpected indent\n" ] } ], @@ -1553,7 +1540,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 129, "id": "583ef53c", "metadata": { "tags": [ @@ -1575,7 +1562,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 130, "id": "2f4b6082", "metadata": { "tags": [ @@ -1590,7 +1577,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[52], line 5\u001b[0m\n\u001b[1;32m 3\u001b[0m denominator \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m\n\u001b[1;32m 4\u001b[0m ratio \u001b[38;5;241m=\u001b[39m numerator \u001b[38;5;241m/\u001b[39m\u001b[38;5;241m/\u001b[39m denominator\n\u001b[0;32m----> 5\u001b[0m decibels \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m \u001b[38;5;241m*\u001b[39m \u001b[43mmath\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlog10\u001b[49m\u001b[43m(\u001b[49m\u001b[43mratio\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[130], line 5\u001b[0m\n\u001b[1;32m 3\u001b[0m denominator \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m\n\u001b[1;32m 4\u001b[0m ratio \u001b[38;5;241m=\u001b[39m numerator \u001b[38;5;241m/\u001b[39m\u001b[38;5;241m/\u001b[39m denominator\n\u001b[0;32m----> 5\u001b[0m decibels \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m \u001b[38;5;241m*\u001b[39m \u001b[43mmath\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlog10\u001b[49m\u001b[43m(\u001b[49m\u001b[43mratio\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[0;31mValueError\u001b[0m: math domain error" ] } @@ -1677,12 +1664,25 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "66aae3cb", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "02f9f1d7", "metadata": {}, "source": [ - "### Ask an assistant\n", + "### Ask a virtual assistant\n", "\n", "* Ask a virtual assistant, \"What are some uses of the modulus operator?\"\n", "\n", @@ -1695,7 +1695,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 131, "id": "ade1ecb4", "metadata": { "tags": [ @@ -1710,7 +1710,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 132, "id": "dc7026c2", "metadata": {}, "outputs": [ @@ -1742,7 +1742,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 133, "id": "1fd919ea", "metadata": {}, "outputs": [ @@ -1770,7 +1770,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 134, "id": "1e71702e", "metadata": {}, "outputs": [ @@ -1797,7 +1797,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 135, "id": "84cbd5a4", "metadata": {}, "outputs": [], @@ -1820,7 +1820,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 136, "id": "b0918789", "metadata": {}, "outputs": [ @@ -1861,17 +1861,17 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 137, "id": "1e7a2c07", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1711201546.6627047" + "1711217277.4267917" ] }, - "execution_count": 59, + "execution_count": 137, "metadata": {}, "output_type": "execute_result" } @@ -1895,7 +1895,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 138, "id": "c5fa57b2", "metadata": {}, "outputs": [ @@ -1905,7 +1905,7 @@ "19805.0" ] }, - "execution_count": 60, + "execution_count": 138, "metadata": {}, "output_type": "execute_result" } @@ -1914,17 +1914,17 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 139, "id": "322ddd0a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "13.0" + "18.0" ] }, - "execution_count": 61, + "execution_count": 139, "metadata": {}, "output_type": "execute_result" } @@ -1933,17 +1933,17 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 140, "id": "fc43df7d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "45.0" + "7.0" ] }, - "execution_count": 62, + "execution_count": 140, "metadata": {}, "output_type": "execute_result" } @@ -1952,17 +1952,17 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 141, "id": "0184d21a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "46.66270470619202" + "57.42679166793823" ] }, - "execution_count": 63, + "execution_count": 141, "metadata": {}, "output_type": "execute_result" } @@ -1996,7 +1996,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 142, "id": "06381639", "metadata": { "tags": [ @@ -2020,7 +2020,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 143, "id": "156273af", "metadata": { "tags": [ @@ -2042,7 +2042,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 144, "id": "e00793f4", "metadata": { "tags": [ @@ -2064,7 +2064,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 145, "id": "d2911c71", "metadata": { "tags": [ @@ -2086,7 +2086,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 146, "id": "2b05586e", "metadata": { "tags": [ @@ -2119,7 +2119,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 147, "id": "dac374ad", "metadata": {}, "outputs": [ @@ -2143,7 +2143,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 148, "id": "a438cec5", "metadata": {}, "outputs": [ @@ -2153,7 +2153,7 @@ "'Adds up the numbers from 1 to `n`\\n\\nn: integer\\ns: the sum of the numbers so far\\n'" ] }, - "execution_count": 70, + "execution_count": 148, "metadata": {}, "output_type": "execute_result" } @@ -2162,7 +2162,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 149, "id": "2e3f56c7", "metadata": { "tags": [ @@ -2200,7 +2200,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 150, "id": "2b0d60a1", "metadata": {}, "outputs": [], @@ -2223,7 +2223,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 151, "id": "ef0256ee", "metadata": {}, "outputs": [ @@ -2413,7 +2413,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 152, "id": "c1acc853", "metadata": {}, "outputs": [], @@ -2429,7 +2429,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 153, "id": "55507716", "metadata": {}, "outputs": [ @@ -2602,7 +2602,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 154, "id": "50750abc", "metadata": {}, "outputs": [], @@ -2612,7 +2612,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 155, "id": "86d3123b", "metadata": { "tags": [ @@ -3053,7 +3053,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 156, "id": "68439acf", "metadata": {}, "outputs": [], @@ -3069,7 +3069,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 157, "id": "43470b3d", "metadata": {}, "outputs": [ diff --git a/_sources/chap06.ipynb b/_sources/chap06.ipynb index b6a4b02..fc6cbdd 100644 --- a/_sources/chap06.ipynb +++ b/_sources/chap06.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 87, "id": "56b1c184", "metadata": { "tags": [ @@ -22,28 +22,10 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py')\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py');\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/Turtle.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d2f2def5", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "import thinkpython\n", - "\n", - "thinkpython.traceback('Minimal')\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -76,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 88, "id": "e0e1dd91", "metadata": {}, "outputs": [ @@ -86,7 +68,7 @@ "3.656366395715726" ] }, - "execution_count": 4, + "execution_count": 88, "metadata": {}, "output_type": "execute_result" } @@ -107,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 89, "id": "5aaf62d2", "metadata": {}, "outputs": [], @@ -125,7 +107,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 90, "id": "741f7386", "metadata": {}, "outputs": [ @@ -135,7 +117,7 @@ "3.656366395715726" ] }, - "execution_count": 6, + "execution_count": 90, "metadata": {}, "output_type": "execute_result" } @@ -154,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 91, "id": "e56d39c4", "metadata": {}, "outputs": [ @@ -164,7 +146,7 @@ "7.312732791431452" ] }, - "execution_count": 7, + "execution_count": 91, "metadata": {}, "output_type": "execute_result" } @@ -183,7 +165,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 92, "id": "50a9a9be", "metadata": {}, "outputs": [], @@ -207,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 93, "id": "d70fd9b5", "metadata": {}, "outputs": [ @@ -217,7 +199,7 @@ "42.00000000000001" ] }, - "execution_count": 9, + "execution_count": 93, "metadata": {}, "output_type": "execute_result" } @@ -236,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 94, "id": "ef20ba8c", "metadata": {}, "outputs": [], @@ -254,7 +236,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 95, "id": "0a4670f4", "metadata": {}, "outputs": [ @@ -264,7 +246,7 @@ "63.000000000000014" ] }, - "execution_count": 11, + "execution_count": 95, "metadata": {}, "output_type": "execute_result" } @@ -283,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 96, "id": "6e6460b9", "metadata": {}, "outputs": [ @@ -293,7 +275,7 @@ "42.00000000000001" ] }, - "execution_count": 12, + "execution_count": 96, "metadata": {}, "output_type": "execute_result" } @@ -312,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 97, "id": "77613df9", "metadata": { "tags": [ @@ -355,7 +337,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 98, "id": "89c083f8", "metadata": {}, "outputs": [], @@ -374,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 99, "id": "737b67ca", "metadata": {}, "outputs": [ @@ -401,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 100, "id": "9b4fa14f", "metadata": {}, "outputs": [ @@ -427,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 101, "id": "50f96bcb", "metadata": {}, "outputs": [], @@ -446,7 +428,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 102, "id": "6712f2df", "metadata": {}, "outputs": [ @@ -474,7 +456,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 103, "id": "0ec1afd3", "metadata": {}, "outputs": [], @@ -496,7 +478,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 104, "id": "c82334b6", "metadata": {}, "outputs": [], @@ -514,7 +496,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 105, "id": "595ec598", "metadata": {}, "outputs": [ @@ -524,7 +506,7 @@ "'Spam, Spam, Spam, Spam, '" ] }, - "execution_count": 21, + "execution_count": 105, "metadata": {}, "output_type": "execute_result" } @@ -553,7 +535,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 106, "id": "236c59e6", "metadata": {}, "outputs": [], @@ -580,7 +562,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 107, "id": "2f60639c", "metadata": {}, "outputs": [], @@ -602,7 +584,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 108, "id": "c9dae6c8", "metadata": {}, "outputs": [], @@ -622,7 +604,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 109, "id": "c8c4edee", "metadata": {}, "outputs": [], @@ -679,7 +661,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 110, "id": "bbcab1ed", "metadata": {}, "outputs": [], @@ -701,7 +683,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 111, "id": "923d96db", "metadata": {}, "outputs": [ @@ -711,7 +693,7 @@ "0.0" ] }, - "execution_count": 27, + "execution_count": 111, "metadata": {}, "output_type": "execute_result" } @@ -736,7 +718,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 112, "id": "9374cfe3", "metadata": {}, "outputs": [], @@ -762,7 +744,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 113, "id": "405af839", "metadata": {}, "outputs": [ @@ -780,7 +762,7 @@ "0.0" ] }, - "execution_count": 29, + "execution_count": 113, "metadata": {}, "output_type": "execute_result" } @@ -799,7 +781,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 114, "id": "e52b3b04", "metadata": {}, "outputs": [], @@ -822,7 +804,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 115, "id": "38eebbf3", "metadata": {}, "outputs": [ @@ -839,7 +821,7 @@ "0.0" ] }, - "execution_count": 31, + "execution_count": 115, "metadata": {}, "output_type": "execute_result" } @@ -858,7 +840,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 116, "id": "b4536ea0", "metadata": {}, "outputs": [], @@ -881,7 +863,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 117, "id": "325efb93", "metadata": {}, "outputs": [ @@ -909,7 +891,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 118, "id": "3cd982ce", "metadata": {}, "outputs": [], @@ -933,7 +915,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 119, "id": "c734f5b2", "metadata": {}, "outputs": [ @@ -943,7 +925,7 @@ "5.0" ] }, - "execution_count": 35, + "execution_count": 119, "metadata": {}, "output_type": "execute_result" } @@ -962,7 +944,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 120, "id": "094a242f", "metadata": {}, "outputs": [], @@ -1004,7 +986,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 121, "id": "64207948", "metadata": {}, "outputs": [], @@ -1026,7 +1008,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 122, "id": "c367cdae", "metadata": {}, "outputs": [ @@ -1036,7 +1018,7 @@ "False" ] }, - "execution_count": 42, + "execution_count": 122, "metadata": {}, "output_type": "execute_result" } @@ -1047,7 +1029,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 123, "id": "837f4f95", "metadata": {}, "outputs": [ @@ -1057,7 +1039,7 @@ "True" ] }, - "execution_count": 43, + "execution_count": 123, "metadata": {}, "output_type": "execute_result" } @@ -1077,7 +1059,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 124, "id": "e411354f", "metadata": {}, "outputs": [], @@ -1096,7 +1078,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 125, "id": "925e7d4f", "metadata": {}, "outputs": [ @@ -1123,7 +1105,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 126, "id": "62178e75", "metadata": {}, "outputs": [ @@ -1178,7 +1160,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 127, "id": "23e37c79", "metadata": {}, "outputs": [], @@ -1197,7 +1179,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 128, "id": "5ea57d9f", "metadata": {}, "outputs": [], @@ -1220,7 +1202,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 129, "id": "b66e670b", "metadata": {}, "outputs": [], @@ -1268,7 +1250,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 130, "id": "455f0457", "metadata": { "tags": [ @@ -1304,7 +1286,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 131, "id": "a75ccd9b", "metadata": { "tags": [ @@ -1314,9 +1296,9 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3IAAALiCAYAAACcxuQWAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAC4jAAAuIwF4pT92AACmJElEQVR4nOzdd3QU9f7/8dcmIQFSIaEXSSihhKKhSRMQISoKKkpVEVGsV8GGXz1e9Hqt1wsoXhSpIgKKCIiASFOQUBNIQi8BaaEEQhJIgGT390dO5peQ3dTNFng+zvGc3fl8Zua9K5md93yayWKxWAQAAAAAcBsezg4AAAAAAFAyJHIAAAAA4GZI5AAAAADAzZDIAQAAAICbIZEDAAAAADdDIgcAAAAAboZEDgAAAADcDIkcAAAAALgZEjkAAAAAcDMkcgAAAADgZkjkAAAAAMDNkMgBAAAAgJshkQMAAAAAN0MiBwAAAABuhkQOAAAAANwMiRwAAAAAuBkSOQAAAABwMyRyAAAAAOBmSOQAAAAAwM2QyAEAAACAmyGRAwAAAAA3QyIHAAAAAG6GRA4AAAAA3AyJHAAAAAC4GRI5AAAAAHAzJHIAAAAA4GZI5AAAAADAzZDIAQAAAICbIZEDAAAAADdDIgcAAAAAboZEDgAAAADcDIkcAAAAALgZEjkAAAAAcDMkcgAAAADgZkjkAAAAAMDNkMgBAAAAgJshkQMAAAAAN0MiBwAAAABuhkQOAAAAANwMiRwAAAAAuBkSOQAAAABwMyRyAAAAAOBmSOQAAAAAwM2QyAEAAACAmyGRAwAAAAA3QyIHAAAAAG6GRA4AAAAA3AyJHAAAAAC4GRI5AAAAAHAzJHIAAAAA4GZI5AAAAADAzZDIAQAAAICbIZEDAAAAADdDIgcAAAAAboZEDgAAAADcDIkcAAAAALgZEjkAAAAAcDMkcgAAAADgZkjkAAAAAMDNkMgBAAAAgJshkQMAAAAAN0MiBwAAAABuxsvZAQA3i7S0NGeH4FD+/v7ODgEAAOCGRYscAAAAALgZEjkAAAAAcDMkcgAAAADgZkjkAAAAAMDNkMgBAAAAgJshkQMAAAAAN0MiBwAAAABuhkQOAAAAANwMiRwAAAAAuBkSOQAAAABwMyRyAAAAAOBmSOQAAAAAwM2QyN3Ejhw5IpPJJJPJpAYNGjg7HAAAAADFRCIHAAAAAG6GRA4AAAAA3AyJHAAAAAC4GZPFYrE4OwjgZpCWlubsEBzK39/f2SEAAADcsGiRAwAAAAA3QyIHAAAAAG6GRK6Y1q1bZ0zV3717d2P70qVL9eCDD6pBgwaqWLGigoODdffdd2vZsmUFjmE2m7V48WL17dtXoaGhqlixomrVqqWHH35YmzZtKlYcGRkZWrRokf7xj3+oS5cuqlGjhry9veXn56cGDRrogQce0LRp03T16tUij1Xc5Qdy65hMJmPbvn379PLLL6tZs2by8/NTQECAWrdurTfffFPnzp0r1mcBAAAAUDqMkSumdevWqUePHpKkO+64Q8uWLdOTTz6pefPm2dznn//8p8aNGydJOnv2rPr376+NGzdarWsymfT555/rhRdesHm8zZs3q1evXkpPTy8y3gYNGmjhwoW69dZbbdY5cuSIQkNDJUm33HKLjhw5YjO2XBaLRV999ZVefvllXblyxWr94OBgrVixQm3bti0yzpsJY+QAAABgL17ODsBd5SZxXl5e6ty5sxo1aqTLly9rzZo1On36tCTp3XffVXh4uPr376/evXtrx44dqlixorp166b69esrJSVFq1ev1oULF2SxWPSPf/xDkZGRuv32262e88KFC0YSV716dbVo0UJ169aVr6+vLl++rIMHD2rLli3KysrSkSNHdMcddygmJkaNGjWy2+eeOXOmnn32WUlSeHi42rZtq0qVKmnv3r3666+/ZLFYlJycrPvvv1979uxRYGCg3c4NAAAAIActcsWUt0XOx8dHV65cUadOnTR79myFhYUZ9TIyMvT444/rxx9/lCQ1btxYUVFR+uKLL/TAAw/oq6++UvXq1Y36Fy5cUP/+/fXnn39Kknr06KE1a9ZYjWHz5s1asmSJBg8erIiICKt1zpw5o1dffVWzZ8+WJN15551atWqV1bqlaZHz8fFRQECAvv32W0VFReWr9+eff+q+++5TamqqpJxE9p133rF6zJsRLXIAAACwFxK5YsqbyEk5rVHbt2+Xr69vgbppaWlq0KCBzp8/b2zr2bOnfv/9d3l4FByWePToUTVs2FDZ2dkymUw6efKkatasWaZ477nnHi1fvlyStHv3bjVr1qxAndImclu2bFGrVq2s1v3yyy+N7qFNmzbVnj17yvIxbigkcgAAALAXJjsppY8++shqEifl3MDee++9+bb997//tZrESTlJVKdOnSTljEHbtm1bmeMbPny48dpWi1xpPP300zaTOEl67LHH5OWV02N33759RuscAAAAAPthjFwpVKpUqUCidr2WLVsarxs1aqTWrVsXWj8iIkLr16+XJCUmJhYZw+XLl7Vp0ybFx8fr7NmzSktLU3Z2tlF+4sQJ4/WOHTuKPF5xPfzww4WW+/v7q2HDhtq3b58sFouOHj2a77sAAAAAUHYkcqXQpEkTVahQodA6VapUMV63aNGiyGNWrVrVeF1YK9b58+f1zjvv6Ntvvy12Vz17LgdQnKQsODjYeE2LHAAAAGB/JHKlUJyZGHO7F5am/rVr16zWOXr0qLp166a///67GFH+f/Ycm1Wcz5I3ybX1WQAAAACUHmPkSiHv5B/lUd+WIUOGGEmcv7+/Ro8erRUrVujw4cNKT09Xdna2LBaLLBaL1q5da+xnNpvtcn7Jfp8FAAAAQOnRIucmNm7caCwm7ufnp02bNql58+Y2699sMyQCAAAANxNa5NzE6tWrjdePP/54oUmclNMNEwAAAMCNiUTOTZw8edJ4XZwJR3IXGAcAAABw4yGRcxN516C7fPlyoXVPnjypxYsXl3dIAAAAAJyERM5NhIWFGa+XLFlis152draefvppXb161RFhAQAAAHACEjk3ce+99xozRq5bt06vvvqqMjIy8tVJSkrSQw89pF9//VW+vr7OCBMAAACAA5DIuYmmTZvq0UcfNd5/9tlnatiwofr166ennnpKd911l2655RYtXrxY/v7++uyzz5wYLQAAAIDyxPIDbmTy5MlKSkrSypUrJUmnTp0q0M2ybt26mjdvHgtxAwAAADcwWuTcSOXKlbV8+XLNnj1bvXr1UnBwsCpUqKBatWqpc+fO+u9//6u4uDh17tzZ2aECAAAAKEcmi8VicXYQwM3gZluk3d/f39khAAAA3LBokQMAAAAAN0MiBwAAAABuhkQOAAAAANwMiRwAAAAAuBmXXX7gwIEDmjhxYqn3P3TokBo2bCiz2SwPj9Lnq5MmTSr1vvj/zp8/r3feeadMx0hMTJQkhYaGluk47733nqpWrVqmYwAAAADO5LKzVq5bt049evRwdhhy0a/H7Rw5cqTMCZi9JCYmqkGDBg4/L7NWAgAAwF7oWgkAAAAAbsZlW+SAGw0tcgAAALAXlx0jBwAAAMC9nDt3TnFxcapevbqaNWsmT09PZ4d0wyKRAwAAAFBm6enp+uqrr5SdnW1se/TRRxUWFubEqG5cjJEDAAAAUGZHjhzJl8RJ0uzZs/XDDz8oJSXFOUHdwGiRAwAAAFBm9evXt7p9z549OnDggHr06KGOHTuWaWkw/H98iwAAAADKLCAgQK1bt7ZalpWVpd9//13Tpk3TmTNnHBzZjYlZKwEHYdZKAABwo8vOztaUKVMKTdY8PDzUrVs3denShclQyoAWOQAAAAB24enpqQceeKDQ7pNms1nr1q3TlClTdPLkSQdGd2MhkQMAAABgNzVr1lSXLl2KrHfmzBlNnTpVv//+u65du+aAyG4sJHIAAAAA7KpLly4KCgoqsp7FYtHGjRv19ddfKykpqfwDu4GQyAEAAACwqwoVKqhPnz7Frp+cnKypU6dq27ZtYgqP4iGRAwAAAGB34eHhatiwYbHrZ2dn69dff9XChQt15cqVcozsxkAiBwAAAMDuTCaToqKiSrxuXEJCgqZMmUJXyyKw/AAAAACAcrNy5UpFR0eXeD9PT09FRUUpMjJSJpOpHCJzb7TIAQAAACg3d9xxh/z8/Eq8X96ullevXi2HyNwbiRwAAACAcuPj46NevXqVev+EhATNnDlTaWlpdozK/ZHIAQAAAChXrVq1Uo0aNUq9/6lTpzRt2jSdOXPGjlG5NxI5AAAAAOXKZDIVa5Hwwly8eFHTp0/X4cOH7RSVeyORAwAAAFDumjdvrqpVq5bpGFeuXNGcOXMUGxtrp6jcF4kcAAAAgHLn4eGhzp07l/k4ZrNZS5Ys0Zo1a27qxcNJ5AAAAAA4ROvWreXv72+XY61fv14///yzsrOz7XI8d0MiBwAAAMAhPD091alTJ7sdLz4+XgsWLLgpkzkSOQAAAAAOc9ttt6lSpUp2O97evXs1f/58ZWVl2e2Y7oBEDgAAAIDDeHt7q127dnY95oEDB/TTTz/Z9ZiujkQOAAAAgEO1atXK7sfcu3evkpKS7H5cV0UiBwAAAMChgoODVbt2bbsf18fHx+7HdFUkcgAAAAAcLiIiwm7HMplM6tOnj6pUqWK3Y7o6EjkAAAAADmevRC4sLEzPPvusOnbsaJfjuQsvZwcAAAAA4Obj7++v0NBQJSYmlmr/qlWrqnfv3mrSpIlMJpOdo3N9JHIAAAAAnCIiIqJUiVxgYKCeffZZeXndvOkMXSsBAAAAOEWzZs1K1Zp28eLFUrfk3ShI5AAAAAA4RaVKlVSzZs1S7bt69WpZLBY7R+Q+SOQAAAAAOE2DBg1slvn7+6tGjRpWy06fPq1du3aVU1Suj0QOAAAAgNOEhYUV2Obl5aVu3brphRde0P33329z37/++qs8Q3NpJHIAAAAAnKZhw4YKDQ013jdv3lzPP/+8evToIW9vb9WuXVvNmze3um9SUpJOnjzpqFBdislyM3csBQAAAOB0FotFx48fl7+/v4KCggqUnz17Vv/73/+s7hsZGam+ffuWc4SuhxY5AAAAAE5lMplUr149q0mcJFWrVi1fq11e8fHxunr1ajlG55pI5AAAAAC4vNtuu83q9qtXryohIcHB0TgfiRwAAAAAl9e0aVNVqlTJallMTIyDo3E+EjkAAAAALs/Ly0utW7e2WnbixAmdPn3awRE5F4kcAAAAALdgq3ulJG3fvt2BkTgfiRwAAAAAt1CtWjXVr1/fallCQoJupgn5SeQAAAAAuA1brXIZGRlKSkpycDTO4+XsAAAAAACguJo2bSoPDw+ZzeYCZYcPH1atWrXKdPyYmBj98MMPWrVqlU6cOKHz588rODhYNWvWVJs2bdSjRw/dddddqlmzZpnOU1YsCA4AAADArcyYMUN///13ge1hYWF69NFHS3XMM2fOaMyYMZozZ06RdZ9//nlNmjSpVOexF1rkAAAAALiV0NBQq4nc0aNHde3aNVWoUKFEx/v777/VvXt3JSYmGtvCw8PVsmVLBQcH6/Llyzp06JB27Nihy5cvlzl+eyCRAwAAAOBWwsLC9McffxTYnp2drePHjys0NLTYx7p48aJ69OhhJHE9evTQhAkT1KpVqwJ1r169qjVr1igtLa30wdsJiRwAAAAAt1KnTh15e3vr6tWrBcqSkpJKlMi9+uqrOnz4sCRp4MCBmjNnjjw9Pa3W9fb2VlRUVOmCtjNmrQQAAADgVjw9PW1ONnL27NliH2fHjh2aOnWqJKlevXr65ptvbCZxroZEDgAAAIDbCQkJsbr93LlzxT7GV199Zbx+/vnn5e/vX+a4HIVEDgAAAIDbqVatmtXtZ8+eLdbC4NnZ2Zo7d67x/qGHHrJbbI5AIgcAAADA7dhqkcvMzNSlS5eK3D8hIUGpqamSpMDAQDVs2FBZWVmaMWOG7rzzTtWsWVM+Pj6qU6eO7r77bk2ePFlXrlyx62coCyY7AQAAAOB2bLXISdKFCxfk5+dX6P5bt241XterV0/Hjx/XgAEDtGXLlnz1Tp48qZMnT2rFihX66KOPtGDBArVr165swdsBiRwAAAAAt1NYolaclrNjx47le3/33Xdr165dkqSmTZuqXbt28vT0VFxcnGJiYiT9//Xm/vzzT0VGRpYh+rIjkQMAAADgdjw9PeXl5aWsrKwCZZmZmUXun5KSYrxOSEiQJFWuXFkzZ87Uww8/nK/u2rVr9cgjj+jcuXO6fPmyBg4cqN27d8vb27tsH6IMGCMHAAAAwC35+PhY3V6cFjlr4+i+++67AkmclLNI+JIlS+ThkZM+HTp0SHPmzClhtPZFIgcAAADALdlK5IrTIlexYsV872+//XY98MADNuvffvvtevDBB4338+fPL2aU5YNEDgAAAIBbKkuL3PVj7ApL4qzV2bhxY5H1yxOJHAAAAAC3ZDabrW739PQsct/g4OB875s3b17kPs2aNTNep6WlKS0trch9yguJHAAAAAC3dPXqVavbizMJSdOmTfO9L2q5Akny9/fP955EDgAAAABKyFYXyuIkchEREfnep6enF7nP9YlbYGBgkfuUFxI5AAAAAG6pLC1yoaGhCg0NNd7v3r27yH327NljvK5atap8fX2LEWX5IJEDAAAA4HbMZrPVNeQkqUKFCsU6Rt5ZKBctWlRk/bx1unXrVqxzlBcSOQAAAABuJ++C3terXLlysY7x7LPPGknfxo0btWTJEpt1t2zZooULFxrvhw8fXqxzlBcSOQAAAABu59y5czbLrp+R0paGDRvqueeeM94PGTIkX7KW648//lDfvn2VnZ0tSerYsaPuv//+EkZsX15OPTsAAAAAlIKtRK5ixYrFbpGTpI8//lgxMTFav369Ll26pIceekjNmjVTu3bt5Onpqbi4OG3fvt2oX6tWLf3www8ymUxl/gxlQSIHAAAAwO0kJydb3R4cHFyiJMvHx0e//PKLnn32Wc2dO1dSzqQmeSc2ydWhQwf9+OOPqlevXumCtiO6VgIAAABwO7Za5IrbrTKvwMBAff/99/rjjz/05JNPKjw8XH5+fqpUqZIaNGigQYMGaeHChYqOjnaJJE6iRQ4AAACAm8nKytKJEyeslpUmkcvVrVs3p89GWVy0yAEAAABwK8eOHTMmHrle/fr1HRyNc5DIAQAAAHAriYmJVrd7enqqbt26Do7GOUjkAAAAALiVI0eOWN1er149eXndHKPHbo5PCZRQWlqas0MAAABwCf7+/s4OIZ/MzEyb4+MaNGjg2GCciBY5AAAAAG5jz549MpvNVstCQ0MdHI3zkMgBAAAAcBtxcXFWt1eqVEl16tRxcDTOQyIHAAAAwC1cvHjR5vi45s2by9PT07EBORGJHAAAAAC3EB8fb7OsVatWDozE+UjkAAAAALg8i8WinTt3Wi0LCgpSvXr1HByRc5HIAQAAAHB5e/fu1blz56yWtWzZUiaTycEROReJHAAAAACXZrFY9Oeff9osv9m6VUokcgAAAABc3P79+5WUlGS1LDw8XCEhIQ6OyPlI5AAAAAC4rKJa47p16+bAaFwHiRwAAAAAl3Xw4EGdPHnSalmjRo1Uu3ZtB0fkGkjkAAAAALgki8WiP/74w2b5zdoaJ5HIAQAAAHBRhw8f1okTJ6yWhYaG3nRLDuRFIgcAAADA5WRlZem3336zWX4zt8ZJJHIAAAAAXNC6det09uxZq2X169fXLbfc4uCIXAuJHAAAAACXcvz4cW3cuNFmebdu3W66BcCvRyIHAAAAwGVcu3ZNixYtksVisVrerFkzhYWFOTgq10MiBwAAAMBlrFmzRsnJyVbLKleurHvvvfemb42TSOQAAAAAuIi///5bmzZtsll+7733ytfX14ERuS4SOQAAAABOd/XqVS1atMhmeUREhJo3b+64gFwciRwAAAAAp1u9erUuXLhgtczX11d33323gyNybSRyAAAAAJwqMTFRW7ZssVnet29fVa5c2YERuT4SOQAAAABOc+XKFS1ZssRmeatWrdS0aVMHRuQeSOQAAAAAOIXZbNaiRYuUkpJitdzPz09RUVGODcpNkMgBAAAAcDiLxaIVK1Zo7969Nuvcd999qlSpkgOjch8kcgAAAAAcLjo6Wlu3brVZ3qZNGzVp0sSBEbkXEjkAAAAADpWQkKDff//dZnlgYKD69OnjwIjcD4kcAAAAAIc5cuRIoevF+fj4aMiQIapYsaLjgnJDJHIAAAAAHOLMmTOaN2+esrOzrZZ7eHho4MCBql69uoMjcz8kcgAAAADKXWpqqubMmaMrV67YrNO/f3+FhoY6MCr35ZKJXHJyst5991116NBBVapUkaenp0wmk0wmk2bOnOns8G5YR44cMb7nBg0aOOy848aNM847btw4h50XAAAAjnHlyhV9//33Sk1NtVnnzjvvVMuWLR0YlXvzcnYA1zt8+LC6deumEydOODsUAHA7ycnJ2rRpk7Zv365du3YpMTFRSUlJSk9PV4UKFRQUFKRmzZqpa9euGjx4sGrXru3skAHgpnH06FGtXbtWGzZs0O7du3X8+HGlp6fLz89PderUUfv27fXwww+rS5cuzg7VrrKzs/XDDz/o9OnTNuu0bdtWnTt3dmBU7s/lErlRo0YZSVylSpXUq1cv1alTR56enpKkZs2aOTO8Is2cOVNPPPGEJOnxxx+nBRGAQz3zzDP67bffrJZlZWUpIyNDp06d0po1a/TRRx9pzJgxeuONN+Th4ZIdNADghrBz5069/PLL2r59u9XylJQUpaSkaNeuXZoxY4a6du2qr776SvXq1XNwpPZnsVi0ZMkSHT582Gad8PBw3X333TKZTA6MzP25VCJ36tQprVq1SlLObDU7d+5U48aNnRwVALin4OBghYeHq169evLz89Ply5d1+PBhbd++XVlZWbpy5Yo+/PBDHTlyRF9//bWzwwWAG9aBAwcKJHGNGjVS8+bNFRwcrIsXL2rz5s1GY8b69evVq1cvrVixwq3Hi+Uu+B0XF2ezTp06dfTQQw/xQLEUXCqRi42NNV537dqVJM7BGjRoIIvF4uwwAJRB165ddffdd+uOO+5Qw4YNrdY5c+aMxo4dqwULFkiS5s6dq7vvvlv9+/d3YKQAcPMJCwvT448/roEDBxbo2m42mzVnzhy99tprunz5sk6dOqWRI0dq1apVbtlSZTabtXTp0nz399erUqWKBg8erAoVKjgwshuHSyVyFy5cMF7XqlXLiZEAgHv6xz/+UWSd6tWra9q0aTp79qz++OMPSdKMGTNI5ACgnNSsWVOTJ0/WoEGDjOFC1/Pw8NCjjz6qoKAgDR06VJK0detWrV69Wr169XJkuGVmNpu1ePHiQlviKleurKFDh8rX19eBkd1YXKoN89q1a8ZrmlcBoPyYTCbjRkHKGb8BACgfXbp00dChQ20mcXndd999ioyMNN7bGvfsqrKzs/XTTz8VmsR5eXlp8ODBCg4OdmBkNx6nZ0vr1q0zpp7PnSREkmbNmmVsz/1v+PDh+fbdvn27PvzwQ/Xt21dhYWHy8/OTt7e3atSooU6dOumtt97S33//XeKYMjMzNX36dD3yyCNq2LChAgIC5O3trerVq6tr164aO3asNm/enG+f4cOHF+szmEwmde/e3ea509PT9fnnn6tPnz6qW7euKlasqCpVqigiIkIvvPBCgfPakvd8uXbu3KmXXnpJERERqlq1qkwmU74n8CVZfuDo0aOaPHmyBg8erIiICAUGBqpChQoKDg5Wy5Yt9eyzz2rTpk3FihXWBQQEGP/lOnDggN544w21bdtWtWrVUp06ddSpUyeNGzdOycnJTowWuRITE7V06VJt2LBBaWlpzg6nUCEhIcbr9PR0J0aC61n7+4+Pj9frr7+uDh06qH79+goICNDgwYOt7p+cnKwvvvhC/fr1U7NmzVStWjXVq1dP7dq105gxYxQTE1PimKKjo/X666+rS5cuCgsLU9WqVVWnTh117NhRo0aN0o8//qiMjIwC+61fv974LPfcc0+pP39x6hT3O9q+fbteeeUVde3aVfXr11eVKlVUvXp1NW7cWD179tTo0aO1cOFCXbp0qchYL126pKlTp+qRRx5RixYtVKNGDdWuXVtt2rTRc889Z7R6w/nc6frcsWNH43Vp7mWdJSsrSz/88IN2795ts46Hh4cGDBigunXrOjCyG5NLda0sifbt22vr1q1Wy86cOaMzZ84oOjpan376qd5//329/vrrxTruwoUL9Y9//MPq8gdnz57V2bNntWHDBn388ceaPHmynnnmmTJ9jryWLl2qp556SklJSfm2X7lyxZjJ6Msvv9SQIUP0zTffqHLlysU+9rhx4/T+++8rOzu7zHG+9tpr+uyzz6yOpzt//rzOnz+vhIQEffXVVxo0aJCmTZtWolhh3bRp0zR27NgCi2gmJCQoISFBM2fO1MKFC3Xbbbc5KUJcuHBBixYtMv42tm/froiICLVv317+/v5Ojq6gvXv3Gq/r16/vxEhQlA8++ECffvppsa7hU6ZM0b/+9S9dvHgx3/YrV67o4sWL2rdvn6ZNm6Zhw4Zp/Pjx8vb2LvR4J06c0HPPPae1a9cWKEtLS9Pu3bu1e/duzZ07V23bttWaNWtK9uHspDjfUVZWll555RXNmDGjQFl2drYyMzN1+vRpbdu2TdOmTdOrr76qd955x+bxfv75Z73++utWp1RPT0/X4cOH9d133ykqKkrffPONAgMDS/fhUGbu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UJ57wzB9+nStXr2aMRooFrPZrHnz5hVIUKT/fw1PS0vTW2+9JUny9vbWzz//rMcff9xqr4tu3bpp8eLFxvV5woQJVte2mjBhgtG7IjAwUCtXrtTDDz9sNfHx9fXVI488ov/973+l/6BlUJzvaMuWLcYN5kMPPaShQ4fa7ClSs2ZNPfPMM3rsscesnmv06NEym82SpBkzZmj06NFWk75WrVrpl19+UfXq1SVJ3377baHdzlB+uD6XP19fX/Xp06fA9sDAwCLvG1E6bpvIFdeAAQPk5+cnScZi49f76aefdOTIEUk5/whnzJghLy/HNFampKRo/vz5xvtPPvmk0G5xL730klq0aCEp58ck9+liYQYMGKBu3bqVPdgSatCggXr06CFJ2rp1q1JTUx0ew41i+PDhioiIsFk+ePBg49/sgQMH+K6dyNfXVz179tSIESO4YYBd9O/fX507dy60zuzZs42WhpEjR6pdu3aF1g8PD9eQIUMk5Yxtvv738cqVK5o6darxfty4cS69tmtxvqO8f2dluaFcvny5keD27dtX9913X6H1a9Sooeeee05SzkPln3/+udTnRtlwfS5/bdu2zTesKCQkRE888QQTm5QTp3ettIe4uDjFxsbqyJEjSk1NLdBdMPeJW3x8vMxmc4GxACtWrDBeDx482KFPDDZu3GjEGxISUuQPgoeHh0aMGKFXXnlFkqwOPr/eoEGDyh6oDX///be2bNmi/fv3KyUlRRkZGfn6oOe2GFosFu3cuVNdu3Ytt1huZA888ECh5f7+/goNDdWBAwdksVh07NgxI+EvrczMTOOJM0rO09NTHTt2VEREhHbs2KHdu3cX+D5zbxji4+PVrFkzdezYkfGNKOChhx4qss7KlSuN1w8//HCxjtutWzdNnz5dUs6MlPfff79RtnXrViMx9Pf3N5I+V1Wc76hu3brG619++UWvvPKKqlWrVuJzlfa7zhUdHa0XXnihxOfNi+tz2XB9Ll9dunRRkyZNdPHiRYWFhVlNmGEfbp3IzZo1Sx988IH2799frPrXrl3TxYsX841tk5RvmvzcFiRHiY2NNV63b9++WC2BeZ865g6kLmwikcjIyLIFaUV0dLTGjh2r9evXW5290ppz587ZPY6bRfPmzYusk/dpV1meIKalpWnRokU6e/ZsqY+BkjGbzdq1a5d27dqlNm3aqGfPns4OCS6kTZs2RdbJO4vzzJkz9f333xe5T+4EWpIKdPfLe7y2bdvaHMbgKorzHbVr105169bV8ePHdezYMbVv317Dhg3T3XffrbZt2xY5e2euLVu2GK8XL16sDRs2FLlP3l4Sx48fL9Z5rOH67Hhcn0unevXqRpdilB+3TOQsFouefPJJq9MHFyUtLa1AIpd32uCwsLAyx1cSeS/Gt9xyS7H2ybvG29WrV5WWllboWjuleeJYmOnTp2vkyJHFTuBy0T2h9IrzFDDvWJhr166V+lzR0dHcJDjRjh071L17d5uzCOLmU1QvkfT09HzX11mzZpX4HNdPAHHmzBnjta1JwlxJcXrSVKhQQVOmTNEjjzyi9PR0JScna+LEiZo4caIqVqyoW2+9VZ07d1bv3r3VoUMHmw9IT506ZbxeuHBhiWO9/rsuCa7PzsX1Ga7GLf8lfvPNN/mSuKioKM2aNUvx8fG6cOGCrly5IovFYvyXN0Gy1hUh7w9g7ng6R8md5ERSsVe3v75eUQmSPZ+k7t69W6NGjTKSuBYtWmjixInasmWLTp8+bXStzP0v74QwdAMpPZZuAG5eRV3D7TEm9vpZ5krz2+RMxf2d69Kli/766y8NHjw43z6ZmZmKjo7Wf/7zH/Xu3VuRkZFaunSp1WOU9fu2x3quACC5aYtc3pkb33333UIX65SKTnT8/f114cIFSfl/vBwhb+J46dKlYu1zfT1HTpM7YcIE4we/T58+WrJkSaHdUWiFcz+33367zpw5k++JPBynTZs2PO1FiVw/W+LRo0cL9DwpqdL8NtlLeT/0Cw0N1ddff63//ve/io6OVnR0tDZv3qytW7cqIyNDknTw4EENGTJEH3zwQYHxbL6+vsZi6+vXr1fr1q3LNd68uD47F9dnuBq3S+SOHTtmLNQZFBSkN998s9D6qampRpJmS40aNYw6iYmJ+RYeL295uz0WtohiXrkzbEo500w7MpHLXSNPkt5///0ixxQcPXq0vEOCnfn7+2vYsGEMpreT9PR0xcbGas+ePTa/Tw8PDwbTo9SCgoLk4+NjTJx15syZMidyece25P3NKY283b6trS91vdwkqbz5+vqqV69e6tWrlyQpIyNDv/32mz7++GPt2rVLUs5snQ8++KBq165t7FetWjUjxrxDMxyB67N9cX2Gu3O7RC7v4OymTZtaXSMnrw0bNhQ5lqtjx47au3evJGnNmjUaPHhwqeMraRe4W2+91Xi9ZcsWZWdnFzm7z8aNG/Pt78hud3m//5YtWxZa9+LFi4qLiyvvkFBOcteYQumkpaVpy5YtSkhIsNmVytPTkwVoYReRkZHGb8OmTZsUHh5epuPlXb4gt6WqtN308/7bPn/+fJH1d+/eXarzlFWlSpXUv39/devWTe3bt9eZM2d09epVrV69Wo8++qhRr23btjp48KCknO+6d+/eDo+V63PZcH3GjcLt2ofzNmlbW8D0epMnTy6yzt133228njdvXplmV8x7cS3OhBOdOnUyFis9e/asfv3110Lrm83mfOMDHT17Ukm+/6lTp5Zp0g3AHV26dEmrV6/W9OnTtXPnTqs3CZ6enmrdurVGjBihO++8k5sElFlUVJTxetq0aSWejOp67dq1U1BQkKScm965c+eW+lj16tUzHjgePny4yCEMpZlAxJ6qVq2ar2fO9d0Y837X3333nTIzMx0WG8qG6zNuNG6XyIWGhho/CAkJCTp8+LDNuvPnz7c5WDmvBx980JgQJT09XU888USxun9YExwcbLy+fjpna4KCgjRw4EDj/WuvvVbouLJJkyYpPj5eUk5S9fTTT5cqztLKO6vnkiVLbNY7cOCA3n33XUeEBLiMjIwMzZs3jxsEONwTTzxhJF47duzQhx9+WOx9k5OTC/x79fHx0ciRI433//znP41hDSUVEBCgJk2aSMrpWvnDDz/YrLtz585SzbpZHMnJycWum3eJgOtnfu7Xr5/xW5iUlKQxY8YUO3FOT093+JhD5OD6jBuR2yVyISEhxpMys9msAQMGaN++ffnqmM1mffnll3r00Ufl6elZZBcELy8vTZo0yUgQly5dqj59+hjdLa935MgRvfPOO/r2228LlEVERBivN2/eXKxxb++8844xsHz//v3q06dPgQTVbDZr4sSJGjNmjLHt+eefz7cUgSPkXbB8zJgx+u233wrUWb16tbp37660tDS3mO0MsJcTJ05YHd/DDQLKW2BgYL7k7aOPPtKoUaN07Ngxq/UtFos2bdqk0aNHq3nz5sYkH3m9/PLLxtIDFy9eVO/evbVgwQKrScvly5f1448/6rnnnrN6vrwLZ48bN07R0dEF6qxcuVL9+/cvt+ECX3/9tTp37qypU6faHNuWnp6u9957TzExMZJy/nav7/ni6emp8ePHG8MgvvvuO6v3InnFxcXpnXfeUfPmzRk77iRcn3EjcrsxcpL0r3/9S71795bZbFZsbKxatmypzp07KywsTOnp6Vq/fr2xzsu///1vTZkypcgLZ9++ffXhhx9q7NixknLGyjVv3lytW7dWixYt5Ofnp/PnzysuLs64WI8fP77AcWrWrKlOnTpp48aNyszMVOvWrRUVFaVatWoZ3RIbNmyoZ5991tinYcOGmjp1qoYOHars7GxFR0crPDxcXbt2VcOGDY3PlLeFr2PHjvrkk0/K9kWWwssvv6ypU6fq7NmzOn/+vKKionTbbbepefPmMplMiomJMQaJ9+nTR9WrV9fs2bMdHifgDMHBwTKZTMaNrrPGWDz00ENKSkrKty3vjWtsbKw6d+5cYL8FCxaoVq1a5R4fysfQoUOVmJho/DbMnTtXP/zwg1q1aqXGjRvLz89P6enpOnnypOLj44ucVCQgIEBz5sxRv379dPbsWSUnJ2vEiBEaO3asOnTooJCQEGVmZioxMVE7d+5URkaGzbHTo0aN0rRp03Tq1CmlpKQoKipKHTt2VJMmTZSZmanY2Fjt379fUs6QiLy/kfYUHx+vMWPG6JVXXlFoaKiaN2+u4OBgXbt2TUlJSdqyZUu+rp+jR49W3bp1CxynR48eGj9+vEaPHq3s7Gz9/vvvWrVqlZo2baoWLVooICBAly9f1unTpxUfH1+mIRuwD67PuBG5ZSJ355136ssvv9SLL76orKwsXbt2TevWrdO6deuMOh4eHnr77bf15ptvasqUKcU67htvvKEGDRropZde0unTp2WxWLRjxw7t2LHDan1brU0TJ05Uz549lZaWppSUFM2bNy9f+R133FHgR2rgwIHy9fXVyJEjdfr0aWVlZWnt2rVau3ZtgeMPHjxYU6dOdcpg5+rVq2vx4sW6//77jR+mmJgY4+llrv79+2vmzJl66aWXHB4j4CxVqlRR//79tWvXLgUFBal169ZOebq7b9++QnsDXLp0yeiindfVq1fLMyw4wNtvv63mzZvrzTff1KlTp5Sdna3Y2FjFxsba3CcyMtLmxGERERFau3atRo0apb/++ktSzpixX375xWp9W7+LgYGBmj9/vh544AElJyfLYrEYU//n8vb21ocffqihQ4eWSyKX92/RYrHo8OHDNodneHt769VXXzUe7lozfPhwhYWF6aWXXtKhQ4dksVi0Z88e7dmzx+Y+zZo1K/OMoigdrs+4EbllIidJzzzzjDp37qzx48dr7dq1OnnypCpVqqQ6deqoZ8+eGjFiRL4ZIYtr4MCB6tu3r7799lstX75cO3fu1NmzZ5Wdna0qVaooPDxcXbp00YABA2wev23btoqLi9MXX3yhtWvXGoO7i1oEtG/fvjp48KCmT5+upUuXateuXTp37pwqVaqk2rVrq0ePHnrsscfUoUOHEn8ue7r99tu1a9cuTZgwQb/88ovxQ1irVi1FRkZq2LBh+bpgAjeT0NBQozsa4AwPPvig7r33Xi1YsECrV69WTEyMkpOTlZ6eLl9fX9WqVUvh4eG6/fbb1bt3bzVu3LjQ49WvX1/Lly/XunXrtGjRIm3cuFFJSUlG9/l69eqpTZs26tOnj+655x6bx2nTpo22bdumL7/8UsuXL9fRo0dlNpuN37ennnpKTZs2tffXYXjxxRd1//33a+3atdq8ebN2796to0ePKi0tTR4eHgoMDFR4eLi6deumwYMHq379+kUes1u3btq2bZuWLl2q3377TVu3btXp06eVlpamypUrq1q1amrSpIk6dOigu+66S61atSq3z4eicX3GjcZkKevUVsANiIXMAQAAcjB20DW53WQnAAAAAHCzI5EDAAAAADdDIgcAAAAAboZEDgAAAADcDIkcAAAAALgZEjkAAAAAcDMkcgAAAADgZkjkAAAAANjFiRMntGbNGu3fv18sV12+vJwdAAAAAAD3d+rUKU2dOjXftqefflq1atVyUkQ3NlrkAAAAAJTZoUOHCmybMmWKfvnlF12+fNkJEd3YSOQAAAAAlFmDBg2sbo+JidEXX3yhHTt20N3SjkjkAAAAAJRZ3bp1Vb16datlmZmZWrx4sb7//ntdvHjRwZHdmEwW0mKggLS0NGeHAAAA4BL8/f2LXTcjI0NffPGFMjIybNbx9vZWr1691LZtW5lMJnuEeFMikQOsIJEDAADIUZJETpL279+vuXPnFlnvlltu0X333afg4ODShnZTo2slAAAAALtp0qSJWrduXWS9o0eP6quvvtLGjRtlNpsdENmNhUQOAAAAgF316tVLPj4+RdbLysrS77//runTpyslJaX8A7uBkMgBAAAAsCs/Pz9179692PVPnDihr7/+Wnv37i2/oG4wJHIAAAAA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", + "image/png": "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", "text/plain": [ - "
" + "
" ] }, "metadata": {}, @@ -1389,7 +1371,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 132, "id": "cad75752", "metadata": {}, "outputs": [], @@ -1427,7 +1409,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 133, "id": "5e4b5f1d", "metadata": { "tags": [ @@ -1469,7 +1451,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 134, "id": "3f607dff", "metadata": {}, "outputs": [ @@ -1479,7 +1461,7 @@ "True" ] }, - "execution_count": 54, + "execution_count": 134, "metadata": {}, "output_type": "execute_result" } @@ -1490,7 +1472,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 135, "id": "ab638bfe", "metadata": {}, "outputs": [ @@ -1500,7 +1482,7 @@ "False" ] }, - "execution_count": 55, + "execution_count": 135, "metadata": {}, "output_type": "execute_result" } @@ -1519,7 +1501,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 136, "id": "73aafac0", "metadata": {}, "outputs": [], @@ -1549,7 +1531,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 137, "id": "be881cb7", "metadata": {}, "outputs": [ @@ -1576,7 +1558,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 138, "id": "fa83014f", "metadata": {}, "outputs": [ @@ -1636,7 +1618,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 139, "id": "1d50479e", "metadata": {}, "outputs": [], @@ -1665,7 +1647,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 140, "id": "798db5c4", "metadata": {}, "outputs": [ @@ -1689,7 +1671,7 @@ "6" ] }, - "execution_count": 60, + "execution_count": 140, "metadata": {}, "output_type": "execute_result" } @@ -1745,6 +1727,27 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": 142, + "id": "e0f15ca4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exception reporting mode: Verbose\n" + ] + } + ], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "0da2daaf", @@ -1757,7 +1760,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "id": "90b4979f", "metadata": {}, "outputs": [], @@ -1779,7 +1782,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "id": "9217f038", "metadata": {}, "outputs": [], @@ -1803,7 +1806,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "id": "3168489b", "metadata": {}, "outputs": [], @@ -1842,7 +1845,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": null, "id": "62267fa3", "metadata": {}, "outputs": [], @@ -1850,26 +1853,15 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": null, "id": "5f8fa829", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 65, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [] }, { "cell_type": "code", - "execution_count": 66, + "execution_count": null, "id": "3d129b03", "metadata": {}, "outputs": [], @@ -1877,33 +1869,15 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": null, "id": "030179b6", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "25\n" - ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 67, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": null, "id": "d737b468", "metadata": {}, "outputs": [], @@ -1911,33 +1885,15 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": null, "id": "77a74879", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5.0\n" - ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 69, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": null, "id": "0521d267", "metadata": {}, "outputs": [], @@ -1945,26 +1901,15 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": null, "id": "468a31e9", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5.0" - ] - }, - "execution_count": 71, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [] }, { "cell_type": "code", - "execution_count": 72, + "execution_count": null, "id": "abbe3ebf", "metadata": {}, "outputs": [], @@ -1972,21 +1917,10 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": null, "id": "651295e4", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5.0" - ] - }, - "execution_count": 73, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [] }, { @@ -2002,7 +1936,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": null, "id": "0a4ee482", "metadata": {}, "outputs": [], @@ -2022,100 +1956,56 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": null, "id": "956ed6d7", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 75, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_between(1, 2, 3) # should be True" ] }, { "cell_type": "code", - "execution_count": 76, + "execution_count": null, "id": "a994eaa6", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 76, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_between(3, 2, 1) # should be True" ] }, { "cell_type": "code", - "execution_count": 77, + "execution_count": null, "id": "4318028d", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 77, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_between(1, 3, 2) # should be False" ] }, { "cell_type": "code", - "execution_count": 78, + "execution_count": null, "id": "05208c8b", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 78, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_between(2, 3, 1) # should be False" ] @@ -2143,7 +2033,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": null, "id": "7eb85c5c", "metadata": {}, "outputs": [], @@ -2163,75 +2053,42 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": null, "id": "687a3e5a", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "29" - ] - }, - "execution_count": 80, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "ackermann(3, 2) # should be 29" ] }, { "cell_type": "code", - "execution_count": 81, + "execution_count": null, "id": "c49e9749", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "61" - ] - }, - "execution_count": 81, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "ackermann(3, 3) # should be 61" ] }, { "cell_type": "code", - "execution_count": 82, + "execution_count": null, "id": "8497dec4", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "125" - ] - }, - "execution_count": 82, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "ackermann(3, 4) # should be 125" ] @@ -2250,7 +2107,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": null, "id": "76be4d15", "metadata": { "tags": [ @@ -2258,16 +2115,7 @@ "remove-cell" ] }, - "outputs": [ - { - "ename": "RecursionError", - "evalue": "maximum recursion depth exceeded in comparison", - "output_type": "error", - "traceback": [ - "\u001b[0;31mRecursionError\u001b[0m\u001b[0;31m:\u001b[0m maximum recursion depth exceeded in comparison\n" - ] - } - ], + "outputs": [], "source": [ "\n", "ackermann(5, 5)" @@ -2304,7 +2152,7 @@ }, { "cell_type": "code", - "execution_count": 84, + "execution_count": null, "id": "0bcba5fe", "metadata": {}, "outputs": [], @@ -2324,100 +2172,56 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": null, "id": "4b6656e6", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 85, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_power(65536, 2) # should be True" ] }, { "cell_type": "code", - "execution_count": 86, + "execution_count": null, "id": "36d9e92a", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 86, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_power(27, 3) # should be True" ] }, { "cell_type": "code", - "execution_count": 87, + "execution_count": null, "id": "1d944b42", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 87, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_power(24, 2) # should be False" ] }, { "cell_type": "code", - "execution_count": 88, + "execution_count": null, "id": "63ec57c9", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 88, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "is_power(1, 17) # should be True" ] @@ -2442,7 +2246,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": null, "id": "4e067bfb", "metadata": {}, "outputs": [], @@ -2462,50 +2266,28 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": null, "id": "2a7c1c21", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "4" - ] - }, - "execution_count": 90, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "gcd(12, 8) # should be 4" ] }, { "cell_type": "code", - "execution_count": 91, + "execution_count": null, "id": "5df00229", "metadata": { "tags": [ "remove-cell" ] }, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 91, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "gcd(13, 17) # should be 1" ] diff --git a/_sources/chap07.ipynb b/_sources/chap07.ipynb index c5efdff..7eb65fe 100644 --- a/_sources/chap07.ipynb +++ b/_sources/chap07.ipynb @@ -4,7 +4,11 @@ "cell_type": "code", "execution_count": 2, "id": "f0c8eb18", - "metadata": {}, + "metadata": { + "tags": [ + "remove-cell" + ] + }, "outputs": [], "source": [ "from os.path import basename, exists\n", @@ -19,22 +23,9 @@ " return filename\n", "\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", - "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "9a282fd3", - "metadata": {}, - "outputs": [], - "source": [ - "import thinkpython\n", - "\n", - "thinkpython.traceback('Minimal')\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -115154,12 +115145,25 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc58db59", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "8e8606b8-9a48-4cbd-a0b0-ea848666c77d", "metadata": {}, "source": [ - "### Ask a Virtual Assistant\n", + "### Ask a virtual assistant\n", "\n", "In `uses_any`, you might have noticed that the first `return` statement is inside the loop and the second is outside." ] diff --git a/_sources/chap08.ipynb b/_sources/chap08.ipynb index 0cc842d..54a46f0 100644 --- a/_sources/chap08.ipynb +++ b/_sources/chap08.ipynb @@ -23,26 +23,9 @@ " return filename\n", "\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", - "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "id": "70147171", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "import thinkpython\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", - "thinkpython.traceback('Minimal')\n", - "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -7061,6 +7044,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "18bced21", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "code", "execution_count": 126, diff --git a/_sources/chap09.ipynb b/_sources/chap09.ipynb index 142343a..6613493 100644 --- a/_sources/chap09.ipynb +++ b/_sources/chap09.ipynb @@ -22,27 +22,10 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py');\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e127238c", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ - "import thinkpython\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", - "thinkpython.traceback('Minimal')\n", - "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -2178,6 +2161,19 @@ "\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4e34564", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "ae9c42da", diff --git a/_sources/chap10.ipynb b/_sources/chap10.ipynb index 015e6cd..5bf8b47 100644 --- a/_sources/chap10.ipynb +++ b/_sources/chap10.ipynb @@ -22,36 +22,10 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py');\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "5f94d971", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], - "source": [ - "import thinkpython\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "\n", - "thinkpython.traceback('Minimal')\n", - "\n", - "%load_ext autoreload\n", - "%autoreload 2" + "import thinkpython" ] }, { @@ -1740,6 +1714,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e3c12ec", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "170f1deb", diff --git a/_sources/chap11.ipynb b/_sources/chap11.ipynb index a4308c3..3bcd24f 100644 --- a/_sources/chap11.ipynb +++ b/_sources/chap11.ipynb @@ -22,21 +22,9 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py');\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "61c3c28e", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "\n", "import thinkpython" ] }, @@ -2195,6 +2183,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "c65d68d2", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "97a0352d", @@ -2324,8 +2325,13 @@ { "cell_type": "markdown", "id": "bdfc8c27", - "metadata": {}, + "metadata": { + "tags": [ + "section_exercise_11" + ] + }, "source": [ + "(section_exercise_11)=\n", "### Exercise\n", "\n", "In this chapter we made a dictionary that maps from each letter to its index in the alphabet." @@ -2547,13 +2553,8 @@ { "cell_type": "markdown", "id": "779f13af", - "metadata": { - "tags": [ - "section_exercise11" - ] - }, + "metadata": {}, "source": [ - "(section_exercise11)=\n", "### Exercise\n", "\n", "Write a function called `most_frequent_letters` that takes a string and prints the letters in decreasing order of frequency.\n", diff --git a/_sources/chap12.ipynb b/_sources/chap12.ipynb index 02c35db..839466d 100644 --- a/_sources/chap12.ipynb +++ b/_sources/chap12.ipynb @@ -22,21 +22,9 @@ " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py');\n", - "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/diagram.py');" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e7518c68", - "metadata": { - "tags": [ - "remove-cell" - ] - }, - "outputs": [], - "source": [ + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "\n", "import thinkpython" ] }, @@ -2221,7 +2209,10 @@ "A sequence of three elements.\n", "\n", "**n-gram:**\n", - "A sequence of an unspecified number of elements." + "A sequence of an unspecified number of elements.\n", + "\n", + "**rubber duck debugging:**\n", + "A way of debugging by explaining a problem aloud to an inanimate object." ] }, { @@ -2232,6 +2223,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "05752b6d", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "9b0efab8", diff --git a/_sources/chap13.ipynb b/_sources/chap13.ipynb index 85e291b..513429c 100644 --- a/_sources/chap13.ipynb +++ b/_sources/chap13.ipynb @@ -1299,7 +1299,7 @@ "In the previous example, the keys and values in the shelf are strings.\n", "But we can also use a shelf to contain data structures like lists and dictionaries.\n", "\n", - "As an example, let's revisit the anagram example from an exercise in [Chapter 11](section_exercise11).\n", + "As an example, let's revisit the anagram example from an exercise in [Chapter 11](section_exercise_11).\n", "Recall that we made a dictionary that maps from a sorted string of letters to the\n", "list of words that can be spelled with those letters.\n", "For example, the key `'opst'` maps to the list `['opts', 'post', 'pots', 'spot', 'stop', 'tops']`.\n", @@ -1437,7 +1437,7 @@ "metadata": {}, "source": [ "The key is the same as in the previous example, so we want to append a second word to the same list of strings.\n", - "Here's how we would do it if `anagrams` were a dictionary." + "Here's how we would do it if `db` were a dictionary." ] }, { @@ -2080,6 +2080,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd885ba1", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "9a537173", diff --git a/_sources/chap14.ipynb b/_sources/chap14.ipynb index 3bcd9b3..327ec61 100644 --- a/_sources/chap14.ipynb +++ b/_sources/chap14.ipynb @@ -1591,6 +1591,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab3d0104", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "da0aea86", diff --git a/_sources/chap15.ipynb b/_sources/chap15.ipynb index a530687..5ee8065 100644 --- a/_sources/chap15.ipynb +++ b/_sources/chap15.ipynb @@ -1052,6 +1052,19 @@ "## Exercises" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "3115ea33", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, { "cell_type": "markdown", "id": "25cd6888", diff --git a/_sources/chap16.ipynb b/_sources/chap16.ipynb new file mode 100644 index 0000000..d9bb9b4 --- /dev/null +++ b/_sources/chap16.ipynb @@ -0,0 +1,2735 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "ae5a86f8", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from os.path import basename, exists\n", + "\n", + "def download(url):\n", + " filename = basename(url)\n", + " if not exists(filename):\n", + " from urllib.request import urlretrieve\n", + "\n", + " local, _ = urlretrieve(url, filename)\n", + " print(\"Downloaded \" + str(local))\n", + " return filename\n", + "\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "download('https://github.com/ramalho/jupyturtle/releases/download/2024-03/jupyturtle.py');\n", + "\n", + "import thinkpython" + ] + }, + { + "cell_type": "markdown", + "id": "e826e661", + "metadata": {}, + "source": [ + "# Classes and Objects\n", + "\n", + "At this point we have defined classes and created objects that represent the time of day and the day of the year.\n", + "And we've defined methods that create, modify, and perform computations with these objects.\n", + "\n", + "In this chapter we'll continue our tour of object-oriented programming (OOP) by defining classes that represent geometric objects, including points, lines, rectangles, and circles.\n", + "We'll write methods that create and modify these objects, and we'll use the `jupyturtle` module to draw them.\n", + "\n", + "I'll use these classes to demonstrate OOP topics including object identity and equivalence, shallow and deep copying, and polymorphism." + ] + }, + { + "cell_type": "markdown", + "id": "6b414d4a", + "metadata": { + "tags": [ + "section_create_point" + ] + }, + "source": [ + "(section_create_point)=\n", + "## Creating a Point\n", + "\n", + "In computer graphics a location on the screen is often represented using a pair of coordinates in an `x`-`y` plane.\n", + "By convention, the point `(0, 0)` usually represents the upper-left corner of the screen, and `(x, y)` represents the point `x` units to the right and `y` units down from the origin.\n", + "Compared to the Cartesian coordinate system you might have seen in a math class, the `y` axis is upside-down.\n", + "\n", + "There are several ways we might represent a point in Python:\n", + "\n", + "- We can store the coordinates separately in two variables, `x` and `y`.\n", + "\n", + "- We can store the coordinates as elements in a list or tuple.\n", + "\n", + "- We can create a new type to represent points as objects.\n", + "\n", + "In object-oriented programming, it would be most idiomatic to create a new type.\n", + "To do that, we'll start with a class definition for `Point`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9c99d2c", + "metadata": {}, + "outputs": [], + "source": [ + "class Point:\n", + " \"\"\"Represents a point in 2-D space.\"\"\"\n", + " \n", + " def __init__(self, x, y):\n", + " self.x = x\n", + " self.y = y\n", + " \n", + " def __str__(self):\n", + " return f'Point({self.x}, {self.y})'" + ] + }, + { + "cell_type": "markdown", + "id": "3d35a095", + "metadata": {}, + "source": [ + "The `__init__` method takes the coordinates as parameters and assigns them to attributes `x` and `y`.\n", + "The `__str__` method returns a string representation of the `Point`.\n", + "\n", + "Now we can instantiate and display a `Point` object like this." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d318001a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(0, 0)\n" + ] + } + ], + "source": [ + "start = Point(0, 0)\n", + "print(start)" + ] + }, + { + "cell_type": "markdown", + "id": "b3fd8858", + "metadata": {}, + "source": [ + "The following diagram shows the state of the new object. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3eb47826", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from diagram import make_frame, make_binding\n", + "\n", + "d1 = vars(start)\n", + "frame = make_frame(d1, name='Point', dy=-0.25, offsetx=0.18)\n", + "binding = make_binding('start', frame)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6702a353", + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from diagram import diagram, adjust\n", + "\n", + "width, height, x, y = [1.41, 0.89, 0.26, 0.5]\n", + "ax = diagram(width, height)\n", + "bbox = binding.draw(ax, x, y)\n", + "#adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "713b7410", + "metadata": {}, + "source": [ + "As usual, a programmer-defined type is represented by a box with the name of the type outside and the attributes inside.\n", + "\n", + "In general, programmer-defined types are mutable, so we can write a method like `translate` that takes two numbers, `dx` and `dy`, and adds them to the attributes `x` and `y`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0f710c4f", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Point\n", + "\n", + " def translate(self, dx, dy):\n", + " self.x += dx\n", + " self.y += dy" + ] + }, + { + "cell_type": "markdown", + "id": "4d183292", + "metadata": {}, + "source": [ + "This function translates the `Point` from one location in the plane to another.\n", + "If we don't want to modify an existing `Point`, we can use `copy` to copy the original object and then modify the copy." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6e37126b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(300, 0)\n" + ] + } + ], + "source": [ + "from copy import copy\n", + "\n", + "end1 = copy(start)\n", + "end1.translate(300, 0)\n", + "print(end1)" + ] + }, + { + "cell_type": "markdown", + "id": "562567c2", + "metadata": {}, + "source": [ + "We can encapsulate those steps in another method called `translated`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f38bfaaa", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Point\n", + "\n", + " def translated(self, dx=0, dy=0):\n", + " point = copy(self)\n", + " point.translate(dx, dy)\n", + " return point" + ] + }, + { + "cell_type": "markdown", + "id": "a7a635ee", + "metadata": {}, + "source": [ + "In the same way that the built in function `sort` modifies a list, and the `sorted` function creates a new list, now we have a `translate` method that modifies a `Point` and a `translated` method that creates a new one.\n", + "\n", + "Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cef864a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(0, 150)\n" + ] + } + ], + "source": [ + "end2 = start.translated(0, 150)\n", + "print(end2)" + ] + }, + { + "cell_type": "markdown", + "id": "923362d2", + "metadata": {}, + "source": [ + "In the next section, we'll use these points to define and draw a line." + ] + }, + { + "cell_type": "markdown", + "id": "837f98fd", + "metadata": {}, + "source": [ + "## Creating a Line\n", + "\n", + "Now let's define a class that represents the line segment between two points.\n", + "As usual, we'll start with an `__init__` method and a `__str__` method." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6ae012a1", + "metadata": {}, + "outputs": [], + "source": [ + "class Line:\n", + " def __init__(self, p1, p2):\n", + " self.p1 = p1\n", + " self.p2 = p2\n", + " \n", + " def __str__(self):\n", + " return f'Line({self.p1}, {self.p2})'" + ] + }, + { + "cell_type": "markdown", + "id": "d7dad30e", + "metadata": {}, + "source": [ + "With those two methods, we can instantiate and display a `Line` object we'll use to represent the `x` axis." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "39b2ae2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Line(Point(0, 0), Point(300, 0))\n" + ] + } + ], + "source": [ + "line1 = Line(start, end1)\n", + "print(line1)" + ] + }, + { + "cell_type": "markdown", + "id": "e7b5fd9a", + "metadata": {}, + "source": [ + "When we call `print` and pass `line` as a parameter, `print` invokes `__str__` on `line`.\n", + "The `__str__` method uses an f-string to create a string representation of the `line`. \n", + "\n", + "The f-string contains two expressions in curly braces, `self.p1` and `self.p2`.\n", + "When those expressions are evaluated, the results are `Point` objects.\n", + "Then, when they are converted to strings, the `__str__` method from the `Point` class gets invoked.\n", + "\n", + "That's why, when we display a `Line`, the result contains the string representations of the `Point` objects.\n", + "\n", + "The following object diagram shows the state of this `Line` object." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "dee93202", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from diagram import Binding, Value, Frame\n", + "\n", + "d1 = vars(line1.p1)\n", + "frame1 = make_frame(d1, name='Point', dy=-0.25, offsetx=0.17)\n", + "\n", + "d2 = vars(line1.p2)\n", + "frame2 = make_frame(d2, name='Point', dy=-0.25, offsetx=0.17)\n", + "\n", + "binding1 = Binding(Value('start'), frame1, dx=0.4)\n", + "binding2 = Binding(Value('end'), frame2, dx=0.4)\n", + "frame3 = Frame([binding1, binding2], name='Line', dy=-0.9, offsetx=0.4, offsety=-0.25)\n", + "\n", + "binding = make_binding('line1', frame3)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "43682c57", + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQkAAADoCAYAAAD8BxH3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAUe0lEQVR4nO3dbVCU1/3/8TdxEW8gIpFERQUUHK27BKIscqPRaMMEtKOSRIemrTot1HGmhKkGJ9CMfeBkTGdqgk0Z09ZoEkNIgjpRaIi23iaC92gpUaMELWqjEUErogR+Dxz56185sLLLrvp5PUKuc53ru+Pw4VwXu+fr1dLS0oKISBsecXcBIuLZFBIiYqSQEBEjhYSIGCkkRMRIISEiRgoJETFSSIiIkUJCRIwUEiJipJAQESOFhIgYKSRExEghISJGCgkRMVJIiIiRQkJEjBQSImKkkBARI4WEiBgpJETESCHhJCEhIRw8ePC27/3yl79ky5Yt7ilIxEks7i7gQfbXv/7V3SWIdJpWEi40YcIE1q9fD8Ds2bNJT09n0qRJDB8+nBkzZnDt2jUArl+/zqJFi7Db7URGRvLiiy9SW1vrxspF/h+FRBc6ePAgGzZsoLKykv/+978UFhYC8Ic//IHevXuze/duDh48iM1mIycnx83Vityg240uNH36dHr16gWA3W7n+PHjAKxfv566urrW0Lh27RohISHuKlPkNgqJLtSjR4/Wr7t160ZTUxMALS0tLF++nGeffdZdpYm0SbcbHmDatGksW7aMK1euAHDlyhUqKircXJXIDVpJOFFiYiLe3t6t/7515WCSlZVFY2MjMTExeHl5tX5v1KhRLqlTxBFe6iouIia63RARI4WEiBgpJETESA8uneDSpUvuLkHEYX5+fh0ap5WEiBgpJETESCEhIkYKCRExUkiIiJFCQkSMHAoJLy8vLl68CEBSUhJHjhzp1MVXrlyJzWbDYrHw5ptvdmouEXGNe36fRHFxcacvPnr0aD7++GNef/31Ts8lIq5xz7cbt278OmHCBBYsWMC4ceMYNmwYv/71r1vHXbp0iV/96lfY7XYiIiJIS0tr3bbtySefZOTIkTzyiGfc9fzvf/9jxYoVbN++natXr7q7HBGP4LSfzuPHj7Nlyxb+9a9/UVJSwq5duwD47W9/y7hx49i9ezfl5eU0Nzfz1ltvOeuyTtWzZ09CQkLYvn07b731lsLCxaxWK0899RTx8fFER0fzzjvvGMenpKRw7NixdufduHEju3fvdlaZDz2nvS175syZWCwWLBYLkZGRHD9+nNjYWNavX8+uXbv44x//CEBDQwPdunVzaO6jR4+yb98+Z5XaruDgYC5cuMDWrVvZtm0bzzzzDPHx8V12/YfJqlWriIiI4OTJk8TFxREXF4fVar3r2Jvb+7Vn48aN2Gw27Ha7M0t9aDktJExbsxUWFjJ8+HBnXcrltMVG1xsyZAhhYWEcPXqU119/nePHj9PS0kJ6ejpz584Fbqw8PvzwQyIiIkhKSiIqKoq9e/dy5swZnnnmGd58801KSkooLi5my5YtfPjhh6SlpfGLX/zCza/u/ubyD3hNmzaNpUuXsmLFCiwWC7W1tXz//feEhYV1eI7hw4d3Scg0NzfzxRdfsHfvXry9vZkwYQJ2u73DO0zJvauoqODYsWOsXbuW8PBw1qxZw7lz5xg/fjxWq/Wuq4KqqiqKioq4fv06drudsrIyEhMTSUpKwmazMX/+fDe8kgePy58YLlu2jJ49exIZGUlERASTJk3i22+/BW4sNQcNGsQnn3zC4sWLGTRoEAcOHHB1SW1qaGigurqa8ePHk5GRwfjx4xUQLjZ79mzi4+PJyMjg7bffZseOHcyZMweAwMBApk6dytatW+967owZM7BYLPTs2RObzUZVVVUXVv7wcGglcesy/OYPOnDHf+Knn37a+rWvry9/+tOf7jrf7NmzmT17tiMluFTv3r1JT093dxkPlZvPJG7KyMi47fjNPT/v5v+/xf3hhx+cX6DoHZfiWSZOnMjq1asBOH/+PBs2bGDixIkOzeHn50d9fb0rynsoKSTEo7zxxhscOXKEsWPHkpyczIIFC4iOjnZojlmzZrFu3ToSEhJaA0funXbLdgLtTCX3I+1MJSJOoZAQESOFhIgYabdscYvLly+7uwSP5+vr6+4SAK0kRKQdCgkRMVJIiIiRQkJEjBQSImKkkBARI4WEiBgpJETESCEhIkYKCQ9QXV3N3/72t3s+/9ChQ7dt9COe48SJE0ydOpWEhASee+65Tje0cgeFhAc4efIkK1euvKdzm5qaOHz4cId3kpaulZWVxUsvvcTOnTuZP38+L7/8srtLcpj2k3ACR/aTaGhoYN68eVRUVODt7U1gYCD/+c9/OHXqFOHh4QwaNIiCggKys7PZuXMnTU1N+Pn5sXz5csLDwwF49NFHycrK4osvvuBHP/oR27Zto76+nuDgYKKjo93WMvHKlSsUFhYSHh5OVFQUPj4+bY7t6Gc3vvnmG2bOnMnatWsJDg4mLy+PHTt28MEHH7ilqVNDQwPFxcWEhoZitVrp3r17m2PPnz9PXFwc//73v7FYLLS0tBAZGcn69esJDQ1t91qu/uxGR/eT0Ae8utjmzZu5ePEie/bsAeDChQtUVFSwaNEivvzyy9ZxmZmZLFmyBLixZ+grr7zCunXrWo9369aNbdu2AbBmzRo2btxIfn5+F76SO/Xo0YPBgwdTVlbGvn37GD16dLth0Z6wsDBycnJIT0/ntddeY9WqVRQXF7ut65uPjw8DBw7kwIEDHD58GJvN1mZYnD59mscffxyL5caPmZeXF0FBQdTU1HQoJDyFQqKLWa1Wjh49SmZmJgkJCTz77LN3HffPf/6TFStWcPnyZZqbm6mtrb3t+M9+9rMOX/PEiRMcOnSoU3U7IigoiIsXL7Jr1y5KS0uJi4vrVKOc6dOn89VXX5GamkpBQQGPPfbYHWOqq6uprKzsTNkOeeKJJ6ivr2fv3r3s37+fMWPGEBkZ2WXX70p6JtHFQkND2b17Nz/+8Y8pLS1l7NixrZ3abzp16hQLFizgL3/5C2VlZbz77rs0NjbeNqZ3795dWLV7NTU18fXXX+Pv78/Zs2fdXU6HDRw4kO++++62RlU1NTUEBQW5uTLHaCXRxWpqavD39ycpKYnJkydTVFREQEDAbbs719fX4+3tTf/+/WlpaWm3R2Z7u0MPHTqUoUOHOu01tKW5uZnt27dTXl6OxWIhNja207cbAEuWLGHYsGHk5uby/PPPExERccdyPTg4mODg4E5dpyOam5spLS2lsrISi8XCmDFj2rzd6NevHzabjcLCQmbOnElRUREDBgy4r241QCHR5SoqKvj9739PS0sLTU1NzJo1i5iYGEaMGEFMTAwhISEUFBSQkpJCTEwMAQEBJCcnG+d8+umnyc3NJTY2lpiYGLc9uLx69SqnTp0iJibGKeEAsGnTJrZu3UpRURG9evVi8eLFpKen89lnn7mlcVJjYyNnzpwhKiqq3QeXAEuXLiUzM5Ply5fj6+vLsmXLuqhS59FfN5xAu2U7TjtTtc9T/rqhZxIiYqSQEBEjhYSIGCkkRMRIISEiRgoJETFSSIiIkUJCRIwUEiJipJAQESOFhIgYKSRExEghISJGCgkRMVJIiIiRQkJEjBQSIi6k5jwiYvQgNOdRSIhHy8vLY+HCha3/rqurY9SoUXe0GOgqDQ0NFBYWsn//fq5du2Yce/78ecrLy0lJSQEgOTmZ06dPU1VV1RWlOo1CQjxaamoqn3/+OXV1dQAUFBSQmJhI37593VLPrc158vPzjWFhas5zP9Fu2eLR+vTpw5QpU/joo49IS0vjvffeIy8v745xas7jOgoJ8Xhz585lzpw5hIeHExAQgM1mc3dJHXJrc56bvUDVnEfEBcLDwxkyZAgLFy4kJyfnrmPUnMd11HfDCdR3w3GO9t0oKioiOzubPXv24O3t7aKq2udIV3G40RU9MzOT2tra1uY8I0eO7NC1PKXvhkLCCRQSjnM0JF599VUCAwPJzMx0UUWex1NCQrcb4tHOnj3LCy+8gL+/P/n5+e4u56GklYQTaCXhOLX5a5+nrCT0PgkRMVJIiIiRQkJEjBQSImKkkBARI4WEiBgpJETESCEhIkYKCREx0tuyxS1c/W5CcR6tJETESCEhIkYKCRExUkiIiJFCQkSMFBIiYqSQEBEjhYSIGCkkRMRIISEiRgoJETFSSIiIkUJCRIwUEiJipJAQESOFhIgYKSRExEghISJGCgkRMVJIiIiRQkJEjBQSImKkkBC3sVqtPPXUU8THxxMdHc0777xjHJ+SksKxY8fanXfjxo3s3r3bWWU+9NR3Q9xq1apVREREcPLkSeLi4oiLi8Nqtd51bGFhYYfm3LhxIzabDbvd7sxSH1paSYhHGDJkCGFhYRw9epSf/vSnjB07lpiYGFauXNk6xmq1cujQIQCSkpLIzs4mMTGRiIgIXn75ZQBKSkooLi4mNzeX+Ph4Vq9e7Y6X80DRSkI8QkVFBceOHWPt2rWEh4ezZs0azp07x/jx47FarXddFVRVVVFUVMT169ex2+2UlZWRmJhIUlISNpuN+fPnu+GVPHi0khC3mj17NvHx8WRkZPD222+zY8cO5syZA0BgYCBTp05l69atdz13xowZWCwWevbsic1mo6qqqgsrf3hoJSFudfOZxE0ZGRm3Hffy8mrz3B49erR+3a1bN3744QfnFyhaSYhnmThxYutzhPPnz7NhwwYmTpzo0Bx+fn7U19e7oryHkkJCPMobb7zBkSNHGDt2LMnJySxYsIDo6GiH5pg1axbr1q0jISFBDy6dwKulpaXF3UXc7y5duuTuEkQc5ufn16FxWkmIiJFCQkSMFBIiYqQ/gYpbXL582d0lPLB8fX2dOp9WEiJipJAQESOFhIgYKSRExEghISJGCgkRMVJIiIiRQkJEjBQSImKkkHiAhISEUF1d7e4y5BazZs1i0qRJTJ48mWnTpnH48GEATpw4wdSpU0lISOC5557jyJEjreeYjrmDQkLEhVasWME//vEPNm/eTHp6OpmZmQBkZWXx0ksvsXPnTubPn9+6kW97x9xBIeEh9u3bx5QpU3j66adJSEhg3bp1VFdXM3jwYJYsWcL48eN58sknKSkpaT2nuLiYMWPGEBsby+9+9zs3Vn/DlStXeP/99yktLaWxsdEpc37zzTeMHj26dYWUl5dHamoqzc3NTpnfUQ0NDRQWFrJ//36uXbvW7vg+ffq0fn1zt6zz589TXl5OSkoKAMnJyZw+fZqqqirjMXfRB7w8wMWLF8nIyODTTz+lf//+fP/994wbN46VK1dSV1eH1WolOzubTZs2kZWVRWJiIufOnWPevHmUlJQwYsQI3n33XS5cuODW19GjRw8GDx5MWVkZ+/btY/To0URFReHj43PPc4aFhZGTk0N6ejqvvfYaq1atori4mEcecc/vNx8fHwYOHMiBAwc4fPgwNpsNq9VK9+7d2zznN7/5DV9++SUAH3zwAadPn+bxxx/HYrnx4+fl5UVQUBA1NTU8+uijbR4LDQ11/Qu8C4WEBygrK+Pbb79t/e1x07Fjx+jRowc/+clPALDb7a2/Ufbs2YPVamXEiBEA/PznP2fhwoV3nf/EiROt/Sq6QlBQEBcvXmTXrl2UlpYSFxfXqUY506dP56uvviI1NZWCggIee+yxO8ZUV1dTWVnZmbId8sQTT1BfX8/evXvZv38/Y8aMITIy8q5jc3NzAfj4449ZsmQJr7zySpfV6QwKCQ/Q0tLCiBEj2Lx5823fr66uxsfHp3XHaNOO0KZdpe93TU1NfP311/j7+3P27Fl3l3PPXnzxRRYtWsSAAQP47rvvaGpqwmKx0NLSQk1NDUFBQfj5+bV5zF0UEh4gJiaG6upqtmzZ0roz9KFDh+jZs2eb59jtdubNm8fRo0cZPnw477//fpv3yEOHDmXo0KEuqf1Wzc3NbN++nfLyciwWC7GxsZ2+3QBYsmQJw4YNIzc3l+eff56IiIg7lt7BwcEEBwd36jod0dzcTGlpKZWVlVgsFsaMGdPm7UZdXR0NDQ30798fgL///e/07duXfv36YbPZKCwsZObMmRQVFTFgwIDW12Q65g4KCQ/Qt29fPvnkE3JycsjOzub69esMGjSIpUuXtnlOv379+POf/0xqairdu3dn8uTJBAQEdGHVd7p69SqnTp0iJibGKeEAsGnTJrZu3UpRURG9evVi8eLFpKen89lnn93Wd6OrNDY2cubMGaKiotp9FnHp0iXS0tK4evUqjzzyCAEBAaxevRovLy+WLl1KZmYmy5cvx9fXl2XLlrWeZzrmDtot2wm0W7bjtDOV63R0Zyrtli0iTqGQEBEjhYSIGCkkRMRIISEiRgoJETFSSIiIkUJCRIwUEiJipJAQESOFhIgYKSRExEghISJGCgkRMVJIiIiRQkJEjBQSIi7kaY127oVCQsSFPK3Rzr1QSIhHy8vLu61VQF1dHaNGjaK2ttYt9TjSnMcTG+3cC4WEeLTU1FQ+//xz6urqACgoKCAxMZG+ffu6pZ5bm/Pk5+cbw8LUhOd+ot2yxaP16dOHKVOm8NFHH5GWlsZ7771HXl7eHeM8uTnP/U4hIR5v7ty5zJkzh/DwcAICArDZbO4uqUMGDhzocY127oVCQjxeeHg4Q4YMYeHCheTk5Nx1jCc252mvCc/9Qn03nEB9NxznaN+NoqIisrOz2bNnD97e3i6qqn0NDQ0UFxcTGhrabnMeuNEVPTMzk9ra2tZGOyNHjnRpjc7uu6GQcAKFhOMcDYlXX32VwMBAMjMzXVTRg8PZIaHbDfFoZ8+e5YUXXsDf35/8/Hx3l/NQ0krCCbSScJza/LmO2vyJSJdSSIiIkUJCRIwUEiJipJAQESOFhIgYKSRExEghISJGCgkRMdLbssUtOvquQHE/rSRExEghISJGCgkRMVJIiIiRQkJEjBQSImKkkBARI4WEiBgpJETESHtcioiRVhIiYqSQEBEjhYSIGCkkRMRIISEiRgoJETFSSIiIkUJCRIwUEiJipJAQESOFhIgYKSRExEghISJGCgkRMVJIiIiRQkJEjBQSImKkkBARI4WEiBgpJETE6P8A1KbuSiik4PoAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "width, height, x, y = [2.45, 2.12, 0.27, 1.76]\n", + "ax = diagram(width, height)\n", + "bbox = binding.draw(ax, x, y)\n", + "#adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "079859b5", + "metadata": {}, + "source": [ + "String representations and object diagrams are useful for debugging, but the point of this example is to generate graphics, not text!\n", + "So we'll use the `jupyturtle` module to draw lines on the screen.\n", + "\n", + "As we did in [Chapter 4](section_turtle_module), we'll use `make_turtle` to create a `Turtle` object and a small canvas where it can draw.\n", + "To draw lines, we'll use two new functions from the `jupyturtle` module:\n", + "\n", + "* `jumpto`, which takes two coordinates and moves the `Turtle` to the given location without drawing a line, and \n", + "\n", + "* `moveto`, which moves the `Turtle` from its current location to the given location, and draws a line segment between them.\n", + "\n", + "Here's how we import them." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5225d870", + "metadata": {}, + "outputs": [], + "source": [ + "from jupyturtle import make_turtle, jumpto, moveto" + ] + }, + { + "cell_type": "markdown", + "id": "9d2dd88f", + "metadata": {}, + "source": [ + "And here's a method that draws a `Line`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "999843cb", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Line\n", + "\n", + " def draw(self):\n", + " jumpto(self.p1.x, self.p1.y)\n", + " moveto(self.p2.x, self.p2.y)" + ] + }, + { + "cell_type": "markdown", + "id": "2341f0e0", + "metadata": {}, + "source": [ + "To show how it's used, I'll create a second line that represents the `y` axis." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "6ac10ec7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Line(Point(0, 0), Point(0, 150))\n" + ] + } + ], + "source": [ + "line2 = Line(start, end2)\n", + "print(line2)" + ] + }, + { + "cell_type": "markdown", + "id": "d7450736", + "metadata": {}, + "source": [ + "And then draw the axes." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b15b70dd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "line1.draw()\n", + "line2.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "473c156f", + "metadata": {}, + "source": [ + "As we define and draw more objects, we'll use these lines again.\n", + "But first let's talk about object equivalence and identity." + ] + }, + { + "cell_type": "markdown", + "id": "950da673", + "metadata": {}, + "source": [ + "## Equivalence and identity\n", + "\n", + "Suppose we create two points with the same coordinates." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "62414805", + "metadata": {}, + "outputs": [], + "source": [ + "p1 = Point(200, 100)\n", + "p2 = Point(200, 100)" + ] + }, + { + "cell_type": "markdown", + "id": "82b14526", + "metadata": {}, + "source": [ + "If we use the `==` operator to compare them, we get the default behavior for programmer-defined types -- the result is `True` only if they are the same object, which they are not." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "7c611eb2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1 == p2" + ] + }, + { + "cell_type": "markdown", + "id": "96be0ff8", + "metadata": {}, + "source": [ + "If we want to change that behavior, we can provide a special method called `__eq__` that defines what it means for two `Point` objects to be equal." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "3a976072", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Point\n", + "\n", + "def __eq__(self, other):\n", + " return (self.x == other.x) and (self.y == other.y)" + ] + }, + { + "cell_type": "markdown", + "id": "7f4409de", + "metadata": {}, + "source": [ + "This definition considers two `Points` to be equal if their attributes are equal.\n", + "Now when we use the `==` operator, it invokes the `__eq__` method, which indicates that `p1` and `p2` are considered equal." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "7660d756", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1 == p2" + ] + }, + { + "cell_type": "markdown", + "id": "52662e6a", + "metadata": {}, + "source": [ + "But the `is` operator still indicates that they are different objects." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e76ff9ef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1 is p2" + ] + }, + { + "cell_type": "markdown", + "id": "c008d3dd", + "metadata": {}, + "source": [ + "It's not possible to override the `is` operator -- it always checks whether the objects are **identical**.\n", + "But for programmer-defined types, you can override the `==` operator so it checks whether the objects are **equivalent**.\n", + "And you can define what equivalent means." + ] + }, + { + "cell_type": "markdown", + "id": "893a8cab", + "metadata": {}, + "source": [ + "## Creating a Rectangle\n", + "\n", + "Now let's define a class that represents and draws rectangles.\n", + "To keep things simple, we'll assume that the rectangles are either vertical or horizontal, not at an angle.\n", + "What attributes do you think we should use to specify the location and size of a rectangle?\n", + "\n", + "There are at least two possibilities:\n", + "\n", + "- You could specify the width and height of the rectangle and the location of one corner.\n", + "\n", + "- You could specify two opposing corners.\n", + "\n", + "At this point it's hard to say whether either is better than the other, so let's implement the first one.\n", + "Here is the class definition." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "c2a2aa29", + "metadata": {}, + "outputs": [], + "source": [ + "class Rectangle:\n", + " \"\"\"Represents a rectangle. \n", + "\n", + " attributes: width, height, corner.\n", + " \"\"\"\n", + " def __init__(self, width, height, corner):\n", + " self.width = width\n", + " self.height = height\n", + " self.corner = corner\n", + " \n", + " def __str__(self):\n", + " return f'Rectangle({self.width}, {self.height}, {self.corner})'" + ] + }, + { + "cell_type": "markdown", + "id": "df2852f3", + "metadata": {}, + "source": [ + "As usual, the `__init__` method assigns the parameters to attributes and the `__str__` returns a string representation of the object.\n", + "Now we can instantiate a `Rectangle` object, using a `Point` as the location of the upper-left corner." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "5d36d561", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rectangle(100, 50, Point(30, 20))\n" + ] + } + ], + "source": [ + "corner = Point(30, 20)\n", + "box1 = Rectangle(100, 50, corner)\n", + "print(box1)" + ] + }, + { + "cell_type": "markdown", + "id": "a9e0b5ec", + "metadata": {}, + "source": [ + "The following diagram shows the state of this object." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "6e47d99a", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from diagram import Binding, Value\n", + "\n", + "def make_rectangle_binding(name, box, **options):\n", + " d1 = vars(box.corner)\n", + " frame_corner = make_frame(d1, name='Point', dy=-0.25, offsetx=0.07)\n", + "\n", + " d2 = dict(width=box.width, height=box.height)\n", + " frame = make_frame(d2, name='Rectangle', dy=-0.25, offsetx=0.45)\n", + " binding = Binding(Value('corner'), frame1, dx=0.92, draw_value=False, **options)\n", + " frame.bindings.append(binding)\n", + "\n", + " binding = Binding(Value(name), frame)\n", + " return binding, frame_corner\n", + "\n", + "binding_box1, frame_corner1 = make_rectangle_binding('box1', box1)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1e7e4a72", + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from diagram import Bbox\n", + "\n", + "width, height, x, y = [2.83, 1.49, 0.27, 1.1]\n", + "ax = diagram(width, height)\n", + "bbox1 = binding_box1.draw(ax, x, y)\n", + "bbox2 = frame_corner1.draw(ax, x+1.85, y-0.6)\n", + "bbox = Bbox.union([bbox1, bbox2])\n", + "#adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "bb54e6b5", + "metadata": {}, + "source": [ + "To draw a rectangle, we'll use the following method to make four `Point` objects to represent the corners." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "8fb74cd8", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def make_points(self):\n", + " p1 = self.corner\n", + " p2 = p1.translated(self.width, 0)\n", + " p3 = p2.translated(0, self.height)\n", + " p4 = p3.translated(-self.width, 0)\n", + " return p1, p2, p3, p4" + ] + }, + { + "cell_type": "markdown", + "id": "20dbe0cb", + "metadata": {}, + "source": [ + "Then we'll make four `Line` objects to represent the sides." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "1c419c73", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def make_lines(self):\n", + " p1, p2, p3, p4 = self.make_points()\n", + " return Line(p1, p2), Line(p2, p3), Line(p3, p4), Line(p4, p1)" + ] + }, + { + "cell_type": "markdown", + "id": "30fe41cc", + "metadata": {}, + "source": [ + "Then we'll draw the sides." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "5ceccb7d", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def draw(self):\n", + " lines = self.make_lines()\n", + " for line in lines:\n", + " line.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "390ba3e7", + "metadata": {}, + "source": [ + "Here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "2870c782", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "line1.draw()\n", + "line2.draw()\n", + "box1.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "532a4f69", + "metadata": {}, + "source": [ + "The figure includes two lines to represent the axes." + ] + }, + { + "cell_type": "markdown", + "id": "0e713a90", + "metadata": {}, + "source": [ + "## Changing rectangles\n", + "\n", + "Now let's consider two methods that modify rectangles, `grow` and `translate`.\n", + "We'll see that `grow` works as expected, but `translate` has a subtle bug.\n", + "See if you can figure it out before I explain.\n", + "\n", + "`grow` takes two numbers, `dwidth` and `dheight`, and adds them to the `width` and `height` attributes of the rectangle." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "21537ed7", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def grow(self, dwidth, dheight):\n", + " self.width += dwidth\n", + " self.height += dheight" + ] + }, + { + "cell_type": "markdown", + "id": "a51913e2", + "metadata": {}, + "source": [ + "Here's an example that demonstrates the effect by making a copy of `box1` and invoking `grow` on the copy." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "243ca804", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rectangle(160, 90, Point(30, 20))\n" + ] + } + ], + "source": [ + "box2 = copy(box1)\n", + "box2.grow(60, 40)\n", + "print(box2)" + ] + }, + { + "cell_type": "markdown", + "id": "6d74da62", + "metadata": {}, + "source": [ + "If we draw `box1` and `box2`, we can confirm that `grow` works as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "110f995f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "line1.draw()\n", + "line2.draw()\n", + "box1.draw()\n", + "box2.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "0c940008", + "metadata": {}, + "source": [ + "Now let's see about `translate`.\n", + "It takes two numbers, `dx` and `dy`, and moves the rectangle the given distances in the `x` and `y` directions. " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "8a5e7e73", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def translate(self, dx, dy):\n", + " self.corner.translate(dx, dy)" + ] + }, + { + "cell_type": "markdown", + "id": "c27fe91d", + "metadata": {}, + "source": [ + "To demonstrate the effect, we'll translate `box2` to the right and down." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "78b9e344", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rectangle(160, 90, Point(60, 40))\n" + ] + } + ], + "source": [ + "box2.translate(30, 20)\n", + "print(box2)" + ] + }, + { + "cell_type": "markdown", + "id": "e01badbc", + "metadata": {}, + "source": [ + "Now let's see what happens if we draw `box1` and `box2` again." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "b70bcad9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "line1.draw()\n", + "line2.draw()\n", + "box1.draw()\n", + "box2.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "5310bdd7", + "metadata": {}, + "source": [ + "It looks like both rectangles moved, which is not what we intended!\n", + "The next section explains what went wrong." + ] + }, + { + "cell_type": "markdown", + "id": "940adbeb", + "metadata": {}, + "source": [ + "## Deep copy\n", + "\n", + "When we use `copy` to duplicate `box1`, it copies the `Rectangle` object but not the `Point` object it contains.\n", + "So `box1` and `box2` are different objects, as intended." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "b67ce168", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "box1 is box2" + ] + }, + { + "cell_type": "markdown", + "id": "eac5309b", + "metadata": {}, + "source": [ + "But their `corner` attributes refer to the same object." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "1d9cf4da", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "box1.corner is box2.corner" + ] + }, + { + "cell_type": "markdown", + "id": "f0cc51b5", + "metadata": {}, + "source": [ + "The following diagram shows the state of these objects." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "6d883459", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from diagram import Stack\n", + "from copy import deepcopy\n", + "\n", + "binding_box1, frame_corner1 = make_rectangle_binding('box1', box1)\n", + "binding_box2, frame_corner2 = make_rectangle_binding('box2', box2, dy=0.4)\n", + "binding_box2.value.bindings.reverse()\n", + "\n", + "stack = Stack([binding_box1, binding_box2], dy=-1.3)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "637042fb", + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from diagram import Bbox\n", + "\n", + "width, height, x, y = [2.76, 2.54, 0.27, 2.16]\n", + "ax = diagram(width, height)\n", + "bbox1 = stack.draw(ax, x, y)\n", + "bbox2 = frame_corner1.draw(ax, x+1.85, y-0.6)\n", + "bbox = Bbox.union([bbox1, bbox2])\n", + "# adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "35f3e7e1", + "metadata": {}, + "source": [ + "What `copy` does is called a **shallow copy** because it copies the object but not the objects it contains.\n", + "As a result, changing the `width` or `height` of one `Rectangle` does not affect the other, but changing the attributes of the shared `Point` affects both!\n", + "This behavior is confusing and error-prone.\n", + "\n", + "Fortunately, the `copy` module provides another function, called `deepcopy`, that copies not only the object but also the objects it refers to, and the objects *they* refer to, and so on. \n", + "This operation is called a **deep copy**.\n", + "\n", + "To demonstrate, let's start with a new `Rectangle` that contains a new `Point`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "168277a9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rectangle(100, 50, Point(20, 20))\n" + ] + } + ], + "source": [ + "corner = Point(20, 20)\n", + "box3 = Rectangle(100, 50, corner)\n", + "print(box3)" + ] + }, + { + "cell_type": "markdown", + "id": "ff9ee872", + "metadata": {}, + "source": [ + "And we'll make a deep copy." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "75219ef6", + "metadata": {}, + "outputs": [], + "source": [ + "from copy import deepcopy\n", + "\n", + "box4 = deepcopy(box3)" + ] + }, + { + "cell_type": "markdown", + "id": "7efd0e6a", + "metadata": {}, + "source": [ + "We can confirm that the two `Rectangle` objects refer to different `Point` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "fde2486c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "box3.corner is box4.corner" + ] + }, + { + "cell_type": "markdown", + "id": "ca925206", + "metadata": {}, + "source": [ + "Because `box3` and `box4` are completely separate objects, we can modify one without affecting the other.\n", + "To demonstrate, we'll move `box3` and grow `box4`." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "3f6d1d6b", + "metadata": {}, + "outputs": [], + "source": [ + "box3.translate(50, 30)\n", + "box4.grow(100, 60)" + ] + }, + { + "cell_type": "markdown", + "id": "3ff31c7c", + "metadata": {}, + "source": [ + "And we can confirm that the effect is as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "092ded2c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "line1.draw()\n", + "line2.draw()\n", + "box3.draw()\n", + "box4.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "67051d62", + "metadata": {}, + "source": [ + "## Polymorphism\n", + "\n", + "In the previous example, we invoked the `draw` method on two `Line` objects and two `Rectangle` objects.\n", + "We can do the same thing more concisely by making a list of objects." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "c846343c", + "metadata": {}, + "outputs": [], + "source": [ + "shapes = [line1, line2, box3, box4]" + ] + }, + { + "cell_type": "markdown", + "id": "773955dd", + "metadata": {}, + "source": [ + "The elements of this list are different types, but they all provide a `draw` method, so we can loop through the list and invoke `draw` on each one." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "10912b62", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "\n", + "for shape in shapes:\n", + " shape.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "a1ae190c", + "metadata": {}, + "source": [ + "The first and second time through the loop, `shape` refers to a `Line` object, so when `draw` is invoked, the method that runs is the one defined in the `Line` class.\n", + "\n", + "The third and fourth time through the loop, `shape` refers to a `Rectangle` object, so when `draw` is invoked, the method that runs is the one defined in the `Rectangle` class.\n", + "\n", + "In a sense, each object knows how to draw itself.\n", + "This feature is called **polymorphism**.\n", + "The word comes from Greek roots that mean \"many shaped\".\n", + "In object-oriented programming, polymorphism is the ability of different types to provide the same methods, which makes it possible to perform many computations -- like drawing shapes -- by invoking the same method on different types of objects.\n", + "\n", + "As an exercise at the end of this chapter, you'll define a new class that represents a circle and provides a `draw` method.\n", + "Then you can use polymorphism to draw lines, rectangles, and circles." + ] + }, + { + "cell_type": "markdown", + "id": "74d1b48f", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "In this chapter, we ran into a subtle bug that happened because we created a `Point` that was shared by two `Rectangle` objects, and then we modified the `Point`.\n", + "In general, there are two ways to avoid problems like this: you can avoid sharing objects or you can avoid modifying them.\n", + "\n", + "To avoid sharing objects, you can use deep copy, as we did in this chapter.\n", + "\n", + "To avoid modifying objects, consider replacing modifiers like `translate` with pure functions like `translated`.\n", + "For example, here's a version of `translated` that creates a new `Point` and never modifies its attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "c803c91a", + "metadata": {}, + "outputs": [], + "source": [ + " def translated(self, dx=0, dy=0):\n", + " x = self.x + dx\n", + " y = self.y + dy\n", + " return Point(x, y)" + ] + }, + { + "cell_type": "markdown", + "id": "76972167", + "metadata": {}, + "source": [ + "Python provides features that make it easier to avoid modifying objects.\n", + "They are beyond the scope of this book, but if you are curious, ask a virtual assistant, \"How do I make a Python object immutable?\"\n", + "\n", + "Creating a new object takes more time than modifying an existing one, but the difference seldom matters in practice.\n", + "Programs that avoid shared objects and modifiers are often easier to develop, test, and debug -- and the best kind of debugging is the kind you don't have to do." + ] + }, + { + "cell_type": "markdown", + "id": "02106995", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**identical:**\n", + "Being the same object (which implies equivalence).\n", + "\n", + "**equivalent:**\n", + "Having the same value.\n", + "\n", + "**shallow copy:**\n", + "A copy operation that does not copy nested objects.\n", + "\n", + "**deep copy:**\n", + "A copy operation that also copies nested objects.\n", + "\n", + "**polymorphism:**\n", + "The ability of a method or operator to work with multiple types of objects." + ] + }, + { + "cell_type": "markdown", + "id": "09dd41c1", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32b151a5", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, + { + "cell_type": "markdown", + "id": "da0aea86", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "For all of the following exercises, consider asking a virtual assistant for help.\n", + "If you do, you'll want include as part of the prompt the class definitions for `Point`, `Line`, and `Rectangle` -- otherwise the VA will make a guess about their attributes and functions, and the code it generates won't work." + ] + }, + { + "cell_type": "markdown", + "id": "7721e47b", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write an `__eq__` method for the `Line` class that returns `True` if the `Line` objects refer to `Point` objects that are equivalent, in either order." + ] + }, + { + "cell_type": "markdown", + "id": "2e488e0f", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "92c07380", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Line\n", + "\n", + "def __eq__(self, other):\n", + " return None" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "a99446c4", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Line\n", + "\n", + "def __eq__(self, other):\n", + " if (self.p1 == other.p1) and (self.p2 == other.p2):\n", + " return True\n", + " if (self.p1 == other.p2) and (self.p2 == other.p1):\n", + " return True\n", + " return False" + ] + }, + { + "cell_type": "markdown", + "id": "3a44e45a", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use these examples to test your code." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "aa086dd1", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "start1 = Point(0, 0)\n", + "start2 = Point(0, 0)\n", + "end = Point(200, 100)" + ] + }, + { + "cell_type": "markdown", + "id": "e825f049", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in the same order." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "857cba26", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "line_a = Line(start1, end)\n", + "line_b = Line(start2, end)\n", + "line_a == line_b # should be True" + ] + }, + { + "cell_type": "markdown", + "id": "c3d8fb2b", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in reverse order." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "b45def0a", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "line_c = Line(end, start1)\n", + "line_a == line_c # should be True" + ] + }, + { + "cell_type": "markdown", + "id": "8c9c787b", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "Equivalence should always be transitive -- that is, if `line_a` and `line_b` are equivalent, and `line_a` and `line_c` are equivalent, then `line_b` and `line_c` should also be equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "9784300c", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "line_b == line_c # should be True" + ] + }, + { + "cell_type": "markdown", + "id": "d4f385fa", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "This example should be `False` because the `Line` objects refer to `Point` objects that are not equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "5435c8e4", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "line_d = Line(start1, start2)\n", + "line_a == line_d # should be False" + ] + }, + { + "cell_type": "markdown", + "id": "0e629491", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a `Line` method called `midpoint` that computes the midpoint of a line segment and returns the result as a `Point` object." + ] + }, + { + "cell_type": "markdown", + "id": "b8c52d19", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "f377afbb", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Line\n", + "\n", + " def midpoint(self):\n", + " return Point(0, 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "96d81b90", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Line\n", + "\n", + " def midpoint(self):\n", + " mid_x = (self.p1.x + self.p2.x) / 2\n", + " mid_y = (self.p1.y + self.p2.y) / 2\n", + " return Point(mid_x, mid_y)" + ] + }, + { + "cell_type": "markdown", + "id": "4df69a9f", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following examples to test your code and draw the result." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "0d603aa3", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "start = Point(0, 0)\n", + "end1 = Point(300, 0)\n", + "end2 = Point(0, 150)\n", + "line1 = Line(start, end1)\n", + "line2 = Line(start, end2)" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "647d0982", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(150.0, 0.0)\n" + ] + } + ], + "source": [ + "mid1 = line1.midpoint()\n", + "print(mid1)" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "e351bea3", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(0.0, 75.0)\n" + ] + } + ], + "source": [ + "mid2 = line2.midpoint()\n", + "print(mid2)" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "5ad5a076", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "line3 = Line(mid1, mid2)" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "8effaff0", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "\n", + "for shape in [line1, line2, line3]:\n", + " shape.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "0518c200", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a `Rectangle` method called `midpoint` that find the point in the center of a rectangle and returns the result as a `Point` object." + ] + }, + { + "cell_type": "markdown", + "id": "c586a3ed", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "d94a6350", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def midpoint(self):\n", + " return Point(0, 0)" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "de6756da", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def midpoint(self):\n", + " mid_x = self.corner.x + self.width / 2\n", + " mid_y = self.corner.y + self.height / 2\n", + " return Point(mid_x, mid_y)" + ] + }, + { + "cell_type": "markdown", + "id": "d186c84b", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following example to test your code." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "4aec759c", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "corner = Point(30, 20)\n", + "rectangle = Rectangle(100, 80, corner)" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "7ec3339d", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(80.0, 60.0)\n" + ] + } + ], + "source": [ + "mid = rectangle.midpoint()\n", + "print(mid)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "326dbf24", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "diagonal = Line(corner, mid)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "4da710d4", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "\n", + "for shape in [line1, line2, rectangle, diagonal]:\n", + " shape.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "00cbc4d9", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a `Rectangle` method called `make_cross` that:\n", + "\n", + "1. Uses `make_lines` to get a list of `Line` objects that represent the four sides of the rectangle.\n", + "\n", + "2. Computes the midpoints of the four lines.\n", + "\n", + "3. Makes and returns a list of two `Line` objects that represent lines connecting opposite midpoints, forming a cross through the middle of the rectangle." + ] + }, + { + "cell_type": "markdown", + "id": "29e994c6", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use this outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "30cc0726", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def make_diagonals(self):\n", + " return []" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "6cdde16e", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Rectangle\n", + "\n", + " def make_cross(self):\n", + " midpoints = []\n", + "\n", + " for line in self.make_lines():\n", + " midpoints.append(line.midpoint())\n", + "\n", + " p1, p2, p3, p4 = midpoints\n", + " return Line(p1, p3), Line(p2, p4)" + ] + }, + { + "cell_type": "markdown", + "id": "970fcbca", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following example to test your code." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "2afd718c", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "corner = Point(30, 20)\n", + "rectangle = Rectangle(100, 80, corner)" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "b7bdb467", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "lines = rectangle.make_cross()" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "9d09b2c3", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle()\n", + "\n", + "rectangle.draw()\n", + "for line in lines:\n", + " line.draw()" + ] + }, + { + "cell_type": "markdown", + "id": "0f707fe3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a definition for a class named `Circle` with attributes `center` and `radius`, where `center` is a Point object and `radius` is a number.\n", + "Include special methods `__init__` and a `__str__`, and a method called `draw` that uses `jupyturtle` functions to draw the circle." + ] + }, + { + "cell_type": "markdown", + "id": "cb1b24a3", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following function, which is a version of the `circle` function we wrote in Chapter 4." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "b3d2328f", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from jupyturtle import make_turtle, forward, left, right\n", + "import math\n", + " \n", + "def draw_circle(radius):\n", + " circumference = 2 * math.pi * radius\n", + " n = 30\n", + " length = circumference / n\n", + " angle = 360 / n\n", + " left(angle / 2)\n", + " for i in range(n):\n", + " forward(length)\n", + " left(angle)" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "189c30d4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "b4325143", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following example to test your code.\n", + "We'll start with a square `Rectangle` with width and height `100`." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "49074ed5", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "corner = Point(20, 20)\n", + "rectangle = Rectangle(100, 100, corner)" + ] + }, + { + "cell_type": "markdown", + "id": "2cdecfa9", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "The following code should create a `Circle` that fits inside the square." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "d65a9163", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circle(Point(70.0, 70.0), 50.0)\n" + ] + } + ], + "source": [ + "center = rectangle.midpoint()\n", + "radius = rectangle.height / 2\n", + "\n", + "circle = Circle(center, radius)\n", + "print(circle)" + ] + }, + { + "cell_type": "markdown", + "id": "37e94d98", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "If everything worked correctly, the following code should draw the circle inside the square (touching on all four sides)." + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "e3b23b4d", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "make_turtle(delay=0.01)\n", + "\n", + "rectangle.draw()\n", + "circle.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48abaaae", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/_sources/chap17.ipynb b/_sources/chap17.ipynb new file mode 100644 index 0000000..782d03a --- /dev/null +++ b/_sources/chap17.ipynb @@ -0,0 +1,3356 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "217fc9bf", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from os.path import basename, exists\n", + "\n", + "def download(url):\n", + " filename = basename(url)\n", + " if not exists(filename):\n", + " from urllib.request import urlretrieve\n", + "\n", + " local, _ = urlretrieve(url, filename)\n", + " print(\"Downloaded \" + str(local))\n", + " return filename\n", + "\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "\n", + "import thinkpython" + ] + }, + { + "cell_type": "markdown", + "id": "ced31782", + "metadata": { + "tags": [ + "chapter_inheritance" + ] + }, + "source": [ + "(chapter_inheritance)=\n", + "# Inheritance\n", + "\n", + "The language feature most often associated with object-oriented programming is **inheritance**.\n", + "Inheritance is the ability to define a new class that is a modified version of an existing class.\n", + "In this chapter I demonstrate inheritance using classes that represent playing cards, decks of cards, and poker hands.\n", + "If you don't play poker, don't worry -- I'll tell you what you need to know." + ] + }, + { + "cell_type": "markdown", + "id": "b19c4dae", + "metadata": {}, + "source": [ + "## Representing cards\n", + "\n", + "There are 52 playing cards in a standard deck -- each of them belongs to one of four suits and one of thirteen ranks. \n", + "The suits are Spades, Hearts, Diamonds, and Clubs.\n", + "The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King.\n", + "Depending on which game you are playing, an Ace can be higher than King or lower than 2.\n", + "\n", + "If we want to define a new object to represent a playing card, it is obvious what the attributes should be: `rank` and `suit`.\n", + "It is less obvious what type the attributes should be.\n", + "One possibility is to use strings containing words like `'Spade'` for suits and `'Queen'` for ranks.\n", + "A problem with this implementation is that it would not be easy to compare cards to see which had a higher rank or suit.\n", + "\n", + "An alternative is to use integers to **encode** the ranks and suits.\n", + "In this context, \"encode\" means that we are going to define a mapping between numbers and suits, or between numbers and ranks.\n", + "This kind of encoding is not meant to be a secret (that would be \"encryption\")." + ] + }, + { + "cell_type": "markdown", + "id": "a9bafecf", + "metadata": {}, + "source": [ + "For example, this table shows the suits and the corresponding integer codes:\n", + "\n", + "\n", + "| Suit | Code |\n", + "| --- | --- |\n", + "| Spades | 3 |\n", + "| Hearts | 2 |\n", + "| Diamonds | 1 |\n", + "| Clubs | 0 |\n", + "\n", + "With this encoding, we can compare suits by comparing their codes." + ] + }, + { + "cell_type": "markdown", + "id": "a1b46b1a", + "metadata": {}, + "source": [ + "To encode the ranks, we'll use the integer `2` to represent the rank `2`, `3` to represent `3`, and so on up to `10`.\n", + "The following table shows the codes for the face cards.\n", + "\n", + " \n", + "| Rank | Code |\n", + "| --- | --- |\n", + "| Jack | 11 |\n", + "| Queen | 12 |\n", + "| King | 13 |\n", + "\n", + "And we can use either `1` or `14` to represent an Ace, depending on whether we want it to be considered lower or higher than the other ranks.\n", + "\n", + "To represent these encodings, we will use two lists of strings, one with the names of the suits and the other with the names of the ranks.\n", + "\n", + "Here's a definition for a class that represents a playing card, with these lists of strings as **class variables**, which are variables defined inside a class definition, but not inside a method." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ef26adf0", + "metadata": {}, + "outputs": [], + "source": [ + "class Card:\n", + " \"\"\"Represents a standard playing card.\"\"\"\n", + "\n", + " suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']\n", + " rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', \n", + " '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']" + ] + }, + { + "cell_type": "markdown", + "id": "d63f798a", + "metadata": {}, + "source": [ + "The first element of `rank_names` is `None` because there is no card with rank zero. By including `None` as a place-keeper, we get a list with the nice property that the index 2 maps to the string `'2'`, and so on.\n", + "\n", + "Class variables are associated with the class, rather than an instance of the class, so we can access them like this." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4e4bd268", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Clubs', 'Diamonds', 'Hearts', 'Spades']" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Card.suit_names" + ] + }, + { + "cell_type": "markdown", + "id": "c837fff6", + "metadata": {}, + "source": [ + "We can use `suit_names` to look up a suit and get the corresponding string." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8aec2a6a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Clubs'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Card.suit_names[0]" + ] + }, + { + "cell_type": "markdown", + "id": "a59d905e", + "metadata": {}, + "source": [ + "And `rank_names` to look up a rank." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "baf029e9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Jack'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Card.rank_names[11]" + ] + }, + { + "cell_type": "markdown", + "id": "50dda19b", + "metadata": {}, + "source": [ + "## Card attributes\n", + "\n", + "Here's an `__init__` method for the `Card` class -- it takes `suit` and `rank` as parameters and assigns them to attributes with the same names." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "91320ea3", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Card\n", + "\n", + " def __init__(self, suit, rank):\n", + " self.suit = suit\n", + " self.rank = rank" + ] + }, + { + "cell_type": "markdown", + "id": "31a2782d", + "metadata": {}, + "source": [ + "Now we can create a `Card` object like this." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c04bb77e", + "metadata": {}, + "outputs": [], + "source": [ + "queen = Card(1, 12)" + ] + }, + { + "cell_type": "markdown", + "id": "85e5cf5d", + "metadata": {}, + "source": [ + "We can use the new instance to access the attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b182e6fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 12)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen.suit, queen.rank" + ] + }, + { + "cell_type": "markdown", + "id": "449225d3", + "metadata": {}, + "source": [ + "It is also legal to use the instance to access the class variables." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "17ce1a51", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Clubs', 'Diamonds', 'Hearts', 'Spades']" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen.suit_names" + ] + }, + { + "cell_type": "markdown", + "id": "97232ffa", + "metadata": {}, + "source": [ + "But if you use the class, it is clearer that they are class variables, not attributes." + ] + }, + { + "cell_type": "markdown", + "id": "7a0a79ae", + "metadata": {}, + "source": [ + "## Printing cards\n", + "\n", + "Here's a `__str__` method for `Card` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6709b45a", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Card\n", + "\n", + " def __str__(self):\n", + " rank_name = Card.rank_names[self.rank]\n", + " suit_name = Card.suit_names[self.suit]\n", + " return f'{rank_name} of {suit_name}' " + ] + }, + { + "cell_type": "markdown", + "id": "d6c51352", + "metadata": {}, + "source": [ + "When we print a `Card`, Python calls the `__str__` method to get a human-readable representation of the card." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e7f9304d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Queen of Diamonds\n" + ] + } + ], + "source": [ + "print(queen)" + ] + }, + { + "cell_type": "markdown", + "id": "76044b9e", + "metadata": {}, + "source": [ + "The following is a diagram of the `Card` class object and the Card instance.\n", + "`Card` is a class object, so its type is `type`.\n", + "`queen` is an instance of `Card`, so its type is `Card`.\n", + "To save space, I didn't draw the contents of `suit_names` and `rank_names`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d589ed70", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from diagram import Binding, Value, Frame, Stack\n", + "\n", + "bindings = [Binding(Value(name), draw_value=False)\n", + " for name in ['suit_names', 'rank_names']]\n", + " \n", + "frame1 = Frame(bindings, name='type', dy=-0.5, offsetx=0.77)\n", + "binding1 = Binding(Value('Card'), frame1)\n", + "\n", + "bindings = [Binding(Value(name), Value(value))\n", + " for name, value in zip(['suit', 'rank'], [1, 11])]\n", + " \n", + "frame2 = Frame(bindings, name='Card', dy=-0.3, offsetx=0.33)\n", + "binding2 = Binding(Value('queen'), frame2)\n", + "\n", + "stack = Stack([binding1, binding2], dy=-1.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "5518455f", + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from diagram import diagram, Bbox, make_list, adjust\n", + "\n", + "width, height, x, y = [2.11, 2.14, 0.35, 1.76]\n", + "ax = diagram(width, height)\n", + "bbox = stack.draw(ax, x, y)\n", + "\n", + "value = make_list([])\n", + "bbox2 = value.draw(ax, x+1.66, y)\n", + "\n", + "value = make_list([])\n", + "bbox3 = value.draw(ax, x+1.66, y-0.5)\n", + "\n", + "bbox = Bbox.union([bbox, bbox2, bbox3])\n", + "#adjust(x, y, bbox)" + ] + }, + { + "cell_type": "markdown", + "id": "ccb8e41d", + "metadata": {}, + "source": [ + "Every `Card` instance has its own `suit` and `rank` attributes, but there is only one `Card` class object, and only one copy of the class variables `suit_names` and `rank_names`." + ] + }, + { + "cell_type": "markdown", + "id": "98c6508d", + "metadata": {}, + "source": [ + "## Comparing cards\n", + "\n", + "Suppose we create a second `Card` object with the same suit and rank." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cadb115d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Queen of Diamonds\n" + ] + } + ], + "source": [ + "queen2 = Card(1, 12)\n", + "print(queen2)" + ] + }, + { + "cell_type": "markdown", + "id": "3c92779c", + "metadata": {}, + "source": [ + "If we use the `==` operator to compare them, it checks whether `queen` and `queen2` refer to the same object." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "6a625fde", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen == queen2" + ] + }, + { + "cell_type": "markdown", + "id": "278d8abe", + "metadata": {}, + "source": [ + "They don't, so it returns false.\n", + "We can change this behavior by defining a special method called `__eq__`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "4f394e57", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Card\n", + "\n", + " def __eq__(self, other):\n", + " return self.suit == other.suit and self.rank == other.rank" + ] + }, + { + "cell_type": "markdown", + "id": "bd66a9d3", + "metadata": {}, + "source": [ + "`__eq__` takes two `Card` objects as parameters and returns `True` if they have the same suit and rank, even if they are not the same object.\n", + "In other words, it checks whether they are equivalent, even if they are not identical.\n", + "\n", + "When we use the `==` operator with `Card` objects, Python calls the `__eq__` method." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "2c10425b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen == queen2" + ] + }, + { + "cell_type": "markdown", + "id": "23d99d3e", + "metadata": {}, + "source": [ + "As a second test, let's create a card with the same suit and a different rank." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "c2a695b4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6 of Diamonds\n" + ] + } + ], + "source": [ + "six = Card(1, 6)\n", + "print(six)" + ] + }, + { + "cell_type": "markdown", + "id": "c5f66404", + "metadata": {}, + "source": [ + "We can confirm that `queen` and `six` are not equivalent." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "400c3340", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen == six" + ] + }, + { + "cell_type": "markdown", + "id": "1dcb561f", + "metadata": {}, + "source": [ + "If we use the `!=` operator, Python invokes a special method called `__ne__`, if it exists.\n", + "Otherwise it invokes`__eq__` and inverts the result -- so if `__eq__` returns `True`, the result of the `!=` operator is `False`. " + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c7d731b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen != queen2" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "d2be6c82", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen != six" + ] + }, + { + "cell_type": "markdown", + "id": "77c48464", + "metadata": {}, + "source": [ + "Now suppose we want to compare two cards to see which is bigger.\n", + "If we use one of the relational operators, we get a `TypeError`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "aa63fe2a", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'<' not supported between instances of 'Card' and 'Card'", + "output_type": "error", + "traceback": [ + "\u001b[0;31mTypeError\u001b[0m\u001b[0;31m:\u001b[0m '<' not supported between instances of 'Card' and 'Card'\n" + ] + } + ], + "source": [ + "\n", + "queen < queen2" + ] + }, + { + "cell_type": "markdown", + "id": "4db0ad52", + "metadata": {}, + "source": [ + "To change the behavior of the `<` operator, we can define a special method called `__lt__`, which is short for \"less than\".\n", + "For the sake of this example, let's assume that suit is more important than rank -- so all Spades outrank all Hearts, which outrank all Diamonds, and so on.\n", + "If two cards have the same suit, the one with the higher rank wins.\n", + "\n", + "To implement this logic, we'll use the following method, which returns a tuple containing a card's suit and rank, in that order." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "b2126f79", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Card\n", + "\n", + " def to_tuple(self):\n", + " return (self.suit, self.rank)" + ] + }, + { + "cell_type": "markdown", + "id": "d5062348", + "metadata": {}, + "source": [ + "We can use this method to write `__lt__`." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d4d0a652", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Card\n", + "\n", + " def __lt__(self, other):\n", + " return self.to_tuple() < other.to_tuple()" + ] + }, + { + "cell_type": "markdown", + "id": "bd9ef8f5", + "metadata": {}, + "source": [ + "Tuple comparison compares the first elements from each tuple, which represent the suits.\n", + "If they are the same, it compares the second elements, which represent the ranks.\n", + "\n", + "Now if we use the `<` operator, it invokes the `__lt__` operator." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "9d4ea1f8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "six < queen" + ] + }, + { + "cell_type": "markdown", + "id": "83289a77", + "metadata": {}, + "source": [ + "If we use the `>` operator, it invokes a special method called `__gt__`, if it exists.\n", + "Otherwise it invokes `__lt__` with the arguments in the opposite order." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "676ede7e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen < queen2" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "3c4854fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen > queen2" + ] + }, + { + "cell_type": "markdown", + "id": "5d0a91de", + "metadata": {}, + "source": [ + "Finally, if we use the `<=` operator, it invokes a special method called `__le__`." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "27280fc2", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Card\n", + "\n", + " def __le__(self, other):\n", + " return self.to_tuple() <= other.to_tuple()" + ] + }, + { + "cell_type": "markdown", + "id": "6c85ac69", + "metadata": {}, + "source": [ + "So we can check whether one card is less than or equal to another." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "bea50d85", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen <= queen2" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "8d539454", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen <= six" + ] + }, + { + "cell_type": "markdown", + "id": "7af7b289", + "metadata": {}, + "source": [ + "If we use the `>=` operator, it uses `__ge__` if it exists. Otherwise, it invokes `__le__` with the arguments in the opposite order." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e7edb7cb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "queen >= six" + ] + }, + { + "cell_type": "markdown", + "id": "fe2a81cc", + "metadata": {}, + "source": [ + "As we have defined them, these methods are complete in the sense that we can compare any two `Card` objects, and consistent in the sense that results from different operators don't contradict each other.\n", + "With these two properties, we can say that `Card` objects are **totally ordered**.\n", + "And that means, as we'll see soon, that they can be sorted." + ] + }, + { + "cell_type": "markdown", + "id": "199f8bfc", + "metadata": {}, + "source": [ + "## Decks\n", + "\n", + "Now that we have objects that represent cards, let's define objects that represent decks.\n", + "The following is a class definition for `Deck` with\n", + "an `__init__` method takes a list of `Card` objects as a parameter and assigns it to an attribute called `cards`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "b55140e3", + "metadata": {}, + "outputs": [], + "source": [ + "class Deck:\n", + "\n", + " def __init__(self, cards):\n", + " self.cards = cards" + ] + }, + { + "cell_type": "markdown", + "id": "2d529789", + "metadata": {}, + "source": [ + "To create a list that contains the 52 cards in a standard deck, we'll use the following static method." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "836f1a32", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + "\n", + " def make_cards():\n", + " cards = []\n", + " for suit in range(4):\n", + " for rank in range(2, 15):\n", + " card = Card(suit, rank)\n", + " cards.append(card)\n", + " return cards" + ] + }, + { + "cell_type": "markdown", + "id": "47ae8f71", + "metadata": {}, + "source": [ + "In `make_cards`, the outer loop enumerates the suits from `0` to `3`.\n", + "The inner loop enumerates the ranks from `2` to `14` -- where `14` represents an Ace that outranks a King.\n", + "Each iteration creates a new `Card` with the current suit and rank, and appends it to `cards`.\n", + "\n", + "Here's how we make a list of cards and a `Deck` object that contains it." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ca50c79b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "52" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cards = Deck.make_cards()\n", + "deck = Deck(cards)\n", + "len(deck.cards)" + ] + }, + { + "cell_type": "markdown", + "id": "032ec302", + "metadata": {}, + "source": [ + "It contains 52 cards, as intended." + ] + }, + { + "cell_type": "markdown", + "id": "c2ec7f01", + "metadata": { + "tags": [ + "section_print_deck" + ] + }, + "source": [ + "(section_print_deck)=\n", + "## Printing the deck\n", + "\n", + "Here is a `__str__` method for `Deck`." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "1f1b923e", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + "\n", + " def __str__(self):\n", + " res = []\n", + " for card in self.cards:\n", + " res.append(str(card))\n", + " return '\\n'.join(res)" + ] + }, + { + "cell_type": "markdown", + "id": "660f18e6", + "metadata": {}, + "source": [ + "This method demonstrates an efficient way to accumulate a large string -- building a list of strings and then using the string method `join`. \n", + "\n", + "We'll test this method with a deck that only contains two cards." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "0c55a663", + "metadata": {}, + "outputs": [], + "source": [ + "small_deck = Deck([queen, six])" + ] + }, + { + "cell_type": "markdown", + "id": "91c7145f", + "metadata": {}, + "source": [ + "If we call `str`, it invokes `__str__`." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "fb3350ef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Queen of Diamonds\\n6 of Diamonds'" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "str(small_deck)" + ] + }, + { + "cell_type": "markdown", + "id": "00270656", + "metadata": {}, + "source": [ + "When Jupyter displays a string, it shows the \"representational\" form of the string, which represents a newline with the sequence `\\n`.\n", + "\n", + "However, if we print the result, Jupyter shows the \"printable\" form of the string, which prints the newline as whitespace." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "d67f8fd5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Queen of Diamonds\n", + "6 of Diamonds\n" + ] + } + ], + "source": [ + "print(small_deck)" + ] + }, + { + "cell_type": "markdown", + "id": "e97810c4", + "metadata": {}, + "source": [ + "So the cards appear on separate lines." + ] + }, + { + "cell_type": "markdown", + "id": "52d3d597", + "metadata": {}, + "source": [ + "## Add, remove, shuffle and sort\n", + "\n", + "To deal cards, we would like a method that removes a card from the deck\n", + "and returns it. The list method `pop` provides a convenient way to do\n", + "that." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "3836c48c", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + "\n", + " def take_card(self):\n", + " return self.cards.pop()" + ] + }, + { + "cell_type": "markdown", + "id": "1fcef47b", + "metadata": {}, + "source": [ + "Here's how we use it." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "5afccad6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ace of Spades\n" + ] + } + ], + "source": [ + "card = deck.take_card()\n", + "print(card)" + ] + }, + { + "cell_type": "markdown", + "id": "65427954", + "metadata": {}, + "source": [ + "We can confirm that there are `51` cards left in the deck." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "58f9473a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "51" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(deck.cards)" + ] + }, + { + "cell_type": "markdown", + "id": "7ca3614e", + "metadata": {}, + "source": [ + "To add a card, we can use the list method `append`." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "f3eac4b5", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + "\n", + " def put_card(self, card):\n", + " self.cards.append(card)" + ] + }, + { + "cell_type": "markdown", + "id": "2ecd8703", + "metadata": {}, + "source": [ + "As an example, we can put back the card we just popped." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "f234eff4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "52" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "deck.put_card(card)\n", + "len(deck.cards)" + ] + }, + { + "cell_type": "markdown", + "id": "8b5af8ce", + "metadata": {}, + "source": [ + "To shuffle the deck, we can use the `shuffle` function from the `random` module:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "81e60a08", + "metadata": {}, + "outputs": [], + "source": [ + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "b630cbb8", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "# This cell initializes the random number generator so we\n", + "# always get the same results.\n", + "\n", + "random.seed(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "bea615ea", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + " \n", + " def shuffle(self):\n", + " random.shuffle(self.cards)" + ] + }, + { + "cell_type": "markdown", + "id": "a8cb1a7f", + "metadata": {}, + "source": [ + "If we shuffle the deck and print the first few cards, we can see that they are in no apparent order." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "6b0f6b02", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Diamonds\n", + "4 of Hearts\n", + "5 of Clubs\n", + "8 of Diamonds\n" + ] + } + ], + "source": [ + "deck.shuffle()\n", + "for card in deck.cards[:4]:\n", + " print(card)" + ] + }, + { + "cell_type": "markdown", + "id": "a198dde3", + "metadata": {}, + "source": [ + "To sort the cards, we can use the list method `sort`, which sorts the elements \"in place\" -- that is, it modifies the list rather than creating a new list." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "6bff10b6", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + " \n", + " def sort(self):\n", + " self.cards.sort()" + ] + }, + { + "cell_type": "markdown", + "id": "d4f017c7", + "metadata": {}, + "source": [ + "When we invoke `sort`, it uses the `__lt__` method to compare cards." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "568a6583", + "metadata": {}, + "outputs": [], + "source": [ + "deck.sort()" + ] + }, + { + "cell_type": "markdown", + "id": "2bb966fd", + "metadata": {}, + "source": [ + "If we print the first few cards, we can confirm that they are in increasing order." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "54e91c91", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Clubs\n", + "3 of Clubs\n", + "4 of Clubs\n", + "5 of Clubs\n" + ] + } + ], + "source": [ + "for card in deck.cards[:4]:\n", + " print(card)" + ] + }, + { + "cell_type": "markdown", + "id": "5c41ce4d", + "metadata": {}, + "source": [ + "In this example, `Deck.sort` doesn't do anything other than invoke `list.sort`.\n", + "Passing along responsibility like this is called **delegation**." + ] + }, + { + "cell_type": "markdown", + "id": "0502961b", + "metadata": {}, + "source": [ + "## Parents and children\n", + "\n", + "Inheritance is the ability to define a new class that is a modified version of an existing class.\n", + "As an example, let's say we want a class to represent a \"hand\", that is, the cards held by one player.\n", + "\n", + "* A hand is similar to a deck -- both are made up of a collection of cards, and both require operations like adding and removing cards.\n", + "\n", + "* A hand is also different from a deck -- there are operations we want for hands that don't make sense for a deck. For example, in poker we might compare two hands to see which one wins. In bridge, we might compute a score for a hand in order to make a bid.\n", + "\n", + "This relationship between classes -- where one is a specialized version of another -- lends itself to inheritance. \n", + "\n", + "To define a new class that is based on an existing class, we put the name of the existing class in parentheses." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "f39fc598", + "metadata": {}, + "outputs": [], + "source": [ + "class Hand(Deck):\n", + " \"\"\"Represents a hand of playing cards.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "339295cd", + "metadata": {}, + "source": [ + "This definition indicates that `Hand` inherits from `Deck`, which means that `Hand` objects can access methods defined in `Deck`, like `take_card` and `put_card`.\n", + "\n", + "`Hand` also inherits `__init__` from `Deck`, but if we define `__init__` in the `Hand` class, it overrides the one in the `Deck` class." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "9e7a1045", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Hand\n", + "\n", + " def __init__(self, label=''):\n", + " self.label = label\n", + " self.cards = []" + ] + }, + { + "cell_type": "markdown", + "id": "9b6a763a", + "metadata": {}, + "source": [ + "This version of `__init__` takes an optional string as a parameter, and always starts with an empty list of cards.\n", + "When we create a `Hand`, Python invokes this method, not the one in `Deck` -- which we can confirm by checking that the result has a `label` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "8de8cff4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'player 1'" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hand = Hand('player 1')\n", + "hand.label" + ] + }, + { + "cell_type": "markdown", + "id": "b1e2a67d", + "metadata": {}, + "source": [ + "To deal a card, we can use `take_card` to remove a card from a `Deck`, and `put_card` to add the card to a `Hand`." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "9f582ce0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ace of Spades\n" + ] + } + ], + "source": [ + "deck = Deck(cards)\n", + "card = deck.take_card()\n", + "hand.put_card(card)\n", + "print(hand)" + ] + }, + { + "cell_type": "markdown", + "id": "dc2ce06b", + "metadata": {}, + "source": [ + "Let's encapsulate this code in a `Deck` method called `move_cards`." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "180069eb", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + "\n", + " def move_cards(self, other, num):\n", + " for i in range(num):\n", + " card = self.take_card()\n", + " other.put_card(card)" + ] + }, + { + "cell_type": "markdown", + "id": "16e6c404", + "metadata": {}, + "source": [ + "This method is polymorphic -- that is, it works with more than one type: `self` and `other` can be either a `Hand` or a `Deck`.\n", + "So we can use this method to deal a card from `Deck` to a `Hand`, from one `Hand` to another, or from a `Hand` back to a `Deck`." + ] + }, + { + "cell_type": "markdown", + "id": "e648a722", + "metadata": {}, + "source": [ + "When a new class inherits from an existing one, the existing one is called the **parent** and the new class is called the **child**. In general:\n", + "\n", + "* Instances of the child class should have all of the attributes of the parent class, but they can have additional attributes.\n", + "\n", + "* The child class should have all of the methods of the parent class, but it can have additional methods.\n", + "\n", + "* If a child class overrides a method from the parent class, the new method should take the same parameters and return a compatible result.\n", + "\n", + "This set of rules is called the \"Liskov substitution principle\" after computer scientist Barbara Liskov.\n", + "\n", + "If you follow these rules, any function or method designed to work with an instance of a parent class, like a `Deck`, will also work with instances of a child class, like `Hand`.\n", + "If you violate these rules, your code will collapse like a house of cards (sorry)." + ] + }, + { + "cell_type": "markdown", + "id": "e80873dd", + "metadata": {}, + "source": [ + "## Specialization\n", + "\n", + "Let's make a class called `BridgeHand` that represents a hand in bridge -- a widely played card game.\n", + "We'll inherit from `Hand` and add a new method called `high_card_point_count` that evaluates a hand using a \"high card point\" method, which adds up points for the high cards in the hand.\n", + "\n", + "Here's a class definition that contains as a class variable a dictionary that maps from card names to their point values." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "b9e949cf", + "metadata": {}, + "outputs": [], + "source": [ + "class BridgeHand(Hand):\n", + " \"\"\"Represents a bridge hand.\"\"\"\n", + "\n", + " hcp_dict = {\n", + " 'Ace': 4,\n", + " 'King': 3,\n", + " 'Queen': 2,\n", + " 'Jack': 1,\n", + " }" + ] + }, + { + "cell_type": "markdown", + "id": "4c038717", + "metadata": {}, + "source": [ + "Given the rank of a card, like `12`, we can use `Card.rank_names` to get the string representation of the rank, and then use `hcp_dict` to get its score." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "0586b764", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('Queen', 2)" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rank = 12\n", + "rank_name = Card.rank_names[rank]\n", + "score = BridgeHand.hcp_dict.get(rank_name, 0)\n", + "rank_name, score" + ] + }, + { + "cell_type": "markdown", + "id": "c3a7820d", + "metadata": {}, + "source": [ + "The following method loops through the cards in a `BridgeHand` and adds up their scores." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "f01e8027", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to BridgeHand\n", + "\n", + " def high_card_point_count(self):\n", + " count = 0\n", + " for card in self.cards:\n", + " rank_name = Card.rank_names[card.rank]\n", + " count += BridgeHand.hcp_dict.get(rank_name, 0)\n", + " return count" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "227bef61", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "# This cell makes a fresh Deck and \n", + "# initializes the random number generator\n", + "\n", + "cards = Deck.make_cards()\n", + "deck = Deck(cards)\n", + "random.seed(3)" + ] + }, + { + "cell_type": "markdown", + "id": "94535d8e", + "metadata": {}, + "source": [ + "To test it, we'll deal a hand with five cards -- a bridge hand usually has thirteen, but it's easier to test code with small examples." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "f7d9275a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4 of Diamonds\n", + "King of Hearts\n", + "10 of Hearts\n", + "10 of Clubs\n", + "Queen of Diamonds\n" + ] + } + ], + "source": [ + "hand = BridgeHand('player 2')\n", + "\n", + "deck.shuffle()\n", + "deck.move_cards(hand, 5)\n", + "print(hand)" + ] + }, + { + "cell_type": "markdown", + "id": "a1bd2521", + "metadata": {}, + "source": [ + "And here is the total score for the King and Queen." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "ceb63a74", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hand.high_card_point_count()" + ] + }, + { + "cell_type": "markdown", + "id": "b4f5e107", + "metadata": {}, + "source": [ + "`BridgeHand` inherits the variables and methods of `Hand` and adds a class variable and a method that are specific to bridge.\n", + "This way of using inheritance is called **specialization** because it defines a new class that is specialized for a particular use, like playing bridge." + ] + }, + { + "cell_type": "markdown", + "id": "b493622d", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "Inheritance is a useful feature.\n", + "Some programs that would be repetitive without inheritance can be written more concisely with it.\n", + "Also, inheritance can facilitate code reuse, since you can customize the behavior of a parent class without having to modify it.\n", + "In some cases, the inheritance structure reflects the natural structure of the problem, which makes the design easier to understand.\n", + "\n", + "On the other hand, inheritance can make programs difficult to read.\n", + "When a method is invoked, it is sometimes not clear where to find its definition -- the relevant code may be spread across several modules.\n", + "\n", + "Any time you are unsure about the flow of execution through your program, the simplest solution is to add print statements at the beginning of the relevant methods.\n", + "If `Deck.shuffle` prints a message that says something like `Running Deck.shuffle`, then as the program runs it traces the flow of execution.\n", + "\n", + "As an alternative, you could use the following function, which takes an object and a method name (as a string) and returns the class that provides the definition of the method." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "20633d57", + "metadata": {}, + "outputs": [], + "source": [ + "def find_defining_class(obj, method_name):\n", + " \"\"\"\n", + " \"\"\"\n", + " for typ in type(obj).mro():\n", + " if method_name in vars(typ):\n", + " return typ\n", + " return f'Method {method_name} not found.'" + ] + }, + { + "cell_type": "markdown", + "id": "1ee8f2da", + "metadata": {}, + "source": [ + "`find_defining_class` uses the `mro` method to get the list of class objects (types) that will be searched for methods.\n", + "\"MRO\" stands for \"method resolution order\", which is the sequence of classes Python searches to \"resolve\" a method name -- that is, to find the function object the name refers to.\n", + "\n", + "As an example, let's instantiate a `BridgeHand` and then find the defining class of `shuffle`." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "c5bec04f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Deck" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hand = BridgeHand('player 3')\n", + "find_defining_class(hand, 'shuffle')" + ] + }, + { + "cell_type": "markdown", + "id": "eeb70a14", + "metadata": {}, + "source": [ + "The `shuffle` method for the `BridgeHand` object is the one in `Deck`." + ] + }, + { + "cell_type": "markdown", + "id": "07f4c4bb", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**inheritance:**\n", + " The ability to define a new class that is a modified version of a previously defined class.\n", + "\n", + "**encode:**\n", + " To represent one set of values using another set of values by constructing a mapping between them.\n", + "\n", + "**class variable:**\n", + "A variable defined inside a class definition, but not inside any method.\n", + "\n", + "**totally ordered:**\n", + "A set of objects is totally ordered if we can compare any two elements and the results are consistent.\n", + "\n", + "**delegation:**\n", + "When one method passes responsibility to another method to do most or all of the work.\n", + "\n", + "**parent class:**\n", + "A class that is inherited from.\n", + "\n", + "**child class:**\n", + "A class that inherits from another class.\n", + "\n", + "**specialization:**\n", + "A way of using inheritance to create a new class that is a specialized version of an existing class." + ] + }, + { + "cell_type": "markdown", + "id": "1aea9b2b", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e281457a", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, + { + "cell_type": "markdown", + "id": "7913e6b1", + "metadata": {}, + "source": [ + "### Ask a Virtual Assistant\n", + "\n", + "When it goes well, object-oriented programming can make programs more readable, testable, and reusable.\n", + "But it can also make programs complicated and hard to maintain.\n", + "As a result, OOP is a topic of controversy -- some people love it, and some people don't.\n", + "\n", + "To learn more about the topic, ask a virtual assistant:\n", + "\n", + "* What are some pros and cons of object-oriented programming?\n", + "\n", + "* What does it mean when people say \"favor composition over inheritance\"?\n", + "\n", + "* What is the Liskov substitution principle?\n", + "\n", + "* Is Python an object-oriented language?\n", + "\n", + "* What are the requirements for a set to be totally ordered?\n", + "\n", + "And as always, consider using a virtual assistant to help with the following exercises." + ] + }, + { + "cell_type": "markdown", + "id": "1af81269", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In contract bridge, a \"trick\" is a round of play in which each of four players plays one card.\n", + "To represent those cards, we'll define a class that inherits from `Deck`." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "47d9ce31", + "metadata": {}, + "outputs": [], + "source": [ + "class Trick(Deck):\n", + " \"\"\"Represents a trick in contract bridge.\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "9916d562", + "metadata": {}, + "source": [ + "As an example, consider this trick, where the first player leads with the 3 of Diamonds, which means that Diamonds are the \"led suit\".\n", + "The second and third players \"follow suit\", which means they play a card with the led suit.\n", + "The fourth player plays a card of a different suit, which means they cannot win the trick.\n", + "So the winner of this trick is the third player, because they played the highest card in the led suit." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "7bb54059", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3 of Diamonds\n", + "10 of Diamonds\n", + "Queen of Diamonds\n", + "King of Hearts\n" + ] + } + ], + "source": [ + "cards = [Card(1, 3),\n", + " Card(1, 10),\n", + " Card(1, 12),\n", + " Card(2, 13)]\n", + "trick = Trick(cards)\n", + "print(trick)" + ] + }, + { + "cell_type": "markdown", + "id": "c94a1337", + "metadata": {}, + "source": [ + "Write a `Trick` method called `find_winner` that loops through the cards in the `Trick` and returns the index of the card that wins.\n", + "In the previous example, the index of the winning card is `2`." + ] + }, + { + "cell_type": "markdown", + "id": "60c43389", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "72a410d7", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to Trick\n", + "\n", + " def find_winner(self):\n", + " return 0" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "b09cceb1", + "metadata": { + "tags": [ + "remove-cell", + "solution" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d7e3b94c", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "If you test your method with the previous example, the index of the winning card should be `2`." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "e185c2f7", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trick.find_winner()" + ] + }, + { + "cell_type": "markdown", + "id": "b5b9fb4b", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "The next few exercises ask to you write functions that classify poker hands.\n", + "If you are not familiar with poker, I'll explain what you need to know.\n", + "We'll use the following class to represent poker hands." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "2558ddd1", + "metadata": {}, + "outputs": [], + "source": [ + "class PokerHand(Hand):\n", + " \"\"\"Represents a poker hand.\"\"\"\n", + "\n", + " def get_suit_counts(self):\n", + " counter = {}\n", + " for card in self.cards:\n", + " key = card.suit\n", + " counter[key] = counter.get(key, 0) + 1\n", + " return counter\n", + " \n", + " def get_rank_counts(self):\n", + " counter = {}\n", + " for card in self.cards:\n", + " key = card.rank\n", + " counter[key] = counter.get(key, 0) + 1\n", + " return counter " + ] + }, + { + "cell_type": "markdown", + "id": "2daecced", + "metadata": {}, + "source": [ + "`PokerHand` provides two methods that will help with the exercises.\n", + "\n", + "* `get_suit_counts` loops through the cards in the `PokerHand`, counts the number of cards in each suit, and returns a dictionary that maps from each suit code to the number of times it appears.\n", + "\n", + "* `get_rank_counts` does the same thing with the ranks of the cards, returning a dictionary that maps from each rank code to the number of times it appears.\n", + "\n", + "All of the exercises that follow can be done using only the Python features we have learned so far, but some of them are more difficult than most of the previous exercises.\n", + "I encourage you to ask an AI for help.\n", + "\n", + "For problems like this, it often works well to ask for general advice about strategies and algorithms.\n", + "Then you can either write the code yourself or ask for code.\n", + "If you ask for code, you might want to provide the relevant class definitions as part of the prompt." + ] + }, + { + "cell_type": "markdown", + "id": "ccc2d8ca", + "metadata": {}, + "source": [ + "As a first exercise, we'll write a method called `has_flush` that checks whether a hand has a \"flush\" -- that is, whether it contains at least five cards of the same suit.\n", + "\n", + "In most varieties of poker, a hand contains either five or seven cards, but there are some exotic variations where a hand contains other numbers of cards.\n", + "But regardless of how many cards there are in a hand, the only ones that count are the five that make the best hand." + ] + }, + { + "cell_type": "markdown", + "id": "6911a0f6", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "7aa25f29", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to PokerHand\n", + "\n", + " def has_flush(self):\n", + " \"\"\"Checks whether this hand has a flush.\"\"\"\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "72c769d5", + "metadata": { + "tags": [ + "remove-cell", + "solution" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "07878cb3", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "To test this method, we'll construct a hand with five cards that are all Clubs, so it contains a flush." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "e1c4f474", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10 of Clubs\n", + "Jack of Clubs\n", + "Queen of Clubs\n", + "King of Clubs\n", + "Ace of Clubs\n" + ] + } + ], + "source": [ + "good_hand = PokerHand('good_hand')\n", + "\n", + "suit = 0\n", + "for rank in range(10, 15):\n", + " card = Card(suit, rank)\n", + " good_hand.put_card(card)\n", + " \n", + "print(good_hand)" + ] + }, + { + "cell_type": "markdown", + "id": "83698207", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "If we invoke `get_suit_counts`, we can confirm that the rank code `0` appears `5` times." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "c91e3bdb", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{0: 5}" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.get_suit_counts()" + ] + }, + { + "cell_type": "markdown", + "id": "fdec7276", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "So `has_flush` should return `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "a60e233d", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.has_flush()" + ] + }, + { + "cell_type": "markdown", + "id": "ba10e1ba", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "As a second test, we'll construct a hand with three Clubs and two other suits." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "ae5063e2", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Clubs\n", + "3 of Clubs\n", + "4 of Hearts\n", + "5 of Spades\n", + "7 of Clubs\n" + ] + } + ], + "source": [ + "cards = [Card(0, 2),\n", + " Card(0, 3),\n", + " Card(2, 4),\n", + " Card(3, 5),\n", + " Card(0, 7),\n", + " ]\n", + "\n", + "bad_hand = PokerHand('bad hand')\n", + "for card in cards:\n", + " bad_hand.put_card(card)\n", + " \n", + "print(bad_hand)" + ] + }, + { + "cell_type": "markdown", + "id": "8611610f", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "So `has_flush` should return `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "940928ba", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bad_hand.has_flush()" + ] + }, + { + "cell_type": "markdown", + "id": "ad716880", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Write a method called `has_straight` that checks whether a hand contains a straight, which is a set of five cards with consecutive ranks.\n", + "For example, if a hand contains ranks `5`, `6`, `7`, `8`, and `9`, it contains a straight.\n", + "\n", + "An Ace can come before a two or after a King, so `Ace`, `2`, `3`, `4`, `5` is a straight and so it `10`, `Jack`, `Queen`, `King`, `Ace`.\n", + "But a straight cannot \"wrap around\", so `King`, `Ace`, `2`, `3`, `4` is not a straight." + ] + }, + { + "cell_type": "markdown", + "id": "bbd04e50", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started.\n", + "It includes a few lines of code that count the number of Aces -- represented with the code `1` or `14` -- and store the total in both locations of the counter." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "6513ef14", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to PokerHand\n", + "\n", + " def has_straight(self, n=5):\n", + " \"\"\"Checks whether this hand has a straight with at least `n` cards.\"\"\"\n", + " counter = self.get_rank_counts()\n", + " aces = counter.get(1, 0) + counter.get(14, 0)\n", + " counter[1] = aces\n", + " counter[14] = aces\n", + " \n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "8a220d67", + "metadata": { + "tags": [ + "remove-cell", + "solution" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "d2e576bf", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "`good_hand`, which we created for the previous exercise, contains a straight.\n", + "If we use `get_rank_counts`, we can confirm that it has at least one card with each of five consecutive ranks." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "5a422cae", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{10: 1, 11: 1, 12: 1, 13: 1, 14: 1}" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.get_rank_counts()" + ] + }, + { + "cell_type": "markdown", + "id": "009a20dd", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "So `has_straight` should return `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "24483b99", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.has_straight()" + ] + }, + { + "cell_type": "markdown", + "id": "1d79b2b6", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "`bad_hand` does not contain a straight, so `has_straight` should return `False`." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "69f7cea9", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bad_hand.has_straight()" + ] + }, + { + "cell_type": "markdown", + "id": "c1ecebd3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A hand has a straight flush if it contains a set of five cards that are both a straight and a flush -- that is, five cards of the same suit with consecutive ranks.\n", + "Write a `PokerHand` method that checks whether a hand has a straight flush." + ] + }, + { + "cell_type": "markdown", + "id": "94570008", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "88bd35fb", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to PokerHand\n", + "\n", + " def has_straightflush(self):\n", + " \"\"\"Check whether this hand has a straight flush.\"\"\"\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "5e602773", + "metadata": { + "tags": [ + "remove-cell", + "solution" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "997b120d", + "metadata": { + "tags": [ + "remove-cell", + "solution" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "4a1e17b1", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "Use the following examples to test your method." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "23704ab4", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.has_straightflush() # should return True" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "7d8d2fdb", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bad_hand.has_straightflush() # should return False" + ] + }, + { + "cell_type": "markdown", + "id": "9127ceb1", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "Note that it is not enough to check whether a hand has a straight and a flush.\n", + "To see why, consider the following hand." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "4c5727d9", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Clubs\n", + "3 of Clubs\n", + "4 of Hearts\n", + "5 of Spades\n", + "7 of Clubs\n", + "6 of Clubs\n", + "9 of Clubs\n" + ] + } + ], + "source": [ + "from copy import deepcopy\n", + "\n", + "straight_and_flush = deepcopy(bad_hand)\n", + "straight_and_flush.put_card(Card(0, 6))\n", + "straight_and_flush.put_card(Card(0, 9))\n", + "print(straight_and_flush)" + ] + }, + { + "cell_type": "markdown", + "id": "c4ed18e4", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "This hand contains a straight and a flush, but they are not the same five cards." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "7e65e951", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(True, True)" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + " straight_and_flush.has_straight(), straight_and_flush.has_flush()" + ] + }, + { + "cell_type": "markdown", + "id": "5ff4e160", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "So it does not contain a straight flush." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "758fc922", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "straight_and_flush.has_straightflush() # should return False" + ] + }, + { + "cell_type": "markdown", + "id": "dd742401", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A poker hand has a pair if it contains two or more cards with the same rank.\n", + "Write a `PokerHand` method that checks whether a hand contains a pair." + ] + }, + { + "cell_type": "markdown", + "id": "00464353", + "metadata": {}, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "15a81a40", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to PokerHand\n", + "\n", + " def check_sets(self, *need_list):\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "bc7ca211", + "metadata": { + "tags": [ + "solution", + "remove-cell" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "20b65d89", + "metadata": { + "tags": [ + "solution", + "remove-cell" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9f001207", + "metadata": {}, + "source": [ + "To test your method, here's a hand that has a pair." + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "57e616be", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Clubs\n", + "3 of Clubs\n", + "4 of Hearts\n", + "5 of Spades\n", + "7 of Clubs\n", + "2 of Diamonds\n" + ] + } + ], + "source": [ + "pair = deepcopy(bad_hand)\n", + "pair.put_card(Card(1, 2))\n", + "print(pair)" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "b8d87fd8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 94, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pair.has_pair() # should return True" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "3016e99a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bad_hand.has_pair() # should return False" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "852e3b11", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.has_pair() # should return False" + ] + }, + { + "cell_type": "markdown", + "id": "c4180a64", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "A hand has a full house if it contains three cards of one rank and two cards of another rank.\n", + "Write a `PokerHand` method that checks whether a hand has a full house." + ] + }, + { + "cell_type": "markdown", + "id": "5aa80ee6", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "1f95601d", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "%%add_method_to PokerHand\n", + "\n", + " def has_full_house(self):\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "420ca0fd", + "metadata": { + "tags": [ + "solution", + "remove-cell" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f020cf9e", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use this hand to test your method." + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "1e4957cb", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Clubs\n", + "3 of Clubs\n", + "4 of Hearts\n", + "5 of Spades\n", + "7 of Clubs\n", + "2 of Diamonds\n", + "2 of Hearts\n", + "3 of Hearts\n" + ] + } + ], + "source": [ + "boat = deepcopy(pair)\n", + "boat.put_card(Card(2, 2))\n", + "boat.put_card(Card(2, 3))\n", + "print(boat)" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "4dc8b899", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "boat.has_full_house() # should return True" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "86229763", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pair.has_full_house() # should return False" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "7f1b3664", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "good_hand.has_full_house() # should return False" + ] + }, + { + "cell_type": "markdown", + "id": "666340c1", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "This exercise is a cautionary tale about a common error that can be difficult to debug.\n", + "Consider the following class definition." + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "85b47651", + "metadata": {}, + "outputs": [], + "source": [ + "class Kangaroo:\n", + " \"\"\"A Kangaroo is a marsupial.\"\"\"\n", + " \n", + " def __init__(self, name, contents=[]):\n", + " \"\"\"Initialize the pouch contents.\n", + "\n", + " name: string\n", + " contents: initial pouch contents.\n", + " \"\"\"\n", + " self.name = name\n", + " self.contents = contents\n", + "\n", + " def __str__(self):\n", + " \"\"\"Return a string representaion of this Kangaroo.\n", + " \"\"\"\n", + " t = [ self.name + ' has pouch contents:' ]\n", + " for obj in self.contents:\n", + " s = ' ' + object.__str__(obj)\n", + " t.append(s)\n", + " return '\\n'.join(t)\n", + "\n", + " def put_in_pouch(self, item):\n", + " \"\"\"Adds a new item to the pouch contents.\n", + "\n", + " item: object to be added\n", + " \"\"\"\n", + " self.contents.append(item)" + ] + }, + { + "cell_type": "markdown", + "id": "1e349832", + "metadata": {}, + "source": [ + "`__init__` takes two parameters: `name` is required, but `contents` is optional -- if it's not provided, the default value is an empty list.\n", + "\n", + "`__str__` returns a string representation of the object that includes the name and the contents of the pouch.\n", + "\n", + "`put_in_pouch` takes any object and appends it to `contents`.\n", + "\n", + "Now let's see how this class works.\n", + "We'll create two `Kangaroo` objects with the names Kanga and Roo." + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "13f1e1f3", + "metadata": {}, + "outputs": [], + "source": [ + "kanga = Kangaroo('Kanga')\n", + "roo = Kangaroo('Roo')" + ] + }, + { + "cell_type": "markdown", + "id": "533982d1", + "metadata": {}, + "source": [ + "To Kanga's pouch we'll add two strings and Roo." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "e882e2d6", + "metadata": {}, + "outputs": [], + "source": [ + "kanga.put_in_pouch('wallet')\n", + "kanga.put_in_pouch('car keys')\n", + "kanga.put_in_pouch(roo)" + ] + }, + { + "cell_type": "markdown", + "id": "41cd6d6e", + "metadata": {}, + "source": [ + "If we print `kanga`, it seems like everything worked." + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "724805bb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Kanga has pouch contents:\n", + " 'wallet'\n", + " 'car keys'\n", + " <__main__.Kangaroo object at 0x7f6e2ea11900>\n" + ] + } + ], + "source": [ + "print(kanga)" + ] + }, + { + "cell_type": "markdown", + "id": "0ba26163", + "metadata": {}, + "source": [ + "But what happens if we print `roo`?" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "id": "3f6cff16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Roo has pouch contents:\n", + " 'wallet'\n", + " 'car keys'\n", + " <__main__.Kangaroo object at 0x7f6e2ea11900>\n" + ] + } + ], + "source": [ + "print(roo)" + ] + }, + { + "cell_type": "markdown", + "id": "a2aef813", + "metadata": {}, + "source": [ + "Roo's pouch contains the same contents as Kanga's, including a reference to `roo`!\n", + "\n", + "See if you can figure out what went wrong.\n", + "Then ask a virtual assistant, \"What's wrong with the following program?\" and paste in the definition of `Kangaroo`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ef15c8c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/_sources/chap18.ipynb b/_sources/chap18.ipynb new file mode 100644 index 0000000..eb18c6b --- /dev/null +++ b/_sources/chap18.ipynb @@ -0,0 +1,3327 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "260216be", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from os.path import basename, exists\n", + "\n", + "def download(url):\n", + " filename = basename(url)\n", + " if not exists(filename):\n", + " from urllib.request import urlretrieve\n", + "\n", + " local, _ = urlretrieve(url, filename)\n", + " print(\"Downloaded \" + str(local))\n", + " return filename\n", + "\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", + "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", + "\n", + "import thinkpython" + ] + }, + { + "cell_type": "markdown", + "id": "f5cb253c", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "Here are versions of the `Card`, `Deck`, and `Hand` classes from Chapter 17, which we will use in some examples in this chapter." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ad0da137", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "class Card:\n", + " suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']\n", + " rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', \n", + " '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']\n", + " \n", + " def __init__(self, suit, rank):\n", + " self.suit = suit\n", + " self.rank = rank\n", + "\n", + " def __str__(self):\n", + " rank_name = Card.rank_names[self.rank]\n", + " suit_name = Card.suit_names[self.suit]\n", + " return f'{rank_name} of {suit_name}' " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8b00461b", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "import random\n", + "\n", + "class Deck:\n", + " def __init__(self, cards):\n", + " self.cards = cards\n", + " \n", + " def __str__(self):\n", + " res = []\n", + " for card in self.cards:\n", + " res.append(str(card))\n", + " return '\\n'.join(res)\n", + " \n", + " def make_cards():\n", + " cards = []\n", + " for suit in range(4):\n", + " for rank in range(2, 15):\n", + " card = Card(suit, rank)\n", + " cards.append(card)\n", + " return cards\n", + " \n", + " def shuffle(self):\n", + " random.shuffle(self.cards)\n", + " \n", + " def pop_card(self):\n", + " return self.cards.pop()\n", + " \n", + " def add_card(self, card):\n", + " self.cards.append(card)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "737b8979", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "class Hand(Deck):\n", + " def __init__(self, label=''):\n", + " self.label = label\n", + " self.cards = []" + ] + }, + { + "cell_type": "markdown", + "id": "27e8d827", + "metadata": {}, + "source": [ + "# Python Extras\n", + "\n", + "One of my goals for this book has been to teach you as little Python as possible. \n", + "When there were two ways to do something, I picked one and avoided mentioning the other.\n", + "Or sometimes I put the second one into an exercise.\n", + "\n", + "Now I want to go back for some of the good bits that got left behind.\n", + "Python provides a number of features that are not really necessary -- you can write good code without them -- but with them you can write code that's more concise, readable, or efficient, and sometimes all three." + ] + }, + { + "cell_type": "markdown", + "id": "7ddcece8", + "metadata": {}, + "source": [ + "## Sets\n", + "\n", + "Python provides a class called `set` that represents a collection of unique elements.\n", + "To create an empty set, we can use the class object like a function." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "77f98242", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "set()" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s1 = set()\n", + "s1" + ] + }, + { + "cell_type": "markdown", + "id": "904e2071", + "metadata": {}, + "source": [ + "We can use the `add` method to add elements." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "85157902", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a', 'b'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s1.add('a')\n", + "s1.add('b')\n", + "s1" + ] + }, + { + "cell_type": "markdown", + "id": "beee02fc", + "metadata": {}, + "source": [ + "Or we can pass any kind of sequence to `set`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b470e34a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a', 'c', 'd'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s2 = set('acd')\n", + "s2" + ] + }, + { + "cell_type": "markdown", + "id": "42f99153", + "metadata": {}, + "source": [ + "An element can only appear once in a `set`.\n", + "If you add an element that's already there, it has no effect." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e6a2d78b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a', 'b'}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s1.add('a')\n", + "s1" + ] + }, + { + "cell_type": "markdown", + "id": "9b0a82ee", + "metadata": {}, + "source": [ + "Or if you create a set with a sequence that contains duplicates, the result contains only unique elements." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a4f5ff4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a', 'b', 'n'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set('banana')" + ] + }, + { + "cell_type": "markdown", + "id": "328e2009", + "metadata": {}, + "source": [ + "Some of the exercises in this book can be done concisely and efficiently with sets. \n", + "For example, here is a solution to an exercise in Chapter 11 that uses a dictionary to check whether there are any duplicate elements in a sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "483046f2", + "metadata": {}, + "outputs": [], + "source": [ + "def has_duplicates(t):\n", + " d = {}\n", + " for x in t:\n", + " d[x] = True\n", + " return len(d) < len(t)" + ] + }, + { + "cell_type": "markdown", + "id": "0b250e58", + "metadata": {}, + "source": [ + "This version adds the element of `t` as keys in a dictionary, and then checks whether there are fewer keys than elements.\n", + "Using sets, we can write the same function like this." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fe22c739", + "metadata": {}, + "outputs": [], + "source": [ + "def has_duplicates(t):\n", + " s = set(t)\n", + " return len(s) < len(t)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3bbd3b72", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "has_duplicates('abba')" + ] + }, + { + "cell_type": "markdown", + "id": "30cf3158", + "metadata": {}, + "source": [ + "An element can only appear in a set once, so if an element in `t` appears more than once, the set will be smaller than `t`.\n", + "If there are no duplicates, the set will be the same size as `t`.\n", + "\n", + "`set` objects provide methods that perform set operations.\n", + "For example, `union` computes the union of two sets, which is a new set that contains all elements that appear in either set." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0a8234b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a', 'b', 'c', 'd'}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s1.union(s2)" + ] + }, + { + "cell_type": "markdown", + "id": "57b1d50c", + "metadata": {}, + "source": [ + "Some arithmetic operators work with sets.\n", + "For example, the `-` operator performs set subtraction -- the result is a new set that contains all elements from the first set that are _not_ in the second set." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5b81bb56", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'b'}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s1 - s2" + ] + }, + { + "cell_type": "markdown", + "id": "5099226c", + "metadata": {}, + "source": [ + "In [Chapter 12](section_dictionary_subtraction) we used dictionaries to find the words that appear in a document but not in a word list.\n", + "We used the following function, which takes two dictionaries and returns a new dictionary that contains only the keys from the first that don't appear in the second." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "06a94266", + "metadata": {}, + "outputs": [], + "source": [ + "def subtract(d1, d2):\n", + " res = {}\n", + " for key in d1:\n", + " if key not in d2:\n", + " res[key] = d1[key]\n", + " return res" + ] + }, + { + "cell_type": "markdown", + "id": "455c3e34", + "metadata": {}, + "source": [ + "With sets, we don't have to write this function ourselves.\n", + "If `word_counter` is a dictionary that contains the unique words in the document and `word_list` is a list of valid words, we can compute the set difference like this." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "3f303544", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "# this cell creates a small example so we can run the following\n", + "# cell without loading the actual data\n", + "\n", + "word_counter = {'word': 1}\n", + "word_list = ['word']" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b200cbc2", + "metadata": { + "tags": [ + "remove-output" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "set()" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set(word_counter) - set(word_list)" + ] + }, + { + "cell_type": "markdown", + "id": "91efe708", + "metadata": {}, + "source": [ + "The result is a set that contains the words in the document that don't appear in the word list.\n", + "\n", + "The comparison operators work with sets.\n", + "For example, `<=` checks whether one set is a subset of another, including the possibility that they are equal." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "15919aca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set('ab') <= set('abc')" + ] + }, + { + "cell_type": "markdown", + "id": "74d4d824", + "metadata": {}, + "source": [ + "With these operators, we can use sets to do some of the exercises in Chapter 7.\n", + "For example, here's a version of `uses_only` that uses a loop." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "21940f25", + "metadata": {}, + "outputs": [], + "source": [ + "def uses_only(word, available):\n", + " for letter in word: \n", + " if letter not in available:\n", + " return False\n", + " return True" + ] + }, + { + "cell_type": "markdown", + "id": "58c1da26", + "metadata": {}, + "source": [ + "`uses_only` checks whether all letters in `word` are in `available`.\n", + "With sets, we can rewrite it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "5769968e", + "metadata": {}, + "outputs": [], + "source": [ + "def uses_only(word, available):\n", + " return set(word) <= set(available)" + ] + }, + { + "cell_type": "markdown", + "id": "01ce8cff", + "metadata": {}, + "source": [ + "If the letters in `word` are a subset of the letters in `available`, that means that `word` uses only letters in `available`." + ] + }, + { + "cell_type": "markdown", + "id": "d7c22ef5", + "metadata": {}, + "source": [ + "## Counters\n", + "\n", + "A `Counter` is like a set, except that if an element appears more than once, the `Counter` keeps track of how many times it appears.\n", + "If you are familiar with the mathematical idea of a \"multiset\", a `Counter` is a\n", + "natural way to represent a multiset.\n", + "\n", + "The `Counter` class is defined in a standard module called `collections`, so you have to import it.\n", + "Then you can use the class object as a function and pass as an argument a string, list, or any other kind of sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1baff39a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({'a': 3, 'n': 2, 'b': 1})" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from collections import Counter\n", + "\n", + "counter = Counter('banana')\n", + "counter" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "14d5aee5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({1: 3, 2: 2, 3: 1})" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from collections import Counter\n", + "\n", + "t = (1, 1, 1, 2, 2, 3)\n", + "counter = Counter(t)\n", + "counter" + ] + }, + { + "cell_type": "markdown", + "id": "8da28fe5", + "metadata": {}, + "source": [ + "A `Counter` object is like a dictionary that maps from each key to the number of times it appears.\n", + "As in dictionaries, the keys have to be hashable.\n", + "\n", + "Unlike dictionaries, `Counter` objects don't raise an exception if you access an\n", + "element that doesn't appear.\n", + "Instead, they return `0`." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "28eb9235", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter['d']" + ] + }, + { + "cell_type": "markdown", + "id": "9bb2b650", + "metadata": {}, + "source": [ + "We can use `Counter` objects to solve one of the exercises from Chapter 10, which asks for a function that takes two words and checks whether they are anagrams -- that is, whether the letters from one can be rearranged to spell the other.\n", + "\n", + "Here's a solution using `Counter` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "67aadb0b", + "metadata": {}, + "outputs": [], + "source": [ + "def is_anagram(word1, word2):\n", + " return Counter(word1) == Counter(word2)" + ] + }, + { + "cell_type": "markdown", + "id": "6907f368", + "metadata": {}, + "source": [ + "If two words are anagrams, they contain the same letters with the same counts, so their `Counter` objects are equivalent.\n", + "\n", + "`Counter` provides a method called `most_common` that returns a list of value-frequency pairs, sorted from most common to least." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "0c771fb9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 3), (2, 2), (3, 1)]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter.most_common()" + ] + }, + { + "cell_type": "markdown", + "id": "b02b7dff", + "metadata": {}, + "source": [ + "They also provide methods and operators to perform set-like operations, including addition, subtraction, union and intersection.\n", + "For example, the `+` operator combines two `Counter` objects and creates a new `Counter` that contains the keys from both and the sums of the counts.\n", + "\n", + "We can test it by making a `Counter` with the letters from `bans` and adding it to the letters from `banana`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "38a511cc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({1: 3, 2: 2, 3: 1, 'b': 1, 'a': 1, 'n': 1, 's': 1})" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter2 = Counter('bans')\n", + "counter + counter2" + ] + }, + { + "cell_type": "markdown", + "id": "5461328e", + "metadata": {}, + "source": [ + "You'll have a chance to explore other `Counter` operations in the exercises at the end of this chapter." + ] + }, + { + "cell_type": "markdown", + "id": "7cf47e67", + "metadata": {}, + "source": [ + "## defaultdict\n", + "\n", + "The `collections` module also provides `defaultdict`, which is like a dictionary except that if you access a key that doesn't exist, it generates a new value automatically.\n", + "\n", + "When you create a `defaultdict`, you provide a function that's used to create new values.\n", + "A function that create objects is sometimes called a **factory**.\n", + "The built-in functions that create lists, sets, and other types can be used as factories.\n", + "\n", + "For example, here's a `defaultdict` that creates a new `list` when needed. " + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "5303812d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(list, {})" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from collections import defaultdict\n", + "\n", + "d = defaultdict(list)\n", + "d" + ] + }, + { + "cell_type": "markdown", + "id": "9f43d537", + "metadata": {}, + "source": [ + "Notice that the argument is `list`, which is a class object, not `list()`, which is a function call that creates a new list.\n", + "The factory function doesn't get called unless we access a key that doesn't exist." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "4955b14f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t = d['new key']\n", + "t" + ] + }, + { + "cell_type": "markdown", + "id": "01f87415", + "metadata": {}, + "source": [ + "The new list, which we're calling `t`, is also added to the dictionary.\n", + "So if we modify `t`, the change appears in `d`:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "d6a771af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['new value']" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t.append('new value')\n", + "d['new key']" + ] + }, + { + "cell_type": "markdown", + "id": "3e5d0151", + "metadata": {}, + "source": [ + "If you are making a dictionary of lists, you can often write simpler\n", + "code using `defaultdict`. \n", + "\n", + "In one of the exercises in [Chapter 11](chapter_tuples), I made a dictionary that maps from a sorted string of letters to the list of words that can be spelled with those letters.\n", + "For example, the string `'opst'` maps to the list `['opts', 'post', 'pots', 'spot', 'stop', 'tops']`.\n", + "Here's the original code." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "9c4166e4", + "metadata": {}, + "outputs": [], + "source": [ + "def all_anagrams(filename):\n", + " d = {}\n", + " for line in open(filename):\n", + " word = line.strip().lower()\n", + " t = signature(word)\n", + " if t not in d:\n", + " d[t] = [word]\n", + " else:\n", + " d[t].append(word)\n", + " return d" + ] + }, + { + "cell_type": "markdown", + "id": "8e9a0a2b", + "metadata": {}, + "source": [ + "And here's a simpler version using a `defaultdict`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "e908188e", + "metadata": {}, + "outputs": [], + "source": [ + "def all_anagrams(filename):\n", + " d = defaultdict(list)\n", + " for line in open(filename):\n", + " word = line.strip().lower()\n", + " t = signature(word)\n", + " d[t].append(word)\n", + " return d" + ] + }, + { + "cell_type": "markdown", + "id": "cccdd46c", + "metadata": {}, + "source": [ + "In the exercises at the end of the chapter, you'll have a chance to practice using `defaultdict` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "4e1e35f7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['woods']" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from collections import defaultdict\n", + "\n", + "d = defaultdict(list)\n", + "key = ('into', 'the')\n", + "d[key].append('woods')\n", + "d[key]" + ] + }, + { + "cell_type": "markdown", + "id": "610359c1", + "metadata": {}, + "source": [ + "## Conditional expressions\n", + "\n", + "Conditional statements are often used to choose one of two values, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "25ff32b9", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "import math\n", + "x = 5" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "11676b80", + "metadata": {}, + "outputs": [], + "source": [ + "if x > 0:\n", + " y = math.log(x)\n", + "else:\n", + " y = float('nan')" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "0249c4c8", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.6094379124341003" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y" + ] + }, + { + "cell_type": "markdown", + "id": "2c5fc3dd", + "metadata": {}, + "source": [ + "This statement checks whether `x` is positive. If so, it computes its logarithm. \n", + "If not, `math.log` would raise a ValueError.\n", + "To avoid stopping the program, we generate a `NaN`, which is a special floating-point value that represents \"Not a Number\".\n", + "\n", + "We can write this statement more concisely using a **conditional expression**." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "803a39c4", + "metadata": {}, + "outputs": [], + "source": [ + "y = math.log(x) if x > 0 else float('nan')" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "03b1afa9", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.6094379124341003" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y" + ] + }, + { + "cell_type": "markdown", + "id": "6a7cf27b", + "metadata": {}, + "source": [ + "You can almost read this line like English: \"`y` gets log-`x` if `x` is greater than 0; otherwise it gets `NaN`\".\n", + "\n", + "Recursive functions can sometimes be written concisely using conditional expressions. \n", + "For example, here is a version of `factorial` with a conditional _statement_." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "73d3e7e9", + "metadata": {}, + "outputs": [], + "source": [ + "def factorial(n):\n", + " if n == 0:\n", + " return 1\n", + " else:\n", + " return n * factorial(n-1)" + ] + }, + { + "cell_type": "markdown", + "id": "56052b5c", + "metadata": {}, + "source": [ + "And here's a version with a conditional _expression_." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "d14e82df", + "metadata": {}, + "outputs": [], + "source": [ + "def factorial(n):\n", + " return 1 if n == 0 else n * factorial(n-1)" + ] + }, + { + "cell_type": "markdown", + "id": "d53fbc15", + "metadata": {}, + "source": [ + "Another use of conditional expressions is handling optional arguments.\n", + "For example, here is class definition with an `__init__` method that uses a conditional statement to check a parameter with a default value." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "aa8fcfe5", + "metadata": {}, + "outputs": [], + "source": [ + "class Kangaroo:\n", + " def __init__(self, name, contents=None):\n", + " self.name = name\n", + " if contents is None:\n", + " contents = []\n", + " self.contents = contents" + ] + }, + { + "cell_type": "markdown", + "id": "655bfc46", + "metadata": {}, + "source": [ + "Here's a version that uses a conditional expression." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "fcb67453", + "metadata": {}, + "outputs": [], + "source": [ + "def __init__(self, name, contents=None):\n", + " self.name = name\n", + " self.contents = [] if contents is None else contents " + ] + }, + { + "cell_type": "markdown", + "id": "fef85229", + "metadata": {}, + "source": [ + "In general, you can replace a conditional statement with a conditional expression if both branches contain a single expression and no statements." + ] + }, + { + "cell_type": "markdown", + "id": "45d3b306", + "metadata": {}, + "source": [ + "## List comprehensions\n", + "\n", + "In previous chapters, we've seen a few examples where we start with an empty list and add elements, one at a time, using the `append` method.\n", + "For example, suppose we have a string that contains the title of a movie, and we want to capitalize all of the words." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "a1d31625", + "metadata": {}, + "outputs": [], + "source": [ + "title = 'monty python and the holy grail'" + ] + }, + { + "cell_type": "markdown", + "id": "9eeb45a6", + "metadata": {}, + "source": [ + "We can split it into a list of strings, loop through the strings, capitalize them, and append them to a list." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "595cdbc8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Monty Python And The Holy Grail'" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t = []\n", + "for word in title.split():\n", + " t.append(word.capitalize())\n", + "\n", + "' '.join(t)" + ] + }, + { + "cell_type": "markdown", + "id": "b96197c2", + "metadata": {}, + "source": [ + "We can do the same thing more concisely using a **list comprehension**:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "ac327d75", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Monty Python And The Holy Grail'" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t = [word.capitalize() for word in title.split()]\n", + "\n", + "' '.join(t)" + ] + }, + { + "cell_type": "markdown", + "id": "e5b565ad", + "metadata": {}, + "source": [ + "The bracket operators indicate that we are constructing a new list.\n", + "The expression inside the brackets specifies the elements of the list, and the `for` clause indicates what sequence we are looping through.\n", + "\n", + "The syntax of a list comprehension might seem strange, because the loop variable -- `word` in this example -- appears in the expression before we get to its definition.\n", + "But you get used to it.\n", + "\n", + "As another example, in [Chapter 9](section_word_list) we used this loop to read words from a file and append them to a list." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "036ec803", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython2/master/code/words.txt');" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "1437f1f1", + "metadata": {}, + "outputs": [], + "source": [ + "word_list = []\n", + "\n", + "for line in open('words.txt'):\n", + " word = line.strip()\n", + " word_list.append(word)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "c9c2a932", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "113783" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(word_list)" + ] + }, + { + "cell_type": "markdown", + "id": "2d1df49b", + "metadata": {}, + "source": [ + "Here's how we can write that as a list comprehension." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "d4f854c6", + "metadata": {}, + "outputs": [], + "source": [ + "word_list = [line.strip() for line in open('words.txt')]" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "3d797346", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "113783" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(word_list)" + ] + }, + { + "cell_type": "markdown", + "id": "92d856ba", + "metadata": {}, + "source": [ + "A list comprehension can also have an `if` clause that determines which elements are included in the list.\n", + "For example, here's a `for` loop we used in [Chapter 10](section_palindrome_list) to make a list of only the words in `word_list` that are palindromes." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "d476e43c", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "def is_palindrome(word):\n", + " return list(reversed(word)) == list(word)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "6b295593", + "metadata": {}, + "outputs": [], + "source": [ + "palindromes = []\n", + "\n", + "for word in word_list:\n", + " if is_palindrome(word):\n", + " palindromes.append(word)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "f7b1cd91", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['aa', 'aba', 'aga', 'aha', 'ala', 'alula', 'ama', 'ana', 'anna', 'ava']" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "palindromes[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "151621d8", + "metadata": {}, + "source": [ + "Here's how we can do the same thing with an list comprehension." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "c356d9fb", + "metadata": {}, + "outputs": [], + "source": [ + "palindromes = [word for word in word_list if is_palindrome(word)]" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "52e32b33", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['aa', 'aba', 'aga', 'aha', 'ala', 'alula', 'ama', 'ana', 'anna', 'ava']" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "palindromes[:10]" + ] + }, + { + "cell_type": "markdown", + "id": "5fc4eab1", + "metadata": {}, + "source": [ + "When a list comprehension is used as an argument to a function, we can often omit the brackets.\n", + "For example, suppose we want to add up $1 / 2^n$ for values of $n$ from 0 to 9.\n", + "We can use a list comprehension like this." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "dcf239d7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.998046875" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum([1/2**n for n in range(10)])" + ] + }, + { + "cell_type": "markdown", + "id": "2ee312e0", + "metadata": {}, + "source": [ + "Or we can leave out the brackets like this." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "b9bdec8d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.998046875" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(1/2**n for n in range(10))" + ] + }, + { + "cell_type": "markdown", + "id": "3d56d584", + "metadata": {}, + "source": [ + "In this example, the argument is technically a **generator expression**, not a list comprehension, and it never actually makes a list.\n", + "But other than that, the behavior is the same.\n", + "\n", + "List comprehensions and generator expressions are concise and easy to read, at least for simple expressions.\n", + "And they are usually faster than the equivalent for loops, sometimes much faster.\n", + "So if you are mad at me for not mentioning them earlier, I understand.\n", + "\n", + "But, in my defense, list comprehensions are harder to debug because you can't put a print statement inside the loop.\n", + "I suggest you use them only if the computation is simple enough that you are likely to get it\n", + "right the first time.\n", + "Or consider writing and debugging a `for` loop and then converting it to a list comprehension." + ] + }, + { + "cell_type": "markdown", + "id": "f9fac860", + "metadata": {}, + "source": [ + "## `any` and `all`\n", + "\n", + "Python provides a built-in function, `any`, that takes a sequence of boolean values and returns `True` if any of the values are `True`." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "81a15164", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "any([False, False, True])" + ] + }, + { + "cell_type": "markdown", + "id": "43217186", + "metadata": {}, + "source": [ + "`any` is often used with generator expressions." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "7756c9ea", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "any(letter == 't' for letter in 'monty')" + ] + }, + { + "cell_type": "markdown", + "id": "22395487", + "metadata": {}, + "source": [ + "That example isn't very useful because it does the same thing as the `in` operator. \n", + "But we could use `any` to write concise solutions to some of the exercises in [Chapter 7](chapter_search). For example, we can write `uses_none` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "70133073", + "metadata": {}, + "outputs": [], + "source": [ + "def uses_none(word, forbidden):\n", + " \"\"\"Checks whether a word avoids forbidden letters.\"\"\"\n", + " return not any(letter in forbidden for letter in word)" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "aec0523b", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "uses_none('banana', 'xyz')" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "863252da", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "uses_none('apple', 'efg')" + ] + }, + { + "cell_type": "markdown", + "id": "fbefe3c1", + "metadata": {}, + "source": [ + "This function loops through the letters in `word` and checks whether any of them are in `forbidden`.\n", + "Using `any` with a generator expression is efficient because it stops immediately if it finds a `True` value, so it doesn't have to loop through the whole sequence.\n", + "\n", + "Python provides another built-in function, `all`, that returns `True` if every element of the sequence is `True`.\n", + "We can use it to write a concise version of `uses_all`." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "4b4046aa", + "metadata": {}, + "outputs": [], + "source": [ + "def uses_all(word, required):\n", + " \"\"\"Check whether a word uses all required letters.\"\"\"\n", + " return all(letter in word for letter in required)" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "id": "5c6addfb", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "uses_all('banana', 'ban')" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "54df18a1", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "uses_all('apple', 'api')" + ] + }, + { + "cell_type": "markdown", + "id": "8d9f7364", + "metadata": {}, + "source": [ + "Expressions using `any` and `all` can be concise, efficient, and easy to read." + ] + }, + { + "cell_type": "markdown", + "id": "911857a3", + "metadata": {}, + "source": [ + "## Named tuples\n", + "\n", + "The `collections` module provides a function called `namedtuple` that can be used to create simple classes.\n", + "For example, the `Point` object in [Chapter 16](section_create_point) has only two attributes, `x` and `y`.\n", + "Here's how we defined it." + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "3550c5a4", + "metadata": {}, + "outputs": [], + "source": [ + "class Point:\n", + " \"\"\"Represents a point in 2-D space.\"\"\"\n", + " \n", + " def __init__(self, x, y):\n", + " self.x = x\n", + " self.y = y\n", + " \n", + " def __str__(self):\n", + " return f'({self.x}, {self.y})'" + ] + }, + { + "cell_type": "markdown", + "id": "36f08927", + "metadata": {}, + "source": [ + "That's a lot of code to convey a small amount of information.\n", + "`namedtuple` provides a more concise way to define classes like this." + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "id": "4852da0c", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import namedtuple\n", + "\n", + "Point = namedtuple('Point', ['x', 'y'])" + ] + }, + { + "cell_type": "markdown", + "id": "942a0877", + "metadata": {}, + "source": [ + "The first argument is the name of the class you want to create. The\n", + "second is a list of the attributes `Point` objects should have.\n", + "The result is a class object, which is why it is assigned to a capitalized variable name.\n", + "\n", + "A class created with `namedtuple` provides an `__init__` method that assigns values to the attributes and a `__str__` that displays the object in a readable form.\n", + "So we can create and display a `Point` object like this." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "8acb172d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Point(x=1, y=2)" + ] + }, + "execution_count": 67, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p = Point(1, 2)\n", + "p" + ] + }, + { + "cell_type": "markdown", + "id": "b42ee9a2", + "metadata": {}, + "source": [ + "`Point` also provides an `__eq__` method that checks whether two `Point` objects are equivalent -- that is, whether their attributes are the same." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "494b78ac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p == Point(1, 2)" + ] + }, + { + "cell_type": "markdown", + "id": "9bcf275a", + "metadata": {}, + "source": [ + "You can access the elements of a named tuple by name or by index." + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "f59e85f4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 2)" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p.x, p.y" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "742795a9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 2)" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p[0], p[1]" + ] + }, + { + "cell_type": "markdown", + "id": "0768ff41", + "metadata": {}, + "source": [ + "You can also treat a named tuple as a tuple, as in this assignment." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "id": "df42d98a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 2)" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x, y = p\n", + "x, y" + ] + }, + { + "cell_type": "markdown", + "id": "964aa3bd", + "metadata": {}, + "source": [ + "But `namedtuple` objects are immutable.\n", + "After the attributes are initialized, they can't be changed." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "id": "e61693ba", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "'Point' object does not support item assignment", + "output_type": "error", + "traceback": [ + "\u001b[0;31mTypeError\u001b[0m\u001b[0;31m:\u001b[0m 'Point' object does not support item assignment\n" + ] + } + ], + "source": [ + "\n", + "p[0] = 3" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "871dafae", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "can't set attribute", + "output_type": "error", + "traceback": [ + "\u001b[0;31mAttributeError\u001b[0m\u001b[0;31m:\u001b[0m can't set attribute\n" + ] + } + ], + "source": [ + "\n", + "p.x = 3" + ] + }, + { + "cell_type": "markdown", + "id": "f2db7783", + "metadata": {}, + "source": [ + "`namedtuple` provides a quick way to define simple classes.\n", + "The drawback is that simple classes don't always stay simple.\n", + "You might decide later that you want to add methods to a named tuple.\n", + "In that case, you can define a new class that inherits from the named tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "586d8c51", + "metadata": {}, + "outputs": [], + "source": [ + "class Pointier(Point):\n", + " \"\"\"This class inherits from Point\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "805475ce", + "metadata": {}, + "source": [ + "Or at that point you could switch to a conventional class definition." + ] + }, + { + "cell_type": "markdown", + "id": "4f3713a0", + "metadata": {}, + "source": [ + "## Packing keyword arguments\n", + "\n", + "In [Chapter 11](section_argument_pack), we wrote a function that packs its arguments into a tuple." + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "6de26a32", + "metadata": {}, + "outputs": [], + "source": [ + "def mean(*args):\n", + " return sum(args) / len(args)" + ] + }, + { + "cell_type": "markdown", + "id": "71e3b049", + "metadata": {}, + "source": [ + "You can call this function with any number of positional arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "ecb6a9fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.0" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mean(1, 2, 3)" + ] + }, + { + "cell_type": "markdown", + "id": "486a690f", + "metadata": {}, + "source": [ + "But the `*` operator doesn't pack keyword arguments.\n", + "So calling this function with a keyword argument causes an error." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "5871229d", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "mean() got an unexpected keyword argument 'start'", + "output_type": "error", + "traceback": [ + "\u001b[0;31mTypeError\u001b[0m\u001b[0;31m:\u001b[0m mean() got an unexpected keyword argument 'start'\n" + ] + } + ], + "source": [ + "\n", + "mean(1, 2, start=3)" + ] + }, + { + "cell_type": "markdown", + "id": "eb7f9281", + "metadata": {}, + "source": [ + "To pack keyword arguments, we can use the `**` operator:" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "b274db6a", + "metadata": {}, + "outputs": [], + "source": [ + "def mean(*args, **kwargs):\n", + " print(kwargs)\n", + " return sum(args) / len(args)" + ] + }, + { + "cell_type": "markdown", + "id": "067bf7c4", + "metadata": {}, + "source": [ + "The keyword-packing parameter can have any name, but `kwargs` is a common choice.\n", + "The result is a dictionary that maps from keywords to values." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "3a1b131c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'start': 3}\n" + ] + }, + { + "data": { + "text/plain": [ + "1.5" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mean(1, 2, start=3)" + ] + }, + { + "cell_type": "markdown", + "id": "07be77f3", + "metadata": {}, + "source": [ + "In this example, the value of `kwargs` is printed, but otherwise is has no effect.\n", + "\n", + "But the `**` operator can also be used in an argument list to unpack a dictionary.\n", + "For example, here's a version of `mean` that packs any keyword arguments it gets and then unpacks them as keyword arguments for `sum`." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "a0305500", + "metadata": {}, + "outputs": [], + "source": [ + "def mean(*args, **kwargs):\n", + " return sum(args, **kwargs) / len(args)" + ] + }, + { + "cell_type": "markdown", + "id": "ba00858c", + "metadata": {}, + "source": [ + "Now if we call `mean` with `start` as a keyword argument, it gets passed along to sum, which uses it as the starting point of the summation.\n", + "In the following example `start=3` adds `3` to the sum before computing the mean, so the sum is `6` and the result is `3`." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "457d5610", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.0" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mean(1, 2, start=3)" + ] + }, + { + "cell_type": "markdown", + "id": "949a2ca3", + "metadata": {}, + "source": [ + "As another example, if we have a dictionary with keys `x` and `y`, we can use it with the unpack operator to create a `Point` object." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "020c1243", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Point(x=1, y=2)" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d = dict(x=1, y=2)\n", + "Point(**d)" + ] + }, + { + "cell_type": "markdown", + "id": "8aaf128a", + "metadata": {}, + "source": [ + "Without the unpack operator, `d` is treated as a single positional argument, so it gets assigned to `x`, and we get a `TypeError` because there's no second argument to assign to `y`." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "ba91298b", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "Point.__new__() missing 1 required positional argument: 'y'", + "output_type": "error", + "traceback": [ + "\u001b[0;31mTypeError\u001b[0m\u001b[0;31m:\u001b[0m Point.__new__() missing 1 required positional argument: 'y'\n" + ] + } + ], + "source": [ + "\n", + "d = dict(x=1, y=2)\n", + "Point(d)" + ] + }, + { + "cell_type": "markdown", + "id": "e8acb958", + "metadata": {}, + "source": [ + "When you are working with functions that have a large number of keyword arguments, it is often useful to create and pass around dictionaries that specify frequently used options." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "1b36b5ed", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'a': 1, 'b': 2}\n" + ] + } + ], + "source": [ + "def pack_and_print(**kwargs):\n", + " print(kwargs)\n", + " \n", + "pack_and_print(a=1, b=2)" + ] + }, + { + "cell_type": "markdown", + "id": "e046e382", + "metadata": {}, + "source": [ + "## Debugging\n", + "\n", + "In previous chapters, we used `doctest` to test functions.\n", + "For example, here's a function called `add` that takes two numbers and returns their sum.\n", + "In includes a doctest that checks whether `2 + 2` is `4`." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "e1366e43", + "metadata": {}, + "outputs": [], + "source": [ + "def add(a, b):\n", + " '''Add two numbers.\n", + " \n", + " >>> add(2, 2)\n", + " 4\n", + " '''\n", + " return a + b" + ] + }, + { + "cell_type": "markdown", + "id": "a5e332d3", + "metadata": {}, + "source": [ + "This function takes a function object and runs its doctests." + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "13802dc4", + "metadata": {}, + "outputs": [], + "source": [ + "from doctest import run_docstring_examples\n", + "\n", + "def run_doctests(func):\n", + " run_docstring_examples(func, globals(), name=func.__name__)" + ] + }, + { + "cell_type": "markdown", + "id": "2d752a40", + "metadata": {}, + "source": [ + "So we can test `add` like this." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "2136083a", + "metadata": {}, + "outputs": [], + "source": [ + "run_doctests(add)" + ] + }, + { + "cell_type": "markdown", + "id": "77d36e9b", + "metadata": {}, + "source": [ + "There's no output, which means all tests passed.\n", + "\n", + "Python provides another tool for running automated tests, called `unittest`.\n", + "It is a little more complicated to use, but here's an example." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "d6bc8bae", + "metadata": {}, + "outputs": [], + "source": [ + "from unittest import TestCase\n", + "\n", + "class TestExample(TestCase):\n", + "\n", + " def test_add(self):\n", + " result = add(2, 2)\n", + " self.assertEqual(result, 4)" + ] + }, + { + "cell_type": "markdown", + "id": "59b4212a", + "metadata": {}, + "source": [ + "First we import `TestCase`, which is a class in the `unittest` module.\n", + "To use it, we have to define a new class that inherits from `TestCase` and provides at least one test method.\n", + "The name of the test method must begin with `test` and should indicate which function it tests.\n", + "\n", + "In this example, `test_add` tests the `add` function by calling it, saving the result, and invoking `assertEqual`, which is inherited from `TestCase`.\n", + "`assertEqual` takes two arguments and checks whether they are equal.\n", + "\n", + "In order to run this test method, we have to run a function in `unittest` called `main` and provide several keyword arguments.\n", + "The following function shows the details -- if you are curious, you can ask a virtual assistant to explain how it works." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "96587ab7", + "metadata": {}, + "outputs": [], + "source": [ + "import unittest\n", + "\n", + "def run_unittest():\n", + " unittest.main(argv=[''], verbosity=0, exit=False)" + ] + }, + { + "cell_type": "markdown", + "id": "5409ea0c", + "metadata": {}, + "source": [ + "`run_unittest` does not take `TestExample` as an argument -- instead, it searches for classes that inherit from `TestCase`.\n", + "Then is searches for methods that begin with `test` and runs them.\n", + "This process is called **test discovery**.\n", + "\n", + "Here's what happens when we call `run_unittest`." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "d3db5642", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.000s\n", + "\n", + "OK\n" + ] + } + ], + "source": [ + "run_unittest()" + ] + }, + { + "cell_type": "markdown", + "id": "7775304a", + "metadata": {}, + "source": [ + "`unittest.main` reports the number of tests it ran and the results.\n", + "In this case `OK` indicates that the tests passed.\n", + "\n", + "To see what happens when a test fails, we'll add an incorrect test method to `TestExample`." + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "e5320931", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to TestExample\n", + "\n", + " def test_add_broken(self):\n", + " result = add(2, 2)\n", + " self.assertEqual(result, 100)" + ] + }, + { + "cell_type": "markdown", + "id": "96810614", + "metadata": {}, + "source": [ + "Here's what happens when we run the tests." + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "c8d4fa4a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "======================================================================\n", + "FAIL: test_add_broken (__main__.TestExample)\n", + "----------------------------------------------------------------------\n", + "Traceback (most recent call last):\n", + " File \"/tmp/ipykernel_1109857/3833266738.py\", line 3, in test_add_broken\n", + " self.assertEqual(result, 100)\n", + "AssertionError: 4 != 100\n", + "\n", + "----------------------------------------------------------------------\n", + "Ran 2 tests in 0.000s\n", + "\n", + "FAILED (failures=1)\n" + ] + } + ], + "source": [ + "run_unittest()" + ] + }, + { + "cell_type": "markdown", + "id": "64b743cb", + "metadata": {}, + "source": [ + "The report includes the test method that failed and an error message showing where.\n", + "The summary indicates that two tests ran and one failed.\n", + "\n", + "In the exercises below, I'll suggest some prompts you can use to ask a virtual assistant for more information about `unittest`." + ] + }, + { + "cell_type": "markdown", + "id": "7d0fb256", + "metadata": {}, + "source": [ + "## Glossary\n", + "\n", + "**factory:**\n", + " A function used to create objects, often passed as a parameter to a function.\n", + "\n", + "**conditional expression:**\n", + "An expression that uses a conditional to select one of two values.\n", + "\n", + "**list comprehension:**\n", + "A concise way to loop through a sequence and create a list.\n", + "\n", + "**generator expression:**\n", + "Similar to a list comprehension except that it does not create a list.\n", + "\n", + "**test discovery:**\n", + "A process used to find and run tests." + ] + }, + { + "cell_type": "markdown", + "id": "bc03f15d", + "metadata": {}, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5029c76d", + "metadata": {}, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, + { + "cell_type": "markdown", + "id": "fe10415e", + "metadata": {}, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "There are a few topics in this chapter you might want to learn about.\n", + "Here are some question to ask an AI.\n", + "\n", + "* \"What are the methods and operators of Python's set class?\"\n", + "\n", + "* \"What are the methods and operators of Python's Counter class?\"\n", + "\n", + "* \"What is the difference between a Python list comprehension and a generator expression?\"\n", + "\n", + "* \"When should I use Python's `namedtuple` rather than define a new class?\"\n", + "\n", + "* \"What are some uses of packing and unpacking keyword arguments?\"\n", + "\n", + "* \"How does `unittest` do test discovery?\"\n", + "\n", + "\"Along with `assertequal`, what are the most commonly used methods in `unittest.TestCase`?\"\n", + "\n", + "\"What are the pros and cons of `doctest` and `unittest`?\"\n", + "\n", + "For the following exercises, consider asking an AI for help, but as always, remember to test the results." + ] + }, + { + "cell_type": "markdown", + "id": "c61ecde2", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "One of the exercises in Chapter 7 asks for a function called `uses_none` that takes a word and a string of forbidden letters, and returns `True` if the word does not use any of the letters. Here's a solution." + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "f75b48e0", + "metadata": {}, + "outputs": [], + "source": [ + "def uses_none(word, forbidden):\n", + " for letter in word.lower():\n", + " if letter in forbidden.lower():\n", + " return False\n", + " return True" + ] + }, + { + "cell_type": "markdown", + "id": "b558b8b3", + "metadata": {}, + "source": [ + "Write a version of this function that uses `set` operations instead of a `for` loop.\n", + "Hint: ask an AI \"How do I compute the intersection of Python sets?\"" + ] + }, + { + "cell_type": "markdown", + "id": "e97dfead", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use this outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "2024d4c6", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "def uses_none(word, forbidden):\n", + " \"\"\"Checks whether a word avoid forbidden letters.\n", + " \n", + " >>> uses_none('banana', 'xyz')\n", + " True\n", + " >>> uses_none('apple', 'efg')\n", + " False\n", + " >>> uses_none('', 'abc')\n", + " True\n", + " \"\"\"\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "46a13618", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "160a29b3", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "from doctest import run_docstring_examples\n", + "\n", + "def run_doctests(func):\n", + " run_docstring_examples(func, globals(), name=func.__name__)" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "8db988e6", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "run_doctests(uses_none)" + ] + }, + { + "cell_type": "markdown", + "id": "d2d670cf", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Scrabble is a board game where the objective is to use letter tiles to spell words.\n", + "For example, if we have tiles with the letters `T`, `A`, `B`, `L`, `E`, we can spell `BELT` and `LATE` using a subset of the tiles -- but we can't spell `BEET` because we don't have two `E`s.\n", + "\n", + "Write a function that takes a string of letters and a word, and checks whether the letters can spell the word, taking into account how many times each letter appears." + ] + }, + { + "cell_type": "markdown", + "id": "091919bd", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use the following outline to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "b3c2c475", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "def can_spell(letters, word):\n", + " \"\"\"Check whether the letters can spell the word.\n", + " \n", + " >>> can_spell('table', 'belt')\n", + " True\n", + " >>> can_spell('table', 'late')\n", + " True\n", + " >>> can_spell('table', 'beet')\n", + " False\n", + " \"\"\"\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "a67768c0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "7900d2ed", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "run_doctests(can_spell)" + ] + }, + { + "cell_type": "markdown", + "id": "de2dc099", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "In one of the exercises from [Chapter 17](chapter_inheritance), my solution to `has_straightflush` uses the following method, which partitions a `PokerHand` into a list of four hands, where each hand contains cards of the same suit." + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "661f1635", + "metadata": {}, + "outputs": [], + "source": [ + " def partition(self):\n", + " \"\"\"Make a list of four hands, each containing only one suit.\"\"\"\n", + " hands = []\n", + " for i in range(4):\n", + " hands.append(PokerHand())\n", + " \n", + " for card in self.cards:\n", + " hands[card.suit].add_card(card)\n", + " \n", + " return hands" + ] + }, + { + "cell_type": "markdown", + "id": "cd04a7a3", + "metadata": {}, + "source": [ + "Write a simplified version of this function using a `defaultdict`." + ] + }, + { + "cell_type": "markdown", + "id": "c236c5c7", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "Here's an outline of the `PokerHand` class and the `partition_suits` function you can use to get started." + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "b9cf2e47", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "class PokerHand(Hand):\n", + " \n", + " def partition(self):\n", + " return {}" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "33f15964", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "9b176e67", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "To test your code, we'll make a deck and shuffle it." + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "339f912a", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "cards = Deck.make_cards()\n", + "deck = Deck(cards)\n", + "deck.shuffle()" + ] + }, + { + "cell_type": "markdown", + "id": "d253e3ad", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "Then create a `PokerHand` and add seven cards to it." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "b4efde58", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6 of Diamonds\n", + "8 of Hearts\n", + "4 of Spades\n", + "Queen of Hearts\n", + "Queen of Clubs\n", + "Queen of Spades\n", + "Jack of Hearts\n" + ] + } + ], + "source": [ + "random_hand = PokerHand('random')\n", + "\n", + "for i in range(7):\n", + " card = deck.pop_card()\n", + " random_hand.add_card(card)\n", + " \n", + "print(random_hand)" + ] + }, + { + "cell_type": "markdown", + "id": "a714e145", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "If you invoke `partition` and print the results, each hand should contain cards of one suit only." + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "bfddf015", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6 of Diamonds\n", + "\n", + "8 of Hearts\n", + "Queen of Hearts\n", + "Jack of Hearts\n", + "\n", + "4 of Spades\n", + "Queen of Spades\n", + "\n", + "Queen of Clubs\n", + "\n" + ] + } + ], + "source": [ + "hand_dict = random_hand.partition()\n", + "\n", + "for hand in hand_dict.values():\n", + " print(hand)\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "218798e3", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Here's the function from Chapter 11 that computes Fibonacci numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "id": "f854f6d5", + "metadata": {}, + "outputs": [], + "source": [ + "def fibonacci(n):\n", + " if n == 0:\n", + " return 0\n", + " \n", + " if n == 1:\n", + " return 1\n", + "\n", + " return fibonacci(n-1) + fibonacci(n-2)" + ] + }, + { + "cell_type": "markdown", + "id": "6acab624", + "metadata": {}, + "source": [ + "Write a version of this function with a single return statement that use two conditional expressions, one nested inside the other." + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "4efa9382", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 109, + "id": "6440d14b", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "55" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(10) # should be 55" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "id": "3b9d5bac", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "6765" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fibonacci(20) # should be 6765" + ] + }, + { + "cell_type": "markdown", + "id": "2deb0e1f", + "metadata": {}, + "source": [ + "### Exercise\n", + "The following is a function that computes the binomial coefficient\n", + "recursively." + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "id": "9a9e43fa", + "metadata": {}, + "outputs": [], + "source": [ + "def binomial_coeff(n, k):\n", + " \"\"\"Compute the binomial coefficient \"n choose k\".\n", + "\n", + " n: number of trials\n", + " k: number of successes\n", + "\n", + " returns: int\n", + " \"\"\"\n", + " if k == 0:\n", + " return 1\n", + " \n", + " if n == 0:\n", + " return 0\n", + "\n", + " return binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)" + ] + }, + { + "cell_type": "markdown", + "id": "656c61f6", + "metadata": {}, + "source": [ + "Rewrite the body of the function using nested conditional expressions.\n", + "\n", + "This function is not very efficient because it ends up computing the same values over and over.\n", + "Make it more efficient by memoizing it, as described in [Chapter 10](section_memos)." + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "id": "07226bce", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 113, + "id": "1e3b8009", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "210" + ] + }, + "execution_count": 113, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "binomial_coeff(10, 4) # should be 210" + ] + }, + { + "cell_type": "markdown", + "id": "921719dc", + "metadata": {}, + "source": [ + "### Exercise\n", + "\n", + "Here's the `__str__` method from the `Deck` class in [Chapter 17](section_print_deck)." + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "id": "4fd0793d", + "metadata": {}, + "outputs": [], + "source": [ + "%%add_method_to Deck\n", + "\n", + " def __str__(self):\n", + " res = []\n", + " for card in self.cards:\n", + " res.append(str(card))\n", + " return '\\n'.join(res)" + ] + }, + { + "cell_type": "markdown", + "id": "27f189cf", + "metadata": {}, + "source": [ + "Write a more concise version of this method with a list comprehension or generator expression." + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "id": "2aa7da71", + "metadata": { + "tags": [ + "solution", + "remove-cell" + ] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "25525582", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "source": [ + "You can use this example to test your solution." + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "id": "cd8bb7d2", + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 of Clubs\n", + "3 of Clubs\n", + "4 of Clubs\n", + "5 of Clubs\n", + "6 of Clubs\n", + "7 of Clubs\n", + "8 of Clubs\n", + "9 of Clubs\n", + "10 of Clubs\n", + "Jack of Clubs\n", + "Queen of Clubs\n", + "King of Clubs\n", + "Ace of Clubs\n", + "2 of Diamonds\n", + "3 of Diamonds\n", + "4 of Diamonds\n", + "5 of Diamonds\n", + "6 of Diamonds\n", + "7 of Diamonds\n", + "8 of Diamonds\n", + "9 of Diamonds\n", + "10 of Diamonds\n", + "Jack of Diamonds\n", + "Queen of Diamonds\n", + "King of Diamonds\n", + "Ace of Diamonds\n", + "2 of Hearts\n", + "3 of Hearts\n", + "4 of Hearts\n", + "5 of Hearts\n", + "6 of Hearts\n", + "7 of Hearts\n", + "8 of Hearts\n", + "9 of Hearts\n", + "10 of Hearts\n", + "Jack of Hearts\n", + "Queen of Hearts\n", + "King of Hearts\n", + "Ace of Hearts\n", + "2 of Spades\n", + "3 of Spades\n", + "4 of Spades\n", + "5 of Spades\n", + "6 of Spades\n", + "7 of Spades\n", + "8 of Spades\n", + "9 of Spades\n", + "10 of Spades\n", + "Jack of Spades\n", + "Queen of Spades\n", + "King of Spades\n", + "Ace of Spades\n" + ] + } + ], + "source": [ + "cards = Deck.make_cards()\n", + "deck = Deck(cards)\n", + "print(deck)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16ce7d9f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "celltoolbar": "Tags", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/_sources/index.md b/_sources/index.md index 501db6a..b25249a 100644 --- a/_sources/index.md +++ b/_sources/index.md @@ -78,7 +78,7 @@ Starting in February 2024, I plan to release new chapters here, about one per we * [Click here to run Chapter 13 on Colab](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/chap13.ipynb) -### Chapter 14: Classes and Function +### Chapter 14: Classes and Functions * [Click here to run Chapter 14 on Colab](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/chap14.ipynb) diff --git a/chap00.html b/chap00.html index cf2645c..e0d24b3 100644 --- a/chap00.html +++ b/chap00.html @@ -167,6 +167,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • diff --git a/chap01.html b/chap01.html index 4eb6b8a..8c368cf 100644 --- a/chap01.html +++ b/chap01.html @@ -167,6 +167,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -998,6 +1001,16 @@

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask a virtual assistant#

    As you work through this book, there are several ways you can use a virtual assistant or chatbot to help you learn.

    @@ -1006,6 +1019,7 @@

    Ask a virtual assistant

    If you are having a hard time with any of the exercises, you can ask for help.

    In each chapter, I’ll suggest exercises you can do with a virtual assistant, but I encourage you to try things on your own and see what works for you.

    +

    Here are some topics you could ask a virtual assistant about:

    @@ -464,7 +467,7 @@

    State diagrams
    -_images/41bb7f6224804927e13a6b33f420d665911efd99c70e892e4617de280ff6a39b.png +_images/54319c437cca81caa5fa9ee52a1edfb01810e693b7ebd67c56de713f1d132655.png

    This kind of figure is called a state diagram because it shows what state each of the variables is in (think of it as the variable’s state of mind). @@ -485,7 +488,7 @@

    Variable names -
      Cell In[15], line 1
    +
      Cell In[12], line 1
         million! = 1000000
                ^
     SyntaxError: invalid syntax
    @@ -501,7 +504,7 @@ 

    Variable names -
      Cell In[16], line 1
    +
      Cell In[13], line 1
         76trombones = 'big parade'
          ^
     SyntaxError: invalid decimal literal
    @@ -518,7 +521,7 @@ 

    Variable names -
      Cell In[17], line 1
    +
      Cell In[14], line 1
         class = 'Self-Defence Against Fresh Fruit'
               ^
     SyntaxError: invalid syntax
    @@ -888,7 +891,7 @@ 

    Debugging -
      Cell In[43], line 1
    +
      Cell In[40], line 1
         million! = 1000000
                ^
     SyntaxError: invalid syntax
    @@ -963,6 +966,16 @@ 

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask a virtual assistant#

    Again, I encourage you to use a virtual assistant to learn more about any of the topics in this chapter.

    diff --git a/chap03.html b/chap03.html index bf2f604..755a681 100644 --- a/chap03.html +++ b/chap03.html @@ -167,6 +167,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -339,7 +342,7 @@

    Contents

  • Debugging
  • Glossary
  • Exercises
  • @@ -346,7 +349,7 @@

    Contents

  • Exercise
  • Exercise
  • Exercise
  • -
  • Ask an assistant
  • +
  • Ask a virtual assistant
  • @@ -1172,6 +1175,21 @@

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +
    +
    Exception reporting mode: Verbose
    +
    +
    +
    +

    For the exercises below, there are a few more turtle functions you might want to use.

    • penup lifts the turtle’s imaginary pen so it doesn’t leave a trail when it moves.

    • @@ -3362,8 +3380,8 @@

      Exercise

    -
    -

    Ask an assistant#

    +
    +

    Ask a virtual assistant#

    There are several modules like jupyturtle in Python, and the one we used in this chapter has been customized for this book. So if you ask a virtual assistant for help, it won’t know which module to use. But if you give it a few examples to work with, it can probably figure it out. @@ -3483,7 +3501,7 @@

    Ask an assistantExercise
  • Exercise
  • Exercise
  • -
  • Ask an assistant
  • +
  • Ask a virtual assistant
  • diff --git a/chap05.html b/chap05.html index a43be74..adeeee7 100644 --- a/chap05.html +++ b/chap05.html @@ -167,6 +167,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -342,7 +345,7 @@

    Contents

  • Debugging
  • Glossary
  • Exercises @@ -337,7 +340,7 @@

    Contents

  • Doctest
  • Glossary
  • Exercises @@ -1262,6 +1265,16 @@

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask a virtual assistant#

    In this chapter, I used the words “contrafibularities” and “anaspeptic”, but they are not actually English words. diff --git a/chap10.html b/chap10.html index 6461d67..a142a55 100644 --- a/chap10.html +++ b/chap10.html @@ -167,6 +167,9 @@

  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -1076,6 +1079,16 @@

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask an assistant#

    In this chapter, I said the keys in a dictionary have to be hashable and I gave a short explanation. If you would like more details, ask a virtual assistant, “Why do keys in Python dictionaries have to be hashable?”

    diff --git a/chap11.html b/chap11.html index ba0eba5..2b57aae 100644 --- a/chap11.html +++ b/chap11.html @@ -167,6 +167,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -341,8 +344,8 @@

    Contents

  • Exercises @@ -896,7 +899,7 @@

    Shelve

    Storing data structures#

    In the previous example, the keys and values in the shelf are strings. But we can also use a shelf to contain data structures like lists and dictionaries.

    -

    As an example, let’s revisit the anagram example from an exercise in Chapter 11. +

    As an example, let’s revisit the anagram example from an exercise in Chapter 11. Recall that we made a dictionary that maps from a sorted string of letters to the list of words that can be spelled with those letters. For example, the key 'opst' maps to the list ['opts', 'post', 'pots', 'spot', 'stop', 'tops'].

    @@ -964,7 +967,7 @@

    Shelve

    The key is the same as in the previous example, so we want to append a second word to the same list of strings. -Here’s how we would do it if anagrams were a dictionary.

    +Here’s how we would do it if db were a dictionary.

    db[key].append(word)          # INCORRECT
    @@ -1294,6 +1297,16 @@ 

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask a virtual assistant#

    There are several topics that came up in this chapter that I did not explain in detail. diff --git a/chap14.html b/chap14.html index b3b2534..78a1ce2 100644 --- a/chap14.html +++ b/chap14.html @@ -167,6 +167,9 @@

  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -1106,6 +1109,16 @@

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask a virtual assistant#

    There is a lot of new vocabulary in this chapter. diff --git a/chap15.html b/chap15.html index 81ba679..a1ba842 100644 --- a/chap15.html +++ b/chap15.html @@ -64,6 +64,7 @@ + @@ -166,6 +167,9 @@

  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -902,6 +906,16 @@

    Glossary

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +

    Ask a virtual assistant#

    For more information about static methods, ask a virtual assistant:

    @@ -978,6 +992,15 @@

    ExerciseClasses and Functions

    + +
    +

    next

    +

    Classes and Objects

    +
    + +
    diff --git a/chap16.html b/chap16.html new file mode 100644 index 0000000..ab54603 --- /dev/null +++ b/chap16.html @@ -0,0 +1,1348 @@ + + + + + + + + + + + + Classes and Objects — Think Python, 3rd edition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + + + +
    +
    + + + +
    + + + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    +
    + + + + + + + + +
    + +
    +

    Classes and Objects#

    +

    At this point we have defined classes and created objects that represent the time of day and the day of the year. +And we’ve defined methods that create, modify, and perform computations with these objects.

    +

    In this chapter we’ll continue our tour of object-oriented programming (OOP) by defining classes that represent geometric objects, including points, lines, rectangles, and circles. +We’ll write methods that create and modify these objects, and we’ll use the jupyturtle module to draw them.

    +

    I’ll use these classes to demonstrate OOP topics including object identity and equivalence, shallow and deep copying, and polymorphism.

    +
    +

    Creating a Point#

    +

    In computer graphics a location on the screen is often represented using a pair of coordinates in an x-y plane. +By convention, the point (0, 0) usually represents the upper-left corner of the screen, and (x, y) represents the point x units to the right and y units down from the origin. +Compared to the Cartesian coordinate system you might have seen in a math class, the y axis is upside-down.

    +

    There are several ways we might represent a point in Python:

    +
      +
    • We can store the coordinates separately in two variables, x and y.

    • +
    • We can store the coordinates as elements in a list or tuple.

    • +
    • We can create a new type to represent points as objects.

    • +
    +

    In object-oriented programming, it would be most idiomatic to create a new type. +To do that, we’ll start with a class definition for Point.

    +
    +
    +
    class Point:
    +    """Represents a point in 2-D space."""
    +    
    +    def __init__(self, x, y):
    +        self.x = x
    +        self.y = y
    +        
    +    def __str__(self):
    +        return f'Point({self.x}, {self.y})'
    +
    +
    +
    +
    +

    The __init__ method takes the coordinates as parameters and assigns them to attributes x and y. +The __str__ method returns a string representation of the Point.

    +

    Now we can instantiate and display a Point object like this.

    +
    +
    +
    start = Point(0, 0)
    +print(start)
    +
    +
    +
    +
    +
    Point(0, 0)
    +
    +
    +
    +
    +

    The following diagram shows the state of the new object.

    +
    +
    +_images/6e851969c74483fc4efb36d87b6fcdd9ee1479e2274f2efebc840e7f3520ce6f.png +
    +
    +

    As usual, a programmer-defined type is represented by a box with the name of the type outside and the attributes inside.

    +

    In general, programmer-defined types are mutable, so we can write a method like translate that takes two numbers, dx and dy, and adds them to the attributes x and y.

    +
    +
    +
    %%add_method_to Point
    +
    +    def translate(self, dx, dy):
    +        self.x += dx
    +        self.y += dy
    +
    +
    +
    +
    +

    This function translates the Point from one location in the plane to another. +If we don’t want to modify an existing Point, we can use copy to copy the original object and then modify the copy.

    +
    +
    +
    from copy import copy
    +
    +end1 = copy(start)
    +end1.translate(300, 0)
    +print(end1)
    +
    +
    +
    +
    +
    Point(300, 0)
    +
    +
    +
    +
    +

    We can encapsulate those steps in another method called translated.

    +
    +
    +
    %%add_method_to Point
    +
    +    def translated(self, dx=0, dy=0):
    +        point = copy(self)
    +        point.translate(dx, dy)
    +        return point
    +
    +
    +
    +
    +

    In the same way that the built in function sort modifies a list, and the sorted function creates a new list, now we have a translate method that modifies a Point and a translated method that creates a new one.

    +

    Here’s an example:

    +
    +
    +
    end2 = start.translated(0, 150)
    +print(end2)
    +
    +
    +
    +
    +
    Point(0, 150)
    +
    +
    +
    +
    +

    In the next section, we’ll use these points to define and draw a line.

    +
    +
    +

    Creating a Line#

    +

    Now let’s define a class that represents the line segment between two points. +As usual, we’ll start with an __init__ method and a __str__ method.

    +
    +
    +
    class Line:
    +    def __init__(self, p1, p2):
    +        self.p1 = p1
    +        self.p2 = p2
    +        
    +    def __str__(self):
    +        return f'Line({self.p1}, {self.p2})'
    +
    +
    +
    +
    +

    With those two methods, we can instantiate and display a Line object we’ll use to represent the x axis.

    +
    +
    +
    line1 = Line(start, end1)
    +print(line1)
    +
    +
    +
    +
    +
    Line(Point(0, 0), Point(300, 0))
    +
    +
    +
    +
    +

    When we call print and pass line as a parameter, print invokes __str__ on line. +The __str__ method uses an f-string to create a string representation of the line.

    +

    The f-string contains two expressions in curly braces, self.p1 and self.p2. +When those expressions are evaluated, the results are Point objects. +Then, when they are converted to strings, the __str__ method from the Point class gets invoked.

    +

    That’s why, when we display a Line, the result contains the string representations of the Point objects.

    +

    The following object diagram shows the state of this Line object.

    +
    +
    +_images/4aaaffd556f4fee05dc8c25d40d9a66f559504d4c1b89fbe148c631e206580b0.png +
    +
    +

    String representations and object diagrams are useful for debugging, but the point of this example is to generate graphics, not text! +So we’ll use the jupyturtle module to draw lines on the screen.

    +

    As we did in Chapter 4, we’ll use make_turtle to create a Turtle object and a small canvas where it can draw. +To draw lines, we’ll use two new functions from the jupyturtle module:

    +
      +
    • jumpto, which takes two coordinates and moves the Turtle to the given location without drawing a line, and

    • +
    • moveto, which moves the Turtle from its current location to the given location, and draws a line segment between them.

    • +
    +

    Here’s how we import them.

    +
    +
    +
    from jupyturtle import make_turtle, jumpto, moveto
    +
    +
    +
    +
    +

    And here’s a method that draws a Line.

    +
    +
    +
    %%add_method_to Line
    +
    +    def draw(self):
    +        jumpto(self.p1.x, self.p1.y)
    +        moveto(self.p2.x, self.p2.y)
    +
    +
    +
    +
    +

    To show how it’s used, I’ll create a second line that represents the y axis.

    +
    +
    +
    line2 = Line(start, end2)
    +print(line2)
    +
    +
    +
    +
    +
    Line(Point(0, 0), Point(0, 150))
    +
    +
    +
    +
    +

    And then draw the axes.

    +
    +
    +
    make_turtle()
    +line1.draw()
    +line2.draw()
    +
    +
    +
    +
    +
    + + + + + + + + + + +
    +
    +

    As we define and draw more objects, we’ll use these lines again. +But first let’s talk about object equivalence and identity.

    +
    +
    +

    Equivalence and identity#

    +

    Suppose we create two points with the same coordinates.

    +
    +
    +
    p1 = Point(200, 100)
    +p2 = Point(200, 100)
    +
    +
    +
    +
    +

    If we use the == operator to compare them, we get the default behavior for programmer-defined types – the result is True only if they are the same object, which they are not.

    +
    +
    +
    p1 == p2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    If we want to change that behavior, we can provide a special method called __eq__ that defines what it means for two Point objects to be equal.

    +
    +
    +
    %%add_method_to Point
    +
    +def __eq__(self, other):
    +    return (self.x == other.x) and (self.y == other.y)
    +
    +
    +
    +
    +

    This definition considers two Points to be equal if their attributes are equal. +Now when we use the == operator, it invokes the __eq__ method, which indicates that p1 and p2 are considered equal.

    +
    +
    +
    p1 == p2
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    But the is operator still indicates that they are different objects.

    +
    +
    +
    p1 is p2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    It’s not possible to override the is operator – it always checks whether the objects are identical. +But for programmer-defined types, you can override the == operator so it checks whether the objects are equivalent. +And you can define what equivalent means.

    +
    +
    +

    Creating a Rectangle#

    +

    Now let’s define a class that represents and draws rectangles. +To keep things simple, we’ll assume that the rectangles are either vertical or horizontal, not at an angle. +What attributes do you think we should use to specify the location and size of a rectangle?

    +

    There are at least two possibilities:

    +
      +
    • You could specify the width and height of the rectangle and the location of one corner.

    • +
    • You could specify two opposing corners.

    • +
    +

    At this point it’s hard to say whether either is better than the other, so let’s implement the first one. +Here is the class definition.

    +
    +
    +
    class Rectangle:
    +    """Represents a rectangle. 
    +
    +    attributes: width, height, corner.
    +    """
    +    def __init__(self, width, height, corner):
    +        self.width = width
    +        self.height = height
    +        self.corner = corner
    +        
    +    def __str__(self):
    +        return f'Rectangle({self.width}, {self.height}, {self.corner})'
    +
    +
    +
    +
    +

    As usual, the __init__ method assigns the parameters to attributes and the __str__ returns a string representation of the object. +Now we can instantiate a Rectangle object, using a Point as the location of the upper-left corner.

    +
    +
    +
    corner = Point(30, 20)
    +box1 = Rectangle(100, 50, corner)
    +print(box1)
    +
    +
    +
    +
    +
    Rectangle(100, 50, Point(30, 20))
    +
    +
    +
    +
    +

    The following diagram shows the state of this object.

    +
    +
    +_images/93ab30dffba5edf8630e6bc3afd2c786600c5a1461f0695f96fd869a561a08c7.png +
    +
    +

    To draw a rectangle, we’ll use the following method to make four Point objects to represent the corners.

    +
    +
    +
    %%add_method_to Rectangle
    +
    +    def make_points(self):
    +        p1 = self.corner
    +        p2 = p1.translated(self.width, 0)
    +        p3 = p2.translated(0, self.height)
    +        p4 = p3.translated(-self.width, 0)
    +        return p1, p2, p3, p4
    +
    +
    +
    +
    +

    Then we’ll make four Line objects to represent the sides.

    +
    +
    +
    %%add_method_to Rectangle
    +
    +    def make_lines(self):
    +        p1, p2, p3, p4 = self.make_points()
    +        return Line(p1, p2), Line(p2, p3), Line(p3, p4), Line(p4, p1)
    +
    +
    +
    +
    +

    Then we’ll draw the sides.

    +
    +
    +
    %%add_method_to Rectangle
    +
    +    def draw(self):
    +        lines = self.make_lines()
    +        for line in lines:
    +            line.draw()
    +
    +
    +
    +
    +

    Here’s an example.

    +
    +
    +
    make_turtle()
    +line1.draw()
    +line2.draw()
    +box1.draw()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + +
    +
    +

    The figure includes two lines to represent the axes.

    +
    +
    +

    Changing rectangles#

    +

    Now let’s consider two methods that modify rectangles, grow and translate. +We’ll see that grow works as expected, but translate has a subtle bug. +See if you can figure it out before I explain.

    +

    grow takes two numbers, dwidth and dheight, and adds them to the width and height attributes of the rectangle.

    +
    +
    +
    %%add_method_to Rectangle
    +
    +    def grow(self, dwidth, dheight):
    +        self.width += dwidth
    +        self.height += dheight
    +
    +
    +
    +
    +

    Here’s an example that demonstrates the effect by making a copy of box1 and invoking grow on the copy.

    +
    +
    +
    box2 = copy(box1)
    +box2.grow(60, 40)
    +print(box2)
    +
    +
    +
    +
    +
    Rectangle(160, 90, Point(30, 20))
    +
    +
    +
    +
    +

    If we draw box1 and box2, we can confirm that grow works as expected.

    +
    +
    +
    make_turtle()
    +line1.draw()
    +line2.draw()
    +box1.draw()
    +box2.draw()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    +

    Now let’s see about translate. +It takes two numbers, dx and dy, and moves the rectangle the given distances in the x and y directions.

    +
    +
    +
    %%add_method_to Rectangle
    +
    +    def translate(self, dx, dy):
    +        self.corner.translate(dx, dy)
    +
    +
    +
    +
    +

    To demonstrate the effect, we’ll translate box2 to the right and down.

    +
    +
    +
    box2.translate(30, 20)
    +print(box2)
    +
    +
    +
    +
    +
    Rectangle(160, 90, Point(60, 40))
    +
    +
    +
    +
    +

    Now let’s see what happens if we draw box1 and box2 again.

    +
    +
    +
    make_turtle()
    +line1.draw()
    +line2.draw()
    +box1.draw()
    +box2.draw()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    +

    It looks like both rectangles moved, which is not what we intended! +The next section explains what went wrong.

    +
    +
    +

    Deep copy#

    +

    When we use copy to duplicate box1, it copies the Rectangle object but not the Point object it contains. +So box1 and box2 are different objects, as intended.

    +
    +
    +
    box1 is box2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    But their corner attributes refer to the same object.

    +
    +
    +
    box1.corner is box2.corner
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    The following diagram shows the state of these objects.

    +
    +
    +_images/351c7b94fa9021934acda94ae1dd3d5b3af81e1fc228a8aaee3ea80575486ff0.png +
    +
    +

    What copy does is called a shallow copy because it copies the object but not the objects it contains. +As a result, changing the width or height of one Rectangle does not affect the other, but changing the attributes of the shared Point affects both! +This behavior is confusing and error-prone.

    +

    Fortunately, the copy module provides another function, called deepcopy, that copies not only the object but also the objects it refers to, and the objects they refer to, and so on. +This operation is called a deep copy.

    +

    To demonstrate, let’s start with a new Rectangle that contains a new Point.

    +
    +
    +
    corner = Point(20, 20)
    +box3 = Rectangle(100, 50, corner)
    +print(box3)
    +
    +
    +
    +
    +
    Rectangle(100, 50, Point(20, 20))
    +
    +
    +
    +
    +

    And we’ll make a deep copy.

    +
    +
    +
    from copy import deepcopy
    +
    +box4 = deepcopy(box3)
    +
    +
    +
    +
    +

    We can confirm that the two Rectangle objects refer to different Point objects.

    +
    +
    +
    box3.corner is box4.corner
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    Because box3 and box4 are completely separate objects, we can modify one without affecting the other. +To demonstrate, we’ll move box3 and grow box4.

    +
    +
    +
    box3.translate(50, 30)
    +box4.grow(100, 60)
    +
    +
    +
    +
    +

    And we can confirm that the effect is as expected.

    +
    +
    +
    make_turtle()
    +line1.draw()
    +line2.draw()
    +box3.draw()
    +box4.draw()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +

    Polymorphism#

    +

    In the previous example, we invoked the draw method on two Line objects and two Rectangle objects. +We can do the same thing more concisely by making a list of objects.

    +
    +
    +
    shapes = [line1, line2, box3, box4]
    +
    +
    +
    +
    +

    The elements of this list are different types, but they all provide a draw method, so we can loop through the list and invoke draw on each one.

    +
    +
    +
    make_turtle()
    +
    +for shape in shapes:
    +    shape.draw()
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    +

    The first and second time through the loop, shape refers to a Line object, so when draw is invoked, the method that runs is the one defined in the Line class.

    +

    The third and fourth time through the loop, shape refers to a Rectangle object, so when draw is invoked, the method that runs is the one defined in the Rectangle class.

    +

    In a sense, each object knows how to draw itself. +This feature is called polymorphism. +The word comes from Greek roots that mean “many shaped”. +In object-oriented programming, polymorphism is the ability of different types to provide the same methods, which makes it possible to perform many computations – like drawing shapes – by invoking the same method on different types of objects.

    +

    As an exercise at the end of this chapter, you’ll define a new class that represents a circle and provides a draw method. +Then you can use polymorphism to draw lines, rectangles, and circles.

    +
    +
    +

    Debugging#

    +

    In this chapter, we ran into a subtle bug that happened because we created a Point that was shared by two Rectangle objects, and then we modified the Point. +In general, there are two ways to avoid problems like this: you can avoid sharing objects or you can avoid modifying them.

    +

    To avoid sharing objects, you can use deep copy, as we did in this chapter.

    +

    To avoid modifying objects, consider replacing modifiers like translate with pure functions like translated. +For example, here’s a version of translated that creates a new Point and never modifies its attributes.

    +
    +
    +
        def translated(self, dx=0, dy=0):
    +        x = self.x + dx
    +        y = self.y + dy
    +        return Point(x, y)
    +
    +
    +
    +
    +

    Python provides features that make it easier to avoid modifying objects. +They are beyond the scope of this book, but if you are curious, ask a virtual assistant, “How do I make a Python object immutable?”

    +

    Creating a new object takes more time than modifying an existing one, but the difference seldom matters in practice. +Programs that avoid shared objects and modifiers are often easier to develop, test, and debug – and the best kind of debugging is the kind you don’t have to do.

    +
    +
    +

    Glossary#

    +

    identical: +Being the same object (which implies equivalence).

    +

    equivalent: +Having the same value.

    +

    shallow copy: +A copy operation that does not copy nested objects.

    +

    deep copy: +A copy operation that also copies nested objects.

    +

    polymorphism: +The ability of a method or operator to work with multiple types of objects.

    +
    +
    +

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +
    +
    +

    Ask a virtual assistant#

    +

    For all of the following exercises, consider asking a virtual assistant for help. +If you do, you’ll want include as part of the prompt the class definitions for Point, Line, and Rectangle – otherwise the VA will make a guess about their attributes and functions, and the code it generates won’t work.

    +
    +
    +

    Exercise#

    +

    Write an __eq__ method for the Line class that returns True if the Line objects refer to Point objects that are equivalent, in either order.

    +
    +
    +

    Exercise#

    +

    Write a Line method called midpoint that computes the midpoint of a line segment and returns the result as a Point object.

    +
    +
    +

    Exercise#

    +

    Write a Rectangle method called midpoint that find the point in the center of a rectangle and returns the result as a Point object.

    +
    +
    +

    Exercise#

    +

    Write a Rectangle method called make_cross that:

    +
      +
    1. Uses make_lines to get a list of Line objects that represent the four sides of the rectangle.

    2. +
    3. Computes the midpoints of the four lines.

    4. +
    5. Makes and returns a list of two Line objects that represent lines connecting opposite midpoints, forming a cross through the middle of the rectangle.

    6. +
    +
    +
    +

    Exercise#

    +

    Write a definition for a class named Circle with attributes center and radius, where center is a Point object and radius is a number. +Include special methods __init__ and a __str__, and a method called draw that uses jupyturtle functions to draw the circle.

    +
    +
    +
    + + + + +
    + + + + + + +
    + + + + + + +
    +
    + + +
    + + +
    +
    +
    + + + + + +
    +
    + + \ No newline at end of file diff --git a/chap17.html b/chap17.html new file mode 100644 index 0000000..d075f98 --- /dev/null +++ b/chap17.html @@ -0,0 +1,1727 @@ + + + + + + + + + + + + Inheritance — Think Python, 3rd edition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + + + +
    +
    + + + +
    + + + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    +
    + + + + + + + + +
    + +
    +

    Inheritance#

    +

    The language feature most often associated with object-oriented programming is inheritance. +Inheritance is the ability to define a new class that is a modified version of an existing class. +In this chapter I demonstrate inheritance using classes that represent playing cards, decks of cards, and poker hands. +If you don’t play poker, don’t worry – I’ll tell you what you need to know.

    +
    +

    Representing cards#

    +

    There are 52 playing cards in a standard deck – each of them belongs to one of four suits and one of thirteen ranks. +The suits are Spades, Hearts, Diamonds, and Clubs. +The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King. +Depending on which game you are playing, an Ace can be higher than King or lower than 2.

    +

    If we want to define a new object to represent a playing card, it is obvious what the attributes should be: rank and suit. +It is less obvious what type the attributes should be. +One possibility is to use strings containing words like 'Spade' for suits and 'Queen' for ranks. +A problem with this implementation is that it would not be easy to compare cards to see which had a higher rank or suit.

    +

    An alternative is to use integers to encode the ranks and suits. +In this context, “encode” means that we are going to define a mapping between numbers and suits, or between numbers and ranks. +This kind of encoding is not meant to be a secret (that would be “encryption”).

    +

    For example, this table shows the suits and the corresponding integer codes:

    + + + + + + + + + + + + + + + + + + + + +

    Suit

    Code

    Spades

    3

    Hearts

    2

    Diamonds

    1

    Clubs

    0

    +

    With this encoding, we can compare suits by comparing their codes.

    +

    To encode the ranks, we’ll use the integer 2 to represent the rank 2, 3 to represent 3, and so on up to 10. +The following table shows the codes for the face cards.

    + + + + + + + + + + + + + + + + + +

    Rank

    Code

    Jack

    11

    Queen

    12

    King

    13

    +

    And we can use either 1 or 14 to represent an Ace, depending on whether we want it to be considered lower or higher than the other ranks.

    +

    To represent these encodings, we will use two lists of strings, one with the names of the suits and the other with the names of the ranks.

    +

    Here’s a definition for a class that represents a playing card, with these lists of strings as class variables, which are variables defined inside a class definition, but not inside a method.

    +
    +
    +
    class Card:
    +    """Represents a standard playing card."""
    +
    +    suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
    +    rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', 
    +                  '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']
    +
    +
    +
    +
    +

    The first element of rank_names is None because there is no card with rank zero. By including None as a place-keeper, we get a list with the nice property that the index 2 maps to the string '2', and so on.

    +

    Class variables are associated with the class, rather than an instance of the class, so we can access them like this.

    +
    +
    +
    Card.suit_names
    +
    +
    +
    +
    +
    ['Clubs', 'Diamonds', 'Hearts', 'Spades']
    +
    +
    +
    +
    +

    We can use suit_names to look up a suit and get the corresponding string.

    +
    +
    +
    Card.suit_names[0]
    +
    +
    +
    +
    +
    'Clubs'
    +
    +
    +
    +
    +

    And rank_names to look up a rank.

    +
    +
    +
    Card.rank_names[11]
    +
    +
    +
    +
    +
    'Jack'
    +
    +
    +
    +
    +
    +
    +

    Card attributes#

    +

    Here’s an __init__ method for the Card class – it takes suit and rank as parameters and assigns them to attributes with the same names.

    +
    +
    +
    %%add_method_to Card
    +
    +    def __init__(self, suit, rank):
    +        self.suit = suit
    +        self.rank = rank
    +
    +
    +
    +
    +

    Now we can create a Card object like this.

    +
    +
    +
    queen = Card(1, 12)
    +
    +
    +
    +
    +

    We can use the new instance to access the attributes.

    +
    +
    +
    queen.suit, queen.rank
    +
    +
    +
    +
    +
    (1, 12)
    +
    +
    +
    +
    +

    It is also legal to use the instance to access the class variables.

    +
    +
    +
    queen.suit_names
    +
    +
    +
    +
    +
    ['Clubs', 'Diamonds', 'Hearts', 'Spades']
    +
    +
    +
    +
    +

    But if you use the class, it is clearer that they are class variables, not attributes.

    +
    +
    +

    Printing cards#

    +

    Here’s a __str__ method for Card objects.

    +
    +
    +
    %%add_method_to Card
    +
    +    def __str__(self):
    +        rank_name = Card.rank_names[self.rank]
    +        suit_name = Card.suit_names[self.suit]
    +        return f'{rank_name} of {suit_name}' 
    +
    +
    +
    +
    +

    When we print a Card, Python calls the __str__ method to get a human-readable representation of the card.

    +
    +
    +
    print(queen)
    +
    +
    +
    +
    +
    Queen of Diamonds
    +
    +
    +
    +
    +

    The following is a diagram of the Card class object and the Card instance. +Card is a class object, so its type is type. +queen is an instance of Card, so its type is Card. +To save space, I didn’t draw the contents of suit_names and rank_names.

    +
    +
    +_images/5f1d5a265aab792dbe104aaedafa1a65dded15806c5dff8a8854f2f3896703eb.png +
    +
    +

    Every Card instance has its own suit and rank attributes, but there is only one Card class object, and only one copy of the class variables suit_names and rank_names.

    +
    +
    +

    Comparing cards#

    +

    Suppose we create a second Card object with the same suit and rank.

    +
    +
    +
    queen2 = Card(1, 12)
    +print(queen2)
    +
    +
    +
    +
    +
    Queen of Diamonds
    +
    +
    +
    +
    +

    If we use the == operator to compare them, it checks whether queen and queen2 refer to the same object.

    +
    +
    +
    queen == queen2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    They don’t, so it returns false. +We can change this behavior by defining a special method called __eq__.

    +
    +
    +
    %%add_method_to Card
    +
    +    def __eq__(self, other):
    +        return self.suit == other.suit and self.rank == other.rank
    +
    +
    +
    +
    +

    __eq__ takes two Card objects as parameters and returns True if they have the same suit and rank, even if they are not the same object. +In other words, it checks whether they are equivalent, even if they are not identical.

    +

    When we use the == operator with Card objects, Python calls the __eq__ method.

    +
    +
    +
    queen == queen2
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    As a second test, let’s create a card with the same suit and a different rank.

    +
    +
    +
    six = Card(1, 6)
    +print(six)
    +
    +
    +
    +
    +
    6 of Diamonds
    +
    +
    +
    +
    +

    We can confirm that queen and six are not equivalent.

    +
    +
    +
    queen == six
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    If we use the != operator, Python invokes a special method called __ne__, if it exists. +Otherwise it invokes__eq__ and inverts the result – so if __eq__ returns True, the result of the != operator is False.

    +
    +
    +
    queen != queen2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +
    +
    +
    queen != six
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    Now suppose we want to compare two cards to see which is bigger. +If we use one of the relational operators, we get a TypeError.

    +
    +
    +
    queen < queen2
    +
    +
    +
    +
    +
    TypeError: '<' not supported between instances of 'Card' and 'Card'
    +
    +
    +
    +
    +

    To change the behavior of the < operator, we can define a special method called __lt__, which is short for “less than”. +For the sake of this example, let’s assume that suit is more important than rank – so all Spades outrank all Hearts, which outrank all Diamonds, and so on. +If two cards have the same suit, the one with the higher rank wins.

    +

    To implement this logic, we’ll use the following method, which returns a tuple containing a card’s suit and rank, in that order.

    +
    +
    +
    %%add_method_to Card
    +
    +    def to_tuple(self):
    +        return (self.suit, self.rank)
    +
    +
    +
    +
    +

    We can use this method to write __lt__.

    +
    +
    +
    %%add_method_to Card
    +
    +    def __lt__(self, other):
    +        return self.to_tuple() < other.to_tuple()
    +
    +
    +
    +
    +

    Tuple comparison compares the first elements from each tuple, which represent the suits. +If they are the same, it compares the second elements, which represent the ranks.

    +

    Now if we use the < operator, it invokes the __lt__ operator.

    +
    +
    +
    six < queen
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    If we use the > operator, it invokes a special method called __gt__, if it exists. +Otherwise it invokes __lt__ with the arguments in the opposite order.

    +
    +
    +
    queen < queen2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +
    +
    +
    queen > queen2
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    Finally, if we use the <= operator, it invokes a special method called __le__.

    +
    +
    +
    %%add_method_to Card
    +
    +    def __le__(self, other):
    +        return self.to_tuple() <= other.to_tuple()
    +
    +
    +
    +
    +

    So we can check whether one card is less than or equal to another.

    +
    +
    +
    queen <= queen2
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +
    +
    +
    queen <= six
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +

    If we use the >= operator, it uses __ge__ if it exists. Otherwise, it invokes __le__ with the arguments in the opposite order.

    +
    +
    +
    queen >= six
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    As we have defined them, these methods are complete in the sense that we can compare any two Card objects, and consistent in the sense that results from different operators don’t contradict each other. +With these two properties, we can say that Card objects are totally ordered. +And that means, as we’ll see soon, that they can be sorted.

    +
    +
    +

    Decks#

    +

    Now that we have objects that represent cards, let’s define objects that represent decks. +The following is a class definition for Deck with +an __init__ method takes a list of Card objects as a parameter and assigns it to an attribute called cards.

    +
    +
    +
    class Deck:
    +
    +    def __init__(self, cards):
    +        self.cards = cards
    +
    +
    +
    +
    +

    To create a list that contains the 52 cards in a standard deck, we’ll use the following static method.

    +
    +
    +
    %%add_method_to Deck
    +
    +    def make_cards():
    +        cards = []
    +        for suit in range(4):
    +            for rank in range(2, 15):
    +                card = Card(suit, rank)
    +                cards.append(card)
    +        return cards
    +
    +
    +
    +
    +

    In make_cards, the outer loop enumerates the suits from 0 to 3. +The inner loop enumerates the ranks from 2 to 14 – where 14 represents an Ace that outranks a King. +Each iteration creates a new Card with the current suit and rank, and appends it to cards.

    +

    Here’s how we make a list of cards and a Deck object that contains it.

    +
    +
    +
    cards = Deck.make_cards()
    +deck = Deck(cards)
    +len(deck.cards)
    +
    +
    +
    +
    +
    52
    +
    +
    +
    +
    +

    It contains 52 cards, as intended.

    +
    +
    +

    Printing the deck#

    +

    Here is a __str__ method for Deck.

    +
    +
    +
    %%add_method_to Deck
    +
    +    def __str__(self):
    +        res = []
    +        for card in self.cards:
    +            res.append(str(card))
    +        return '\n'.join(res)
    +
    +
    +
    +
    +

    This method demonstrates an efficient way to accumulate a large string – building a list of strings and then using the string method join.

    +

    We’ll test this method with a deck that only contains two cards.

    +
    +
    +
    small_deck = Deck([queen, six])
    +
    +
    +
    +
    +

    If we call str, it invokes __str__.

    +
    +
    +
    str(small_deck)
    +
    +
    +
    +
    +
    'Queen of Diamonds\n6 of Diamonds'
    +
    +
    +
    +
    +

    When Jupyter displays a string, it shows the “representational” form of the string, which represents a newline with the sequence \n.

    +

    However, if we print the result, Jupyter shows the “printable” form of the string, which prints the newline as whitespace.

    +
    +
    +
    print(small_deck)
    +
    +
    +
    +
    +
    Queen of Diamonds
    +6 of Diamonds
    +
    +
    +
    +
    +

    So the cards appear on separate lines.

    +
    +
    +

    Add, remove, shuffle and sort#

    +

    To deal cards, we would like a method that removes a card from the deck +and returns it. The list method pop provides a convenient way to do +that.

    +
    +
    +
    %%add_method_to Deck
    +
    +    def take_card(self):
    +        return self.cards.pop()
    +
    +
    +
    +
    +

    Here’s how we use it.

    +
    +
    +
    card = deck.take_card()
    +print(card)
    +
    +
    +
    +
    +
    Ace of Spades
    +
    +
    +
    +
    +

    We can confirm that there are 51 cards left in the deck.

    +
    +
    +
    len(deck.cards)
    +
    +
    +
    +
    +
    51
    +
    +
    +
    +
    +

    To add a card, we can use the list method append.

    +
    +
    +
    %%add_method_to Deck
    +
    +    def put_card(self, card):
    +        self.cards.append(card)
    +
    +
    +
    +
    +

    As an example, we can put back the card we just popped.

    +
    +
    +
    deck.put_card(card)
    +len(deck.cards)
    +
    +
    +
    +
    +
    52
    +
    +
    +
    +
    +

    To shuffle the deck, we can use the shuffle function from the random module:

    +
    +
    +
    import random
    +
    +
    +
    +
    +
    +
    +
    %%add_method_to Deck
    +            
    +    def shuffle(self):
    +        random.shuffle(self.cards)
    +
    +
    +
    +
    +

    If we shuffle the deck and print the first few cards, we can see that they are in no apparent order.

    +
    +
    +
    deck.shuffle()
    +for card in deck.cards[:4]:
    +    print(card)
    +
    +
    +
    +
    +
    2 of Diamonds
    +4 of Hearts
    +5 of Clubs
    +8 of Diamonds
    +
    +
    +
    +
    +

    To sort the cards, we can use the list method sort, which sorts the elements “in place” – that is, it modifies the list rather than creating a new list.

    +
    +
    +
    %%add_method_to Deck
    +            
    +    def sort(self):
    +        self.cards.sort()
    +
    +
    +
    +
    +

    When we invoke sort, it uses the __lt__ method to compare cards.

    +
    +
    +
    deck.sort()
    +
    +
    +
    +
    +

    If we print the first few cards, we can confirm that they are in increasing order.

    +
    +
    +
    for card in deck.cards[:4]:
    +    print(card)
    +
    +
    +
    +
    +
    2 of Clubs
    +3 of Clubs
    +4 of Clubs
    +5 of Clubs
    +
    +
    +
    +
    +

    In this example, Deck.sort doesn’t do anything other than invoke list.sort. +Passing along responsibility like this is called delegation.

    +
    +
    +

    Parents and children#

    +

    Inheritance is the ability to define a new class that is a modified version of an existing class. +As an example, let’s say we want a class to represent a “hand”, that is, the cards held by one player.

    +
      +
    • A hand is similar to a deck – both are made up of a collection of cards, and both require operations like adding and removing cards.

    • +
    • A hand is also different from a deck – there are operations we want for hands that don’t make sense for a deck. For example, in poker we might compare two hands to see which one wins. In bridge, we might compute a score for a hand in order to make a bid.

    • +
    +

    This relationship between classes – where one is a specialized version of another – lends itself to inheritance.

    +

    To define a new class that is based on an existing class, we put the name of the existing class in parentheses.

    +
    +
    +
    class Hand(Deck):
    +    """Represents a hand of playing cards."""
    +
    +
    +
    +
    +

    This definition indicates that Hand inherits from Deck, which means that Hand objects can access methods defined in Deck, like take_card and put_card.

    +

    Hand also inherits __init__ from Deck, but if we define __init__ in the Hand class, it overrides the one in the Deck class.

    +
    +
    +
    %%add_method_to Hand
    +
    +    def __init__(self, label=''):
    +        self.label = label
    +        self.cards = []
    +
    +
    +
    +
    +

    This version of __init__ takes an optional string as a parameter, and always starts with an empty list of cards. +When we create a Hand, Python invokes this method, not the one in Deck – which we can confirm by checking that the result has a label attribute.

    +
    +
    +
    hand = Hand('player 1')
    +hand.label
    +
    +
    +
    +
    +
    'player 1'
    +
    +
    +
    +
    +

    To deal a card, we can use take_card to remove a card from a Deck, and put_card to add the card to a Hand.

    +
    +
    +
    deck = Deck(cards)
    +card = deck.take_card()
    +hand.put_card(card)
    +print(hand)
    +
    +
    +
    +
    +
    Ace of Spades
    +
    +
    +
    +
    +

    Let’s encapsulate this code in a Deck method called move_cards.

    +
    +
    +
    %%add_method_to Deck
    +
    +    def move_cards(self, other, num):
    +        for i in range(num):
    +            card = self.take_card()
    +            other.put_card(card)
    +
    +
    +
    +
    +

    This method is polymorphic – that is, it works with more than one type: self and other can be either a Hand or a Deck. +So we can use this method to deal a card from Deck to a Hand, from one Hand to another, or from a Hand back to a Deck.

    +

    When a new class inherits from an existing one, the existing one is called the parent and the new class is called the child. In general:

    +
      +
    • Instances of the child class should have all of the attributes of the parent class, but they can have additional attributes.

    • +
    • The child class should have all of the methods of the parent class, but it can have additional methods.

    • +
    • If a child class overrides a method from the parent class, the new method should take the same parameters and return a compatible result.

    • +
    +

    This set of rules is called the “Liskov substitution principle” after computer scientist Barbara Liskov.

    +

    If you follow these rules, any function or method designed to work with an instance of a parent class, like a Deck, will also work with instances of a child class, like Hand. +If you violate these rules, your code will collapse like a house of cards (sorry).

    +
    +
    +

    Specialization#

    +

    Let’s make a class called BridgeHand that represents a hand in bridge – a widely played card game. +We’ll inherit from Hand and add a new method called high_card_point_count that evaluates a hand using a “high card point” method, which adds up points for the high cards in the hand.

    +

    Here’s a class definition that contains as a class variable a dictionary that maps from card names to their point values.

    +
    +
    +
    class BridgeHand(Hand):
    +    """Represents a bridge hand."""
    +
    +    hcp_dict = {
    +        'Ace': 4,
    +        'King': 3,
    +        'Queen': 2,
    +        'Jack': 1,
    +    }
    +
    +
    +
    +
    +

    Given the rank of a card, like 12, we can use Card.rank_names to get the string representation of the rank, and then use hcp_dict to get its score.

    +
    +
    +
    rank = 12
    +rank_name = Card.rank_names[rank]
    +score = BridgeHand.hcp_dict.get(rank_name, 0)
    +rank_name, score
    +
    +
    +
    +
    +
    ('Queen', 2)
    +
    +
    +
    +
    +

    The following method loops through the cards in a BridgeHand and adds up their scores.

    +
    +
    +
    %%add_method_to BridgeHand
    +
    +    def high_card_point_count(self):
    +        count = 0
    +        for card in self.cards:
    +            rank_name = Card.rank_names[card.rank]
    +            count += BridgeHand.hcp_dict.get(rank_name, 0)
    +        return count
    +
    +
    +
    +
    +

    To test it, we’ll deal a hand with five cards – a bridge hand usually has thirteen, but it’s easier to test code with small examples.

    +
    +
    +
    hand = BridgeHand('player 2')
    +
    +deck.shuffle()
    +deck.move_cards(hand, 5)
    +print(hand)
    +
    +
    +
    +
    +
    4 of Diamonds
    +King of Hearts
    +10 of Hearts
    +10 of Clubs
    +Queen of Diamonds
    +
    +
    +
    +
    +

    And here is the total score for the King and Queen.

    +
    +
    +
    hand.high_card_point_count()
    +
    +
    +
    +
    +
    5
    +
    +
    +
    +
    +

    BridgeHand inherits the variables and methods of Hand and adds a class variable and a method that are specific to bridge. +This way of using inheritance is called specialization because it defines a new class that is specialized for a particular use, like playing bridge.

    +
    +
    +

    Debugging#

    +

    Inheritance is a useful feature. +Some programs that would be repetitive without inheritance can be written more concisely with it. +Also, inheritance can facilitate code reuse, since you can customize the behavior of a parent class without having to modify it. +In some cases, the inheritance structure reflects the natural structure of the problem, which makes the design easier to understand.

    +

    On the other hand, inheritance can make programs difficult to read. +When a method is invoked, it is sometimes not clear where to find its definition – the relevant code may be spread across several modules.

    +

    Any time you are unsure about the flow of execution through your program, the simplest solution is to add print statements at the beginning of the relevant methods. +If Deck.shuffle prints a message that says something like Running Deck.shuffle, then as the program runs it traces the flow of execution.

    +

    As an alternative, you could use the following function, which takes an object and a method name (as a string) and returns the class that provides the definition of the method.

    +
    +
    +
    def find_defining_class(obj, method_name):
    +    """
    +    """
    +    for typ in type(obj).mro():
    +        if method_name in vars(typ):
    +            return typ
    +    return f'Method {method_name} not found.'
    +
    +
    +
    +
    +

    find_defining_class uses the mro method to get the list of class objects (types) that will be searched for methods. +“MRO” stands for “method resolution order”, which is the sequence of classes Python searches to “resolve” a method name – that is, to find the function object the name refers to.

    +

    As an example, let’s instantiate a BridgeHand and then find the defining class of shuffle.

    +
    +
    +
    hand = BridgeHand('player 3')
    +find_defining_class(hand, 'shuffle')
    +
    +
    +
    +
    +
    __main__.Deck
    +
    +
    +
    +
    +

    The shuffle method for the BridgeHand object is the one in Deck.

    +
    +
    +

    Glossary#

    +

    inheritance: +The ability to define a new class that is a modified version of a previously defined class.

    +

    encode: +To represent one set of values using another set of values by constructing a mapping between them.

    +

    class variable: +A variable defined inside a class definition, but not inside any method.

    +

    totally ordered: +A set of objects is totally ordered if we can compare any two elements and the results are consistent.

    +

    delegation: +When one method passes responsibility to another method to do most or all of the work.

    +

    parent class: +A class that is inherited from.

    +

    child class: +A class that inherits from another class.

    +

    specialization: +A way of using inheritance to create a new class that is a specialized version of an existing class.

    +
    +
    +

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +
    +
    +

    Ask a Virtual Assistant#

    +

    When it goes well, object-oriented programming can make programs more readable, testable, and reusable. +But it can also make programs complicated and hard to maintain. +As a result, OOP is a topic of controversy – some people love it, and some people don’t.

    +

    To learn more about the topic, ask a virtual assistant:

    +
      +
    • What are some pros and cons of object-oriented programming?

    • +
    • What does it mean when people say “favor composition over inheritance”?

    • +
    • What is the Liskov substitution principle?

    • +
    • Is Python an object-oriented language?

    • +
    • What are the requirements for a set to be totally ordered?

    • +
    +

    And as always, consider using a virtual assistant to help with the following exercises.

    +
    +
    +

    Exercise#

    +

    In contract bridge, a “trick” is a round of play in which each of four players plays one card. +To represent those cards, we’ll define a class that inherits from Deck.

    +
    +
    +
    class Trick(Deck):
    +    """Represents a trick in contract bridge."""
    +
    +
    +
    +
    +

    As an example, consider this trick, where the first player leads with the 3 of Diamonds, which means that Diamonds are the “led suit”. +The second and third players “follow suit”, which means they play a card with the led suit. +The fourth player plays a card of a different suit, which means they cannot win the trick. +So the winner of this trick is the third player, because they played the highest card in the led suit.

    +
    +
    +
    cards = [Card(1, 3),
    +         Card(1, 10),
    +         Card(1, 12),
    +         Card(2, 13)]
    +trick = Trick(cards)
    +print(trick)
    +
    +
    +
    +
    +
    3 of Diamonds
    +10 of Diamonds
    +Queen of Diamonds
    +King of Hearts
    +
    +
    +
    +
    +

    Write a Trick method called find_winner that loops through the cards in the Trick and returns the index of the card that wins. +In the previous example, the index of the winning card is 2.

    +
    +
    +

    Exercise#

    +

    The next few exercises ask to you write functions that classify poker hands. +If you are not familiar with poker, I’ll explain what you need to know. +We’ll use the following class to represent poker hands.

    +
    +
    +
    class PokerHand(Hand):
    +    """Represents a poker hand."""
    +
    +    def get_suit_counts(self):
    +        counter = {}
    +        for card in self.cards:
    +            key = card.suit
    +            counter[key] = counter.get(key, 0) + 1
    +        return counter
    +    
    +    def get_rank_counts(self):
    +        counter = {}
    +        for card in self.cards:
    +            key = card.rank
    +            counter[key] = counter.get(key, 0) + 1
    +        return counter    
    +
    +
    +
    +
    +

    PokerHand provides two methods that will help with the exercises.

    +
      +
    • get_suit_counts loops through the cards in the PokerHand, counts the number of cards in each suit, and returns a dictionary that maps from each suit code to the number of times it appears.

    • +
    • get_rank_counts does the same thing with the ranks of the cards, returning a dictionary that maps from each rank code to the number of times it appears.

    • +
    +

    All of the exercises that follow can be done using only the Python features we have learned so far, but some of them are more difficult than most of the previous exercises. +I encourage you to ask an AI for help.

    +

    For problems like this, it often works well to ask for general advice about strategies and algorithms. +Then you can either write the code yourself or ask for code. +If you ask for code, you might want to provide the relevant class definitions as part of the prompt.

    +

    As a first exercise, we’ll write a method called has_flush that checks whether a hand has a “flush” – that is, whether it contains at least five cards of the same suit.

    +

    In most varieties of poker, a hand contains either five or seven cards, but there are some exotic variations where a hand contains other numbers of cards. +But regardless of how many cards there are in a hand, the only ones that count are the five that make the best hand.

    +
    +
    +

    Exercise#

    +

    Write a method called has_straight that checks whether a hand contains a straight, which is a set of five cards with consecutive ranks. +For example, if a hand contains ranks 5, 6, 7, 8, and 9, it contains a straight.

    +

    An Ace can come before a two or after a King, so Ace, 2, 3, 4, 5 is a straight and so it 10, Jack, Queen, King, Ace. +But a straight cannot “wrap around”, so King, Ace, 2, 3, 4 is not a straight.

    +
    +
    +

    Exercise#

    +

    A hand has a straight flush if it contains a set of five cards that are both a straight and a flush – that is, five cards of the same suit with consecutive ranks. +Write a PokerHand method that checks whether a hand has a straight flush.

    +
    +
    +

    Exercise#

    +

    A poker hand has a pair if it contains two or more cards with the same rank. +Write a PokerHand method that checks whether a hand contains a pair.

    +

    You can use the following outline to get started.

    +

    To test your method, here’s a hand that has a pair.

    +
    +
    +
    pair = deepcopy(bad_hand)
    +pair.put_card(Card(1, 2))
    +print(pair)
    +
    +
    +
    +
    +
    2 of Clubs
    +3 of Clubs
    +4 of Hearts
    +5 of Spades
    +7 of Clubs
    +2 of Diamonds
    +
    +
    +
    +
    +
    +
    +
    pair.has_pair()    # should return True
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +
    +
    +
    bad_hand.has_pair()    # should return False
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +
    +
    +
    good_hand.has_pair()   # should return False
    +
    +
    +
    +
    +
    False
    +
    +
    +
    +
    +
    +
    +

    Exercise#

    +

    A hand has a full house if it contains three cards of one rank and two cards of another rank. +Write a PokerHand method that checks whether a hand has a full house.

    +
    +
    +

    Exercise#

    +

    This exercise is a cautionary tale about a common error that can be difficult to debug. +Consider the following class definition.

    +
    +
    +
    class Kangaroo:
    +    """A Kangaroo is a marsupial."""
    +    
    +    def __init__(self, name, contents=[]):
    +        """Initialize the pouch contents.
    +
    +        name: string
    +        contents: initial pouch contents.
    +        """
    +        self.name = name
    +        self.contents = contents
    +
    +    def __str__(self):
    +        """Return a string representaion of this Kangaroo.
    +        """
    +        t = [ self.name + ' has pouch contents:' ]
    +        for obj in self.contents:
    +            s = '    ' + object.__str__(obj)
    +            t.append(s)
    +        return '\n'.join(t)
    +
    +    def put_in_pouch(self, item):
    +        """Adds a new item to the pouch contents.
    +
    +        item: object to be added
    +        """
    +        self.contents.append(item)
    +
    +
    +
    +
    +

    __init__ takes two parameters: name is required, but contents is optional – if it’s not provided, the default value is an empty list.

    +

    __str__ returns a string representation of the object that includes the name and the contents of the pouch.

    +

    put_in_pouch takes any object and appends it to contents.

    +

    Now let’s see how this class works. +We’ll create two Kangaroo objects with the names Kanga and Roo.

    +
    +
    +
    kanga = Kangaroo('Kanga')
    +roo = Kangaroo('Roo')
    +
    +
    +
    +
    +

    To Kanga’s pouch we’ll add two strings and Roo.

    +
    +
    +
    kanga.put_in_pouch('wallet')
    +kanga.put_in_pouch('car keys')
    +kanga.put_in_pouch(roo)
    +
    +
    +
    +
    +

    If we print kanga, it seems like everything worked.

    +
    +
    +
    print(kanga)
    +
    +
    +
    +
    +
    Kanga has pouch contents:
    +    'wallet'
    +    'car keys'
    +    <__main__.Kangaroo object at 0x7f6e2ea11900>
    +
    +
    +
    +
    +

    But what happens if we print roo?

    +
    +
    +
    print(roo)
    +
    +
    +
    +
    +
    Roo has pouch contents:
    +    'wallet'
    +    'car keys'
    +    <__main__.Kangaroo object at 0x7f6e2ea11900>
    +
    +
    +
    +
    +

    Roo’s pouch contains the same contents as Kanga’s, including a reference to roo!

    +

    See if you can figure out what went wrong. +Then ask a virtual assistant, “What’s wrong with the following program?” and paste in the definition of Kangaroo.

    +
    +
    +
    + + + + +
    + + + + + + +
    + + + + + + +
    +
    + + +
    + + +
    +
    +
    + + + + + +
    +
    + + \ No newline at end of file diff --git a/chap18.html b/chap18.html new file mode 100644 index 0000000..5a27e2d --- /dev/null +++ b/chap18.html @@ -0,0 +1,1724 @@ + + + + + + + + + + + + Python Extras — Think Python, 3rd edition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + + + +
    +
    + + + +
    + + + +
    + +
    +
    + +
    +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    +
    + + + + + + + + +
    + +
    +

    Python Extras#

    +

    One of my goals for this book has been to teach you as little Python as possible. +When there were two ways to do something, I picked one and avoided mentioning the other. +Or sometimes I put the second one into an exercise.

    +

    Now I want to go back for some of the good bits that got left behind. +Python provides a number of features that are not really necessary – you can write good code without them – but with them you can write code that’s more concise, readable, or efficient, and sometimes all three.

    +
    +

    Sets#

    +

    Python provides a class called set that represents a collection of unique elements. +To create an empty set, we can use the class object like a function.

    +
    +
    +
    s1 = set()
    +s1
    +
    +
    +
    +
    +
    set()
    +
    +
    +
    +
    +

    We can use the add method to add elements.

    +
    +
    +
    s1.add('a')
    +s1.add('b')
    +s1
    +
    +
    +
    +
    +
    {'a', 'b'}
    +
    +
    +
    +
    +

    Or we can pass any kind of sequence to set.

    +
    +
    +
    s2 = set('acd')
    +s2
    +
    +
    +
    +
    +
    {'a', 'c', 'd'}
    +
    +
    +
    +
    +

    An element can only appear once in a set. +If you add an element that’s already there, it has no effect.

    +
    +
    +
    s1.add('a')
    +s1
    +
    +
    +
    +
    +
    {'a', 'b'}
    +
    +
    +
    +
    +

    Or if you create a set with a sequence that contains duplicates, the result contains only unique elements.

    +
    +
    +
    set('banana')
    +
    +
    +
    +
    +
    {'a', 'b', 'n'}
    +
    +
    +
    +
    +

    Some of the exercises in this book can be done concisely and efficiently with sets. +For example, here is a solution to an exercise in Chapter 11 that uses a dictionary to check whether there are any duplicate elements in a sequence.

    +
    +
    +
    def has_duplicates(t):
    +    d = {}
    +    for x in t:
    +        d[x] = True
    +    return len(d) < len(t)
    +
    +
    +
    +
    +

    This version adds the element of t as keys in a dictionary, and then checks whether there are fewer keys than elements. +Using sets, we can write the same function like this.

    +
    +
    +
    def has_duplicates(t):
    +    s = set(t)
    +    return len(s) < len(t)
    +
    +
    +
    +
    +

    An element can only appear in a set once, so if an element in t appears more than once, the set will be smaller than t. +If there are no duplicates, the set will be the same size as t.

    +

    set objects provide methods that perform set operations. +For example, union computes the union of two sets, which is a new set that contains all elements that appear in either set.

    +
    +
    +
    s1.union(s2)
    +
    +
    +
    +
    +
    {'a', 'b', 'c', 'd'}
    +
    +
    +
    +
    +

    Some arithmetic operators work with sets. +For example, the - operator performs set subtraction – the result is a new set that contains all elements from the first set that are not in the second set.

    +
    +
    +
    s1 - s2
    +
    +
    +
    +
    +
    {'b'}
    +
    +
    +
    +
    +

    In Chapter 12 we used dictionaries to find the words that appear in a document but not in a word list. +We used the following function, which takes two dictionaries and returns a new dictionary that contains only the keys from the first that don’t appear in the second.

    +
    +
    +
    def subtract(d1, d2):
    +    res = {}
    +    for key in d1:
    +        if key not in d2:
    +            res[key] = d1[key]
    +    return res
    +
    +
    +
    +
    +

    With sets, we don’t have to write this function ourselves. +If word_counter is a dictionary that contains the unique words in the document and word_list is a list of valid words, we can compute the set difference like this.

    +
    +
    +
    set(word_counter) - set(word_list)
    +
    +
    +
    +
    +

    The result is a set that contains the words in the document that don’t appear in the word list.

    +

    The comparison operators work with sets. +For example, <= checks whether one set is a subset of another, including the possibility that they are equal.

    +
    +
    +
    set('ab') <= set('abc')
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    With these operators, we can use sets to do some of the exercises in Chapter 7. +For example, here’s a version of uses_only that uses a loop.

    +
    +
    +
    def uses_only(word, available):
    +    for letter in word: 
    +        if letter not in available:
    +            return False
    +    return True
    +
    +
    +
    +
    +

    uses_only checks whether all letters in word are in available. +With sets, we can rewrite it like this.

    +
    +
    +
    def uses_only(word, available):
    +    return set(word) <= set(available)
    +
    +
    +
    +
    +

    If the letters in word are a subset of the letters in available, that means that word uses only letters in available.

    +
    +
    +

    Counters#

    +

    A Counter is like a set, except that if an element appears more than once, the Counter keeps track of how many times it appears. +If you are familiar with the mathematical idea of a “multiset”, a Counter is a +natural way to represent a multiset.

    +

    The Counter class is defined in a standard module called collections, so you have to import it. +Then you can use the class object as a function and pass as an argument a string, list, or any other kind of sequence.

    +
    +
    +
    from collections import Counter
    +
    +counter = Counter('banana')
    +counter
    +
    +
    +
    +
    +
    Counter({'a': 3, 'n': 2, 'b': 1})
    +
    +
    +
    +
    +
    +
    +
    from collections import Counter
    +
    +t = (1, 1, 1, 2, 2, 3)
    +counter = Counter(t)
    +counter
    +
    +
    +
    +
    +
    Counter({1: 3, 2: 2, 3: 1})
    +
    +
    +
    +
    +

    A Counter object is like a dictionary that maps from each key to the number of times it appears. +As in dictionaries, the keys have to be hashable.

    +

    Unlike dictionaries, Counter objects don’t raise an exception if you access an +element that doesn’t appear. +Instead, they return 0.

    +
    +
    +
    counter['d']
    +
    +
    +
    +
    +
    0
    +
    +
    +
    +
    +

    We can use Counter objects to solve one of the exercises from Chapter 10, which asks for a function that takes two words and checks whether they are anagrams – that is, whether the letters from one can be rearranged to spell the other.

    +

    Here’s a solution using Counter objects.

    +
    +
    +
    def is_anagram(word1, word2):
    +    return Counter(word1) == Counter(word2)
    +
    +
    +
    +
    +

    If two words are anagrams, they contain the same letters with the same counts, so their Counter objects are equivalent.

    +

    Counter provides a method called most_common that returns a list of value-frequency pairs, sorted from most common to least.

    +
    +
    +
    counter.most_common()
    +
    +
    +
    +
    +
    [(1, 3), (2, 2), (3, 1)]
    +
    +
    +
    +
    +

    They also provide methods and operators to perform set-like operations, including addition, subtraction, union and intersection. +For example, the + operator combines two Counter objects and creates a new Counter that contains the keys from both and the sums of the counts.

    +

    We can test it by making a Counter with the letters from bans and adding it to the letters from banana.

    +
    +
    +
    counter2 = Counter('bans')
    +counter + counter2
    +
    +
    +
    +
    +
    Counter({1: 3, 2: 2, 3: 1, 'b': 1, 'a': 1, 'n': 1, 's': 1})
    +
    +
    +
    +
    +

    You’ll have a chance to explore other Counter operations in the exercises at the end of this chapter.

    +
    +
    +

    defaultdict#

    +

    The collections module also provides defaultdict, which is like a dictionary except that if you access a key that doesn’t exist, it generates a new value automatically.

    +

    When you create a defaultdict, you provide a function that’s used to create new values. +A function that create objects is sometimes called a factory. +The built-in functions that create lists, sets, and other types can be used as factories.

    +

    For example, here’s a defaultdict that creates a new list when needed.

    +
    +
    +
    from collections import defaultdict
    +
    +d = defaultdict(list)
    +d
    +
    +
    +
    +
    +
    defaultdict(list, {})
    +
    +
    +
    +
    +

    Notice that the argument is list, which is a class object, not list(), which is a function call that creates a new list. +The factory function doesn’t get called unless we access a key that doesn’t exist.

    +
    +
    +
    t = d['new key']
    +t
    +
    +
    +
    +
    +
    []
    +
    +
    +
    +
    +

    The new list, which we’re calling t, is also added to the dictionary. +So if we modify t, the change appears in d:

    +
    +
    +
    t.append('new value')
    +d['new key']
    +
    +
    +
    +
    +
    ['new value']
    +
    +
    +
    +
    +

    If you are making a dictionary of lists, you can often write simpler +code using defaultdict.

    +

    In one of the exercises in Chapter 11, I made a dictionary that maps from a sorted string of letters to the list of words that can be spelled with those letters. +For example, the string 'opst' maps to the list ['opts', 'post', 'pots', 'spot', 'stop', 'tops']. +Here’s the original code.

    +
    +
    +
    def all_anagrams(filename):
    +    d = {}
    +    for line in open(filename):
    +        word = line.strip().lower()
    +        t = signature(word)
    +        if t not in d:
    +            d[t] = [word]
    +        else:
    +            d[t].append(word)
    +    return d
    +
    +
    +
    +
    +

    And here’s a simpler version using a defaultdict.

    +
    +
    +
    def all_anagrams(filename):
    +    d = defaultdict(list)
    +    for line in open(filename):
    +        word = line.strip().lower()
    +        t = signature(word)
    +        d[t].append(word)
    +    return d
    +
    +
    +
    +
    +

    In the exercises at the end of the chapter, you’ll have a chance to practice using defaultdict objects.

    +
    +
    +
    from collections import defaultdict
    +
    +d = defaultdict(list)
    +key = ('into', 'the')
    +d[key].append('woods')
    +d[key]
    +
    +
    +
    +
    +
    ['woods']
    +
    +
    +
    +
    +
    +
    +

    Conditional expressions#

    +

    Conditional statements are often used to choose one of two values, like this:

    +
    +
    +
    if x > 0:
    +    y = math.log(x)
    +else:
    +    y = float('nan')
    +
    +
    +
    +
    +

    This statement checks whether x is positive. If so, it computes its logarithm. +If not, math.log would raise a ValueError. +To avoid stopping the program, we generate a NaN, which is a special floating-point value that represents “Not a Number”.

    +

    We can write this statement more concisely using a conditional expression.

    +
    +
    +
    y = math.log(x) if x > 0 else float('nan')
    +
    +
    +
    +
    +

    You can almost read this line like English: “y gets log-x if x is greater than 0; otherwise it gets NaN”.

    +

    Recursive functions can sometimes be written concisely using conditional expressions. +For example, here is a version of factorial with a conditional statement.

    +
    +
    +
    def factorial(n):
    +    if n == 0:
    +        return 1
    +    else:
    +        return n * factorial(n-1)
    +
    +
    +
    +
    +

    And here’s a version with a conditional expression.

    +
    +
    +
    def factorial(n):
    +    return 1 if n == 0 else n * factorial(n-1)
    +
    +
    +
    +
    +

    Another use of conditional expressions is handling optional arguments. +For example, here is class definition with an __init__ method that uses a conditional statement to check a parameter with a default value.

    +
    +
    +
    class Kangaroo:
    +    def __init__(self, name, contents=None):
    +        self.name = name
    +        if contents is None:
    +            contents = []
    +        self.contents = contents
    +
    +
    +
    +
    +

    Here’s a version that uses a conditional expression.

    +
    +
    +
    def __init__(self, name, contents=None):
    +    self.name = name
    +    self.contents = [] if contents is None else contents 
    +
    +
    +
    +
    +

    In general, you can replace a conditional statement with a conditional expression if both branches contain a single expression and no statements.

    +
    +
    +

    List comprehensions#

    +

    In previous chapters, we’ve seen a few examples where we start with an empty list and add elements, one at a time, using the append method. +For example, suppose we have a string that contains the title of a movie, and we want to capitalize all of the words.

    +
    +
    +
    title = 'monty python and the holy grail'
    +
    +
    +
    +
    +

    We can split it into a list of strings, loop through the strings, capitalize them, and append them to a list.

    +
    +
    +
    t = []
    +for word in title.split():
    +    t.append(word.capitalize())
    +
    +' '.join(t)
    +
    +
    +
    +
    +
    'Monty Python And The Holy Grail'
    +
    +
    +
    +
    +

    We can do the same thing more concisely using a list comprehension:

    +
    +
    +
    t = [word.capitalize() for word in title.split()]
    +
    +' '.join(t)
    +
    +
    +
    +
    +
    'Monty Python And The Holy Grail'
    +
    +
    +
    +
    +

    The bracket operators indicate that we are constructing a new list. +The expression inside the brackets specifies the elements of the list, and the for clause indicates what sequence we are looping through.

    +

    The syntax of a list comprehension might seem strange, because the loop variable – word in this example – appears in the expression before we get to its definition. +But you get used to it.

    +

    As another example, in Chapter 9 we used this loop to read words from a file and append them to a list.

    +
    +
    +
    word_list = []
    +
    +for line in open('words.txt'):
    +    word = line.strip()
    +    word_list.append(word)
    +
    +
    +
    +
    +

    Here’s how we can write that as a list comprehension.

    +
    +
    +
    word_list = [line.strip() for line in open('words.txt')]
    +
    +
    +
    +
    +

    A list comprehension can also have an if clause that determines which elements are included in the list. +For example, here’s a for loop we used in Chapter 10 to make a list of only the words in word_list that are palindromes.

    +
    +
    +
    palindromes = []
    +
    +for word in word_list:
    +    if is_palindrome(word):
    +        palindromes.append(word)
    +
    +
    +
    +
    +

    Here’s how we can do the same thing with an list comprehension.

    +
    +
    +
    palindromes = [word for word in word_list if is_palindrome(word)]
    +
    +
    +
    +
    +

    When a list comprehension is used as an argument to a function, we can often omit the brackets. +For example, suppose we want to add up \(1 / 2^n\) for values of \(n\) from 0 to 9. +We can use a list comprehension like this.

    +
    +
    +
    sum([1/2**n for n in range(10)])
    +
    +
    +
    +
    +
    1.998046875
    +
    +
    +
    +
    +

    Or we can leave out the brackets like this.

    +
    +
    +
    sum(1/2**n for n in range(10))
    +
    +
    +
    +
    +
    1.998046875
    +
    +
    +
    +
    +

    In this example, the argument is technically a generator expression, not a list comprehension, and it never actually makes a list. +But other than that, the behavior is the same.

    +

    List comprehensions and generator expressions are concise and easy to read, at least for simple expressions. +And they are usually faster than the equivalent for loops, sometimes much faster. +So if you are mad at me for not mentioning them earlier, I understand.

    +

    But, in my defense, list comprehensions are harder to debug because you can’t put a print statement inside the loop. +I suggest you use them only if the computation is simple enough that you are likely to get it +right the first time. +Or consider writing and debugging a for loop and then converting it to a list comprehension.

    +
    +
    +

    any and all#

    +

    Python provides a built-in function, any, that takes a sequence of boolean values and returns True if any of the values are True.

    +
    +
    +
    any([False, False, True])
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    any is often used with generator expressions.

    +
    +
    +
    any(letter == 't' for letter in 'monty')
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    That example isn’t very useful because it does the same thing as the in operator. +But we could use any to write concise solutions to some of the exercises in Chapter 7. For example, we can write uses_none like this.

    +
    +
    +
    def uses_none(word, forbidden):
    +    """Checks whether a word avoids forbidden letters."""
    +    return not any(letter in forbidden for letter in word)
    +
    +
    +
    +
    +

    This function loops through the letters in word and checks whether any of them are in forbidden. +Using any with a generator expression is efficient because it stops immediately if it finds a True value, so it doesn’t have to loop through the whole sequence.

    +

    Python provides another built-in function, all, that returns True if every element of the sequence is True. +We can use it to write a concise version of uses_all.

    +
    +
    +
    def uses_all(word, required):
    +    """Check whether a word uses all required letters."""
    +    return all(letter in word for letter in required)
    +
    +
    +
    +
    +

    Expressions using any and all can be concise, efficient, and easy to read.

    +
    +
    +

    Named tuples#

    +

    The collections module provides a function called namedtuple that can be used to create simple classes. +For example, the Point object in Chapter 16 has only two attributes, x and y. +Here’s how we defined it.

    +
    +
    +
    class Point:
    +    """Represents a point in 2-D space."""
    +    
    +    def __init__(self, x, y):
    +        self.x = x
    +        self.y = y
    +        
    +    def __str__(self):
    +        return f'({self.x}, {self.y})'
    +
    +
    +
    +
    +

    That’s a lot of code to convey a small amount of information. +namedtuple provides a more concise way to define classes like this.

    +
    +
    +
    from collections import namedtuple
    +
    +Point = namedtuple('Point', ['x', 'y'])
    +
    +
    +
    +
    +

    The first argument is the name of the class you want to create. The +second is a list of the attributes Point objects should have. +The result is a class object, which is why it is assigned to a capitalized variable name.

    +

    A class created with namedtuple provides an __init__ method that assigns values to the attributes and a __str__ that displays the object in a readable form. +So we can create and display a Point object like this.

    +
    +
    +
    p = Point(1, 2)
    +p
    +
    +
    +
    +
    +
    Point(x=1, y=2)
    +
    +
    +
    +
    +

    Point also provides an __eq__ method that checks whether two Point objects are equivalent – that is, whether their attributes are the same.

    +
    +
    +
    p == Point(1, 2)
    +
    +
    +
    +
    +
    True
    +
    +
    +
    +
    +

    You can access the elements of a named tuple by name or by index.

    +
    +
    +
    p.x, p.y
    +
    +
    +
    +
    +
    (1, 2)
    +
    +
    +
    +
    +
    +
    +
    p[0], p[1]
    +
    +
    +
    +
    +
    (1, 2)
    +
    +
    +
    +
    +

    You can also treat a named tuple as a tuple, as in this assignment.

    +
    +
    +
    x, y = p
    +x, y
    +
    +
    +
    +
    +
    (1, 2)
    +
    +
    +
    +
    +

    But namedtuple objects are immutable. +After the attributes are initialized, they can’t be changed.

    +
    +
    +
    p[0] = 3
    +
    +
    +
    +
    +
    TypeError: 'Point' object does not support item assignment
    +
    +
    +
    +
    +
    +
    +
    p.x = 3
    +
    +
    +
    +
    +
    AttributeError: can't set attribute
    +
    +
    +
    +
    +

    namedtuple provides a quick way to define simple classes. +The drawback is that simple classes don’t always stay simple. +You might decide later that you want to add methods to a named tuple. +In that case, you can define a new class that inherits from the named tuple.

    +
    +
    +
    class Pointier(Point):
    +    """This class inherits from Point"""
    +
    +
    +
    +
    +

    Or at that point you could switch to a conventional class definition.

    +
    +
    +

    Packing keyword arguments#

    +

    In Chapter 11, we wrote a function that packs its arguments into a tuple.

    +
    +
    +
    def mean(*args):
    +    return sum(args) / len(args)
    +
    +
    +
    +
    +

    You can call this function with any number of positional arguments.

    +
    +
    +
    mean(1, 2, 3)
    +
    +
    +
    +
    +
    2.0
    +
    +
    +
    +
    +

    But the * operator doesn’t pack keyword arguments. +So calling this function with a keyword argument causes an error.

    +
    +
    +
    mean(1, 2, start=3)
    +
    +
    +
    +
    +
    TypeError: mean() got an unexpected keyword argument 'start'
    +
    +
    +
    +
    +

    To pack keyword arguments, we can use the ** operator:

    +
    +
    +
    def mean(*args, **kwargs):
    +    print(kwargs)
    +    return sum(args) / len(args)
    +
    +
    +
    +
    +

    The keyword-packing parameter can have any name, but kwargs is a common choice. +The result is a dictionary that maps from keywords to values.

    +
    +
    +
    mean(1, 2, start=3)
    +
    +
    +
    +
    +
    {'start': 3}
    +
    +
    +
    1.5
    +
    +
    +
    +
    +

    In this example, the value of kwargs is printed, but otherwise is has no effect.

    +

    But the ** operator can also be used in an argument list to unpack a dictionary. +For example, here’s a version of mean that packs any keyword arguments it gets and then unpacks them as keyword arguments for sum.

    +
    +
    +
    def mean(*args, **kwargs):
    +    return sum(args, **kwargs) / len(args)
    +
    +
    +
    +
    +

    Now if we call mean with start as a keyword argument, it gets passed along to sum, which uses it as the starting point of the summation. +In the following example start=3 adds 3 to the sum before computing the mean, so the sum is 6 and the result is 3.

    +
    +
    +
    mean(1, 2, start=3)
    +
    +
    +
    +
    +
    3.0
    +
    +
    +
    +
    +

    As another example, if we have a dictionary with keys x and y, we can use it with the unpack operator to create a Point object.

    +
    +
    +
    d = dict(x=1, y=2)
    +Point(**d)
    +
    +
    +
    +
    +
    Point(x=1, y=2)
    +
    +
    +
    +
    +

    Without the unpack operator, d is treated as a single positional argument, so it gets assigned to x, and we get a TypeError because there’s no second argument to assign to y.

    +
    +
    +
    d = dict(x=1, y=2)
    +Point(d)
    +
    +
    +
    +
    +
    TypeError: Point.__new__() missing 1 required positional argument: 'y'
    +
    +
    +
    +
    +

    When you are working with functions that have a large number of keyword arguments, it is often useful to create and pass around dictionaries that specify frequently used options.

    +
    +
    +
    def pack_and_print(**kwargs):
    +    print(kwargs)
    +    
    +pack_and_print(a=1, b=2)
    +
    +
    +
    +
    +
    {'a': 1, 'b': 2}
    +
    +
    +
    +
    +
    +
    +

    Debugging#

    +

    In previous chapters, we used doctest to test functions. +For example, here’s a function called add that takes two numbers and returns their sum. +In includes a doctest that checks whether 2 + 2 is 4.

    +
    +
    +
    def add(a, b):
    +    '''Add two numbers.
    +    
    +    >>> add(2, 2)
    +    4
    +    '''
    +    return a + b
    +
    +
    +
    +
    +

    This function takes a function object and runs its doctests.

    +
    +
    +
    from doctest import run_docstring_examples
    +
    +def run_doctests(func):
    +    run_docstring_examples(func, globals(), name=func.__name__)
    +
    +
    +
    +
    +

    So we can test add like this.

    +
    +
    +
    run_doctests(add)
    +
    +
    +
    +
    +

    There’s no output, which means all tests passed.

    +

    Python provides another tool for running automated tests, called unittest. +It is a little more complicated to use, but here’s an example.

    +
    +
    +
    from unittest import TestCase
    +
    +class TestExample(TestCase):
    +
    +    def test_add(self):
    +        result = add(2, 2)
    +        self.assertEqual(result, 4)
    +
    +
    +
    +
    +

    First we import TestCase, which is a class in the unittest module. +To use it, we have to define a new class that inherits from TestCase and provides at least one test method. +The name of the test method must begin with test and should indicate which function it tests.

    +

    In this example, test_add tests the add function by calling it, saving the result, and invoking assertEqual, which is inherited from TestCase. +assertEqual takes two arguments and checks whether they are equal.

    +

    In order to run this test method, we have to run a function in unittest called main and provide several keyword arguments. +The following function shows the details – if you are curious, you can ask a virtual assistant to explain how it works.

    +
    +
    +
    import unittest
    +
    +def run_unittest():
    +    unittest.main(argv=[''], verbosity=0, exit=False)
    +
    +
    +
    +
    +

    run_unittest does not take TestExample as an argument – instead, it searches for classes that inherit from TestCase. +Then is searches for methods that begin with test and runs them. +This process is called test discovery.

    +

    Here’s what happens when we call run_unittest.

    +
    +
    +
    run_unittest()
    +
    +
    +
    +
    +
    ----------------------------------------------------------------------
    +Ran 1 test in 0.000s
    +
    +OK
    +
    +
    +
    +
    +

    unittest.main reports the number of tests it ran and the results. +In this case OK indicates that the tests passed.

    +

    To see what happens when a test fails, we’ll add an incorrect test method to TestExample.

    +
    +
    +
    %%add_method_to TestExample
    +
    +    def test_add_broken(self):
    +        result = add(2, 2)
    +        self.assertEqual(result, 100)
    +
    +
    +
    +
    +

    Here’s what happens when we run the tests.

    +
    +
    +
    run_unittest()
    +
    +
    +
    +
    +
    ======================================================================
    +FAIL: test_add_broken (__main__.TestExample)
    +----------------------------------------------------------------------
    +Traceback (most recent call last):
    +  File "/tmp/ipykernel_1109857/3833266738.py", line 3, in test_add_broken
    +    self.assertEqual(result, 100)
    +AssertionError: 4 != 100
    +
    +----------------------------------------------------------------------
    +Ran 2 tests in 0.000s
    +
    +FAILED (failures=1)
    +
    +
    +
    +
    +

    The report includes the test method that failed and an error message showing where. +The summary indicates that two tests ran and one failed.

    +

    In the exercises below, I’ll suggest some prompts you can use to ask a virtual assistant for more information about unittest.

    +
    +
    +

    Glossary#

    +

    factory: +A function used to create objects, often passed as a parameter to a function.

    +

    conditional expression: +An expression that uses a conditional to select one of two values.

    +

    list comprehension: +A concise way to loop through a sequence and create a list.

    +

    generator expression: +Similar to a list comprehension except that it does not create a list.

    +

    test discovery: +A process used to find and run tests.

    +
    +
    +

    Exercises#

    +
    +
    +
    # This cell tells Jupyter to provide detailed debugging information
    +# when a runtime error occurs. Run it before working on the exercises.
    +
    +%xmode Verbose
    +
    +
    +
    +
    +
    +

    Ask a virtual assistant#

    +

    There are a few topics in this chapter you might want to learn about. +Here are some question to ask an AI.

    +
      +
    • “What are the methods and operators of Python’s set class?”

    • +
    • “What are the methods and operators of Python’s Counter class?”

    • +
    • “What is the difference between a Python list comprehension and a generator expression?”

    • +
    • “When should I use Python’s namedtuple rather than define a new class?”

    • +
    • “What are some uses of packing and unpacking keyword arguments?”

    • +
    • “How does unittest do test discovery?”

    • +
    +

    “Along with assertequal, what are the most commonly used methods in unittest.TestCase?”

    +

    “What are the pros and cons of doctest and unittest?”

    +

    For the following exercises, consider asking an AI for help, but as always, remember to test the results.

    +
    +
    +

    Exercise#

    +

    One of the exercises in Chapter 7 asks for a function called uses_none that takes a word and a string of forbidden letters, and returns True if the word does not use any of the letters. Here’s a solution.

    +
    +
    +
    def uses_none(word, forbidden):
    +    for letter in word.lower():
    +        if letter in forbidden.lower():
    +            return False
    +    return True
    +
    +
    +
    +
    +

    Write a version of this function that uses set operations instead of a for loop. +Hint: ask an AI “How do I compute the intersection of Python sets?”

    +
    +
    +

    Exercise#

    +

    Scrabble is a board game where the objective is to use letter tiles to spell words. +For example, if we have tiles with the letters T, A, B, L, E, we can spell BELT and LATE using a subset of the tiles – but we can’t spell BEET because we don’t have two Es.

    +

    Write a function that takes a string of letters and a word, and checks whether the letters can spell the word, taking into account how many times each letter appears.

    +
    +
    +

    Exercise#

    +

    In one of the exercises from Chapter 17, my solution to has_straightflush uses the following method, which partitions a PokerHand into a list of four hands, where each hand contains cards of the same suit.

    +
    +
    +
        def partition(self):
    +        """Make a list of four hands, each containing only one suit."""
    +        hands = []
    +        for i in range(4):
    +            hands.append(PokerHand())
    +            
    +        for card in self.cards:
    +            hands[card.suit].add_card(card)
    +            
    +        return hands
    +
    +
    +
    +
    +

    Write a simplified version of this function using a defaultdict.

    +
    +
    +

    Exercise#

    +

    Here’s the function from Chapter 11 that computes Fibonacci numbers.

    +
    +
    +
    def fibonacci(n):
    +    if n == 0:
    +        return 0
    +    
    +    if n == 1:
    +        return 1
    +
    +    return fibonacci(n-1) + fibonacci(n-2)
    +
    +
    +
    +
    +

    Write a version of this function with a single return statement that use two conditional expressions, one nested inside the other.

    +
    +
    +

    Exercise#

    +

    The following is a function that computes the binomial coefficient +recursively.

    +
    +
    +
    def binomial_coeff(n, k):
    +    """Compute the binomial coefficient "n choose k".
    +
    +    n: number of trials
    +    k: number of successes
    +
    +    returns: int
    +    """
    +    if k == 0:
    +        return 1
    +    
    +    if n == 0:
    +        return 0
    +
    +    return binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)
    +
    +
    +
    +
    +

    Rewrite the body of the function using nested conditional expressions.

    +

    This function is not very efficient because it ends up computing the same values over and over. +Make it more efficient by memoizing it, as described in Chapter 10.

    +
    +
    +
    binomial_coeff(10, 4)    # should be 210
    +
    +
    +
    +
    +
    210
    +
    +
    +
    +
    +
    +
    +

    Exercise#

    +

    Here’s the __str__ method from the Deck class in Chapter 17.

    +
    +
    +
    %%add_method_to Deck
    +
    +    def __str__(self):
    +        res = []
    +        for card in self.cards:
    +            res.append(str(card))
    +        return '\n'.join(res)
    +
    +
    +
    +
    +

    Write a more concise version of this method with a list comprehension or generator expression.

    +
    +
    +
    + + + + +
    + + + + + + +
    + + + + + + +
    +
    + + +
    + + +
    +
    +
    + + + + + +
    +
    + + \ No newline at end of file diff --git a/genindex.html b/genindex.html index a3d8e21..a28c677 100644 --- a/genindex.html +++ b/genindex.html @@ -164,6 +164,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • diff --git a/index.html b/index.html index c0d4cfa..3768f60 100644 --- a/index.html +++ b/index.html @@ -166,6 +166,9 @@
  • Files and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • @@ -341,7 +344,7 @@

    Contents

  • Chapter 11: Tuples
  • Chapter 12: Text Analysis and Generation
  • Chapter 13: Files and Databases
  • -
  • Chapter 14: Classes and Function
  • +
  • Chapter 14: Classes and Functions
  • Chapter 15: Classes and Methods
  • @@ -451,8 +454,8 @@

    Chapter 13: Files and DatabasesClick here to run Chapter 13 on Colab

    -
    -

    Chapter 14: Classes and Function#

    +
    +

    Chapter 14: Classes and Functions#

    @@ -550,7 +553,7 @@

    Resources for teachersChapter 11: Tuples
  • Chapter 12: Text Analysis and Generation
  • Chapter 13: Files and Databases
  • -
  • Chapter 14: Classes and Function
  • +
  • Chapter 14: Classes and Functions
  • Chapter 15: Classes and Methods
  • diff --git a/objects.inv b/objects.inv index 50e2df7b5f69eec1f0a633fab0cf857502d273cc..134de6b0af17a4165273b2d34e51974b883b368e 100644 GIT binary patch delta 987 zcmV<110?*}2g3-Ec7Mxm+d2^4=PL-@ZB;~$NB!8MwoT^cksmf1*@N>WYS zufM~WUZmq}1aWlEAvtF_6x-AG{G2FVd?^}6vYap*`MB40Mb;8_l);&e zgvTu-+;WDq^7lf6G<)*2%ZPUAXg7^0xZ%;tF~0-rq{4tBt|H>f5x;}(q`+NOU6|x5 z!d^M-JuZftN!-?fO15{DPZ8ylqdaPfDGf_1yNXJ3_u5KOYDmWCi2d2I|K-XjHjhlt zz3I2y<_lf%>wnK9=X3A;Q`c4j6#1Kbf1lu0lWWZ5L29X0Ye4<7SS7PnN> zEmdK%$nD&_eN$1TJZD-Vd1Q5d8FL;$@0-FiB9HS`%=mBn3s^{MT$j%OAuTX@H*v%XfOp|&jy%xV}y7z8}pWYAQ!SA z;JU(D-4qG^_K<}SPT-*_x(fVEqpv4uz6d7lR8C#YcLw);O-n$mt^5s@WoMe{rlsup zcnUo{3xDv;!ms^^n2~Wes4@xIq&IED@nbBynun>OBt|N7nP6_JAjWgmG#b)orm!*?-|XvOG*P=JmJDfsDe#Yz|ARKznhFqiz@96R`I`dvRZfr$+&`d)o=it629~Az0!_v zF)57k)73FRzlOJ7h6_A><_y25%*NpR(R%@ZplVt(>V@m#5-Xtum1+PNQ(eSn9(%I~ z+kZ;P4l4|Pcl-~nOo!Q-i+&J?x>2VK*TEjlv!}LbiYR1L!=~cN#mz(pLS_lqjCHlO z+BPW@8^p1uG#=q(E4`ZO8f<2?TJ%-9PG{JX({>=^z#!s(29US@dN zz3q&$i)7 z*_iax#mFc&Ow!2Wh^_tycaTP?GxUKzJ1b3S>gNmo7}-|LlRp^#mwV83@XWz~x{)v4 J{{g)fzo3YE^DF=W delta 880 zcmV-$1CRW}2-ydac7IK8+b|5h_g4tmbsp+`wCip~(+&kzplFNr7$`F1Xpv=4lG|p# zev&AEMNX4TATsYgN+d;@ZD@6VPPEB>WF;d>N|^V2+n7R-M+Ixj;LQ8d2$eRdGFP-H zxXcOFgpzlW?cceTUWmDR0DX#@p|b1m#HLZv(@^vU z_xZ9_3Hwi@%73S!@-KBI8c_{Wh%{baExm=c&YyVP>?D%|7z2zlbCN1w}1GZ~PV~>T= z^~xyf%@eZRh3WI!A-W?ycNeW>+7I%PEbV8$g)W+vZgoI1T50dYY4v`SZa6+7ri3wmyni=3=v}jFWw>O@ecpWa_3^ev*n|6R zsg*5>%v4#xQt;8u?TK`R#1pO=s|#Of_ZkuBYsIy@dG_LF_Jy^Es+s+Mf#V4Xyu|U6 zj%oLdZsOdeb$4g6mJzTla9khlScE45?k?}2y4sFiles and Databases
  • Classes and Functions
  • Classes and Methods
  • +
  • Classes and Objects
  • +
  • Inheritance
  • +
  • Python Extras
  • diff --git a/searchindex.js b/searchindex.js index ab633cb..8ac3f27 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["chap00", "chap01", "chap02", "chap03", "chap04", "chap05", "chap06", "chap07", "chap08", "chap09", "chap10", "chap11", "chap12", "chap13", "chap14", "chap15", "index"], "filenames": ["chap00.ipynb", "chap01.ipynb", "chap02.ipynb", "chap03.ipynb", "chap04.ipynb", "chap05.ipynb", "chap06.ipynb", "chap07.ipynb", "chap08.ipynb", "chap09.ipynb", "chap10.ipynb", "chap11.ipynb", "chap12.ipynb", "chap13.ipynb", "chap14.ipynb", "chap15.ipynb", "index.md"], "titles": ["Preface", "Programming as a way of thinking", "Variables and Statements", "Functions", "Functions and Interfaces", "Conditionals and Recursion", "Return Values", "Iteration and Search", "Strings and Regular Expressions", "Lists", "Dictionaries", "Tuples", "Text Analysis and Generation", "Files and Databases", "Classes and Functions", "Classes and Methods", "Think Python, 3rd edition"], "terms": {"from": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "os": [3, 7, 13], "path": [3, 6, 7], "import": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14], "basenam": [3, 7], "exist": [3, 6, 7, 8, 9, 10, 11, 12, 13], "def": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "download": [0, 3, 7, 8, 11, 12, 16], "url": [3, 7, 8], "filenam": [3, 7, 12], "urllib": [3, 7], "request": [3, 7], "urlretriev": [3, 7], "local": [4, 6, 7], "_": [2, 3, 7, 12], "print": [3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "str": [1, 2, 3, 7, 8, 11, 13, 15], "return": [2, 3, 5, 7, 8, 9, 10, 12, 13, 15], "http": [0, 1, 3, 5, 7, 8, 11, 12], "raw": [3, 7, 8, 11], "githubusercont": 11, "com": [0, 3, 7, 11], "allendownei": [0, 3, 7, 11], "thinkpython": [0, 3, 5, 7, 11], "v3": [3, 7, 11], "py": [3, 7, 11], "diagram": [6, 7, 9, 10, 14], "except": [2, 3, 4, 5, 6, 15], "report": [], "mode": [8, 12, 13], "minim": [5, 7], "load_ext": [5, 7], "autoreload": [5, 7], "2": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "The": [0, 1, 3, 6, 8, 9, 11, 12, 13, 14], "first": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15], "goal": [1, 13], "thi": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "book": [1, 2, 3, 4, 7, 8, 10, 12, 13, 15, 16], "teach": [0, 1, 16], "you": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "how": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "python": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "But": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15], "learn": [0, 1, 2, 3, 4, 7, 8, 9, 12, 16], "mean": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], "new": [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "so": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "second": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "help": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15, 16], "like": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16], "comput": [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15], "scientist": [0, 1], "combin": [1, 5, 7, 13], "some": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "best": [0, 1, 8, 10, 12, 16], "featur": [0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 14, 15], "mathemat": [1, 2, 6, 9, 10], "engin": [0, 1], "scienc": [1, 3, 12], "mathematician": 1, "us": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "denot": [1, 2, 6], "idea": [0, 1, 2, 3, 4, 5, 6, 8], "specif": [1, 2, 4, 10], "thei": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "design": [0, 1, 3, 4, 13], "thing": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15], "assembl": [1, 3], "compon": [1, 10, 12], "system": [0, 1, 8, 10, 13], "evalu": [1, 2, 6, 7, 11, 13, 15], "trade": 1, "off": [1, 2, 5, 8, 13], "among": [1, 8], "altern": [0, 1, 4, 5, 6, 10, 13, 14], "observ": [1, 6, 13, 14], "behavior": [1, 4, 5, 8, 9, 11, 14, 15], "complex": [1, 2, 5, 6], "form": [1, 5, 8, 9, 11], "hypothes": 1, "test": [0, 1, 3, 5, 6, 7, 11, 12, 13, 14, 15], "predict": [1, 3], "we": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "start": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "most": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15], "basic": [0, 1, 9], "element": [0, 1, 2, 4, 7, 8, 9, 10, 11, 12], "work": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "our": [1, 8, 10, 14], "up": [1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14], "In": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "chapter": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "repres": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15], "number": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14], "letter": [0, 1, 2, 3, 7, 8, 9, 10, 11, 12, 13], "word": [0, 1, 2, 3, 5, 6, 8, 10, 11, 13], "And": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "ll": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "perform": [1, 2, 6, 8, 9, 10, 11, 13], "also": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "vocabulari": [0, 1, 2, 3, 12, 14], "includ": [0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 13, 15], "term": [0, 1, 2, 13, 15], "need": [0, 1, 2, 5, 7, 11, 12, 13, 14, 15], "understand": [0, 1, 3, 4, 5, 6, 7, 10, 12, 14], "rest": [1, 3, 8, 11, 12], "commun": 1, "other": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "programm": [0, 1, 12, 15], "todo": 5, "jupyt": [0, 6, 8, 15, 16], "introduct": [8, 16], "onlin": [0, 8, 16], "version": [0, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15], "onli": [0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "an": [0, 1, 2, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "symbol": [1, 2, 6, 8, 12, 13], "For": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "exampl": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "plu": 1, "sign": [1, 3, 4, 5], "addit": [0, 1, 2, 5, 8, 13, 14], "30": [1, 4, 10], "12": [0, 1, 5, 8, 13, 14, 15], "42": [1, 2, 5, 6, 9], "minu": 1, "subtract": [1, 5, 8, 14], "43": [1, 2], "1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "asterisk": 1, "multipl": [1, 2, 8, 9, 11, 13], "6": [1, 2, 5, 6, 8, 10, 11, 12], "7": [0, 1, 4, 5, 6, 7, 9, 10, 11], "forward": [1, 4, 5, 9, 10, 13], "slash": [1, 13], "divis": [1, 6], "84": 1, "0": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15], "notic": [1, 2, 4, 6, 7, 10, 12, 14, 15], "result": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "rather": [0, 1, 6, 7, 8, 11, 13, 14, 15], "than": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "That": [0, 1, 3, 4, 6, 7, 10, 11, 13], "s": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "becaus": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "ar": [0, 1, 2, 4, 5, 6, 7, 10, 12, 13, 15, 16], "two": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "integ": [1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15], "which": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "whole": [0, 1, 3, 6, 8, 9, 14, 16], "float": [1, 2, 3, 5, 8, 9, 10, 11], "point": [1, 2, 3, 5, 6, 7, 8, 10, 12, 14], "decim": [1, 2, 5, 12], "If": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "add": [1, 2, 4, 6, 7, 9, 10, 12, 13, 14, 15], "multipli": [1, 6], "divid": [1, 3, 4, 5, 6, 11, 12, 14], "provid": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "anoth": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "alwai": [1, 3, 4, 5, 6, 9, 10, 11, 13, 15], "call": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "floor": [1, 5], "round": [1, 2, 5, 6, 12, 13], "down": [1, 2, 3, 4, 5, 10, 12, 13], "toward": [1, 12], "85": [1, 9], "final": [0, 1, 2, 3, 4, 5, 6, 11, 13], "exponenti": [1, 2], "rais": [1, 2], "power": [0, 1, 2, 5, 8], "49": 1, "caret": 1, "bitwis": 1, "xor": [1, 5], "familiar": [1, 2, 10], "might": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "unexpect": [1, 5], "5": [1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13], "i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "won": [1, 2, 4, 8, 10, 11, 15], "t": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "cover": [0, 1, 12, 14, 15], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "read": [0, 1, 2, 3, 5, 6, 8, 9, 10, 12, 13, 14, 15, 16], "about": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16], "them": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "wiki": [1, 12], "org": [1, 5, 8, 11, 12], "moin": 1, "bitwiseoper": 1, "A": [0, 1, 2, 3, 5, 6, 7, 11, 12, 13, 14, 15], "collect": [1, 2, 8, 11, 13], "contain": [0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "ani": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "here": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "happen": [1, 2, 3, 5, 6, 7, 9, 10, 12, 14, 15], "befor": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], "follow": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "order": [0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 14, 15], "have": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "math": [1, 2, 3, 4, 5, 6], "class": [1, 2], "want": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15], "parenthes": [1, 3, 8, 11], "90": [4, 14], "everi": [0, 1, 2, 4, 5, 6, 7, 10, 12, 13, 15, 16], "ha": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "few": [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16], "take": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "nearest": 1, "4": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13], "ab": [1, 3, 6], "absolut": [1, 13], "posit": [1, 4, 5, 8, 11, 15], "itself": [0, 1, 2, 5, 13], "neg": [1, 5, 6, 8, 9], "when": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "sai": [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 15], "re": [1, 4, 8, 10, 12, 13, 14, 15], "requir": [1, 2, 3, 4, 7, 8, 12, 15], "leav": [1, 4, 8, 9, 10, 12, 14], "out": [0, 1, 2, 4, 5, 6, 8, 10, 12], "get": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "error": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "messag": [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 15], "note": [2, 6, 8, 9, 11, 13], "TO": [], "editor": 0, "line": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "begin": [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "expect": [2, 4, 5, 7, 8, 11, 12, 14], "remov": [6, 7, 8, 9, 11, 12], "automat": [10, 11], "file": [0, 2, 3, 7, 9, 10, 12], "syntaxerror": [1, 2], "invalid": [1, 2, 5], "syntax": [1, 2, 5, 7, 8, 12, 15], "ignor": [1, 2, 5, 7, 8, 13], "doesn": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13], "inform": [1, 2, 5, 7, 8, 12, 13, 14, 15], "right": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12], "now": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "code": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16], "beneath": 1, "indic": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "where": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], "wa": [0, 1, 3, 5, 7, 8, 9, 12, 15], "discov": [1, 5, 7, 13, 14], "last": [1, 2, 3, 5, 6, 8, 11, 12, 13], "someth": [1, 2, 4, 5, 6, 7, 8, 10, 12, 15], "wrong": [1, 2, 3, 5, 6, 7, 9, 11, 12, 15], "structur": [0, 1, 2, 5, 7, 11, 12], "problem": [0, 1, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15], "let": [1, 2, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15], "see": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "what": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "x": [1, 2, 5, 6, 7, 9, 11], "name": [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "all": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "legal": [1, 2, 3, 5, 7, 8], "displai": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15], "explain": [1, 2, 4, 6, 7, 9, 12, 13, 14], "later": [1, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14], "sequenc": [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13], "strung": 1, "togeth": [1, 3, 4, 8, 11, 12, 13], "bead": 1, "necklac": 1, "To": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "write": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16], "put": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "insid": [1, 3, 6, 7, 9, 10, 14, 15], "straight": [1, 5], "quotat": [1, 8, 10, 12, 13], "mark": [1, 3, 8, 10, 12, 13], "hello": [1, 8], "It": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "doubl": [1, 5, 8], "world": [1, 8, 15], "quot": [1, 4], "make": [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], "easi": [0, 1, 4, 5, 6, 13], "apostroph": 1, "same": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "small": [1, 3, 4, 6, 8, 12], "space": [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13], "punctuat": [1, 2, 8], "digit": [1, 2, 5, 8, 13, 14], "well": [1, 3, 4, 5, 8, 12], "join": [1, 9, 10, 11, 12, 13], "singl": [1, 2, 5, 7, 8, 9, 11, 12], "concaten": [1, 3, 8, 9, 11], "copi": [1, 3, 4, 9, 10, 12, 13], "spam": [1, 3, 5, 6, 9, 11], "don": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 14, 16], "len": [1, 2, 3, 8, 9, 10, 11, 12, 13], "length": [1, 4, 5, 6, 8, 9, 10, 11, 12], "count": [1, 3, 5, 8, 9, 10, 11, 12], "between": [1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15], "creat": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "sure": [1, 5, 6, 7, 8, 14, 15], "back": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14], "known": [1, 4, 5, 10, 12, 14], "backtick": 1, "charact": [1, 2, 3, 5, 6, 7, 8, 9, 12, 13], "smart": 1, "curli": [1, 10, 13, 14], "illeg": [1, 2], "far": [0, 1, 2, 3, 5, 6, 7, 11, 12, 13, 14], "ve": [0, 1, 2, 3, 6, 7, 8, 10, 11, 12, 13], "seen": [1, 2, 3, 4, 7, 10, 11, 12, 13], "three": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15], "kind": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "sometim": [1, 2, 3, 4, 5, 10, 12, 13, 14], "belong": [1, 3, 5, 14], "tell": [1, 3, 5, 8, 10, 12, 14], "int": [1, 2, 3, 5, 6, 11, 15], "convert": [1, 5, 7, 8, 9, 11, 12, 13, 14, 15], "9": [0, 1, 4, 5, 11, 14, 15], "confus": [1, 4, 5, 6], "do": [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15], "126": [1, 2], "look": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "actual": [1, 4, 5, 6, 7, 8, 9, 12], "try": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15], "3": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "typeerror": [1, 2, 8, 10, 11], "unsupport": [1, 2], "operand": [1, 2, 5], "gener": [0, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15], "doe": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "support": [1, 2, 8, 11, 15], "larg": [0, 1, 4, 6, 8, 10, 12, 13], "tempt": [1, 6, 8, 9], "comma": [1, 2, 10, 11], "group": [1, 3, 8, 11], "000": [1, 7, 9, 10], "interpret": [0, 1, 2, 5, 13], "separ": [1, 2, 7, 9, 10, 11, 12, 13], "more": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "peopl": [0, 1, 3, 7, 8, 11, 15, 16], "speak": [1, 5, 11], "english": [1, 5, 7, 8, 9], "spanish": 1, "french": 1, "were": [0, 1, 4, 5, 7, 8, 9, 11, 13, 14], "evolv": 1, "applic": [1, 12, 13], "notat": [1, 2, 14], "particularli": 1, "good": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], "relationship": [1, 10, 12], "similarli": [1, 2, 5], "been": [0, 1, 4, 5, 7, 8, 9, 10, 12, 13, 16], "although": [1, 5, 9, 11, 12], "common": [0, 1, 2, 5, 6, 7, 8, 10, 11, 12, 14, 15], "differ": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "ambigu": 1, "full": [1, 8], "deal": [1, 6, 12, 14, 15], "contextu": 1, "clue": [1, 3], "nearli": [1, 12], "complet": [1, 2, 3, 4, 6, 13], "unambigu": 1, "exactli": [1, 2, 5, 8, 11], "one": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "regardless": [1, 10, 15], "context": [1, 11], "redund": [1, 2], "reduc": [1, 2, 10, 11], "misunderstand": [1, 6, 12], "As": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "often": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 15], "verbos": 1, "less": [0, 1, 5, 10, 14, 15], "concis": [0, 1, 4, 5, 6, 10, 11, 12, 14], "liter": [1, 2, 5], "idiom": 1, "metaphor": 1, "grow": 1, "hard": [0, 1, 2, 4, 5, 12, 13, 14, 16], "adjust": [1, 5], "dens": [1, 2], "longer": [1, 7, 10, 11, 12, 14], "top": [1, 4, 9, 10, 13], "bottom": [1, 3, 5], "left": [1, 2, 4, 5, 7, 8, 9, 11, 13, 14], "detail": [1, 4, 10, 13], "matter": [1, 10], "spell": [1, 2, 7, 8, 9, 10, 12, 13], "awai": 1, "big": [0, 1, 2, 4, 11, 12, 14], "mistak": 1, "whimsic": 1, "reason": [0, 1, 2, 3, 8, 13, 14], "bug": [1, 4, 12, 14], "process": [1, 3, 4, 5, 6, 8, 12, 14, 15], "track": [1, 2, 3, 7, 8, 10, 11, 12], "especi": [1, 12, 13, 14], "bring": [1, 3, 13], "strong": 1, "emot": 1, "struggl": 1, "difficult": [1, 2, 5, 7, 8, 11, 12, 14], "feel": [1, 12], "angri": 1, "sad": 1, "embarrass": 1, "prepar": 1, "reaction": 1, "One": [0, 1, 2, 4, 5, 6, 9, 10, 11, 12, 13, 15, 16], "approach": [1, 4, 14], "employe": 1, "certain": [1, 4, 9, 11, 13], "strength": 1, "speed": [1, 4, 5], "precis": [1, 3], "particular": [1, 8, 14], "weak": [1, 11], "lack": 1, "empathi": 1, "inabl": 1, "grasp": 1, "pictur": 1, "your": [0, 1, 2, 3, 4, 5, 6, 9, 11, 12, 13, 14], "job": [1, 10], "manag": 1, "find": [0, 1, 2, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16], "advantag": [1, 4, 7, 14], "mitig": 1, "engag": [0, 1], "without": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 15], "interfer": 1, "abil": [1, 3, 5, 7], "effect": [0, 1, 2, 3, 4, 5, 6, 8, 10, 11, 12, 14, 16], "frustrat": [0, 1, 3], "valuabl": 1, "skill": [0, 1, 3], "mani": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14], "activ": [1, 12], "beyond": 1, "At": [1, 2, 6, 8, 10, 12, 13, 14, 15, 16], "end": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "each": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "section": [0, 1, 4, 5, 8, 10, 12, 13, 14], "my": [0, 1, 2, 8], "suggest": [0, 1, 5, 6, 8, 10, 11, 12, 13, 14, 16], "hope": [0, 1], "fraction": [0, 1, 4, 7, 15], "part": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 15], "variabl": [1, 4, 5, 6, 8, 9, 10, 11, 13, 14], "statement": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15], "mai": [1, 3, 5, 7, 13], "argument": [1, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15], "produc": [1, 2, 5, 7, 8, 13], "run": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16], "consist": [0, 1, 10], "list": [0, 1, 2, 3, 4, 8, 12, 13], "imposs": [1, 12], "pars": 1, "therefor": 1, "categori": [1, 12], "purpos": [1, 2], "correct": [0, 1, 2, 3, 5, 6, 8, 9, 10, 11, 14], "through": [0, 1, 3, 6, 7, 8, 10, 11, 12, 13], "sever": [0, 1, 2, 3, 4, 9, 13, 14, 15], "chatbot": [1, 12], "would": [2, 4, 5, 8, 9, 10, 11, 12, 13, 14], "topic": [0, 1, 2, 5, 11, 12, 13, 14], "anyth": [0, 1, 3, 6, 10, 14, 16], "unclear": 1, "explan": [1, 10], "time": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], "encourag": [1, 2, 11], "own": [0, 1, 3, 5, 6, 12, 16], "earlier": [0, 1, 5, 6, 10], "mention": [1, 8, 10], "didn": [1, 4, 8], "why": [1, 2, 5, 6, 7, 10, 13, 14, 15], "pi": [1, 2, 4, 6], "place": [0, 1, 2, 3, 4, 5, 8, 11, 12, 14, 16], "There": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16], "modulu": [1, 11], "know": [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14], "answer": [1, 6, 7, 9, 11, 12], "question": [1, 3, 5, 7, 9, 11, 12, 13, 15], "pretti": [1, 3, 10], "reliabl": [1, 9, 14], "rememb": [1, 5], "wonder": [1, 3], "figur": [1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 13], "rule": [1, 2, 6, 7, 8], "44": 1, "curiou": [1, 2, 3, 12, 15], "should": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "again": [0, 1, 2, 3, 6, 7, 8, 10, 12], "better": [1, 4, 9, 10, 11, 12, 14, 16], "deliber": 1, "accident": 1, "both": [0, 1, 2, 5, 6, 7, 8, 9, 11, 12, 13, 14], "said": [8, 10, 11, 12], "guess": [1, 8], "765": 1, "718": 1, "give": [0, 1, 2, 3, 4, 5, 6, 7, 11, 12], "chanc": [1, 7, 8, 11, 12], "practic": [0, 1, 2, 6, 11, 15], "minut": [1, 5, 10, 14, 15], "mile": [1, 2], "10": [1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 14], "kilomet": [1, 2], "hint": [1, 3, 4, 5, 9, 11, 12, 13, 14, 15], "61": [1, 2], "race": 1, "averag": [1, 2, 10], "pace": 1, "per": [1, 2, 7, 16], "hour": [1, 2, 5, 14, 15], "alreadi": [0, 1, 6, 7, 8, 10, 11, 12, 13], "next": [1, 3, 5, 6, 7, 9, 10, 11, 12, 14], "below": [0, 3, 4, 5, 11, 16], "option": [2, 4, 5, 8, 9, 11, 13, 15], "view": 13, "nbviewer": [], "colab": [0, 16], "abl": [4, 5, 10], "exercis": [0, 16], "save": [6, 10], "modifi": [0, 3, 4, 7, 9, 10, 11, 12, 13, 14], "googl": 0, "drive": [], "github": [0, 3, 7], "repositori": 0, "click": 16, "assist": 0, "tool": [0, 1, 7, 8, 10, 16], "virtual": [0, 3, 4, 5, 10], "who": 16, "never": [0, 5, 6, 9, 12, 16], "tri": [0, 3, 8, 16], "had": [4, 7, 14, 16], "land": 16, "page": [0, 12, 16], "green": 16, "tea": 16, "press": [5, 16], "third": [2, 5, 6, 7, 8, 9, 10, 12, 14, 16], "biggest": [0, 16], "chang": [0, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16], "entir": [0, 9, 10, 16], "text": [0, 2, 3, 5, 8, 10, 13], "link": [0, 16], "instal": [0, 16], "substanti": [0, 12, 16], "revis": [0, 16], "reorder": 16, "lot": [0, 3, 5, 6, 7, 8, 11, 12, 14, 15, 16], "chatgpt": [0, 9, 16], "ai": 16, "schedul": 16, "publish": [0, 7, 8, 16], "o": [0, 8, 9, 10, 11, 12, 16], "reilli": [0, 16], "media": [0, 16], "juli": 16, "2024": 16, "progress": [0, 16], "januari": 5, "plan": [6, 14, 16], "releas": 16, "week": 16, "earli": [5, 16], "preorder": 16, "amazon": 16, "program": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "come": [0, 3, 6, 8, 10, 12, 14, 15, 16], "languag": [0, 2, 6, 8, 10, 12, 14, 15], "beginn": 0, "demand": 0, "probabl": [0, 4, 5, 11, 12, 14, 15], "easier": [0, 1, 3, 5, 7, 8, 10, 13, 14, 15], "ever": [0, 10], "With": [0, 4, 6, 9, 11, 12, 14], "alon": [0, 9, 12], "throughout": [0, 2], "wai": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "acceler": 0, "primarili": 0, "experi": [0, 9, 12], "too": [0, 2, 3, 4, 8, 10, 11, 12], "slow": 0, "challeng": [0, 3, 7, 11, 13], "talk": [0, 2, 5, 12], "document": [0, 2, 4, 7, 12], "person": [0, 12], "done": [0, 3, 6, 8, 9, 10, 12, 13, 14], "care": [0, 14], "defin": [0, 2, 4, 6, 7, 8, 12], "appear": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], "glossari": 0, "review": [0, 5], "introduc": [0, 2, 3, 4, 11], "mental": 0, "effort": [0, 14], "capac": 0, "just": [0, 5, 6, 8, 9, 10, 12, 13, 14], "carefulli": [0, 5], "warn": 0, "even": [0, 2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14], "experienc": [0, 14], "go": [0, 3, 4, 6, 7, 10, 11, 12, 14], "strategi": [0, 8, 11, 12], "fix": [0, 5, 6, 11, 12, 14], "incorrect": [0, 6, 7, 13], "ones": [0, 8, 12, 14], "build": [0, 6, 10, 12], "previou": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "move": [0, 3, 4, 12, 13, 15], "six": [0, 8, 14], "arithmet": [0, 2, 5, 11, 14], "condit": [0, 4, 7, 8, 15], "loop": [0, 3, 4, 5, 8, 11, 12, 13], "concept": 0, "function": [0, 7, 8, 9, 10, 11, 12, 13, 15], "recurs": [0, 13], "8": [0, 2, 3, 7, 11, 13], "string": [0, 2, 3, 4, 5, 6, 10, 11, 12, 14, 15], "sentenc": [0, 9, 12], "algorithm": [0, 6, 10, 11, 12], "core": [0, 2, 12], "data": [0, 10, 11, 12, 14], "dictionari": [0, 6, 13, 14], "tupl": [0, 12, 14, 15], "effici": [0, 9, 10, 11, 13, 14], "present": [0, 9, 10, 11], "analyz": [0, 12], "randomli": 0, "model": [0, 2, 12], "llm": [0, 12], "13": [0, 10], "store": [0, 2, 6, 10, 11, 12, 14], "long": [0, 2, 3, 4, 5, 8, 10, 12, 13], "storag": [0, 13], "databas": 0, "search": [0, 8, 9, 10, 13], "duplic": [0, 11, 13, 14], "14": 0, "17": [0, 2, 9, 14, 15], "object": [0, 2, 3, 7, 8, 10, 11, 13], "orient": [0, 14, 15], "oop": 0, "organ": [0, 10, 11, 13, 14], "librari": [0, 5], "written": [0, 2, 4, 5, 6, 7, 8, 12, 13], "style": [0, 6, 12, 14], "focu": 0, "subset": [0, 6], "greatest": [0, 6], "capabl": [0, 6, 13], "fewest": 0, "nevertheless": [0, 8], "solv": [0, 5, 7, 8, 10, 11, 14, 15], "18": [0, 1, 2, 13], "19": [0, 2], "thought": 0, "continu": [0, 2, 3, 8, 12], "journei": 0, "driven": 0, "technolog": 0, "notebook": [0, 7, 8, 10, 12, 15], "ordinari": [0, 15], "me": [0, 3], "keep": [0, 3, 4, 7, 8, 10, 11, 12, 13], "instruct": [0, 4, 12], "ad": [0, 4, 6, 10, 11, 12, 14], "advic": [0, 2, 16], "2016": 0, "predecessor": 0, "unawar": 0, "standard": [0, 8, 12, 14], "softwar": 0, "think": [0, 2, 8, 10, 12, 14], "transform": [0, 4, 11, 15], "motiv": 0, "regret": 0, "did": [0, 4, 6, 12, 13], "emphas": 0, "regrett": 0, "omiss": 0, "advent": 0, "autom": 0, "becom": [0, 6, 10, 12], "wide": [0, 4], "doctest": 0, "unittest": 0, "uneven": 0, "interest": [0, 3], "develop": [0, 2], "almost": [0, 2, 5, 8, 9, 10, 13, 15], "rearrang": [0, 9], "compress": 0, "short": [0, 4, 5, 10, 13], "expand": 0, "coverag": 0, "regular": [0, 4], "express": [0, 6, 7, 9, 11, 13, 14, 15], "clarifi": 0, "cut": 0, "could": [0, 2, 3, 4, 5, 6, 8, 10, 11, 12, 13], "am": [0, 5, 8, 12, 14], "veri": [0, 3, 4, 5, 6, 13], "proud": 0, "These": [0, 2, 3, 4, 7, 12, 13], "interact": [8, 15], "environ": [0, 2, 7, 15], "id": 0, "recommend": [0, 16], "avail": [0, 7, 8, 16], "io": 0, "case": [0, 2, 5, 6, 7, 8, 9, 12, 14], "spend": [0, 3, 6, 10, 12], "web": [0, 9], "browser": 0, "oper": [0, 2, 3, 4, 6, 8, 11, 12, 13, 14], "free": 0, "strongli": [0, 2], "solut": [0, 4, 5, 10, 11, 12, 15, 16], "along": [0, 4, 5, 11, 13, 15, 16], "classroom": [0, 16], "jupyter4edu": 0, "edu": 0, "live": [0, 8, 16], "instructor": [0, 9, 16], "student": [0, 16], "great": [0, 3, 16], "train": [0, 16], "carpentri": [0, 16], "thank": 0, "jeff": 0, "elkner": 0, "translat": [0, 6, 8], "java": 0, "got": [0, 2, 5, 7, 11, 12], "project": [0, 4, 8], "turn": [0, 2, 4, 5, 8, 10, 12], "favorit": [0, 3], "chri": 0, "meyer": 0, "contribut": 0, "foundat": [0, 3], "gnu": 0, "licens": [0, 8], "collabor": 0, "possibl": [0, 4, 5, 6, 7, 8, 9, 10, 12, 14], "creativ": 0, "maintain": [0, 13], "turtl": [0, 4, 6], "graphic": [0, 2, 3, 4, 14], "modul": [0, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14], "jupyterbook": 0, "servic": 0, "copilot": 0, "lulu": 0, "special": [0, 2, 3, 5, 6, 8, 9, 13, 14, 15], "technic": [0, 2], "melissa": 0, "lewi": 0, "luciano": 0, "ramalho": 0, "sam": 0, "lau": 0, "contributor": 0, "sent": 0, "100": [0, 4, 5, 7, 8, 10, 12], "sharp": 0, "ei": 0, "reader": [0, 2, 7, 8, 13], "over": [0, 9, 12, 14], "past": [0, 4, 5, 6, 13, 15], "year": [0, 13, 14, 15], "Their": 0, "enthusiasm": 0, "huge": 0, "pleas": 0, "send": [0, 15], "email": [0, 11], "feedback": 0, "least": [0, 5, 7, 9, 12, 13, 14], "fine": [0, 2], "quit": [0, 8, 14], "102": 1, "cell": [1, 2, 3, 5, 6, 11], "caus": [1, 2, 4, 5, 8, 10, 11, 12, 13, 15], "underscor": [1, 2, 15], "1_000_000": 1, "1000000": [1, 2], "recal": [1, 5, 11, 13], "quizz": [0, 16], "summ": [0, 16], "quiz": [0, 16], "soon": [0, 14, 16], "februari": 16, "refer": [2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15], "valu": [2, 3, 4, 5, 7, 8, 10, 12, 13, 15], "assign": [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15], "n": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13], "equal": [2, 5, 6, 8, 11, 12], "141592653589793": [2, 12], "output": [2, 3, 5, 6, 7, 8, 10, 11, 12, 13], "visibl": [2, 6], "howev": [2, 6, 9, 11, 15], "after": [2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15], "25": [2, 5, 6, 11], "283185307179586": 2, "paper": 2, "arrow": [2, 7, 10], "its": [2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "show": [2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14], "mind": [2, 4, 8], "uppercas": [2, 8, 12, 13], "convent": [2, 3, 4, 5, 9, 11, 15], "lower": [2, 7, 12], "your_nam": 2, "airspeed_of_unladen_swallow": 2, "million": [2, 9, 10], "15": [2, 3, 7], "76trombon": 2, "parad": 2, "16": [2, 11, 13], "obviou": [2, 4, 5, 12], "self": [2, 10, 15], "defenc": 2, "against": [2, 8], "fresh": 2, "fruit": [2, 8], "keyword": [2, 3, 4, 5, 7, 11, 12], "specifi": [2, 3, 8, 9, 12, 13, 14, 15], "fals": [2, 5, 6, 7, 9, 10, 13, 14, 15], "await": 2, "els": [2, 6, 7, 8, 10, 11, 12, 13], "pass": [2, 3, 5, 6, 7, 9, 11, 12, 13, 14, 15], "none": [2, 7, 8, 9, 10, 14], "break": [2, 5, 6, 8, 9, 11, 12], "true": [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "lambda": 2, "nonloc": 2, "while": [2, 10, 13], "assert": [2, 15], "del": 2, "global": [2, 7], "async": 2, "elif": [2, 5, 6, 8, 13], "yield": [2, 5, 12, 14], "memor": 2, "color": [2, 7, 8], "constant": [2, 4, 10], "dot": [2, 7, 8, 14], "sqrt": [2, 3, 6], "squar": [2, 6, 9, 10], "root": 2, "pow": [2, 3, 6], "either": [2, 5, 6, 8, 10, 12, 13], "unit": [2, 4], "execut": [2, 3, 4, 5, 6, 10, 15], "20": [2, 4, 9, 14], "approxim": 2, "parenthesi": 2, "normal": [2, 5, 13, 15], "noth": [2, 5, 6, 9, 13, 15], "101": 2, "base": [2, 3, 5, 6, 7, 11, 13, 14, 15], "142": [2, 12], "123": [2, 9], "type": [2, 4, 5, 7, 8, 9, 10, 11, 13, 15], "handl": [2, 5, 8, 12], "must": [2, 5, 7, 8, 11, 12, 15], "real": [2, 12, 15], "check": [2, 4, 5, 7, 8, 9, 10, 11, 12, 14, 15], "annoi": [2, 6], "detect": [2, 3, 10, 15], "bigger": [2, 10, 14], "complic": [2, 6, 7, 8, 12, 14], "formal": 2, "piec": [2, 4, 11, 12], "natur": [2, 6], "60": [2, 4, 5, 14, 15], "everyth": [2, 5, 12, 13], "non": [2, 6], "assum": [2, 5, 6, 7, 12, 14], "useless": 2, "v": [2, 8], "veloc": [2, 5], "tradeoff": 2, "occur": [2, 3, 5, 10], "runtim": [2, 3, 5, 10, 12], "semant": [2, 12], "distinguish": [2, 12, 15], "quickli": [2, 6, 9, 10, 12], "anywher": [2, 8, 9, 13], "immedi": [2, 6, 7, 8, 10], "goe": [2, 5, 8, 9, 11, 14], "stop": [2, 5, 7, 9, 10, 13], "relat": [2, 5, 8, 11], "intend": [2, 4, 6], "identifi": [2, 4, 8, 9, 12, 13, 15], "tricki": [2, 5, 8], "backward": [2, 8, 9, 10, 13], "suppos": [2, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15], "forget": [2, 4, 12], "represent": [2, 3, 13, 14], "set": [2, 4, 7, 10, 11, 12, 13], "definit": [2, 3, 4, 5, 6, 8, 12, 14, 15], "access": [2, 3, 6, 8, 13], "command": [2, 8, 15], "action": 2, "correspond": [2, 5, 10, 11, 13, 15], "paramet": [2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "exit": [2, 7, 8], "those": [2, 5, 6, 8, 9, 13, 15], "discourag": 2, "bad": 2, "built": [2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "consid": [2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "repeat": [2, 3, 4, 6, 7, 9, 12], "whenev": [2, 10, 14], "y": [2, 5, 6, 7, 11], "semi": 2, "colon": [2, 3, 10], "period": [2, 12, 13], "maath": 2, "calcul": [2, 6, 12], "volum": 2, "sphere": 2, "radiu": [2, 4, 6], "r": [2, 6, 7, 8, 9, 10, 11, 13], "frac": 2, "centimet": 2, "cubic": 2, "trigonometri": 2, "co": 2, "sin": 2, "Then": [2, 3, 4, 5, 6, 11, 12, 13, 14, 15], "sine": 2, "cosin": 2, "sum": [2, 5, 6, 9, 11, 14], "close": [2, 8, 13], "exact": 2, "e": [2, 7, 8, 9], "logarithm": [2, 5], "exp": 2, "slightli": 2, "lyric": [3, 12], "monti": [3, 6, 9, 11, 12, 14], "song": [3, 6, 12], "silli": 3, "demonstr": [3, 4, 6, 8, 10, 12, 14], "print_lyr": 3, "m": [3, 6, 8, 9, 11], "lumberjack": 3, "okai": 3, "sleep": 3, "night": 3, "dai": [3, 5, 7, 14, 15], "empti": [3, 8, 9, 10, 11, 12, 13], "header": [3, 5, 7, 14], "bodi": [3, 4, 6, 14], "indent": [3, 5, 6, 9, 15], "By": [3, 4, 6, 8, 10, 15], "four": [3, 4, 5, 7, 9, 10], "__main__": [3, 7, 14], "expon": 3, "print_twic": 3, "denni": 3, "moor": 3, "onc": [3, 4, 6, 7, 8, 9, 10, 11, 12], "www": [3, 8], "songfact": 3, "love": 3, "first_two_lin": 3, "last_three_lin": 3, "vers": 3, "print_vers": 3, "Of": [3, 6, 8, 9, 12], "cours": [3, 6], "fewer": [3, 8, 14], "simpl": [3, 4, 5, 7, 8, 11, 12, 13, 14], "rang": [3, 4, 7, 8, 11, 12], "usual": [3, 5, 7, 12, 13, 14, 15], "around": [3, 11, 13], "print_n_vers": 3, "given": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "anywai": [3, 6], "twice": [3, 4, 10, 11], "cat_twic": 3, "part1": 3, "part2": 3, "cat": [3, 13], "line1": 3, "line2": 3, "bright": 3, "side": [3, 4, 8, 9, 11], "life": 3, "destroi": 3, "nameerror": [3, 6], "outsid": [3, 6, 7, 9, 10], "draw": [3, 4, 5, 6], "state": [3, 5, 7, 9, 10, 14], "frame": [3, 5, 6, 10], "box": [3, 9, 10], "91": [3, 10], "66": [3, 7], "arrang": [3, 5], "topmost": 3, "recent": [3, 5, 12], "21": 3, "26": 3, "clear": [3, 4], "yet": [3, 4, 5, 6, 7, 9, 12, 14], "worth": [3, 7], "troubl": [3, 11], "opportun": [3, 4, 14], "smaller": [3, 6], "elimin": [3, 7, 10, 12], "allow": [3, 4, 7, 13, 14, 15], "reus": [3, 4], "fun": 3, "infer": 3, "event": [3, 5], "led": 3, "experiment": [3, 12], "hypothesi": [3, 12], "modif": 3, "step": [3, 4, 6, 7, 8, 10, 12, 13, 14], "closer": 3, "gradual": [3, 10, 14], "until": [3, 4, 7, 8, 11, 12], "linux": 13, "linu": [], "torvald": [], "explor": [5, 12], "intel": [], "80386": [], "chip": [], "accord": [6, 7, 12], "larri": [], "greenfield": [], "switch": [], "aaaa": [], "bbbb": [], "user": [5, 7, 11, 13, 15], "guid": [], "beta": [], "archiv": [], "20121024232944": [], "tldp": [], "pub": [], "doc": [4, 5], "ldp": [], "pdf": [], "gz": [], "repeatedli": 3, "everyon": 3, "agre": [3, 4], "histori": 3, "debat": 3, "tab": [3, 5, 7, 13], "va": [3, 4, 5, 9, 12, 13, 15], "pick": [3, 8, 13], "describ": [3, 5, 6, 12, 13], "enough": [3, 4, 10], "stuck": [3, 7, 9, 12, 13], "print_right": 3, "lead": [3, 13, 14], "40th": 3, "column": [3, 13, 14], "fly": 3, "circu": 3, "triangl": [3, 4, 5, 6], "pyramid": 3, "height": [3, 5], "made": [3, 8, 9, 11, 13], "level": [3, 14], "l": [3, 9, 11, 12], "shape": [3, 4], "lll": 3, "llll": 3, "lllll": 3, "rectangl": [3, 4], "width": 3, "h": 3, "hhhhh": 3, "99": 3, "bottl": 3, "beer": 3, "wall": 3, "98": [3, 12], "97": 3, "bottle_vers": 3, "imaginari": 4, "polygon": 4, "studi": [], "content": [8, 12, 13], "make_turtl": [4, 5], "canva": 4, "screen": [4, 14], "circular": [4, 6], "shell": [4, 8], "triangular": 4, "head": [4, 6, 8, 12], "locat": [4, 10], "direct": [4, 10, 11, 14], "face": 4, "distanc": [4, 6], "segment": [4, 8], "arbitrari": 4, "size": [4, 10, 11], "depend": [4, 5, 7, 12, 13], "nice": [4, 15], "angl": [4, 5], "degre": [4, 5], "50": [4, 5, 7, 12], "east": [4, 12], "north": 4, "behind": 4, "pair": [4, 10, 11, 12, 13], "wrap": [4, 11], "benefit": 4, "attach": 4, "serv": 4, "current": [4, 5, 7, 13], "360": 4, "adjac": 4, "numer": [4, 5], "remind": [4, 5, 9], "circumfer": 4, "control": [5, 6], "limit": [4, 5], "wast": 4, "arc": 4, "span": [4, 8], "180": 4, "half": [4, 12, 13], "instead": [4, 5, 6, 8, 10, 12, 13], "polylin": 4, "rewrit": [4, 5, 7, 12, 15], "arc_length": 4, "step_angl": 4, "similar": [4, 5, 6, 7, 8, 10, 12, 15], "snail": 4, "70": 4, "improv": [4, 6, 14], "ahead": [4, 14], "avoid": [4, 5, 6, 7, 9, 10, 11], "coher": 4, "retyp": 4, "appropri": 4, "factor": [4, 5], "drawback": 4, "implement": [4, 11, 12, 14], "wrote": [4, 6, 7, 11, 14, 15], "tripl": 4, "multilin": 4, "mayb": [4, 6, 10], "contract": 4, "caller": [4, 6, 9, 10], "understood": 4, "precondit": [4, 6], "convers": [4, 11, 14], "postcondit": [4, 6], "respons": [4, 9], "violat": [4, 6, 15], "correctli": [4, 6, 7, 14], "satisfi": 4, "pre": 4, "window": [4, 12, 13], "replac": [0, 4, 6, 10, 12, 13], "unnecessarili": [4, 14], "qualiti": 4, "enclos": [4, 8, 9, 10, 11], "penup": 4, "lift": 4, "pen": 4, "trail": 4, "pendown": 4, "jump": [4, 8], "80": [4, 12], "40": [4, 10, 14, 15], "tall": 4, "rhombu": 4, "interior": 4, "parallelogram": 4, "quadrilater": 4, "parallel": 4, "400": [], "120": [5, 14], "draw_pi": 4, "flower": 4, "petal": 4, "140": [], "custom": 4, "prompt": [4, 5, 7, 11], "spiral": 4, "unfortun": 0, "rewrot": [0, 15], "total": [7, 8, 9, 10, 14], "2562": [], "211180124223602": [], "21118": [], "412": [], "48200824964016": [], "48": [], "52": [5, 14], "48000000000002": [], "7116666666666667": [], "1118": [], "711666": [], "588017412662682": [], "cm": [], "523": 13, "5987755982989": [], "3890560989306495": [], "38905609893065": [], "num_spac": [], "bottle_lin": [], "suffix": 13, "length1": [], "length2": [], "icoscel": [], "peak": [], "middl": 5, "leg": 6, "pie": [], "radial": [], "spoke": 12, "subtend": [], "increas": [7, 10], "circular_spir": [], "rotat": [9, 10], "recalcul": [], "updat": [12, 13, 14], "traceback": [5, 7], "main": 5, "movi": [5, 14], "105": 5, "75": 5, "remaind": [5, 6, 11], "45": [5, 15], "Or": [5, 6, 8, 11, 13], "seem": [5, 6, 11, 14], "whether": [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "zero": [5, 6, 7, 8, 10, 14], "extract": 5, "clock": [5, 8], "11": [5, 13, 14, 15], "durat": [5, 14, 15], "pm": [5, 14], "compar": [5, 10, 13, 14], "otherwis": [5, 6, 7, 8, 9, 10, 11, 14], "bool": 5, "greater": [5, 8, 10], "negat": 5, "strictli": [5, 11], "strict": 5, "nonzero": 5, "flexibl": 5, "subtleti": 5, "accordingli": 5, "simplest": [5, 9], "block": [5, 10], "occasion": 5, "keeper": 5, "haven": [5, 6, 7, 9], "comment": 5, "odd": 5, "sinc": [5, 6, 7, 10, 12, 14], "branch": [5, 6], "abbrevi": 5, "within": [5, 9], "outer": 5, "appar": 5, "simplifi": [5, 7, 12], "magic": [5, 11], "countdown": [5, 6], "blastoff": 5, "print_n_tim": 5, "reach": [5, 12], "forev": [5, 6], "termin": [5, 6], "exce": [5, 14], "recursionerror": [5, 6], "38": [], "skip": 5, "2958": 5, "maximum": [5, 6, 11], "depth": [5, 6], "exceed": [5, 6], "3000": 5, "encount": [5, 13], "accid": 5, "confirm": [5, 6, 9, 11, 12, 13, 14], "guarante": 5, "accept": [5, 7], "wait": [4, 5], "enter": 5, "resum": 5, "arthur": 5, "king": 5, "briton": 5, "newlin": [5, 7, 8, 13], "airspe": 5, "unladen": 5, "swallow": [5, 12], "african": 5, "european": 5, "valueerror": [5, 9, 11], "overwhelm": [5, 6], "gotcha": 5, "invis": [5, 13], "indentationerror": 5, "mislead": 5, "ratio": 5, "decibel": 5, "denomin": 5, "log10": 5, "51": [], "domain": [5, 11], "valid": [5, 6, 7, 8, 12, 14, 15], "undefin": 5, "whose": [5, 11, 13, 14], "flow": [5, 6], "determin": [5, 6, 11, 12, 14], "seri": [5, 11, 12, 15], "eventu": 5, "exclus": 5, "saw": [5, 6, 7, 8, 10, 13, 14], "unnecessari": [5, 6, 10], "attempt": [5, 8, 11], "countdown_by_two": 5, "unix": [5, 13], "epoch": 5, "1970": 5, "00": [5, 14, 15], "utc": 5, "coordin": [5, 6], "univers": 5, "1708023665": [], "8504143": [], "html": 5, "seconds_per_dai": [], "24": 9, "19768": [], "3600": 14, "850414276123047": [], "stick": 5, "inch": 5, "meet": [5, 8], "cannot": [5, 8], "degener": 5, "is_triangl": 5, "ye": 5, "No": [5, 9], "nn": [], "ns": [], "koch": 5, "curv": 5, "300": [], "sierpi\u0144ski": 5, "yourself": [3, 5, 6, 8, 12], "june": [14, 15], "2023": [9, 13], "draw_sierpinski": 5, "200": [5, 8], "integr": 0, "jupyturtl": [0, 5], "delai": [4, 5], "default": [4, 8, 10, 11, 12, 13, 14, 15], "02": 4, "faster": [3, 4, 10], "fast": 4, "41": 5, "39": 5, "1709330456": [], "272177": [], "grate": 0, "much": [3, 8, 9, 10, 12, 14], "reorgan": 4, "b": [5, 6, 7, 8, 9, 10, 11, 13], "1709908595": [], "7334914": [], "656366395715726": 6, "312732791431452": 6, "circle_area": 6, "area": 6, "circl": 6, "00000000000001": 6, "63": 6, "000000000000014": 6, "finland": 6, "repeat_str": 6, "pure": [6, 7], "absolute_valu": 6, "hit": 6, "absolute_value_wrong": 6, "neither": 6, "extra": [6, 7, 8], "absolute_value_extra_return": 6, "dead": [6, 9], "harm": 6, "someon": [6, 12], "larger": [6, 8], "increasingli": 6, "amount": [6, 10], "x_1": 6, "y_1": 6, "x_2": 6, "y_2": 6, "pythagorean": 6, "theorem": 6, "mathrm": 6, "input": [6, 10, 12], "outlin": [6, 7, 10], "x1": 6, "y1": 6, "x2": 6, "y2": 6, "sampl": [6, 12], "chose": 6, "horizont": 6, "vertic": [6, 8], "hypotenus": 6, "temporari": [6, 11], "dx": 6, "dy": 6, "dsquar": 6, "d": [6, 7, 8, 9, 10, 11, 12, 13], "scaffold": [6, 10, 12], "product": 6, "kei": [6, 10, 11, 12, 13], "aspect": 6, "hold": 6, "intermedi": 6, "conveni": [6, 14], "encapsul": [6, 7, 12, 13, 14], "is_divis": 6, "directli": [6, 7, 11], "comparison": [6, 10], "threshold": 6, "ture": 6, "sens": [6, 7, 9, 12, 13, 14], "being": [6, 12], "truli": [6, 12], "vorpal": 6, "adject": [6, 8, 12], "On": [6, 13], "hand": [6, 8, 10], "factori": [6, 14], "align": 6, "fill": [6, 7], "stack": [6, 9, 14], "shown": [6, 9], "fact": [6, 7, 12, 14], "examin": [6, 12], "convinc": 6, "ourselv": 6, "impli": [6, 9], "bit": 6, "strang": [6, 12], "finish": [6, 12, 13], "explod": 6, "confid": 6, "asid": 6, "ineffici": [6, 10, 11, 13], "infinit": 6, "miss": 6, "initi": [6, 7, 8, 9, 10, 12, 14, 15], "isinst": [6, 14, 15], "crunchi": 6, "frog": 6, "checkpoint": 6, "explicitli": [6, 13], "littl": [6, 11], "dure": [6, 10, 13, 14, 15], "idiomat": [6, 14, 15], "spot": [6, 10, 13], "resembl": 6, "hypot": 6, "is_between": 6, "z": [6, 11], "ackermann": 6, "mbox": 6, "divisor": 6, "gcd": 6, "largest": [6, 9, 10, 11], "1939": 7, "ernest": 7, "vincent": 7, "wright": 7, "novel": [7, 8], "gadsbi": 7, "pattern": [7, 8, 10, 13], "linear": 7, "puzzl": [7, 10, 11], "bee": [7, 12], "g": [7, 9], "has_": 7, "emma": 7, "114": 7, "offici": [7, 13], "crossword": 7, "game": [7, 8, 11, 12], "txt": [7, 8, 9, 10, 12, 13], "open": [7, 8, 9, 10, 12, 13], "file_object": 7, "readlin": 7, "aa": [7, 10], "method": [7, 10, 11, 12, 13, 14], "associ": [7, 9, 10, 11, 14], "lava": 7, "aah": 7, "strip": [7, 8, 9, 12], "whitespac": [7, 13], "old": [7, 11, 13], "solid": 7, "increment": [7, 10, 12, 14], "decreas": [7, 11], "decrement": 7, "113783": [7, 9, 10], "counter": [7, 11], "76162": 7, "percentag": 7, "93618554617122": 7, "craft": [7, 11], "boolean": [7, 8], "lowercas": [7, 8, 9, 13], "unchang": [7, 9, 14], "simpler": 7, "uses_ani": [7, 8], "banana": [7, 8, 9, 10, 11], "aeiou": 7, "appl": [7, 8], "xyz": 7, "uses_onli": 7, "docstr": [7, 14], "run_docstring_exampl": 7, "run_doctest": 7, "func": 7, "__name__": 7, "fail": [7, 9, 12], "uses_any_incorrect": 7, "debug": 7, "uses_non": 7, "forbidden": 7, "efg": 7, "ban": [7, 8], "apl": 7, "uses_al": 7, "api": 7, "york": 7, "daili": 7, "seven": [7, 10, 11], "acdlort": 7, "told": 7, "rat": 7, "ratatat": 7, "check_word": [7, 8], "earn": 7, "pangram": 7, "score_word": 7, "lesson": 7, "score": [7, 11], "word_scor": 7, "card": 7, "cartload": 7, "abc": [7, 11], "appli": 8, "wordl": 8, "alphabet": [8, 11, 12], "white": 8, "select": [8, 9, 10, 11, 12], "bracket": [8, 9, 10, 11, 13], "index": [8, 9, 10, 11, 12], "offset": 8, "0th": 8, "pronounc": [8, 11, 14], "eth": 8, "indexerror": [8, 9], "th": 8, "exclud": 8, "counterintuit": 8, "imagin": [8, 14], "ana": [8, 10], "omit": [8, 9, 10], "intent": 8, "greet": 8, "j": 8, "item": [8, 10, 11, 12, 13], "refin": [8, 11], "variat": 8, "origin": [8, 9, 11, 12, 13, 14], "new_greet": 8, "jello": 8, "onto": 8, "compare_word": 8, "pineappl": 8, "format": [8, 10, 13, 14, 15], "defend": 8, "man": [8, 12], "arm": 8, "varieti": 8, "upper": 8, "new_word": 8, "invoc": 8, "invok": [8, 9, 15], "dracula": 8, "bram": 8, "stoker": 8, "gutenberg": 8, "ebook": 8, "345": 8, "plain": [8, 11], "pg345": 8, "materi": 8, "startswith": [8, 12], "is_special_lin": 8, "OF": 8, "THE": 8, "pg345_clean": 8, "writer": [8, 13], "w": [8, 13], "still": [8, 9, 12, 14], "success": [8, 12], "endswith": 8, "_by_": 8, "endswidth": 8, "iceland": 8, "1901": 8, "jonathan": 8, "thoma": 8, "clean": [8, 12, 13], "15499": 8, "199": 8, "pg345_replac": 8, "harker": 8, "titular": 8, "him": 8, "bid": 8, "welcom": 8, "mr": [8, 12], "hous": 8, "match": 8, "find_first": 8, "journal": 8, "easili": [8, 11], "bar": 8, "mina": 8, "murrai": 8, "she": 8, "luci": 8, "count_match": 8, "229": 8, "hi": 8, "feet": 8, "five": [8, 12], "born": 8, "ireland": 8, "1897": 8, "he": 8, "england": 8, "british": [8, 9], "centr": 8, "colour": [8, 12], "american": 8, "center": 8, "cent": 8, "er": 8, "horsesho": 8, "carpathian": 8, "sort": [8, 12, 13], "colou": 8, "u": [8, 10, 11, 12], "undergar": 8, "apron": 8, "front": 8, "stuff": 8, "edit": [8, 10], "sub": 8, "exclam": [8, 12], "tail": 8, "redirect": 8, "pg345_cleaned_10_lin": 8, "pg345_cleaned_100_lin": 8, "constitut": 8, "preced": 8, "backslash": [8, 13], "scratch": 8, "surfac": 8, "phone": 8, "hyphen": [8, 9, 12], "street": [8, 12], "address": [8, 11], "st": 8, "av": 8, "titl": 8, "capit": [8, 9, 13], "recogn": 8, "proper": 8, "noun": [8, 12], "target": 8, "mower": 8, "spade": 8, "clerk": 8, "totem": 8, "check_word2": 8, "begum": 8, "enzym": 8, "genom": 8, "venom": 8, "mont": 8, "cristo": 8, "alexandr": 8, "duma": 8, "classic": 8, "umberto": 8, "eco": 8, "confess": 8, "found": [8, 11, 12, 14], "badli": 8, "shameless": 8, "repetit": 8, "shudder": 8, "pale": 8, "pallor": 8, "palindrom": [9, 10], "anagram": [9, 11, 13], "chees": 9, "cheddar": 9, "edam": 9, "gouda": 9, "nest": 9, "unlik": 9, "wensleydal": 9, "c": [9, 10, 11, 13], "t1": [9, 11, 14, 15], "t2": [9, 11, 14, 15], "min": [9, 11], "max": [9, 11], "smallest": [9, 10, 11], "append": [9, 10, 11, 12, 13], "extend": [9, 14], "f": [9, 14, 15], "pop": [9, 12], "p": [9, 11, 12], "individu": 9, "split": [9, 10, 11, 12], "pine": 9, "fjord": 9, "delimit": 9, "boundari": 9, "ex": 9, "parrot": [9, 11], "scrambl": 9, "eelrstt": 9, "equival": [9, 14], "ident": [9, 14, 15], "necessarili": 9, "affect": [9, 13], "prone": [9, 11, 14], "safer": 9, "immut": [9, 10], "pop_first": 9, "lst": [9, 10], "04": 9, "06": [9, 14, 15], "persist": [9, 13], "properti": [9, 10], "word_list": [9, 10, 12], "113": 9, "1016511": 9, "demot": 9, "contrafibular": 9, "anaspept": 9, "opposit": 9, "plumag": 9, "attributeerror": [9, 11], "nonetyp": 9, "attribut": [9, 11, 15], "incorrectli": 9, "Being": 9, "televis": 9, "black": 9, "adder": 9, "season": 9, "episod": 9, "ink": 9, "incap": 9, "august": 9, "came": [9, 13], "claim": 9, "tom": 9, "stoppard": 9, "plai": [9, 11], "rosencrantz": 9, "guildenstern": 9, "accur": 9, "gain": 9, "sourc": 9, "role": 9, "is_anagram": 9, "revers": [9, 10, 11, 12], "0x7fe3de636b60": 9, "torrap": 9, "reverse_word": [9, 10], "noon": [9, 10], "is_palindrom": [9, 10], "reverse_sent": 9, "total_length": 9, "902": 9, "728": 9, "eleg": 10, "uniqu": [10, 11], "brace": [10, 13, 14], "isn": 10, "keyerror": [10, 12], "dict": [10, 11], "numbers_copi": 10, "hash": [10, 13], "tabl": [10, 13], "remark": 10, "stress": 10, "dessert": 10, "too_slow": 10, "roughli": 10, "billion": 10, "12946571089": 10, "word_dict": 10, "much_fast": 10, "hundredth": 10, "proport": [10, 12], "value_count": [10, 11], "brontosauru": 10, "travers": 10, "abcd": 10, "unhash": 10, "hashabl": [10, 11], "mutabl": [10, 11], "aren": 10, "task": 10, "aba": 10, "aga": 10, "aha": 10, "ala": 10, "alula": 10, "ama": 10, "anna": 10, "ava": 10, "long_palindrom": 10, "deifi": 10, "halalah": 10, "reifier": 10, "repap": 10, "reviv": 10, "semem": 10, "filter": 10, "ran": 10, "fibonacci": 10, "furthermor": 10, "graph": 10, "connect": [10, 15], "wors": 10, "previous": [10, 15], "memoiz": 10, "fibonacci_memo": 10, "microsecond": 10, "measur": 10, "dataset": 10, "unwieldi": 10, "scale": 10, "themselv": 10, "summari": 10, "saniti": 10, "insan": 10, "pprint": 10, "human": [10, 13], "readabl": [10, 13], "stand": [10, 12, 13], "calle": 10, "futur": 10, "gave": 10, "longest": [10, 12], "unpredict": [10, 12], "has_dupl": 10, "find_repeat": 10, "counter1": 10, "counter2": 10, "apatosauru": 10, "add_count": 10, "interlock": 10, "school": 10, "shoe": 10, "cold": 10, "slice": [10, 11, 12], "altogeth": 10, "is_interlock": 10, "unpack": 11, "tuh": 11, "ple": 11, "rhyme": 11, "suppl": 11, "quadrupl": 11, "necessari": [11, 13, 15], "lupin": 11, "lup": 11, "0x7f56c0072110": 11, "map": [11, 12, 13], "usernam": 11, "swap": 11, "temp": 11, "quotient": 11, "divmod": [11, 14, 15], "min_max": 11, "low": 11, "high": [11, 14], "arg": 11, "though": [11, 12], "treat": [11, 13, 14], "adapt": 11, "lowest": 11, "highest": 11, "trimmed_mean": 11, "trim": 11, "sport": 11, "subject": 11, "judg": 11, "dive": 11, "gymnast": 11, "deviat": 11, "team": 11, "record": [11, 13, 15], "scores1": 11, "scores2": 11, "teeth": 11, "zipper": 11, "0x7f3e9c74f0c0": 11, "pairwis": 11, "victori": 11, "win": 11, "team1": 11, "team2": 11, "sadli": 11, "lost": [11, 13], "abcdefghijklmnopqrstuvwxyz": 11, "letter_map": 11, "enumer": 11, "0x7f3e9c620cc0": 11, "subsequ": 11, "realli": 11, "2000000": 11, "minimum": 11, "frequent": [11, 12], "dict_item": 11, "behav": [11, 13], "ti": 11, "second_el": [11, 12], "sorted_item": 11, "invers": 11, "invert_dict": 11, "compound": 11, "structshap": [11, 14], "summar": 11, "t3": 11, "lt": 11, "pose": 11, "list0": 11, "list1": 11, "usageerror": 11, "encod": 11, "decod": 11, "caesar": 11, "cipher": 11, "encrypt": 11, "involv": [11, 14], "shift": 11, "shift_word": 11, "cheer": 11, "jolli": 11, "melon": 11, "cube": 11, "most_frequent_lett": 11, "frequenc": 11, "delta": 11, "desalt": 11, "salt": 11, "slate": [11, 13], "stale": 11, "retain": 11, "ternari": 11, "greaten": 11, "resmelt": 11, "smelter": 11, "termless": 11, "word_dist": 11, "metathesi": 11, "transposit": 11, "conserv": 11, "credit": 11, "inspir": 11, "puzzler": 11, "statist": 12, "phrase": 12, "dr": 12, "jekyl": 12, "hyde": 12, "robert": 12, "loui": 12, "stevenson": 12, "dr_jekyl": 12, "unique_word": 12, "seq": 12, "6040": 12, "6000": 12, "inspect": 12, "chocol": 12, "superior": 12, "behold": 12, "cool": 12, "frighten": 12, "gentleman": 12, "pocket": 12, "handkerchief": 12, "legitim": 12, "circumscript": 12, "dash": 12, "issu": [12, 13], "split_lin": 12, "unicod": 12, "intern": 12, "unicodedata": 12, "lu": 12, "po": 12, "subcategori": 12, "punc_mark": 12, "char": 12, "clean_word": 12, "unique_words2": 12, "4005": 12, "stricter": 12, "4000": 12, "unimpression": 12, "fellow": 12, "creatur": 12, "word_count": 12, "freq": 12, "sep": 12, "1614": 12, "972": 12, "941": 12, "640": 12, "ndigit": 12, "num": 12, "print_most_common": 12, "overrid": [12, 15], "misspel": 12, "scrabbl": 12, "valid_word": 12, "d1": 12, "d2": 12, "diff": 12, "628": 12, "128": 12, "utterson": 12, "124": 12, "mostli": 12, "friend": 12, "lawyer": 12, "singleton": 12, "gesticul": 12, "abject": 12, "reindu": 12, "fearstruck": 12, "reinduc": 12, "choos": [12, 15], "determinist": 12, "nondeterminist": 12, "fake": 12, "pseudorandom": 12, "simpli": 12, "choic": 12, "chosen": 12, "422": 12, "postur": 12, "ill": 12, "apocryph": 12, "nor": 12, "busi": 12, "account": 12, "weight": 12, "k": 12, "random_word": 12, "edward": 12, "seldom": 12, "articl": 12, "verb": 12, "adverb": 12, "trigram": 12, "unspecifi": 12, "gram": 12, "bigram_count": 12, "count_bigram": 12, "consecut": 12, "slide": 12, "process_word": 12, "thereaft": 12, "178": 12, "139": 12, "94": 12, "73": 12, "random_bigram": 12, "prefac": 12, "detain": 12, "abov": 12, "laboratori": 12, "chain": 12, "eric": 12, "philosoph": 12, "ipso": 12, "facto": 12, "vi": 12, "entiti": 12, "successor_map": 12, "successor": 12, "add_bigram": 12, "process_word_bigram": 12, "hesit": 12, "smile": 12, "wither": 12, "sound": 12, "meant": 12, "rumin": 12, "rubberduck": 12, "rubber": 12, "duck": 12, "en": 12, "wikipedia": 12, "rubber_duck_debug": 12, "retreat": 12, "undo": 12, "rebuild": 12, "brain": 12, "failur": 12, "typograph": 12, "conceptu": 12, "techniqu": 12, "reluct": 12, "delet": 12, "setdefault": 12, "add_word": [12, 13], "gpt": 12, "count_trigram": 12, "process_word_trigram": 12, "add_trigram": 12, "doubt": 12, "iter": 12, "recogniz": 12, "wander": 12, "bonu": 12, "1711200163": [], "3662615": [], "ephemer": 13, "disappear": 13, "shut": 13, "restart": 13, "versatil": 13, "photo": 13, "folder": 13, "getcwd": 13, "home": 13, "dinsdal": 13, "memo": 13, "rel": 13, "abspath": 13, "listdir": 13, "mar": 13, "jan": 13, "feb": 13, "imag": 13, "jpeg": 13, "photo3": 13, "jpg": 13, "photo2": 13, "photo1": 13, "apr": 13, "isdir": 13, "isfil": 13, "maco": 13, "camel": 13, "spotter": 13, "23": 13, "num_year": 13, "num_camel": 13, "easiest": 13, "unless": 13, "explanatori": 13, "month": [13, 14, 15], "configur": 13, "config": 13, "extens": 13, "photo_dir": 13, "data_dir": 13, "photo_info": 13, "dump": 13, "config_filenam": 13, "readback": 13, "safe_load": 13, "config_readback": 13, "serial": 13, "deseri": 13, "row": 13, "shelf": 13, "caption": 13, "makedir": 13, "exist_ok": 13, "db_file": 13, "db": 13, "dbfilenameshelf": 13, "0x7f5a2021c310": [], "casual": 13, "nose": 13, "dbm": 13, "dir": 13, "dat": 13, "revisit": 13, "opst": 13, "opt": 13, "post": 13, "pot": 13, "sort_word": 13, "anagram_map": 13, "anagram_list": 13, "rb": 13, "binari": 13, "byte": 13, "path1": 13, "data1": 13, "path2": 13, "data2": 13, "same_cont": 13, "digest": 13, "hashlib": 13, "md5": 13, "md5_hash": 13, "_hashlib": 13, "hexdigest": 13, "hexadecim": [13, 14], "aa1d2fc25b7ae247b2931f5a0882fa37": 13, "md5_digest": 13, "filename2": 13, "6a501b11b01f89af9c3f6591d7f02c49": 13, "subdirectori": 13, "dirnam": 13, "visit_func": 13, "visit": 13, "repr": 13, "inconsist": 13, "typic": 13, "indefinit": 13, "perman": 13, "load": 13, "relev": 13, "replace_al": 13, "is_imag": 13, "splitext": 13, "add_path": 13, "process_path": 13, "proce": 14, "lunch": 14, "memori": 14, "prefix": 14, "0x": 14, "0x7fbf2c427280": [], "instanti": [14, 15], "instanc": [14, 15], "emphasi": 14, "syllabl": 14, "AT": 14, "trib": 14, "ut": 14, "59": 14, "01": 14, "concern": 14, "82": [], "total_minut": 14, "719": 14, "02d": [14, 15], "print_tim": [14, 15], "make_tim": [14, 15], "surpris": 14, "holi": 14, "grail": 14, "92": 14, "32": [14, 15], "09": [14, 15], "increment_tim": 14, "alia": 14, "add_tim": [14, 15], "resort": 14, "compel": 14, "arriv": 14, "theater": 14, "72": 14, "field": 14, "132": [14, 15], "carri": 14, "tire": 14, "punctur": 14, "deep": 14, "unreli": 14, "insight": 14, "sexagesim": 14, "sixti": 14, "thirti": 14, "hundr": 14, "time_to_int": [14, 15], "3661": 14, "int_to_tim": [14, 15], "harder": 14, "abstract": 14, "intuit": 14, "invest": 14, "shorter": 14, "naiv": 14, "borrow": 14, "iron": 14, "hasattr": 14, "var": 14, "rough": 14, "draft": 14, "solidifi": 14, "pro": 14, "con": 14, "__init__": [14, 15], "patient": 14, "meantim": 14, "subtract_tim": [14, 15], "interv": 14, "is_aft": [14, 15], "date": [14, 15], "make_d": 14, "22": [14, 15], "1933": [14, 15], "print_dat": 14, "septemb": [14, 15], "date_to_tupl": 14, "characterist": 15, "explicit": 15, "receiv": 15, "analog": 15, "rewritten": 15, "add_method_to": 15, "34800": 15, "chicken": 15, "egg": 15, "from_second": 15, "__add__": 15, "invari": 15, "gone": 15, "is_valid": 15, "assertionerror": 15, "notabl": 15, "inherit": 15, "staticmethod": 15, "decor": 15, "to_tupl": 15, "1711201546": 5, "6627047": 5, "0x7fdf082603d0": 13, "0x7f7a700f5060": 14}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"program": [1, 16], "wai": [1, 16], "think": [1, 16], "arithmet": 1, "oper": [1, 5, 7, 9, 10, 15], "express": [1, 2, 5, 8, 16], "function": [1, 2, 3, 4, 5, 6, 14, 16], "string": [1, 7, 8, 9, 13, 16], "valu": [1, 6, 9, 11, 14, 16], "type": [1, 6, 14], "formal": 1, "natur": 1, "languag": 1, "debug": [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15], "glossari": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "exercis": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "ask": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "virtual": [1, 2, 6, 7, 8, 9, 11, 12, 13, 14, 15], "assist": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "python": 16, "3rd": 16, "edit": [0, 16], "The": [2, 4, 5, 7, 10, 15, 16], "notebook": 16, "2": 16, "an": [3, 4, 5, 10], "ai": [], "chapter": 16, "1": 16, "prefac": 0, "who": 0, "Is": 0, "thi": 0, "book": 0, "For": 0, "goal": 0, "navig": 0, "what": 0, "s": 0, "new": [0, 3], "third": 0, "get": 0, "start": 0, "resourc": [0, 16], "teacher": [0, 16], "acknowledg": 0, "variabl": [2, 3, 7, 16], "statement": [2, 5, 16], "state": 2, "diagram": [2, 3, 4, 5], "name": 2, "import": 2, "print": 2, "argument": [2, 9, 11], "comment": 2, "defin": [3, 14, 15], "paramet": [3, 12], "call": 3, "repetit": 3, "ar": [3, 8, 9, 11, 14], "local": 3, "stack": [3, 4, 5], "traceback": 3, "why": 3, "interfac": [4, 16], "turtl": [], "modul": 4, "make": [4, 9], "squar": 4, "encapsul": 4, "gener": [4, 12, 16], "approxim": 4, "circl": 4, "refactor": 4, "A": [4, 8, 9, 10], "develop": [4, 6, 14], "plan": 4, "docstr": 4, "condit": [5, 6, 16], "recurs": [5, 6, 16], "integ": 5, "divis": 5, "modulu": 5, "boolean": [5, 6], "logic": 5, "els": 5, "claus": 5, "chain": 5, "nest": 5, "infinit": 5, "keyboard": 5, "input": 5, "3": 16, "4": 16, "5": 16, "jupyturtl": 4, "return": [6, 11, 14, 16], "some": 6, "have": 6, "And": 6, "none": 6, "increment": 6, "leap": 6, "faith": 6, "fibonacci": 6, "check": [6, 13], "iter": [7, 16], "search": [7, 16], "loop": [7, 9, 10], "read": 7, "word": [7, 9, 12], "list": [7, 9, 10, 11, 16], "updat": 7, "count": 7, "doctest": 7, "regular": [8, 16], "sequenc": [8, 9], "slice": [8, 9], "immut": [8, 11], "comparison": 8, "method": [8, 9, 15, 16], "write": 8, "file": [8, 13, 16], "find": 8, "replac": 8, "substitut": 8, "mutabl": [9, 14], "through": 9, "sort": [9, 11], "object": [9, 14, 15], "alias": 9, "dictionari": [10, 11, 12, 16], "map": 10, "creat": 10, "collect": 10, "counter": 10, "accumul": 10, "memo": 10, "tupl": [11, 16], "like": 11, "But": 11, "assign": 11, "pack": 11, "zip": 11, "compar": [11, 15], "invert": 11, "text": [12, 16], "analysi": [12, 16], "uniqu": 12, "punctuat": 12, "frequenc": 12, "option": 12, "subtract": 12, "random": 12, "number": 12, "bigram": 12, "markov": 12, "6": 16, "7": 16, "8": 16, "9": 16, "10": 16, "11": 16, "12": 16, "databas": [13, 16], "filenam": 13, "path": 13, "f": 13, "yaml": 13, "shelv": 13, "store": 13, "data": 13, "structur": 13, "equival": 13, "walk": 13, "directori": 13, "class": [14, 15, 16], "programm": 14, "attribut": 14, "copi": 14, "pure": 14, "prototyp": 14, "patch": 14, "design": 14, "first": 14, "anoth": 15, "static": 15, "time": 15, "__str__": 15, "init": 15, "overload": 15, "13": 16, "14": 16, "15": 16}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx": 56}}) \ No newline at end of file +Search.setIndex({"docnames": ["chap00", "chap01", "chap02", "chap03", "chap04", "chap05", "chap06", "chap07", "chap08", "chap09", "chap10", "chap11", "chap12", "chap13", "chap14", "chap15", "chap16", "chap17", "chap18", "index"], "filenames": ["chap00.ipynb", "chap01.ipynb", "chap02.ipynb", "chap03.ipynb", "chap04.ipynb", "chap05.ipynb", "chap06.ipynb", "chap07.ipynb", "chap08.ipynb", "chap09.ipynb", "chap10.ipynb", "chap11.ipynb", "chap12.ipynb", "chap13.ipynb", "chap14.ipynb", "chap15.ipynb", "chap16.ipynb", "chap17.ipynb", "chap18.ipynb", "index.md"], "titles": ["Preface", "Programming as a way of thinking", "Variables and Statements", "Functions", "Functions and Interfaces", "Conditionals and Recursion", "Return Values", "Iteration and Search", "Strings and Regular Expressions", "Lists", "Dictionaries", "Tuples", "Text Analysis and Generation", "Files and Databases", "Classes and Functions", "Classes and Methods", "Classes and Objects", "Inheritance", "Python Extras", "Think Python, 3rd edition"], "terms": {"from": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "os": 13, "path": 6, "import": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 16, 17, 18], "basenam": [], "exist": [3, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18], "def": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "download": [0, 7, 8, 11, 12, 19], "url": 8, "filenam": [12, 18], "urllib": [], "request": [], "urlretriev": [], "local": [4, 6], "_": [2, 12], "print": [3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18], "str": [1, 2, 8, 11, 13, 15, 17, 18], "return": [2, 5, 7, 8, 9, 10, 12, 13, 15, 16, 17, 18], "http": [0, 1, 3, 5, 8, 11, 12], "raw": [8, 11], "githubusercont": 11, "com": [0, 3, 11], "allendownei": [0, 11], "thinkpython": [0, 11], "v3": 11, "py": [11, 18], "diagram": [6, 7, 9, 10, 14, 16, 17], "except": [2, 3, 4, 5, 6, 15, 18], "report": [4, 6, 18], "mode": [4, 6, 8, 12, 13], "minim": 5, "load_ext": [], "autoreload": [], "2": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18], "The": [0, 1, 3, 6, 8, 9, 11, 12, 13, 14, 16, 17, 18], "first": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18], "goal": [1, 13, 18], "thi": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "book": [1, 2, 3, 4, 7, 8, 10, 12, 13, 15, 16, 18, 19], "teach": [0, 1, 18, 19], "you": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "how": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "python": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "But": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18], "learn": [0, 1, 2, 3, 4, 7, 8, 9, 12, 17, 18, 19], "mean": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18], "new": [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "so": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "second": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "help": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15, 16, 17, 18, 19], "like": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19], "comput": [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18], "scientist": [0, 1, 17], "combin": [1, 5, 7, 13, 18], "some": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19], "best": [0, 1, 8, 10, 12, 16, 17, 19], "featur": [0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 14, 15, 16, 17, 18], "mathemat": [1, 2, 6, 9, 10, 18], "engin": [0, 1], "scienc": [1, 3, 12], "mathematician": 1, "us": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "denot": [1, 2, 6], "idea": [0, 1, 2, 3, 4, 5, 6, 8, 18], "specif": [1, 2, 4, 10, 17], "thei": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "design": [0, 1, 3, 4, 13, 17], "thing": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "assembl": [1, 3], "compon": [1, 10, 12], "system": [0, 1, 8, 10, 13, 16], "evalu": [1, 2, 6, 7, 11, 13, 15, 16, 17], "trade": 1, "off": [1, 2, 5, 8, 13], "among": [1, 8], "altern": [0, 1, 4, 5, 6, 10, 13, 14, 17], "observ": [1, 6, 13, 14], "behavior": [1, 4, 5, 8, 9, 11, 14, 15, 16, 17, 18], "complex": [1, 2, 5, 6], "form": [1, 5, 8, 9, 11, 16, 17, 18], "hypothes": 1, "test": [0, 1, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16, 17, 18], "predict": [1, 3], "we": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "start": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "most": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18], "basic": [0, 1, 9], "element": [0, 1, 2, 4, 7, 8, 9, 10, 11, 12, 16, 17, 18], "work": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "our": [1, 8, 10, 14, 16], "up": [1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 17, 18], "In": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "chapter": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "repres": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 18], "number": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 16, 17, 18], "letter": [0, 1, 2, 3, 7, 8, 9, 10, 11, 12, 13, 18], "word": [0, 1, 2, 3, 5, 6, 8, 10, 11, 13, 16, 17, 18], "And": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "ll": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "perform": [1, 2, 6, 8, 9, 10, 11, 13, 16, 18], "also": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "vocabulari": [0, 1, 2, 3, 12, 14], "includ": [0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 13, 15, 16, 17, 18], "term": [0, 1, 2, 13, 15], "need": [0, 1, 2, 5, 7, 11, 12, 13, 14, 15, 17, 18], "understand": [0, 1, 3, 4, 5, 6, 7, 10, 12, 14, 17, 18], "rest": [1, 3, 8, 11, 12], "commun": 1, "other": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "programm": [0, 1, 12, 15, 16], "todo": 5, "jupyt": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "introduct": [8, 19], "onlin": [0, 8, 19], "version": [0, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18], "onli": [0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18], "an": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19], "symbol": [1, 2, 6, 8, 12, 13], "For": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "exampl": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "plu": 1, "sign": [1, 3, 4, 5], "addit": [0, 1, 2, 5, 8, 13, 14, 17, 18], "30": [1, 4, 10, 16], "12": [0, 1, 2, 5, 8, 13, 14, 15, 17, 18], "42": [1, 2, 5, 6, 9], "minu": 1, "subtract": [1, 5, 8, 14, 18], "43": 1, "1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18], "asterisk": 1, "multipl": [1, 2, 8, 9, 11, 13, 16], "6": [1, 2, 5, 6, 8, 10, 11, 12, 17, 18], "7": [0, 1, 4, 5, 6, 7, 9, 10, 11, 17, 18], "forward": [1, 4, 5, 9, 10, 13], "slash": [1, 13], "divis": [1, 6], "84": 1, "0": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18], "notic": [1, 2, 4, 6, 7, 10, 12, 14, 15, 18], "result": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "rather": [0, 1, 6, 7, 8, 11, 13, 14, 15, 17, 18], "than": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "That": [0, 1, 3, 4, 6, 7, 10, 11, 13, 16, 18], "s": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "becaus": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "ar": [0, 1, 2, 4, 5, 6, 7, 10, 12, 13, 15, 16, 17, 18, 19], "two": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "integ": [1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15, 17], "which": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "whole": [0, 1, 3, 6, 8, 9, 14, 18, 19], "float": [1, 2, 3, 5, 8, 9, 10, 11, 18], "point": [1, 2, 3, 5, 6, 7, 8, 10, 12, 14, 17, 18], "decim": [1, 2, 5, 12], "If": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "add": [1, 2, 4, 6, 7, 9, 10, 12, 13, 14, 15, 16, 18], "multipli": [1, 6], "divid": [1, 3, 4, 5, 6, 11, 12, 14], "provid": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "anoth": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18], "alwai": [1, 3, 4, 5, 6, 9, 10, 11, 13, 15, 16, 17, 18], "call": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "floor": [1, 5], "round": [1, 2, 5, 6, 12, 13, 17], "down": [1, 2, 3, 4, 5, 10, 12, 13, 16], "toward": [1, 12], "85": [1, 9], "final": [0, 1, 2, 3, 4, 5, 6, 11, 13, 17], "exponenti": [1, 2], "rais": [1, 2, 18], "power": [0, 1, 2, 5, 8], "49": 1, "caret": 1, "bitwis": 1, "xor": [1, 5], "familiar": [1, 2, 10, 17, 18], "might": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "unexpect": [1, 5, 18], "5": [1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 17, 18], "i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "won": [1, 2, 4, 8, 10, 11, 15, 16], "t": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "cover": [0, 1, 12, 14, 15], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "read": [0, 1, 2, 3, 5, 6, 8, 9, 10, 12, 13, 14, 15, 17, 18, 19], "about": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19], "them": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "wiki": [1, 12], "org": [1, 5, 8, 11, 12], "moin": 1, "bitwiseoper": 1, "A": [0, 1, 2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16, 17, 18], "collect": [1, 2, 8, 11, 13, 17, 18], "contain": [0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "ani": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17], "here": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "happen": [1, 2, 3, 5, 6, 7, 9, 10, 12, 14, 15, 16, 17, 18], "befor": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "follow": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "order": [0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18], "have": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "math": [1, 2, 3, 4, 5, 6, 16, 18], "class": [1, 2, 17, 18], "want": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18], "parenthes": [1, 3, 8, 11, 17], "90": [4, 14, 16], "everi": [0, 1, 2, 4, 5, 6, 7, 10, 12, 13, 15, 17, 18, 19], "ha": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "few": [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 17, 18, 19], "take": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "nearest": 1, "4": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13, 16, 17, 18], "ab": [1, 3, 6, 18], "absolut": [1, 13], "posit": [1, 4, 5, 8, 11, 15, 18], "itself": [0, 1, 2, 5, 13, 16, 17], "neg": [1, 5, 6, 8, 9], "when": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "sai": [1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17], "re": [1, 4, 8, 10, 12, 13, 14, 15, 17, 18], "requir": [1, 2, 3, 4, 7, 8, 12, 15, 17, 18], "leav": [1, 4, 8, 9, 10, 12, 14, 18], "out": [0, 1, 2, 4, 5, 6, 8, 10, 12, 16, 17, 18], "get": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "error": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "messag": [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 15, 17, 18], "note": [2, 6, 8, 9, 11, 13], "TO": [], "editor": 0, "line": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18], "begin": [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18], "expect": [2, 4, 5, 7, 8, 11, 12, 14, 16], "remov": [6, 7, 8, 9, 11, 12], "automat": [10, 11, 18], "file": [0, 2, 3, 7, 9, 10, 12, 18], "syntaxerror": [1, 2], "invalid": [1, 2, 5], "syntax": [1, 2, 5, 7, 8, 12, 15, 18], "ignor": [1, 2, 5, 7, 8, 13], "doesn": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 17, 18], "inform": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "right": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 18], "now": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "code": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "beneath": 1, "indic": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "where": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19], "wa": [0, 1, 3, 5, 7, 8, 9, 12, 15, 16], "discov": [1, 5, 7, 13, 14], "last": [1, 2, 3, 5, 6, 8, 11, 12, 13, 18], "someth": [1, 2, 4, 5, 6, 7, 8, 10, 12, 15, 17, 18], "wrong": [1, 2, 3, 5, 6, 7, 9, 11, 12, 15, 16, 17], "structur": [0, 1, 2, 5, 7, 11, 12, 17], "problem": [0, 1, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "let": [1, 2, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17], "see": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18], "what": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "x": [1, 2, 5, 6, 7, 9, 11, 16, 18], "name": [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "all": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], "legal": [1, 2, 3, 5, 7, 8, 17], "displai": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18], "explain": [1, 2, 4, 6, 7, 9, 12, 13, 14, 16, 17, 18], "later": [1, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 18], "sequenc": [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 17, 18], "strung": 1, "togeth": [1, 3, 4, 8, 11, 12, 13], "bead": 1, "necklac": 1, "To": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "write": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "put": [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18], "insid": [1, 3, 6, 7, 9, 10, 14, 15, 16, 17, 18], "straight": [1, 5, 17], "quotat": [1, 8, 10, 12, 13], "mark": [1, 3, 8, 10, 12, 13], "hello": [1, 8], "It": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18], "doubl": [1, 5, 8], "world": [1, 8, 15], "quot": [1, 4], "make": [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18], "easi": [0, 1, 4, 5, 6, 13, 17, 18], "apostroph": 1, "same": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "small": [1, 3, 4, 6, 8, 12, 16, 17, 18], "space": [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 16, 17, 18], "punctuat": [1, 2, 8], "digit": [1, 2, 5, 8, 13, 14], "well": [1, 3, 4, 5, 8, 12, 17], "join": [1, 9, 10, 11, 12, 13, 17, 18], "singl": [1, 2, 5, 7, 8, 9, 11, 12, 18], "concaten": [1, 3, 8, 9, 11], "copi": [1, 3, 4, 9, 10, 12, 13, 17], "spam": [1, 3, 5, 6, 9, 11], "don": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 14, 16, 17, 18, 19], "len": [1, 2, 3, 8, 9, 10, 11, 12, 13, 17, 18], "length": [1, 4, 5, 6, 8, 9, 10, 11, 12], "count": [1, 3, 5, 8, 9, 10, 11, 12, 17, 18], "between": [1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18], "creat": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18], "sure": [1, 5, 6, 7, 8, 14, 15], "back": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 17, 18], "known": [1, 4, 5, 10, 12, 14], "backtick": 1, "charact": [1, 2, 3, 5, 6, 7, 8, 9, 12, 13], "smart": 1, "curli": [1, 10, 13, 14, 16], "illeg": [1, 2], "far": [0, 1, 2, 3, 5, 6, 7, 11, 12, 13, 14, 17], "ve": [0, 1, 2, 3, 6, 7, 8, 10, 11, 12, 13, 16, 18], "seen": [1, 2, 3, 4, 7, 10, 11, 12, 13, 16, 18], "three": [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18], "kind": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "sometim": [1, 2, 3, 4, 5, 10, 12, 13, 14, 17, 18], "belong": [1, 3, 5, 14, 17], "tell": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "int": [1, 2, 3, 5, 6, 11, 15, 18], "convert": [1, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18], "9": [0, 1, 4, 5, 11, 14, 15, 17, 18], "confus": [1, 4, 5, 6, 16], "do": [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "126": [1, 2], "look": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], "actual": [1, 4, 5, 6, 7, 8, 9, 12, 18], "try": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15], "3": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18], "typeerror": [1, 2, 8, 10, 11, 17, 18], "unsupport": [1, 2], "operand": [1, 2, 5], "gener": [0, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 16, 17, 18], "doe": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "support": [1, 2, 8, 11, 15, 17, 18], "larg": [0, 1, 4, 6, 8, 10, 12, 13, 17, 18], "tempt": [1, 6, 8, 9], "comma": [1, 2, 10, 11], "group": [1, 3, 8, 11], "000": [1, 7, 9, 10, 18], "interpret": [0, 1, 2, 5, 13], "separ": [1, 2, 7, 9, 10, 11, 12, 13, 16, 17], "more": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "peopl": [0, 1, 3, 7, 8, 11, 15, 17, 19], "speak": [1, 5, 11], "english": [1, 5, 7, 8, 9, 18], "spanish": 1, "french": 1, "were": [0, 1, 4, 5, 7, 8, 9, 11, 13, 14, 18], "evolv": 1, "applic": [1, 12, 13], "notat": [1, 2, 14], "particularli": 1, "good": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 18], "relationship": [1, 10, 12, 17], "similarli": [1, 2, 5], "been": [0, 1, 4, 5, 7, 8, 9, 10, 12, 13, 18, 19], "although": [1, 5, 9, 11, 12], "common": [0, 1, 2, 5, 6, 7, 8, 10, 11, 12, 14, 15, 17, 18], "differ": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "ambigu": 1, "full": [1, 8, 17], "deal": [1, 6, 12, 14, 15, 17], "contextu": 1, "clue": [1, 3], "nearli": [1, 12], "complet": [1, 2, 3, 4, 6, 13, 16, 17], "unambigu": 1, "exactli": [1, 2, 5, 8, 11], "one": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "regardless": [1, 10, 15, 17], "context": [1, 11, 17], "redund": [1, 2], "reduc": [1, 2, 10, 11], "misunderstand": [1, 6, 12], "As": [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "often": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 15, 16, 17, 18], "verbos": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "less": [0, 1, 5, 10, 14, 15, 17], "concis": [0, 1, 4, 5, 6, 10, 11, 12, 14, 16, 17, 18], "liter": [1, 2, 5], "idiom": 1, "metaphor": 1, "grow": [1, 16], "hard": [0, 1, 2, 4, 5, 12, 13, 14, 16, 17, 19], "adjust": [1, 5], "dens": [1, 2], "longer": [1, 7, 10, 11, 12, 14], "top": [1, 4, 9, 10, 13, 18], "bottom": [1, 3, 5], "left": [1, 2, 4, 5, 7, 8, 9, 11, 13, 14, 16, 17, 18], "detail": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "matter": [1, 10, 16], "spell": [1, 2, 7, 8, 9, 10, 12, 13, 18], "awai": 1, "big": [0, 1, 2, 4, 11, 12, 14], "mistak": 1, "whimsic": 1, "reason": [0, 1, 2, 3, 8, 13, 14], "bug": [1, 4, 12, 14, 16], "process": [1, 3, 4, 5, 6, 8, 12, 14, 15, 18], "track": [1, 2, 3, 7, 8, 10, 11, 12, 18], "especi": [1, 12, 13, 14], "bring": [1, 3, 13], "strong": 1, "emot": 1, "struggl": 1, "difficult": [1, 2, 5, 7, 8, 11, 12, 14, 17], "feel": [1, 12], "angri": 1, "sad": 1, "embarrass": 1, "prepar": 1, "reaction": 1, "One": [0, 1, 2, 4, 5, 6, 9, 10, 11, 12, 13, 15, 17, 18, 19], "approach": [1, 4, 14], "employe": 1, "certain": [1, 4, 9, 11, 13], "strength": 1, "speed": [1, 4, 5], "precis": [1, 3], "particular": [1, 8, 14, 17], "weak": [1, 11], "lack": 1, "empathi": 1, "inabl": 1, "grasp": 1, "pictur": 1, "your": [0, 1, 2, 3, 4, 5, 6, 9, 11, 12, 13, 14, 17], "job": [1, 10], "manag": 1, "find": [0, 1, 2, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "advantag": [1, 4, 7, 14], "mitig": 1, "engag": [0, 1], "without": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 15, 16, 17, 18], "interfer": 1, "abil": [1, 3, 5, 7, 16, 17], "effect": [0, 1, 2, 3, 4, 5, 6, 8, 10, 11, 12, 14, 16, 18, 19], "frustrat": [0, 1, 3], "valuabl": 1, "skill": [0, 1, 3], "mani": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18], "activ": [1, 12], "beyond": [1, 16], "At": [1, 2, 6, 8, 10, 12, 13, 14, 15, 16, 19], "end": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19], "each": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "section": [0, 1, 4, 5, 8, 10, 12, 13, 14, 16], "my": [0, 1, 2, 8, 18], "suggest": [0, 1, 5, 6, 8, 10, 11, 12, 13, 14, 18, 19], "hope": [0, 1], "fraction": [0, 1, 4, 7, 15], "part": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 15, 16, 17], "variabl": [1, 4, 5, 6, 8, 9, 10, 11, 13, 14, 16, 17, 18], "statement": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 18], "mai": [1, 3, 5, 7, 13, 17], "argument": [1, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 17], "produc": [1, 2, 5, 7, 8, 13], "run": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "consist": [0, 1, 10, 17], "list": [0, 1, 2, 3, 4, 8, 12, 13, 16, 17], "imposs": [1, 12], "pars": 1, "therefor": 1, "categori": [1, 12], "purpos": [1, 2], "correct": [0, 1, 2, 3, 5, 6, 8, 9, 10, 11, 14], "through": [0, 1, 3, 6, 7, 8, 10, 11, 12, 13, 16, 17, 18], "sever": [0, 1, 2, 3, 4, 9, 13, 14, 15, 16, 17, 18], "chatbot": [1, 12], "would": [2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18], "topic": [0, 1, 2, 5, 11, 12, 13, 14, 16, 17, 18], "anyth": [0, 1, 3, 6, 10, 14, 17, 19], "unclear": 1, "explan": [1, 10], "time": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19], "encourag": [1, 2, 11, 17], "own": [0, 1, 3, 5, 6, 12, 17, 19], "earlier": [0, 1, 5, 6, 10, 18], "mention": [1, 8, 10, 18], "didn": [1, 4, 8, 17], "why": [1, 2, 5, 6, 7, 10, 13, 14, 15, 16, 18], "pi": [1, 2, 4, 6], "place": [0, 1, 2, 3, 4, 5, 8, 11, 12, 14, 17, 19], "There": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19], "modulu": [1, 11], "know": [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 17], "answer": [1, 6, 7, 9, 11, 12], "question": [1, 3, 5, 7, 9, 11, 12, 13, 15, 18], "pretti": [1, 3, 10], "reliabl": [1, 9, 14], "rememb": [1, 5, 18], "wonder": [1, 3], "figur": [1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 13, 16, 17], "rule": [1, 2, 6, 7, 8, 17], "44": 1, "curiou": [1, 2, 3, 12, 15, 16, 18], "should": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "again": [0, 1, 2, 3, 6, 7, 8, 10, 12, 16], "better": [1, 4, 9, 10, 11, 12, 14, 16, 19], "deliber": 1, "accident": 1, "both": [0, 1, 2, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18], "said": [8, 10, 11, 12], "guess": [1, 8, 16], "765": 1, "718": 1, "give": [0, 1, 2, 3, 4, 5, 6, 7, 11, 12], "chanc": [1, 7, 8, 11, 12, 18], "practic": [0, 1, 2, 6, 11, 15, 16, 18], "minut": [1, 5, 10, 14, 15], "mile": [1, 2], "10": [1, 2, 4, 5, 6, 8, 9, 10, 11, 12, 14, 17, 18], "kilomet": [1, 2], "hint": [1, 3, 4, 5, 9, 11, 12, 13, 14, 15, 18], "61": [1, 2], "race": 1, "averag": [1, 2, 10], "pace": 1, "per": [1, 2, 7, 19], "hour": [1, 2, 5, 14, 15], "alreadi": [0, 1, 6, 7, 8, 10, 11, 12, 13, 18], "next": [1, 3, 5, 6, 7, 9, 10, 11, 12, 14, 16, 17], "below": [0, 3, 4, 5, 11, 18, 19], "option": [2, 4, 5, 8, 9, 11, 13, 15, 17, 18], "view": 13, "nbviewer": [], "colab": [0, 19], "abl": [4, 5, 10], "exercis": [0, 19], "save": [6, 10, 17, 18], "modifi": [0, 3, 4, 7, 9, 10, 11, 12, 13, 14, 16, 17, 18], "googl": 0, "drive": [], "github": 0, "repositori": 0, "click": 19, "assist": 0, "tool": [0, 1, 7, 8, 10, 18, 19], "virtual": [0, 10], "who": 19, "never": [0, 5, 6, 9, 12, 16, 18, 19], "tri": [0, 3, 8, 19], "had": [4, 7, 14, 17, 19], "land": 19, "page": [0, 12, 19], "green": 19, "tea": 19, "press": [5, 19], "third": [2, 5, 6, 7, 8, 9, 10, 12, 14, 16, 17, 19], "biggest": [0, 19], "chang": [0, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, 19], "entir": [0, 9, 10, 19], "text": [0, 2, 3, 5, 8, 10, 13, 16], "link": [0, 19], "instal": [0, 19], "substanti": [0, 12, 19], "revis": [0, 19], "reorder": 19, "lot": [0, 3, 5, 6, 7, 8, 11, 12, 14, 15, 18, 19], "chatgpt": [0, 9, 19], "ai": [17, 18, 19], "schedul": 19, "publish": [0, 7, 8, 19], "o": [0, 8, 9, 10, 11, 12, 19], "reilli": [0, 19], "media": [0, 19], "juli": 19, "2024": 19, "progress": [0, 19], "januari": 5, "plan": [6, 14, 19], "releas": 19, "week": 19, "earli": [5, 19], "preorder": 19, "amazon": 19, "program": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "come": [0, 3, 6, 8, 10, 12, 14, 15, 16, 17, 19], "languag": [0, 2, 6, 8, 10, 12, 14, 15, 17], "beginn": 0, "demand": 0, "probabl": [0, 4, 5, 11, 12, 14, 15], "easier": [0, 1, 3, 5, 7, 8, 10, 13, 14, 15, 16, 17], "ever": [0, 10], "With": [0, 4, 6, 9, 11, 12, 14, 16, 17, 18], "alon": [0, 9, 12], "throughout": [0, 2], "wai": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "acceler": 0, "primarili": 0, "experi": [0, 9, 12], "too": [0, 2, 3, 4, 8, 10, 11, 12], "slow": 0, "challeng": [0, 3, 7, 11, 13], "talk": [0, 2, 5, 12, 16], "document": [0, 2, 4, 7, 12, 18], "person": [0, 12], "done": [0, 3, 6, 8, 9, 10, 12, 13, 14, 17, 18], "care": [0, 14], "defin": [0, 2, 4, 6, 7, 8, 12, 16, 17, 18], "appear": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 17, 18], "glossari": 0, "review": [0, 5], "introduc": [0, 2, 3, 4, 11], "mental": 0, "effort": [0, 14], "capac": 0, "just": [0, 5, 6, 8, 9, 10, 12, 13, 14, 17], "carefulli": [0, 5], "warn": 0, "even": [0, 2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 17], "experienc": [0, 14], "go": [0, 3, 4, 6, 7, 10, 11, 12, 14, 17, 18], "strategi": [0, 8, 11, 12, 17], "fix": [0, 5, 6, 11, 12, 14], "incorrect": [0, 6, 7, 13, 18], "ones": [0, 8, 12, 14, 17], "build": [0, 6, 10, 12, 17], "previou": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "move": [0, 3, 4, 12, 13, 15, 16], "six": [0, 8, 14, 17], "arithmet": [0, 2, 5, 11, 14, 18], "condit": [0, 4, 7, 8, 15], "loop": [0, 3, 4, 5, 8, 11, 12, 13, 16, 17, 18], "concept": 0, "function": [0, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18], "recurs": [0, 13, 18], "8": [0, 2, 3, 7, 11, 13, 17], "string": [0, 2, 3, 4, 5, 6, 10, 11, 12, 14, 15, 16, 17, 18], "sentenc": [0, 9, 12], "algorithm": [0, 6, 10, 11, 12, 17], "core": [0, 2, 12], "data": [0, 10, 11, 12, 14], "dictionari": [0, 6, 13, 14, 17, 18], "tupl": [0, 12, 14, 15, 16, 17], "effici": [0, 9, 10, 11, 13, 14, 17, 18], "present": [0, 9, 10, 11], "analyz": [0, 12], "randomli": 0, "model": [0, 2, 12], "llm": [0, 12], "13": [0, 2, 10, 17], "store": [0, 2, 6, 10, 11, 12, 14, 16], "long": [0, 2, 3, 4, 5, 8, 10, 12, 13], "storag": [0, 13], "databas": 0, "search": [0, 8, 9, 10, 13, 17, 18], "duplic": [0, 11, 13, 14, 16, 18], "14": [0, 2, 17], "17": [0, 2, 9, 14, 15, 18], "object": [0, 2, 3, 7, 8, 10, 11, 12, 13, 17, 18], "orient": [0, 14, 15, 16, 17], "oop": [0, 16, 17], "organ": [0, 10, 11, 13, 14], "librari": [0, 5], "written": [0, 2, 4, 5, 6, 7, 8, 12, 13, 17, 18], "style": [0, 6, 12, 14], "focu": 0, "subset": [0, 6, 18], "greatest": [0, 6], "capabl": [0, 6, 13], "fewest": 0, "nevertheless": [0, 8], "solv": [0, 5, 7, 8, 10, 11, 14, 15, 18], "18": [0, 1, 2, 13], "19": [0, 2], "thought": 0, "continu": [0, 2, 3, 8, 12, 16], "journei": 0, "driven": 0, "technolog": 0, "notebook": [0, 7, 8, 10, 12, 15], "ordinari": [0, 15], "me": [0, 3, 18], "keep": [0, 3, 4, 7, 8, 10, 11, 12, 13, 16, 18], "instruct": [0, 4, 12], "ad": [0, 4, 6, 10, 11, 12, 14, 17, 18], "advic": [0, 2, 17, 19], "2016": 0, "predecessor": 0, "unawar": 0, "standard": [0, 8, 12, 14, 17, 18], "softwar": 0, "think": [0, 2, 8, 10, 12, 14, 16], "transform": [0, 4, 11, 15], "motiv": 0, "regret": 0, "did": [0, 4, 6, 12, 13, 16], "emphas": 0, "regrett": 0, "omiss": 0, "advent": 0, "autom": [0, 18], "becom": [0, 6, 10, 12], "wide": [0, 4, 17], "doctest": [0, 18], "unittest": [0, 18], "uneven": 0, "interest": [0, 3], "develop": [0, 2, 16], "almost": [0, 2, 5, 8, 9, 10, 13, 15, 18], "rearrang": [0, 9, 18], "compress": 0, "short": [0, 4, 5, 10, 13, 17], "expand": 0, "coverag": 0, "regular": [0, 4], "express": [0, 6, 7, 9, 11, 13, 14, 15, 16], "clarifi": 0, "cut": 0, "could": [0, 1, 2, 3, 4, 5, 6, 8, 10, 11, 12, 13, 16, 17, 18], "am": [0, 5, 8, 12, 14], "veri": [0, 3, 4, 5, 6, 13, 18], "proud": 0, "These": [0, 2, 3, 4, 7, 12, 13], "interact": [8, 15], "environ": [0, 2, 7, 15], "id": 0, "recommend": [0, 19], "avail": [0, 7, 8, 18, 19], "io": 0, "case": [0, 2, 5, 6, 7, 8, 9, 12, 14, 17, 18], "spend": [0, 3, 6, 10, 12], "web": [0, 9], "browser": 0, "oper": [0, 2, 3, 4, 6, 8, 11, 12, 13, 14, 16, 17, 18], "free": 0, "strongli": [0, 2], "solut": [0, 4, 5, 10, 11, 12, 15, 17, 18, 19], "along": [0, 4, 5, 11, 13, 15, 17, 18, 19], "classroom": [0, 19], "jupyter4edu": 0, "edu": 0, "live": [0, 8, 19], "instructor": [0, 9, 19], "student": [0, 19], "great": [0, 3, 19], "train": [0, 19], "carpentri": [0, 19], "thank": 0, "jeff": 0, "elkner": 0, "translat": [0, 6, 8, 16], "java": 0, "got": [0, 2, 5, 7, 11, 12, 18], "project": [0, 4, 8], "turn": [0, 2, 4, 5, 8, 10, 12], "favorit": [0, 3], "chri": 0, "meyer": 0, "contribut": 0, "foundat": [0, 3], "gnu": 0, "licens": [0, 8], "collabor": 0, "possibl": [0, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 17, 18], "creativ": 0, "maintain": [0, 13, 17], "turtl": [0, 4, 6, 16], "graphic": [0, 2, 3, 4, 14, 16], "modul": [0, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16, 17, 18], "jupyterbook": 0, "servic": 0, "copilot": 0, "lulu": 0, "special": [0, 2, 3, 5, 6, 8, 9, 13, 14, 15, 16, 18], "technic": [0, 2, 18], "melissa": 0, "lewi": 0, "luciano": 0, "ramalho": 0, "sam": 0, "lau": 0, "contributor": 0, "sent": 0, "100": [0, 4, 5, 7, 8, 10, 12, 16, 18], "sharp": 0, "ei": 0, "reader": [0, 2, 7, 8, 13], "over": [0, 9, 12, 14, 17, 18], "past": [0, 4, 5, 6, 13, 15, 17], "year": [0, 13, 14, 15, 16], "Their": 0, "enthusiasm": 0, "huge": 0, "pleas": 0, "send": [0, 15], "email": [0, 11], "feedback": 0, "least": [0, 5, 7, 9, 12, 13, 14, 16, 17, 18], "fine": [0, 2], "quit": [0, 8, 14], "102": 1, "cell": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "caus": [1, 2, 4, 5, 8, 10, 11, 12, 13, 15, 18], "underscor": [1, 2, 15], "1_000_000": 1, "1000000": [1, 2], "recal": [1, 5, 11, 13], "quizz": [0, 19], "summ": [0, 19], "quiz": [0, 19], "soon": [0, 14, 17, 19], "februari": 19, "refer": [2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15, 16, 17], "valu": [2, 3, 4, 5, 7, 8, 10, 12, 13, 15, 16, 17, 18], "assign": [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18], "n": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 17, 18], "equal": [2, 5, 6, 8, 11, 12, 16, 17, 18], "141592653589793": [2, 12], "output": [2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 18], "visibl": [2, 6], "howev": [2, 6, 9, 11, 15, 17], "after": [2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 17, 18], "25": [2, 5, 6, 11], "283185307179586": 2, "paper": 2, "arrow": [2, 7, 10], "its": [2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "show": [2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 16, 17, 18], "mind": [2, 4, 8], "uppercas": [2, 8, 12, 13], "convent": [2, 3, 4, 5, 9, 11, 15, 16, 18], "lower": [2, 7, 12, 17, 18], "your_nam": 2, "airspeed_of_unladen_swallow": 2, "million": [2, 9, 10], "15": [3, 7, 17], "76trombon": 2, "parad": 2, "16": [11, 13, 18], "obviou": [2, 4, 5, 12, 17], "self": [2, 10, 15, 16, 17, 18], "defenc": 2, "against": [2, 8], "fresh": 2, "fruit": [2, 8], "keyword": [2, 3, 4, 5, 7, 11, 12], "specifi": [2, 3, 8, 9, 12, 13, 14, 15, 16, 18], "fals": [2, 5, 6, 7, 9, 10, 13, 14, 15, 16, 17, 18], "await": 2, "els": [2, 6, 7, 8, 10, 11, 12, 13, 18], "pass": [2, 3, 5, 6, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18], "none": [2, 7, 8, 9, 10, 14, 17, 18], "break": [2, 5, 6, 8, 9, 11, 12], "true": [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "lambda": 2, "nonloc": 2, "while": [2, 10, 13], "assert": [2, 15], "del": 2, "global": [2, 7, 18], "async": 2, "elif": [2, 5, 6, 8, 13], "yield": [2, 5, 12, 14], "memor": 2, "color": [2, 7, 8], "constant": [2, 4, 10], "dot": [2, 7, 8, 14], "sqrt": [2, 3, 6], "squar": [2, 6, 9, 10], "root": [2, 16], "pow": [2, 3, 6], "either": [2, 5, 6, 8, 10, 12, 13, 16, 17, 18], "unit": [2, 4, 16], "execut": [2, 3, 4, 5, 6, 10, 15, 17], "20": [2, 4, 9, 14, 16], "approxim": 2, "parenthesi": 2, "normal": [2, 5, 13, 15], "noth": [2, 5, 6, 9, 13, 15], "101": 2, "base": [2, 3, 5, 6, 7, 11, 13, 14, 15, 17], "142": [2, 12], "123": [2, 9], "type": [2, 4, 5, 7, 8, 9, 10, 11, 13, 15, 16, 17, 18], "handl": [2, 5, 8, 12, 18], "must": [2, 5, 7, 8, 11, 12, 15, 18], "real": [2, 12, 15], "check": [2, 4, 5, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18], "annoi": [2, 6], "detect": [2, 3, 10, 15], "bigger": [2, 10, 14, 17], "complic": [2, 6, 7, 8, 12, 14, 17, 18], "formal": 2, "piec": [2, 4, 11, 12], "natur": [2, 6, 17, 18], "60": [2, 4, 5, 14, 15, 16], "everyth": [2, 5, 12, 13, 17], "non": [2, 6], "assum": [2, 5, 6, 7, 12, 14, 16, 17], "useless": 2, "v": [2, 8], "veloc": [2, 5], "tradeoff": 2, "occur": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "runtim": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "semant": [2, 12], "distinguish": [2, 12, 15], "quickli": [2, 6, 9, 10, 12], "anywher": [2, 8, 9, 13], "immedi": [2, 6, 7, 8, 10, 18], "goe": [2, 5, 8, 9, 11, 14, 17], "stop": [2, 5, 7, 9, 10, 13, 18], "relat": [2, 5, 8, 11, 17], "intend": [2, 4, 6, 16, 17], "identifi": [2, 4, 8, 9, 12, 13, 15], "tricki": [2, 5, 8], "backward": [2, 8, 9, 10, 13], "suppos": [2, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18], "forget": [2, 4, 12], "represent": [2, 3, 13, 14, 16, 17], "set": [2, 4, 7, 10, 11, 12, 13, 17], "definit": [2, 3, 4, 5, 6, 8, 12, 14, 15, 16, 17, 18], "access": [2, 3, 6, 8, 13, 17, 18], "command": [2, 8, 15], "action": 2, "correspond": [2, 5, 10, 11, 13, 15, 17], "paramet": [2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18], "exit": [2, 7, 8, 18], "those": [2, 5, 6, 8, 9, 13, 15, 16, 17, 18], "discourag": 2, "bad": 2, "built": [2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18], "consid": [2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "repeat": [2, 3, 4, 6, 7, 9, 12], "whenev": [2, 10, 14], "y": [2, 5, 6, 7, 11, 16, 18], "semi": 2, "colon": [2, 3, 10], "period": [2, 12, 13], "maath": 2, "calcul": [2, 6, 12], "volum": 2, "sphere": 2, "radiu": [2, 4, 6, 16], "r": [2, 6, 7, 8, 9, 10, 11, 13], "frac": 2, "centimet": 2, "cubic": 2, "trigonometri": 2, "co": 2, "sin": 2, "Then": [2, 3, 4, 5, 6, 11, 12, 13, 14, 15, 16, 17, 18], "sine": 2, "cosin": 2, "sum": [2, 5, 6, 9, 11, 14, 18], "close": [2, 8, 13], "exact": 2, "e": [2, 7, 8, 9, 18], "logarithm": [2, 5, 18], "exp": 2, "slightli": 2, "lyric": [3, 12], "monti": [3, 6, 9, 11, 12, 14, 18], "song": [3, 6, 12], "silli": 3, "demonstr": [3, 4, 6, 8, 10, 12, 14, 16, 17], "print_lyr": 3, "m": [3, 6, 8, 9, 11], "lumberjack": 3, "okai": 3, "sleep": 3, "night": 3, "dai": [3, 5, 7, 14, 15, 16], "empti": [3, 8, 9, 10, 11, 12, 13, 17, 18], "header": [3, 5, 7, 14], "bodi": [3, 4, 6, 14, 18], "indent": [3, 5, 6, 9, 15], "By": [3, 4, 6, 8, 10, 15, 16, 17], "four": [3, 4, 5, 7, 9, 10, 16, 17, 18], "__main__": [3, 7, 14, 17, 18], "expon": 3, "print_twic": 3, "denni": 3, "moor": 3, "onc": [3, 4, 6, 7, 8, 9, 10, 11, 12, 18], "www": [3, 8], "songfact": 3, "love": [3, 17], "first_two_lin": 3, "last_three_lin": 3, "vers": 3, "print_vers": 3, "Of": [3, 6, 8, 9, 12], "cours": [3, 6], "fewer": [3, 8, 14, 18], "simpl": [3, 4, 5, 7, 8, 11, 12, 13, 14, 16, 18], "rang": [3, 4, 7, 8, 11, 12, 17, 18], "usual": [3, 5, 7, 12, 13, 14, 15, 16, 17, 18], "around": [3, 11, 13, 17, 18], "print_n_vers": 3, "given": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17], "anywai": [3, 6], "twice": [3, 4, 10, 11], "cat_twic": 3, "part1": 3, "part2": 3, "cat": [3, 13], "line1": [3, 16], "line2": [3, 16], "bright": 3, "side": [3, 4, 8, 9, 11, 16], "life": 3, "destroi": 3, "nameerror": [3, 6], "outsid": [3, 6, 7, 9, 10, 16], "draw": [3, 4, 5, 6, 16, 17], "state": [3, 5, 7, 9, 10, 14, 16], "frame": [3, 5, 6, 10], "box": [3, 9, 10, 16], "91": [3, 10], "66": [3, 7], "arrang": [3, 5], "topmost": 3, "recent": [3, 5, 12, 18], "21": 3, "26": 3, "clear": [3, 4, 17], "yet": [3, 4, 5, 6, 7, 9, 12, 14], "worth": [3, 7], "troubl": [3, 11], "opportun": [3, 4, 14], "smaller": [3, 6, 18], "elimin": [3, 7, 10, 12], "allow": [3, 4, 7, 13, 14, 15], "reus": [3, 4, 17], "fun": 3, "infer": 3, "event": [3, 5], "led": [3, 17], "experiment": [3, 12], "hypothesi": [3, 12], "modif": 3, "step": [3, 4, 6, 7, 8, 10, 12, 13, 14, 16], "closer": 3, "gradual": [3, 10, 14], "until": [3, 4, 7, 8, 11, 12], "linux": 13, "linu": [], "torvald": [], "explor": [5, 12, 18], "intel": [], "80386": [], "chip": [], "accord": [6, 7, 12], "larri": [], "greenfield": [], "switch": 18, "aaaa": [], "bbbb": [], "user": [5, 7, 11, 13, 15], "guid": [], "beta": [], "archiv": [], "20121024232944": [], "tldp": [], "pub": [], "doc": [4, 5], "ldp": [], "pdf": [], "gz": [], "repeatedli": 3, "everyon": 3, "agre": [3, 4], "histori": 3, "debat": 3, "tab": [3, 5, 7, 13], "va": [3, 4, 5, 9, 12, 13, 15, 16], "pick": [3, 8, 13, 18], "describ": [3, 5, 6, 12, 13, 18], "enough": [3, 4, 10, 18], "stuck": [3, 7, 9, 12, 13], "print_right": 3, "lead": [3, 13, 14, 17], "40th": 3, "column": [3, 13, 14], "fly": 3, "circu": 3, "triangl": [3, 4, 5, 6], "pyramid": 3, "height": [3, 5, 16], "made": [3, 8, 9, 11, 13, 17, 18], "level": [3, 14], "l": [3, 9, 11, 12, 18], "shape": [3, 4, 16], "lll": 3, "llll": 3, "lllll": 3, "rectangl": [3, 4], "width": [3, 16], "h": 3, "hhhhh": 3, "99": 3, "bottl": 3, "beer": 3, "wall": 3, "98": [3, 12], "97": 3, "bottle_vers": 3, "imaginari": 4, "polygon": 4, "studi": [], "content": [8, 12, 13, 17, 18], "make_turtl": [4, 5, 16], "canva": [4, 16], "screen": [4, 14, 16], "circular": [4, 6], "shell": [4, 8], "triangular": 4, "head": [4, 6, 8, 12], "locat": [4, 10, 16], "direct": [4, 10, 11, 14, 16], "face": [4, 17], "distanc": [4, 6, 16], "segment": [4, 8, 16], "arbitrari": 4, "size": [4, 10, 11, 16, 18], "depend": [4, 5, 7, 12, 13, 17], "nice": [4, 15, 17], "angl": [4, 5, 16], "degre": [4, 5], "50": [4, 5, 7, 12, 16], "east": [4, 12], "north": 4, "behind": [4, 18], "pair": [4, 10, 11, 12, 13, 16, 17, 18], "wrap": [4, 11, 17], "benefit": 4, "attach": 4, "serv": 4, "current": [4, 5, 7, 13, 16, 17], "360": 4, "adjac": 4, "numer": [4, 5], "remind": [4, 5, 9], "circumfer": 4, "control": [5, 6], "limit": [4, 5], "wast": 4, "arc": 4, "span": [4, 8], "180": 4, "half": [4, 12, 13], "instead": [4, 5, 6, 8, 10, 12, 13, 18], "polylin": 4, "rewrit": [4, 5, 7, 12, 15, 18], "arc_length": 4, "step_angl": 4, "similar": [4, 5, 6, 7, 8, 10, 12, 15, 17, 18], "snail": 4, "70": 4, "improv": [4, 6, 14], "ahead": [4, 14], "avoid": [4, 5, 6, 7, 9, 10, 11, 16, 18], "coher": 4, "retyp": 4, "appropri": 4, "factor": [4, 5], "drawback": [4, 18], "implement": [4, 11, 12, 14, 16, 17], "wrote": [4, 6, 7, 11, 14, 15, 18], "tripl": 4, "multilin": 4, "mayb": [4, 6, 10], "contract": [4, 17], "caller": [4, 6, 9, 10], "understood": 4, "precondit": [4, 6], "convers": [4, 11, 14], "postcondit": [4, 6], "respons": [4, 9, 17], "violat": [4, 6, 15, 17], "correctli": [4, 6, 7, 14], "satisfi": 4, "pre": 4, "window": [4, 12, 13], "replac": [0, 4, 6, 10, 12, 13, 16, 18], "unnecessarili": [4, 14], "qualiti": 4, "enclos": [4, 8, 9, 10, 11], "penup": 4, "lift": 4, "pen": 4, "trail": 4, "pendown": 4, "jump": [4, 8], "80": [4, 12], "40": [2, 4, 10, 14, 15, 16], "tall": 4, "rhombu": 4, "interior": 4, "parallelogram": 4, "quadrilater": 4, "parallel": 4, "400": [], "120": [5, 14], "draw_pi": 4, "flower": 4, "petal": 4, "140": [], "custom": [4, 17], "prompt": [4, 5, 7, 11, 16, 17, 18], "spiral": 4, "unfortun": 0, "rewrot": [0, 15], "total": [7, 8, 9, 10, 14, 17], "2562": [], "211180124223602": [], "21118": [], "412": [], "48200824964016": [], "48": [], "52": [14, 17], "48000000000002": [], "7116666666666667": [], "1118": [], "711666": [], "588017412662682": [], "cm": [], "523": 13, "5987755982989": [], "3890560989306495": [], "38905609893065": [], "num_spac": [], "bottle_lin": [], "suffix": 13, "length1": [], "length2": [], "icoscel": [], "peak": [], "middl": [5, 16], "leg": 6, "pie": [], "radial": [], "spoke": 12, "subtend": [], "increas": [7, 10, 17], "circular_spir": [], "rotat": [9, 10], "recalcul": [], "updat": [12, 13, 14], "traceback": [5, 18], "main": [5, 18], "movi": [5, 14, 18], "105": 5, "75": 5, "remaind": [5, 6, 11], "45": [5, 15], "Or": [5, 6, 8, 11, 13, 18], "seem": [5, 6, 11, 14, 17, 18], "whether": [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "zero": [5, 6, 7, 8, 10, 14, 17], "extract": 5, "clock": [5, 8], "11": [5, 13, 14, 15, 17, 18], "durat": [5, 14, 15], "pm": [5, 14], "compar": [5, 10, 13, 14, 16], "otherwis": [5, 6, 7, 8, 9, 10, 11, 14, 16, 17, 18], "bool": 5, "greater": [5, 8, 10, 18], "negat": 5, "strictli": [5, 11], "strict": 5, "nonzero": 5, "flexibl": 5, "subtleti": 5, "accordingli": 5, "simplest": [5, 9, 17], "block": [5, 10], "occasion": 5, "keeper": [5, 17], "haven": [5, 6, 7, 9], "comment": 5, "odd": 5, "sinc": [5, 6, 7, 10, 12, 14, 17], "branch": [5, 6, 18], "abbrevi": 5, "within": [5, 9], "outer": [5, 17], "appar": [5, 17], "simplifi": [5, 7, 12, 18], "magic": [5, 11], "countdown": [5, 6], "blastoff": 5, "print_n_tim": 5, "reach": [5, 12], "forev": [5, 6], "termin": [5, 6], "exce": [5, 14], "recursionerror": [5, 6], "38": [], "skip": 5, "2958": 5, "maximum": [5, 6, 11], "depth": [5, 6], "exceed": [5, 6], "3000": 5, "encount": [5, 13], "accid": 5, "confirm": [5, 6, 9, 11, 12, 13, 14, 16, 17], "guarante": 5, "accept": [5, 7], "wait": [4, 5], "enter": 5, "resum": 5, "arthur": 5, "king": [5, 17], "briton": 5, "newlin": [5, 7, 8, 13, 17], "airspe": 5, "unladen": 5, "swallow": [5, 12], "african": 5, "european": 5, "valueerror": [5, 9, 11, 18], "overwhelm": [5, 6], "gotcha": 5, "invis": [5, 13], "indentationerror": 5, "mislead": 5, "ratio": 5, "decibel": 5, "denomin": 5, "log10": 5, "51": 17, "domain": [5, 11], "valid": [5, 6, 7, 8, 12, 14, 15, 18], "undefin": 5, "whose": [5, 11, 13, 14], "flow": [5, 6, 17], "determin": [5, 6, 11, 12, 14, 18], "seri": [5, 11, 12, 15], "eventu": 5, "exclus": 5, "saw": [5, 6, 7, 8, 10, 13, 14], "unnecessari": [5, 6, 10], "attempt": [5, 8, 11], "countdown_by_two": 5, "unix": [5, 13], "epoch": 5, "1970": 5, "00": [5, 14, 15], "utc": 5, "coordin": [5, 6, 16], "univers": 5, "1708023665": [], "8504143": [], "html": 5, "seconds_per_dai": [], "24": 9, "19768": [], "3600": 14, "850414276123047": [], "stick": 5, "inch": 5, "meet": [5, 8], "cannot": [5, 8, 17], "degener": 5, "is_triangl": 5, "ye": 5, "No": [5, 9], "nn": [], "ns": [], "koch": 5, "curv": 5, "300": 16, "sierpi\u0144ski": 5, "yourself": [3, 5, 6, 8, 12, 17], "june": [14, 15], "2023": [9, 13], "draw_sierpinski": 5, "200": [5, 8, 16], "integr": 0, "jupyturtl": [0, 5, 16], "delai": [4, 5], "default": [4, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18], "02": 4, "faster": [3, 4, 10, 18], "fast": 4, "41": [], "39": [], "1709330456": [], "272177": [], "grate": 0, "much": [3, 8, 9, 10, 12, 14, 18], "reorgan": 4, "b": [5, 6, 7, 8, 9, 10, 11, 13, 18], "1709908595": [], "7334914": [], "656366395715726": 6, "312732791431452": 6, "circle_area": 6, "area": 6, "circl": [6, 16], "00000000000001": 6, "63": 6, "000000000000014": 6, "finland": 6, "repeat_str": 6, "pure": [6, 7, 16], "absolute_valu": 6, "hit": 6, "absolute_value_wrong": 6, "neither": 6, "extra": [6, 7, 8], "absolute_value_extra_return": 6, "dead": [6, 9], "harm": 6, "someon": [6, 12], "larger": [6, 8], "increasingli": 6, "amount": [6, 10, 18], "x_1": 6, "y_1": 6, "x_2": 6, "y_2": 6, "pythagorean": 6, "theorem": 6, "mathrm": 6, "input": [6, 10, 12], "outlin": [6, 7, 10, 17], "x1": 6, "y1": 6, "x2": 6, "y2": 6, "sampl": [6, 12], "chose": 6, "horizont": [6, 16], "vertic": [6, 8, 16], "hypotenus": 6, "temporari": [6, 11], "dx": [6, 16], "dy": [6, 16], "dsquar": 6, "d": [6, 7, 8, 9, 10, 11, 12, 13, 16, 18], "scaffold": [6, 10, 12], "product": 6, "kei": [6, 10, 11, 12, 13, 17, 18], "aspect": 6, "hold": 6, "intermedi": 6, "conveni": [6, 14, 17], "encapsul": [6, 7, 12, 13, 14, 16, 17], "is_divis": 6, "directli": [6, 7, 11], "comparison": [6, 10, 17, 18], "threshold": 6, "ture": 6, "sens": [6, 7, 9, 12, 13, 14, 16, 17], "being": [6, 12], "truli": [6, 12], "vorpal": 6, "adject": [6, 8, 12], "On": [6, 13, 17], "hand": [6, 8, 10, 17, 18], "factori": [6, 14, 18], "align": 6, "fill": [6, 7], "stack": [6, 9, 14], "shown": [6, 9], "fact": [6, 7, 12, 14], "examin": [6, 12], "convinc": 6, "ourselv": [6, 18], "impli": [6, 9, 16], "bit": [6, 18], "strang": [6, 12, 18], "finish": [6, 12, 13], "explod": 6, "confid": 6, "asid": 6, "ineffici": [6, 10, 11, 13], "infinit": 6, "miss": [6, 18], "initi": [6, 7, 8, 9, 10, 12, 14, 15, 17, 18], "isinst": [6, 14, 15], "crunchi": 6, "frog": 6, "checkpoint": 6, "explicitli": [6, 13], "littl": [6, 11, 18], "dure": [6, 10, 13, 14, 15], "idiomat": [6, 14, 15, 16], "spot": [6, 10, 13, 18], "resembl": 6, "hypot": 6, "is_between": 6, "z": [6, 11], "ackermann": 6, "mbox": 6, "divisor": 6, "gcd": 6, "largest": [6, 9, 10, 11], "1939": 7, "ernest": 7, "vincent": 7, "wright": 7, "novel": [7, 8], "gadsbi": 7, "pattern": [7, 8, 10, 13], "linear": 7, "puzzl": [7, 10, 11], "bee": [7, 12], "g": [7, 9], "has_": 7, "emma": 7, "114": 7, "offici": [7, 13], "crossword": 7, "game": [7, 8, 11, 12, 17, 18], "txt": [7, 8, 9, 10, 12, 13, 18], "open": [7, 8, 9, 10, 12, 13, 18], "file_object": 7, "readlin": 7, "aa": [7, 10], "method": [7, 10, 11, 12, 13, 14, 16, 17, 18], "associ": [7, 9, 10, 11, 14, 17], "lava": 7, "aah": 7, "strip": [7, 8, 9, 12, 18], "whitespac": [7, 13, 17], "old": [7, 11, 13], "solid": 7, "increment": [7, 10, 12, 14], "decreas": [7, 11], "decrement": 7, "113783": [7, 9, 10], "counter": [7, 11, 17], "76162": 7, "percentag": 7, "93618554617122": 7, "craft": [7, 11], "boolean": [7, 8, 18], "lowercas": [7, 8, 9, 13], "unchang": [7, 9, 14], "simpler": [7, 18], "uses_ani": [7, 8], "banana": [7, 8, 9, 10, 11, 18], "aeiou": 7, "appl": [7, 8], "xyz": 7, "uses_onli": [7, 18], "docstr": [7, 14], "run_docstring_exampl": [7, 18], "run_doctest": [7, 18], "func": [7, 18], "__name__": [7, 18], "fail": [7, 9, 12, 18], "uses_any_incorrect": 7, "debug": 7, "uses_non": [7, 18], "forbidden": [7, 18], "efg": 7, "ban": [7, 8, 18], "apl": 7, "uses_al": [7, 18], "api": 7, "york": 7, "daili": 7, "seven": [7, 10, 11, 17], "acdlort": 7, "told": 7, "rat": 7, "ratatat": 7, "check_word": [7, 8], "earn": 7, "pangram": 7, "score_word": 7, "lesson": 7, "score": [7, 11, 17], "word_scor": 7, "card": [7, 18], "cartload": 7, "abc": [7, 11, 18], "appli": 8, "wordl": 8, "alphabet": [8, 11, 12], "white": 8, "select": [8, 9, 10, 11, 12, 18], "bracket": [8, 9, 10, 11, 13, 18], "index": [8, 9, 10, 11, 12, 17, 18], "offset": 8, "0th": 8, "pronounc": [8, 11, 14], "eth": 8, "indexerror": [8, 9], "th": 8, "exclud": 8, "counterintuit": 8, "imagin": [8, 14], "ana": [8, 10], "omit": [8, 9, 10, 18], "intent": 8, "greet": 8, "j": 8, "item": [8, 10, 11, 12, 13, 17, 18], "refin": [8, 11], "variat": [8, 17], "origin": [8, 9, 11, 12, 13, 14, 16, 18], "new_greet": 8, "jello": 8, "onto": 8, "compare_word": 8, "pineappl": 8, "format": [8, 10, 13, 14, 15], "defend": 8, "man": [8, 12], "arm": 8, "varieti": [8, 17], "upper": [8, 16], "new_word": 8, "invoc": 8, "invok": [8, 9, 15, 16, 17, 18], "dracula": 8, "bram": 8, "stoker": 8, "gutenberg": 8, "ebook": 8, "345": 8, "plain": [8, 11], "pg345": 8, "materi": 8, "startswith": [8, 12], "is_special_lin": 8, "OF": 8, "THE": 8, "pg345_clean": 8, "writer": [8, 13], "w": [8, 13], "still": [8, 9, 12, 14, 16], "success": [8, 12, 18], "endswith": 8, "_by_": 8, "endswidth": 8, "iceland": 8, "1901": 8, "jonathan": 8, "thoma": 8, "clean": [8, 12, 13], "15499": 8, "199": 8, "pg345_replac": 8, "harker": 8, "titular": 8, "him": 8, "bid": [8, 17], "welcom": 8, "mr": [8, 12], "hous": [8, 17], "match": 8, "find_first": 8, "journal": 8, "easili": [8, 11], "bar": 8, "mina": 8, "murrai": 8, "she": 8, "luci": 8, "count_match": 8, "229": 8, "hi": 8, "feet": 8, "five": [8, 12, 17], "born": 8, "ireland": 8, "1897": 8, "he": 8, "england": 8, "british": [8, 9], "centr": 8, "colour": [8, 12], "american": 8, "center": [8, 16], "cent": 8, "er": 8, "horsesho": 8, "carpathian": 8, "sort": [8, 12, 13, 16, 18], "colou": 8, "u": [8, 10, 11, 12], "undergar": 8, "apron": 8, "front": 8, "stuff": 8, "edit": [8, 10], "sub": 8, "exclam": [8, 12], "tail": 8, "redirect": 8, "pg345_cleaned_10_lin": 8, "pg345_cleaned_100_lin": 8, "constitut": 8, "preced": 8, "backslash": [8, 13], "scratch": 8, "surfac": 8, "phone": 8, "hyphen": [8, 9, 12], "street": [8, 12], "address": [8, 11], "st": 8, "av": 8, "titl": [8, 18], "capit": [8, 9, 13, 18], "recogn": 8, "proper": 8, "noun": [8, 12], "target": 8, "mower": 8, "spade": [8, 17], "clerk": 8, "totem": 8, "check_word2": 8, "begum": 8, "enzym": 8, "genom": 8, "venom": 8, "mont": 8, "cristo": 8, "alexandr": 8, "duma": 8, "classic": 8, "umberto": 8, "eco": 8, "confess": 8, "found": [8, 11, 12, 14, 17], "badli": 8, "shameless": 8, "repetit": [8, 17], "shudder": 8, "pale": 8, "pallor": 8, "palindrom": [9, 10, 18], "anagram": [9, 11, 13, 18], "chees": 9, "cheddar": 9, "edam": 9, "gouda": 9, "nest": [9, 16, 18], "unlik": [9, 18], "wensleydal": 9, "c": [9, 10, 11, 13, 18], "t1": [9, 11, 14, 15], "t2": [9, 11, 14, 15], "min": [9, 11], "max": [9, 11], "smallest": [9, 10, 11], "append": [9, 10, 11, 12, 13, 17, 18], "extend": [9, 14], "f": [9, 14, 15, 16, 17, 18], "pop": [9, 12, 17], "p": [9, 11, 12, 18], "individu": 9, "split": [9, 10, 11, 12, 18], "pine": 9, "fjord": 9, "delimit": 9, "boundari": 9, "ex": 9, "parrot": [9, 11], "scrambl": 9, "eelrstt": 9, "equival": [9, 14, 17, 18], "ident": [9, 14, 15, 17], "necessarili": 9, "affect": [9, 13, 16], "prone": [9, 11, 14, 16], "safer": 9, "immut": [9, 10, 16, 18], "pop_first": 9, "lst": [9, 10], "04": 9, "06": [9, 14, 15], "persist": [9, 13], "properti": [9, 10, 17], "word_list": [9, 10, 12, 18], "113": 9, "1016511": 9, "demot": 9, "contrafibular": 9, "anaspept": 9, "opposit": [9, 16, 17], "plumag": 9, "attributeerror": [9, 11, 18], "nonetyp": 9, "attribut": [9, 11, 15, 16, 18], "incorrectli": 9, "Being": [9, 16], "televis": 9, "black": 9, "adder": 9, "season": 9, "episod": 9, "ink": 9, "incap": 9, "august": 9, "came": [9, 13], "claim": 9, "tom": 9, "stoppard": 9, "plai": [9, 11, 17], "rosencrantz": 9, "guildenstern": 9, "accur": 9, "gain": 9, "sourc": 9, "role": 9, "is_anagram": [9, 18], "revers": [9, 10, 11, 12], "0x7fe3de636b60": 9, "torrap": 9, "reverse_word": [9, 10], "noon": [9, 10], "is_palindrom": [9, 10, 18], "reverse_sent": 9, "total_length": 9, "902": 9, "728": 9, "eleg": 10, "uniqu": [10, 11, 18], "brace": [10, 13, 14, 16], "isn": [10, 18], "keyerror": [10, 12], "dict": [10, 11, 18], "numbers_copi": 10, "hash": [10, 13], "tabl": [10, 13, 17], "remark": 10, "stress": 10, "dessert": 10, "too_slow": 10, "roughli": 10, "billion": 10, "12946571089": 10, "word_dict": 10, "much_fast": 10, "hundredth": 10, "proport": [10, 12], "value_count": [10, 11], "brontosauru": 10, "travers": 10, "abcd": 10, "unhash": 10, "hashabl": [10, 11, 18], "mutabl": [10, 11, 16], "aren": 10, "task": 10, "aba": 10, "aga": 10, "aha": 10, "ala": 10, "alula": 10, "ama": 10, "anna": 10, "ava": 10, "long_palindrom": 10, "deifi": 10, "halalah": 10, "reifier": 10, "repap": 10, "reviv": 10, "semem": 10, "filter": 10, "ran": [10, 16, 18], "fibonacci": [10, 18], "furthermor": 10, "graph": 10, "connect": [10, 15, 16], "wors": 10, "previous": [10, 15, 17], "memoiz": [10, 18], "fibonacci_memo": 10, "microsecond": 10, "measur": 10, "dataset": 10, "unwieldi": 10, "scale": 10, "themselv": 10, "summari": [10, 18], "saniti": 10, "insan": 10, "pprint": 10, "human": [10, 13, 17], "readabl": [10, 13, 17, 18], "stand": [10, 12, 13, 17], "calle": 10, "futur": 10, "gave": 10, "longest": [10, 12], "unpredict": [10, 12], "has_dupl": [10, 18], "find_repeat": 10, "counter1": 10, "counter2": [10, 18], "apatosauru": 10, "add_count": 10, "interlock": 10, "school": 10, "shoe": 10, "cold": 10, "slice": [10, 11, 12], "altogeth": 10, "is_interlock": 10, "unpack": [11, 18], "tuh": 11, "ple": 11, "rhyme": 11, "suppl": 11, "quadrupl": 11, "necessari": [11, 13, 15, 18], "lupin": 11, "lup": 11, "0x7f56c0072110": 11, "map": [11, 12, 13, 17, 18], "usernam": 11, "swap": 11, "temp": 11, "quotient": 11, "divmod": [11, 14, 15], "min_max": 11, "low": 11, "high": [11, 14, 17], "arg": [11, 18], "though": [11, 12], "treat": [11, 13, 14, 18], "adapt": 11, "lowest": 11, "highest": [11, 17], "trimmed_mean": 11, "trim": 11, "sport": 11, "subject": 11, "judg": 11, "dive": 11, "gymnast": 11, "deviat": 11, "team": 11, "record": [11, 13, 15], "scores1": 11, "scores2": 11, "teeth": 11, "zipper": 11, "0x7f3e9c74f0c0": 11, "pairwis": 11, "victori": 11, "win": [11, 17], "team1": 11, "team2": 11, "sadli": 11, "lost": [11, 13], "abcdefghijklmnopqrstuvwxyz": 11, "letter_map": 11, "enumer": [11, 17], "0x7f3e9c620cc0": 11, "subsequ": 11, "realli": [11, 18], "2000000": 11, "minimum": 11, "frequent": [11, 12, 18], "dict_item": 11, "behav": [11, 13], "ti": 11, "second_el": [11, 12], "sorted_item": 11, "invers": 11, "invert_dict": 11, "compound": 11, "structshap": [11, 14], "summar": 11, "t3": 11, "lt": 11, "pose": 11, "list0": 11, "list1": 11, "usageerror": 11, "encod": [11, 17], "decod": 11, "caesar": 11, "cipher": 11, "encrypt": [11, 17], "involv": [11, 14], "shift": 11, "shift_word": 11, "cheer": 11, "jolli": 11, "melon": 11, "cube": 11, "most_frequent_lett": 11, "frequenc": [11, 18], "delta": 11, "desalt": 11, "salt": 11, "slate": [11, 13], "stale": 11, "retain": 11, "ternari": 11, "greaten": 11, "resmelt": 11, "smelter": 11, "termless": 11, "word_dist": 11, "metathesi": 11, "transposit": 11, "conserv": 11, "credit": 11, "inspir": 11, "puzzler": 11, "statist": 12, "phrase": 12, "dr": 12, "jekyl": 12, "hyde": 12, "robert": 12, "loui": 12, "stevenson": 12, "dr_jekyl": 12, "unique_word": 12, "seq": 12, "6040": 12, "6000": 12, "inspect": 12, "chocol": 12, "superior": 12, "behold": 12, "cool": 12, "frighten": 12, "gentleman": 12, "pocket": 12, "handkerchief": 12, "legitim": 12, "circumscript": 12, "dash": 12, "issu": [12, 13], "split_lin": 12, "unicod": 12, "intern": 12, "unicodedata": 12, "lu": 12, "po": 12, "subcategori": 12, "punc_mark": 12, "char": 12, "clean_word": 12, "unique_words2": 12, "4005": 12, "stricter": 12, "4000": 12, "unimpression": 12, "fellow": 12, "creatur": 12, "word_count": [12, 18], "freq": 12, "sep": 12, "1614": 12, "972": 12, "941": 12, "640": 12, "ndigit": 12, "num": [12, 17], "print_most_common": 12, "overrid": [12, 15, 16, 17], "misspel": 12, "scrabbl": [12, 18], "valid_word": 12, "d1": [12, 18], "d2": [12, 18], "diff": 12, "628": 12, "128": [5, 12], "utterson": 12, "124": 12, "mostli": 12, "friend": 12, "lawyer": 12, "singleton": 12, "gesticul": 12, "abject": 12, "reindu": 12, "fearstruck": 12, "reinduc": 12, "choos": [12, 15, 18], "determinist": 12, "nondeterminist": 12, "fake": 12, "pseudorandom": 12, "simpli": 12, "choic": [12, 18], "chosen": 12, "422": 12, "postur": 12, "ill": 12, "apocryph": 12, "nor": 12, "busi": 12, "account": [12, 18], "weight": 12, "k": [12, 18], "random_word": 12, "edward": 12, "seldom": [12, 16], "articl": 12, "verb": 12, "adverb": 12, "trigram": 12, "unspecifi": 12, "gram": 12, "bigram_count": 12, "count_bigram": 12, "consecut": [12, 17], "slide": 12, "process_word": 12, "thereaft": 12, "178": 12, "139": 12, "94": 12, "73": 12, "random_bigram": 12, "prefac": 12, "detain": 12, "abov": 12, "laboratori": 12, "chain": 12, "eric": 12, "philosoph": 12, "ipso": 12, "facto": 12, "vi": 12, "entiti": 12, "successor_map": 12, "successor": 12, "add_bigram": 12, "process_word_bigram": 12, "hesit": 12, "smile": 12, "wither": 12, "sound": 12, "meant": [12, 17], "rumin": 12, "rubberduck": 12, "rubber": 12, "duck": 12, "en": 12, "wikipedia": 12, "rubber_duck_debug": 12, "retreat": 12, "undo": 12, "rebuild": 12, "brain": 12, "failur": [12, 18], "typograph": 12, "conceptu": 12, "techniqu": 12, "reluct": 12, "delet": 12, "setdefault": 12, "add_word": [12, 13], "gpt": 12, "count_trigram": 12, "process_word_trigram": 12, "add_trigram": 12, "doubt": 12, "iter": [12, 17], "recogniz": 12, "wander": 12, "bonu": 12, "1711200163": [], "3662615": [], "ephemer": 13, "disappear": 13, "shut": 13, "restart": 13, "versatil": 13, "photo": 13, "folder": 13, "getcwd": 13, "home": 13, "dinsdal": 13, "memo": 13, "rel": 13, "abspath": 13, "listdir": 13, "mar": 13, "jan": 13, "feb": 13, "imag": 13, "jpeg": 13, "photo3": 13, "jpg": 13, "photo2": 13, "photo1": 13, "apr": 13, "isdir": 13, "isfil": 13, "maco": 13, "camel": 13, "spotter": 13, "23": 13, "num_year": 13, "num_camel": 13, "easiest": 13, "unless": [13, 18], "explanatori": 13, "month": [13, 14, 15], "configur": 13, "config": 13, "extens": 13, "photo_dir": 13, "data_dir": 13, "photo_info": 13, "dump": 13, "config_filenam": 13, "readback": 13, "safe_load": 13, "config_readback": 13, "serial": 13, "deseri": 13, "row": 13, "shelf": 13, "caption": 13, "makedir": 13, "exist_ok": 13, "db_file": 13, "db": 13, "dbfilenameshelf": 13, "0x7f5a2021c310": [], "casual": 13, "nose": 13, "dbm": 13, "dir": 13, "dat": 13, "revisit": 13, "opst": [13, 18], "opt": [13, 18], "post": [13, 18], "pot": [13, 18], "sort_word": 13, "anagram_map": 13, "anagram_list": 13, "rb": 13, "binari": 13, "byte": 13, "path1": 13, "data1": 13, "path2": 13, "data2": 13, "same_cont": 13, "digest": 13, "hashlib": 13, "md5": 13, "md5_hash": 13, "_hashlib": 13, "hexdigest": 13, "hexadecim": [13, 14], "aa1d2fc25b7ae247b2931f5a0882fa37": 13, "md5_digest": 13, "filename2": 13, "6a501b11b01f89af9c3f6591d7f02c49": 13, "subdirectori": 13, "dirnam": 13, "visit_func": 13, "visit": 13, "repr": 13, "inconsist": 13, "typic": 13, "indefinit": 13, "perman": 13, "load": 13, "relev": [13, 17], "replace_al": 13, "is_imag": 13, "splitext": 13, "add_path": 13, "process_path": 13, "proce": 14, "lunch": 14, "memori": 14, "prefix": 14, "0x": 14, "0x7fbf2c427280": [], "instanti": [14, 15, 16, 17], "instanc": [14, 15, 17], "emphasi": 14, "syllabl": 14, "AT": 14, "trib": 14, "ut": 14, "59": 14, "01": 14, "concern": 14, "82": [], "total_minut": 14, "719": 14, "02d": [14, 15], "print_tim": [14, 15], "make_tim": [14, 15], "surpris": 14, "holi": [14, 18], "grail": [14, 18], "92": 14, "32": [14, 15], "09": [14, 15], "increment_tim": 14, "alia": 14, "add_tim": [14, 15], "resort": 14, "compel": 14, "arriv": 14, "theater": 14, "72": 14, "field": 14, "132": [14, 15], "carri": 14, "tire": 14, "punctur": 14, "deep": 14, "unreli": 14, "insight": 14, "sexagesim": 14, "sixti": 14, "thirti": 14, "hundr": 14, "time_to_int": [14, 15], "3661": 14, "int_to_tim": [14, 15], "harder": [14, 18], "abstract": 14, "intuit": 14, "invest": 14, "shorter": 14, "naiv": 14, "borrow": 14, "iron": 14, "hasattr": 14, "var": [14, 17], "rough": 14, "draft": 14, "solidifi": 14, "pro": [14, 17, 18], "con": [14, 17, 18], "__init__": [14, 15, 16, 17, 18], "patient": 14, "meantim": 14, "subtract_tim": [14, 15], "interv": 14, "is_aft": [14, 15], "date": [14, 15], "make_d": 14, "22": [14, 15], "1933": [14, 15], "print_dat": 14, "septemb": [14, 15], "date_to_tupl": 14, "characterist": 15, "explicit": 15, "receiv": 15, "analog": 15, "rewritten": 15, "add_method_to": [15, 16, 17, 18], "34800": 15, "chicken": 15, "egg": 15, "from_second": 15, "__add__": 15, "invari": 15, "gone": 15, "is_valid": 15, "assertionerror": [15, 18], "notabl": 15, "inherit": [15, 18], "staticmethod": 15, "decor": 15, "to_tupl": [15, 17], "1711201546": [], "6627047": [], "0x7fdf082603d0": 13, "0x7f7a700f5060": 14, "xmode": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "119": 5, "117": 5, "130": 5, "1711217277": 5, "4267917": 5, "aloud": 12, "inanim": 12, "tour": 16, "geometr": 16, "shallow": 16, "plane": 16, "corner": 16, "cartesian": 16, "axi": 16, "upsid": 16, "__str__": [16, 17, 18], "end1": 16, "end2": 16, "150": 16, "p1": 16, "p2": 16, "jumpto": 16, "moveto": 16, "ax": 16, "__eq__": [16, 17, 18], "oppos": 16, "box1": 16, "make_point": 16, "p3": 16, "p4": 16, "make_lin": 16, "subtl": 16, "dwidth": 16, "dheight": 16, "box2": 16, "160": 16, "went": [16, 17], "share": 16, "fortun": 16, "deepcopi": [16, 17], "box3": 16, "box4": 16, "fourth": [16, 17], "greek": 16, "scope": 16, "midpoint": 16, "make_cross": 16, "cross": 16, "poker": 17, "worri": 17, "suit": [17, 18], "thirteen": 17, "rank": 17, "heart": 17, "diamond": 17, "club": 17, "ac": 17, "jack": 17, "queen": 17, "higher": 17, "secret": 17, "suit_nam": 17, "rank_nam": 17, "clearer": 17, "queen2": 17, "__ne__": 17, "invert": 17, "__lt__": 17, "sake": 17, "outrank": 17, "logic": 17, "__gt__": 17, "__le__": 17, "__ge__": 17, "contradict": 17, "static": 17, "make_card": 17, "inner": 17, "accumul": 17, "small_deck": 17, "n6": 17, "printabl": 17, "take_card": 17, "put_card": 17, "random": 17, "deleg": 17, "held": 17, "player": 17, "bridg": 17, "lend": 17, "label": 17, "move_card": 17, "polymorph": 17, "child": 17, "compat": 17, "liskov": 17, "substitut": 17, "principl": 17, "barbara": 17, "collaps": 17, "sorri": 17, "bridgehand": 17, "high_card_point_count": 17, "hcp_dict": 17, "facilit": 17, "reflect": 17, "spread": 17, "across": 17, "unsur": 17, "trace": 17, "find_defining_class": 17, "obj": 17, "method_nam": 17, "typ": 17, "mro": 17, "resolut": 17, "resolv": 17, "construct": [17, 18], "testabl": 17, "reusabl": 17, "controversi": 17, "favor": 17, "composit": 17, "Is": 17, "trick": 17, "winner": 17, "find_winn": 17, "classifi": 17, "pokerhand": [17, 18], "get_suit_count": 17, "get_rank_count": 17, "has_flush": 17, "flush": 17, "exot": 17, "has_straight": 17, "bad_hand": 17, "has_pair": 17, "good_hand": 17, "cautionari": 17, "tale": 17, "kangaroo": [17, 18], "marsupi": 17, "pouch": 17, "representaion": 17, "put_in_pouch": 17, "kanga": 17, "roo": 17, "wallet": 17, "car": 17, "0x7f6e2ea11900": 17, "s1": 18, "s2": 18, "acd": 18, "union": 18, "multiset": 18, "word1": 18, "word2": 18, "most_common": 18, "intersect": 18, "all_anagram": 18, "signatur": 18, "wood": 18, "log": 18, "nan": 18, "Not": 18, "claus": 18, "998046875": 18, "mad": 18, "defens": 18, "namedtupl": 18, "convei": 18, "quick": 18, "stai": 18, "decid": 18, "pointier": 18, "kwarg": 18, "summat": 18, "__new__": 18, "pack_and_print": 18, "testcas": 18, "testexampl": 18, "test_add": 18, "assertequ": 18, "run_unittest": 18, "argv": 18, "discoveri": 18, "ok": 18, "test_add_broken": 18, "tmp": 18, "ipykernel_1109857": 18, "3833266738": 18, "commonli": 18, "board": 18, "tile": 18, "belt": 18, "late": 18, "beet": 18, "has_straightflush": 18, "partit": 18, "add_card": 18, "binomi": 18, "coeffici": 18, "binomial_coeff": 18, "trial": 18, "210": 18, "deck": 18}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"program": [1, 19], "wai": [1, 19], "think": [1, 19], "arithmet": 1, "oper": [1, 5, 7, 9, 10, 15], "express": [1, 2, 5, 8, 18, 19], "function": [1, 2, 3, 4, 5, 6, 14, 19], "string": [1, 7, 8, 9, 13, 19], "valu": [1, 6, 9, 11, 14, 19], "type": [1, 6, 14], "formal": 1, "natur": 1, "languag": 1, "debug": [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "glossari": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "exercis": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "ask": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "virtual": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18], "assist": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "python": [18, 19], "3rd": 19, "edit": [0, 19], "The": [2, 4, 5, 7, 10, 15, 19], "notebook": 19, "2": 19, "an": 10, "ai": [], "chapter": 19, "1": 19, "prefac": 0, "who": 0, "Is": 0, "thi": 0, "book": 0, "For": 0, "goal": 0, "navig": 0, "what": 0, "s": 0, "new": [0, 3], "third": 0, "get": 0, "start": 0, "resourc": [0, 19], "teacher": [0, 19], "acknowledg": 0, "variabl": [2, 3, 7, 19], "statement": [2, 5, 19], "state": 2, "diagram": [2, 3, 4, 5], "name": [2, 18], "import": 2, "print": [2, 17], "argument": [2, 9, 11, 18], "comment": 2, "defin": [3, 14, 15], "paramet": [3, 12], "call": 3, "repetit": 3, "ar": [3, 8, 9, 11, 14], "local": 3, "stack": [3, 4, 5], "traceback": 3, "why": 3, "interfac": [4, 19], "turtl": [], "modul": 4, "make": [4, 9], "squar": 4, "encapsul": 4, "gener": [4, 12, 19], "approxim": 4, "circl": 4, "refactor": 4, "A": [4, 8, 9, 10], "develop": [4, 6, 14], "plan": 4, "docstr": 4, "condit": [5, 6, 18, 19], "recurs": [5, 6, 19], "integ": 5, "divis": 5, "modulu": 5, "boolean": [5, 6], "logic": 5, "els": 5, "claus": 5, "chain": 5, "nest": 5, "infinit": 5, "keyboard": 5, "input": 5, "3": 19, "4": 19, "5": 19, "jupyturtl": 4, "return": [6, 11, 14, 19], "some": 6, "have": 6, "And": 6, "none": 6, "increment": 6, "leap": 6, "faith": 6, "fibonacci": 6, "check": [6, 13], "iter": [7, 19], "search": [7, 19], "loop": [7, 9, 10], "read": 7, "word": [7, 9, 12], "list": [7, 9, 10, 11, 18, 19], "updat": 7, "count": 7, "doctest": 7, "regular": [8, 19], "sequenc": [8, 9], "slice": [8, 9], "immut": [8, 11], "comparison": 8, "method": [8, 9, 15, 19], "write": 8, "file": [8, 13, 19], "find": 8, "replac": 8, "substitut": 8, "mutabl": [9, 14], "through": 9, "sort": [9, 11, 17], "object": [9, 14, 15, 16], "alias": 9, "dictionari": [10, 11, 12, 19], "map": 10, "creat": [10, 16], "collect": 10, "counter": [10, 18], "accumul": 10, "memo": 10, "tupl": [11, 18, 19], "like": 11, "But": 11, "assign": 11, "pack": [11, 18], "zip": 11, "compar": [11, 15, 17], "invert": 11, "text": [12, 19], "analysi": [12, 19], "uniqu": 12, "punctuat": 12, "frequenc": 12, "option": 12, "subtract": 12, "random": 12, "number": 12, "bigram": 12, "markov": 12, "6": 19, "7": 19, "8": 19, "9": 19, "10": 19, "11": 19, "12": 19, "databas": [13, 19], "filenam": 13, "path": 13, "f": 13, "yaml": 13, "shelv": 13, "store": 13, "data": 13, "structur": 13, "equival": [13, 16], "walk": 13, "directori": 13, "class": [14, 15, 16, 19], "programm": 14, "attribut": [14, 17], "copi": [14, 16], "pure": 14, "prototyp": 14, "patch": 14, "design": 14, "first": 14, "anoth": 15, "static": 15, "time": 15, "__str__": 15, "init": 15, "overload": 15, "13": 19, "14": 19, "15": 19, "point": 16, "line": 16, "ident": 16, "rectangl": 16, "chang": 16, "deep": 16, "polymorph": 16, "inherit": 17, "repres": 17, "card": 17, "deck": 17, "add": 17, "remov": 17, "shuffl": 17, "parent": 17, "children": 17, "special": 17, "extra": 18, "set": 18, "defaultdict": 18, "comprehens": 18, "ani": 18, "all": 18, "keyword": 18}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 6, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx": 56}}) \ No newline at end of file