-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcomp208-notes.html
More file actions
1 lines (1 loc) · 285 KB
/
comp208-notes.html
File metadata and controls
1 lines (1 loc) · 285 KB
1
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>COMP-208: Computer Programming for PS&E</title><meta name="next-head-count" content="3"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack-subset.css"/><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css"/><base href="/comp208-notes/"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /><link rel="preload" href="/_next/static/css/ddaa7ed3a1cb9943.css" as="style"/><link rel="stylesheet" href="/_next/static/css/ddaa7ed3a1cb9943.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-a0deeec5c85c92d3.js" defer=""></script><script src="/_next/static/chunks/framework-ffee79c6390da51e.js" defer=""></script><script src="/_next/static/chunks/main-b714c06b389f6489.js" defer=""></script><script src="/_next/static/chunks/pages/_app-7ea878ea5ec92829.js" defer=""></script><script src="/_next/static/chunks/pages/comp208-notes-696a6d44caa9fd6d.js" defer=""></script><script src="/_next/static/-JQHz_cEzCXhwJZEcxZPK/_buildManifest.js" defer=""></script><script src="/_next/static/-JQHz_cEzCXhwJZEcxZPK/_ssgManifest.js" defer=""></script><style data-href="https://fonts.googleapis.com/css2?family=Encode+Sans+SC:wght@300&display=swap">@font-face{font-family:'Encode Sans SC';font-style:normal;font-weight:300;font-stretch:normal;font-display:swap;src:url(https://fonts.gstatic.com/s/encodesanssc/v9/jVyp7nLwCGzQ9zE7ZyRg0QRXHPZc_uUA6Kb3VJWLE_Pdtm7lcD6qvXT1HCZm8cw.woff) format('woff')}@font-face{font-family:'Encode Sans SC';font-style:normal;font-weight:300;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/encodesanssc/v9/jVyp7nLwCGzQ9zE7ZyRg0QRXHPZc_uUA6Kb3VJWLE_Pdtm7lcD6qvXT1HCZmwcdHP2MHhIbVOo20.woff) format('woff');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Encode Sans SC';font-style:normal;font-weight:300;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/encodesanssc/v9/jVyp7nLwCGzQ9zE7ZyRg0QRXHPZc_uUA6Kb3VJWLE_Pdtm7lcD6qvXT1HCZmwcZHP2MHhIbVOo20.woff) format('woff');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Encode Sans SC';font-style:normal;font-weight:300;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/encodesanssc/v9/jVyp7nLwCGzQ9zE7ZyRg0QRXHPZc_uUA6Kb3VJWLE_Pdtm7lcD6qvXT1HCZmwchHP2MHhIbVOg.woff) format('woff');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style><style data-href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,350;0,430;1,350;1,430&display=swap">@font-face{font-family:'Work Sans';font-style:italic;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGUgGsJoA.woff) format('woff')}@font-face{font-family:'Work Sans';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGU3msJoA.woff) format('woff')}@font-face{font-family:'Work Sans';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGU7GsJoA.woff) format('woff')}@font-face{font-family:'Work Sans';font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32KxfXNis.woff) format('woff')}@font-face{font-family:'Work Sans';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32K0nXNis.woff) format('woff')}@font-face{font-family:'Work Sans';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32K3vXNis.woff) format('woff')}@font-face{font-family:'Work Sans';font-style:italic;font-weight:350;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYqz_wNahGAdqQ43Rh_eZDkv_1i4_D2E4A.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Work Sans';font-style:italic;font-weight:350;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYqz_wNahGAdqQ43Rh_eZDlv_1i4_D2E4A.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Work Sans';font-style:italic;font-weight:350;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYqz_wNahGAdqQ43Rh_eZDrv_1i4_D2.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Work Sans';font-style:italic;font-weight:430;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYqz_wNahGAdqQ43Rh_eZDkv_1i4_D2E4A.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Work Sans';font-style:italic;font-weight:430;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYqz_wNahGAdqQ43Rh_eZDlv_1i4_D2E4A.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Work Sans';font-style:italic;font-weight:430;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYqz_wNahGAdqQ43Rh_eZDrv_1i4_D2.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Work Sans';font-style:normal;font-weight:350;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYsz_wNahGAdqQ43Rh_c6DptfpA4cD3.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Work Sans';font-style:normal;font-weight:350;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYsz_wNahGAdqQ43Rh_cqDptfpA4cD3.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Work Sans';font-style:normal;font-weight:350;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYsz_wNahGAdqQ43Rh_fKDptfpA4Q.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Work Sans';font-style:normal;font-weight:430;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYsz_wNahGAdqQ43Rh_c6DptfpA4cD3.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Work Sans';font-style:normal;font-weight:430;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYsz_wNahGAdqQ43Rh_cqDptfpA4cD3.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Work Sans';font-style:normal;font-weight:430;font-display:swap;src:url(https://fonts.gstatic.com/s/worksans/v19/QGYsz_wNahGAdqQ43Rh_fKDptfpA4Q.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style><style data-href="https://fonts.googleapis.com/css2?family=Alegreya+SC:wght@400;500&family=Alegreya:ital,wght@0,400;0,500;1,400;1,500&family=Lato:ital,wght@0,300;0,400;1,300;1,400&display=swap">@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaSrEBBsBhlBjvfkSLk3abBFkvpkARTPlbgv6ql.woff) format('woff')}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaSrEBBsBhlBjvfkSLk3abBFkvpkARTPlbSv6ql.woff) format('woff')}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UacrEBBsBhlBjvfkQjt71kZfyBzPgNG9hUI_w.woff) format('woff')}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UacrEBBsBhlBjvfkQjt71kZfyBzPgNGxBUI_w.woff) format('woff')}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-a6r.woff) format('woff')}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-rUy.woff) format('woff')}@font-face{font-family:'Lato';font-style:italic;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u_w4BMUTPHjxsI9w2PHw.woff) format('woff')}@font-face{font-family:'Lato';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u8w4BMUTPHjxswWA.woff) format('woff')}@font-face{font-family:'Lato';font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh7USeww.woff) format('woff')}@font-face{font-family:'Lato';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHvxo.woff) format('woff')}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96fp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk967p57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96bp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96np57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96Xp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96Tp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Alegreya';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96rp57F2IwM.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96fp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk967p57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96bp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96np57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96Xp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96Tp57F2IwN-Pw.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Alegreya';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaHrEBBsBhlBjvfkSLk96rp57F2IwM.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLsx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLlx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLtx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLix6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLux6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLvx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Alegreya';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLhx6jj4JN0.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLsx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLlx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLtx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLix6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLux6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLvx6jj4JN0EwI.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Alegreya';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreya/v35/4UaBrEBBsBhlBjvfkSLhx6jj4JN0.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6i2ZAbaqe-LGs.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6r2ZAbaqe-LGs.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6j2ZAbaqe-LGs.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6s2ZAbaqe-LGs.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6g2ZAbaqe-LGs.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6h2ZAbaqe-LGs.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiOGmRtCJ62-O0HhNEa-Z6v2ZAbaqe-.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oU7SKqUFmKCJz4.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oUySKqUFmKCJz4.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oU6SKqUFmKCJz4.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oU1SKqUFmKCJz4.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oU5SKqUFmKCJz4.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oU4SKqUFmKCJz4.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Alegreya SC';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/alegreyasc/v25/taiTGmRtCJ62-O0HhNEa-ZZc-oU2SKqUFmKC.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lato';font-style:italic;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u_w4BMUTPHjxsI9w2_FQftx9897sxZ.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lato';font-style:italic;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u_w4BMUTPHjxsI9w2_Gwftx9897g.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lato';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lato';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u8w4BMUTPHjxsAXC-qNiXg7Q.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lato';font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh7USSwaPGQ3q5d0N7w.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lato';font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lato';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHjxAwXiWtFCfQ7A.woff2) format('woff2');unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lato';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lato/v24/S6uyw4BMUTPHjx4wXiWtFCc.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style></head><body><div id="__next"><style data-emotion="css 1gas0fl">.css-1gas0fl{margin:5em 25%;width:70rem;}</style><div class="css-1gas0fl"><style data-emotion="css 12putv1">.css-12putv1{margin:0.5em 0;}</style><div class="css-12putv1"><a href="Lecture-1.html">1 — Programming Basics</a></div><div class="css-12putv1"><a href="Lecture-2.1.html">2.1 — Variables, Arithmetic & String operations</a></div><div class="css-12putv1"><a href="Lecture-2.2.html">2.2 — Function calls, Defining functions, Types of Errors</a></div><div class="css-12putv1"><a href="Lecture-3.1.html">3.1 — Order of Expression Evaluation, Comparing Values, Conditional Execution</a></div><div class="css-12putv1"><a href="Lecture-3.2.html">3.2 — if statement, for loop, Indexing & Slicing Strings</a></div><div class="css-12putv1"><a href="Lecture-4.1.html">4.1 — return vs. print, while statement, Modules</a></div><div class="css-12putv1"><a href="Lecture-4.2.html">4.2 — String methods, break & continue statements</a></div><div class="css-12putv1"><a href="Lecture-5.1.html">5.1 — Controlling print(), Nested Loops, Lists</a></div><div class="css-12putv1"><a href="Lecture-5.2.html">5.2 — Scope of variables, List operations</a></div><div class="css-12putv1"><a href="Lecture-6.1.html">6.1 — Tuples, Immutable objects, Sets</a></div><div class="css-12putv1"><a href="Lecture-6.2.html">6.2 — More list & set operations, Dictionaries</a></div><div class="css-12putv1"><a href="Lecture-7.1.html">7.1 — Iterables, Unpacking, Nested Lists</a></div><div class="css-12putv1"><a href="Lecture-7.2.html">7.2 — Nested data structures, Comprehensions, Modules</a></div><div class="css-12putv1"><a href="Lecture-8.1.html">8.1 — Nested list comprehensions, Reading & Writing Files</a></div><div class="css-12putv1"><a href="Lecture-8.2.html">8.2 — Shallow vs. deep copy, Handling exceptions</a></div><div class="css-12putv1"><a href="Lecture-9.1.html">9.1 — Object Oriented Programming (OOP)</a></div><div class="css-12putv1"><a href="Lecture-9.2.html">9.2 — Keyword arguments, More on OOP</a></div><div class="css-12putv1"><a href="Lecture-10.1.html">10.1 — NumPy</a></div><div class="css-12putv1"><a href="Lecture-10.2.html">10.2 — More Numpy, Plotting using Matplotlib</a></div><div class="css-12putv1"><a href="Lecture-11.1.html">11.1 — More Numpy, Linear algebra, Random numbers</a></div><div class="css-12putv1"><a href="Lecture-11.2.html">11.2 — Interpolation, Curve fitting, Numerical Integration</a></div><div class="css-12putv1"><a href="Lecture-12.1.html">12.1 — Using SciPy, System of Linear Equations</a></div><div class="css-12putv1"><a href="Lecture-12.2.html">12.2 — Root Finding, Sorting algorithms</a></div><div class="css-12putv1"><a href="Lecture-13.1.html">13.1 — Sorting & Misc. topics</a></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"allPosts":[{"slug":"Lecture-1.md","content":"\n:::blockquote{.notes-only}\nIn the beginning was the Tao. The Tao gave birth to Space and Time.\nTherefore Space and Time are the Yin and Yang of programming.\n:cite[— [Tao of Programming](https://www.mit.edu/~xela/tao.html)]\n:::\n\n## Binary Numbers \n\nIn decimal system, a number is expressed as a sequence of digits $0$ to $9$.\n\nFor example, `Two thousand twenty one ⇔ 2021`\n\n##\n\nIn binary number system the set of digits, is called binary digits or :sc[bits]: $\\{0, 1\\}$. \n\nA binary number is expressed as a sequence of bits. \n\nFor example, $183$ in binary is $10110111$.\n\n## Converting from decimal to binary\n\n::decimal-binary\n\n## Converting from binary to decimal\n\n::binary-decimal\n\n## Groups of bits\n\nA group of $8$ bits is called a :i[byte] e.g. $11010111$\n- $1$ kilobyte (kB) = $1000$ bytes\n- $1$ megabyte (MB) = $10^6$ (million) bytes\n- $1$ gigabyte (GB) = $10^9$ (billion) bytes\n- $1$ terabyte (TB) = $10^{12}$ bytes (1000 billion)\n\n## What is programming? \n\n:::div{.gap1}\nProgramming is the process of creating a set of instructions — a program — to tell a computer how to perform a task.\n\nPrograms take input data, perform some computation — numerical or symbolic (text) — and produce output data.\n:::\n\n##\n\n:::div{.gap1}\nComputers can perform only basic binary operations (such as add or multiply two numbers)\n\nHow do we communicate complex instructions to computers? — Use a programming language!\n:::\n\n## Levels of programming languages\n\n:::div{.my2}\n| Low-level languages | High-level languages |\n|--------------------------------------------------------|---------------------------------------------------|\n| Closer to machine, difficult for humans | Closer to humans, easier for humans to work with |\n| | |\n| Less portable, provide less abstraction over hardware | More portable, more abstraction over hardware |\n| Examples: Assembly Language | Examples: Java, Python |\n:::\n\n\n## How do computers understand high-level languages?\n\n:::div{.gap1}\nHigh-level languages are translated into machine code (for CPU). \n\nProgramming languages come in two main flavors — :i[compiled] languages or :i[interpreted] languages.\n\nCompilers and interpreters are software tools responsible to translate source code into machine code.\n:::\n\n##\n\n:::div{style=\"margin-bottom: 1em;\"}\n:sc[Compiled] languages (e.g. C/C++, Java) \n\n- High-level program (source code) ➞ :sc[Compiler] ➞ Binary executable (e.g. .exe or .dmg)\n- Once compiled, the binary program can be executed without compiler.\n:::\n\n:::div\n:sc[Interpreted] (e.g. Python, Ruby)\n\n- High-level program (source code) ➞ Executed directly by an :sc[Interpreter]\n- The interpreter is required on the machine where the program is executed.\n:::\n\n::divider\n\n## Data in binary\n\nComputers can understand only binary numbers \n\nHow can we encode data in the real world into binary numbers?\n\n::img{src=\"module-1/data-in-binary.svg\" style=\"margin: 2rem auto; width:100%;\" }\n\n## Integers in binary\n\nWe already saw how to represent positive integers in binary e.g. \n:div[$109 = 1101101_2$]{style=\"text-align: center;\"}\n\nFor signed integers (to differentiate negative and positive), an extra leftmost bit is used for sign only, e.g. \n :div[$-109 = \\colorbox{lightblue}{1}1101101_2$]{style=\"text-align: center;\"} \n :div[$+109 = \\colorbox{lightblue}{0}1101101_2$]{style=\"text-align: center;\"}\n\n::div[(For more info: https://en.wikipedia.org/wiki/Signed_number_representations)]{.smaller}\n\n## Real numbers in binary\n\n64-bit :sc[Floating point] format is used to represent numbers with decimal point, e.g. \n:div[$\\colorbox{lightblue}{0}\\colorbox{lightpink}{10000000000}\\colorbox{lightgreen}{1001001000011111101101010100010001000010110100011000} = 3.141592653589793$]{style=\"text-align: center;\"}\n\n::div[(For more info: https://en.wikipedia.org/wiki/Double-precision_floating-point_format)]{.smaller}\n\n##\n\nFloating point format has a :i[finite precision], but digits of $\\pi$ run forever: :div[$3.1415926535897932384626433832795028841...$]{style=\"text-align: center;\"}\n\nWith only 64-bits, we can only have precision up to a fixed digits after decimal point: $3.141592653589793$\n\n\n\n## Text in binary\n\n::p[Letters and punctuations in human languages are encoded in binary using a :i[Character Encoding] such as ASCII or UTF-8 (Unicode).]{.ppt-m-3}\n\n::image{style=\"margin: 0 auto; width: 80%;\" src=\"module-1/ASCII-Table.png\" .ppt-img50} \n::div[(source: https://simple.wikipedia.org/wiki/ASCII)]{style=\"font-size: 0.6em; margin: 0 auto;\"}\n\n## Images, audio \u0026 video in binary\n\nBinary data is stored in a file using a specific format.\n\nPrograms know what to do (play music, show image, etc) based on the format. \n\nWe already know some of these formats:\n- Images: jpeg, png\n- Audio: mp3, m4a, wma\n- Video: mp4, avi, wmv\n\n\n::divider\n\n## Thonny Demo — Editor vs Shell\n\nPython interpreter works in two modes:\n- An interactive :sc[Shell] mode (with the prompt `\u003e\u003e\u003e`)\n - Line(s) of code is executed immediately as soon entered and output is visible immediately\n- :sc[Script] mode\n - Executes a Python file (`.py`) as a program.\n\nThonny allows us to use both modes in one graphical interface.\n\n## Comments\n\nComments are annotations we add to our program and are ignored by the Python interpreter.\n\nIn Python, we start a comment using `#`.\n\n```python\n# Author: Deven\n# My first program\n\n# This is a comment on its own line \u0026 it will be ignored\nprint(\"Hello, world!\") # str\nprint(123) # int\nprint(1.614) # float \n```\n\n## \nWe use comments to:\n- Make the code easier to read and understand by explaining how it works.\n- Indicate authorship and license.\n- Disable some code (prevent it from executing) but still keeping it in the file.\n\n\nIn Thonny, we can use `Edit menu -\u003e Toggle comment` to comment/uncomment the selected lines.\n\n\n## Objects and Data Types\n\nAll data in a Python program is represented by :sc[objects]. \n\nAn object always has a :sc[type] (or :sc[class]) associated with it.\n\nWe can use `type()` function to know the type of an object.\n\n:::hgrid{gap=\"7em\"}\n```python lineno=false\n\u003e\u003e\u003e type(5)\n\u003cclass 'int'\u003e\n```\n\n::image{style=\"margin: 0 auto; width: 40%;\" src=\"module-1/int.svg\" .ppt-img100} \n:::\n\n##\n\n::::div{style=\"display: grid; gap: 3em;\"}\n:::hgrid{gap=\"7em\"}\n```python lineno=false\n\u003e\u003e\u003e type(3.1415)\n\u003cclass 'float'\u003e\n```\n\n::image{style=\"margin: 0 auto; width: 40%;\" src=\"module-1/float.svg\" .ppt-img100} \n:::\n\n:::hgrid{gap=\"7em\"}\n```python lineno=false\n\u003e\u003e\u003e type(\"Hello\")\n\u003cclass 'str'\u003e\n```\n\n::image{style=\"margin: 0 auto; width: 40%;\" src=\"module-1/str.svg\" .ppt-img100} \n:::\n::::\n\n##\n\nAn object's type determines the operations that the object supports:\n\n```python\n# objects of int type can be added using +\n\u003e\u003e\u003e 10 + 5\n15\n\n# But an object of type str cannot be added to an int using +\n\u003e\u003e\u003e \"Hello\" + 5\nTraceback (most recent call last):\n File \"\u003cpyshell\u003e\", line 1, in \u003cmodule\u003e\nTypeError: can only concatenate str (not \"int\") to str\n```\n\n## Summary\n \nWe saw the three basic data types in Python:\n- `int`: Integers such as $..., -1, 0, 1, 2, ...$\n- `float`: Floating-point numbers such as $-1.2, 3.14,$ etc.\n- `str`: Text data (a sequence of characters) such as \"hello world\", \"Python\", etc.\n\nThe terms :sc[Object] and :sc[Value] are used interchangeably. \nSo are the terms :sc[Class] and :sc[Type].\n\n\n::divider","title":"1 — Programming Basics","date":"2024-01-03","published":true},{"slug":"Lecture-2.1.md","content":"\n## Review of last week\n\nAll data in a Python program is represented by :sc[objects]. \n\nAn object always has a :sc[type] (or :sc[class]) associated with it.\n\n- `int`: Integers such as $..., -1, 0, 1, 2, ...$\n- `float`: Floating-point numbers such as $-1.2, 3.14,$ etc.\n- `str`: Text data (a sequence of characters) such as \"hello world\", \"Python\", etc.\n\nIn Thonny, we can use `Edit menu -\u003e Toggle comment` to comment/uncomment the selected lines.\n\n## Variables\n\nIn Python, a :sc[Variable] is a name that refers to an object in computer memory. \nA variable can be created using :sc[Assignment Statement]: \n\n:::div{.center .code .my3}\n:span[variable_name = value]{.bgblue .p1 .br5}\n:::\n\n`=` is known as the :sc[assignment operator].\n\n##\n\n```python\n# create a variable and assign it value 20\ntemperature = 20 \n\n# variable temperature refers to 20 which is displayed\nprint(\"Today's temperature is\", temperature)\n\n# show type of the variable\nprint(\"Type of temperature variable is\", type(temperature))\n```\n\n```output\nToday's temperature is 20\nType of temperature variable is \u003cclass 'int'\u003e\n```\n\n## Arithmetic with numbers\nCalculations with numbers can be done using :i[arithmetic operators].\n\n::::div{.hgrid}\n:::div\n```python\n# Addition\nprint(1.5 + 1.5) # 3.0\n\n# Subtraction\nprint(10 - 20) # -10\n```\n:::\n:::div\n```python\n# Multiplication\nprint(42 * 42) # 1764\n\n# Division\nprint(1 / 5) # 0.2\n\n# Exponentiation (x to the power of y)\nprint(2 ** 16) # 65536\n```\n:::\n::::\n\n##\n\n```python\ntemperature = 20\n# Unary minus operator\nprint(-temperature) # -20\n\n\n# Computing rest mass energy of an electron\nrest_mass = 9.109e-31 # Using scientific notation\nspeed_of_light = 3e8\n\nrest_mass_energy = rest_mass * (speed_of_light ** 2) # E = mc^2\nprint(rest_mass_energy) # 8.198099999999999e-14\n```\n\n## Floor division and remainder\n\n::img{src=\"module-2/divmod.svg\" style=\"margin:2em;\"}\n\n\n```python\n# floor division\nprint(20 // 3) # 6\n\n# remainder\nprint(20 % 3) # 2\n```\n\n## \n\n```python\n# Converting seconds to minutes\n\nduration = 320\nprint(duration, \"seconds equal\", duration / 60, \"minutes.\")\n# 320 seconds equal 5.333333333333333 minutes.\n\n\n# Alternative approach:\nminutes = duration // 60\nseconds = duration % 60\nprint(duration, \"seconds equal\", minutes, \"minutes and\", \n seconds, \"seconds.\")\n# 320 seconds equal 5 minutes and 20 seconds.\n```\n\n## Result type of arithmetic operations\n\n::image{style=\"margin: 0 auto; width: 65%;\" src=\"module-2/result_type.png\" .ppt-img80} \n\n##\n```python\nx = 2 + 1\nprint(x, type(x)) # 3 \u003cclass 'int'\u003e\n\nx = 2 + 1.0\nprint(x, type(x)) # 3.0 \u003cclass 'float'\u003e\n\n# Classic division always results in float\nx = 1 / 2 \nprint(x, type(x)) # 0.5 \u003cclass 'float'\u003e\n```\n\n:::div{.px2 .py1 .my2 style=\"border: solid 1px lightgray; border-radius:5px; background-color: #d6efd6;\"}\n:b[Try the above examples with other operators!]{.p0}\n:::\n\n##\n\nTry the problem \"Distance between two points\" on Ed.\n\n## Basic string operations\n\nStrings are sequences of zero or more characters.\n\nIn Python, strings are enclosed by either single or double quotes.\n \n```python\n\"Hello\"\n'everyone!'\n\"I'm Batman.\" # single quote allowed inside double quotes,\n'You can call me \"Bruce\".' # and vice versa.\n'123' # this is a string, not a number!\n\"\" # this is an empty string\n\" \" # this is a string with just one space\n```\n\n##\n```python\n# a multi-line string using triple quotes\nlines = \"\"\"The woods are lovely, dark and deep, \nBut I have promises to keep, \nAnd miles to go before I sleep, \nAnd miles to go before I sleep.\n\"\"\"\nprint(lines)\n\n# We can also use single quotes for multi-line strings\nprint(\n'''I hold it true, whate'er befall;\nI feel it when I sorrow most;\n'Tis better to have loved and lost\nThan never to have loved at all.\n''')\n```\n\n## String concatenation (joining) using `+` operator\n\n```python\nmessage = \"Hello\" + \"everyone\"\nprint(message) # Helloeveryone\n\nname = \"Alice\"\nmessage = \"Hello \" + name\nprint(message) # Hello Alice\n\nstring = \"1\" + \"2\" + \"3\"\nprint(string) # 123 and not the number 6\n\nprice = 100\nprint(price + \" USD\")\n# TypeError: unsupported operand type(s) for +: 'int' and 'str'\n```\n\n## String repetition\nString can be repeated multiple times using `*` operator.\n\n```python\nprint(\"Welcome! \" * 3) # 'Welcome! Welcome! Welcome! '\n\nprint(4 * \"ha\") # 'hahahaha'\n\n```\n\n## String length\nThe function `len()` returns length of its argument string.\n\n```python\npassword = \"xyz1234\"\nprint(\"Password length:\", len(password))\n# Password length: 7\n\nprint(len(1234))\n# TypeError: object of type 'int' has no len()\n\n```\n\n## Order of Expression Evaluation\n\nWhen we have multiple operators in the same expression, which operator should apply first? \n\nAll Python operators have a :sc[precedence] and :sc[associativity]:\n- Precedence — for two different kinds of operators, which should be applied first?\n- Associativity — for two operators with the same precedence, which should be applied first?\n\n##\nTable below show operators from higher precedence to lower.\n\n:::div{.my2 .hgrid}\n| Operator | Associativity |\n|---------------------|---------------|\n| `()` (parentheses) | - |\n| `**` | Right |\n| Unary `-` | - |\n| `*`, `/`, `//`, `%` | Left |\n| Binary `+`, `-` | Left |\n| `=` (assignment) | Right |\n:::\n\n\n##\n```python\nx = 3\ny = 5\n# Multiplication has higher precedence than addition\nz = x + 2 * y + 1 \nprint(z) # 14\n\n# Need to use parentheses to enforce the order we want\nz = (x + 2) * (y + 1)\nprint(z) # 30\n\n# Same precedence so left to right\nz = x * y / 100 \nprint(z) # 0.15\n```\n\n## \n```python\n# Same as 2 ** (3 ** 2) because \"**\" goes right to left\nz = 2 ** 3 ** 2\nprint(z) # 512\n\n# Using parentheses to enforce the order we want\nz = (2 ** 3) ** 2 \nprint(z) # 64\n\nx = 5\nx = x + 1 # addition happens first and then assignment\nprint(x) # 6\n\n```\n\n## More on Variables\n\nLet us write code that implements the following formula to convert fahrenheit to celsius:\n\n$$c = \\frac{5(f-32)}{9}$$\n\n\n```python\nprint(\"10 F in C is\", 5 * (10 - 32) / 9)\n```\n\n## :span[Variables allow \"saving\" intermediate results of a computation]{style=\"font-size: 0.83em;\"}\n\nWe can use variable to store the result so that we can reuse it in the program later. \n\n```python\nfahrenheit = 10\n\n# Store the result of the expression\ncelsius = 5 * (fahrenheit - 32) / 9\n\nprint(fahrenheit, \"F in C is\", celsius)\n\n# Use variable celsius for more calculations\nprint(\"Adding 10 degrees today:\", celsius + 10)\n```\n\n## :span[Variables can be reassigned new values]{style=\"font-size: 0.85em;\"}\n```python\n# Create variable name \"number\" and assign a value to it\nnumber = 123 \nprint(number) # displays 123\n\n# Assign new value to existing variable \"number\"\nnumber = -50\n\nprint(number) # displays -50\n\n# add 10 and assign the result value to existing variable \"number\"\nnumber = number + 10\n\nprint(number) # displays -40\n```\n\n##\n\nNew values can be of different type. \n\n\u003cdiv style=\"margin-top: 2em; margin-bottom: 2em; width: 800;\"\u003e\n\u003ciframe width=\"800\" height=\"500\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=number%20%3D%20123%20%20%23%20an%20int%20value%0Amessage%20%3D%20%22hello%22%20%20%23%20a%20string%0A%0Aprint%28number,%20type%28number%29%29%0Aprint%28message,%20type%28message%29%29%0A%0A%23%20Now%20variable%20number%20refers%20to%20the%20string%20%22hello%22%0Anumber%20%3D%20message%0A%0Aprint%28number,%20type%28number%29%29%0Aprint%28message,%20type%28message%29%29%0A\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n\u003c/div\u003e\n\n## \nHowever, variables should be changed with caution as it can produce errors or strange results.\n```python\nnumber = 123 # an int value\nmessage = \"hello\" # a string\n\n# Now variable number refers to the string \"hello\"\nnumber = message\nprint(number * 2) # String repetition!\nprint(number - 10) # minus won't work with string.\n```\n\n```output\nhellohello\nTraceback (most recent call last):\n print(number - 10)\nTypeError: unsupported operand type(s) for -: 'str' and 'int'\n```\n\n## Example: Swapping values\nSometimes we need to swap (interchange) values of two variables.\n\n::::div{.hgrid style=\"width: 110%;\"}\n:::div\nA naive attempt (does not work):\n```python\nx = 137\ny = 42\n\n# Try swapping\nx = y\ny = x\n\nprint(x, y) # 42 42\n```\n:::\n\n:::div\nThe following will work:\n```python\nx = 137\ny = 42\n\n# Correct way to swap\ntemp = x\nx = y\ny = temp\n\nprint(x, y) # 42 137\n```\n:::\n::::\n\n##\n\nTry the problem \"Textbox\" on Ed.\n\n\n## Rules for variable names\n\n- A variable name can only contain alpha-numeric characters and underscores `A-Z, a-z, 0-9, _`\n- A variable name cannot start with a number\n- Variable names are case-sensitive\n - (`cat`, `Cat`, and `CAT` are three different variables)\n- They cannot be keywords.\n - Python has 33 reserved keywords, you can see a list of them by typing `help(\"keywords\")` in the Python shell.\n\n:::div{.px2 .my2 style=\"border: solid 1px lightgray; border-radius:5px; background-color: #d6dcef;\"}\nPython filenames must follow the same rules as above.\n:::\n\n## Good practice for naming variables\n\n- Name your variable something descriptive of its purpose or content.\n- If the variable is one word, all letters should be lowercase. Eg: `hour`, `day`.\n- If the variable contains more than one word, then they should all be lowercase and each separated by an underscore. This is called :i[snake case]. \n e.g. `is_sunny`, `cat_name`\n- Good variable names: `hour`, `is_open`, `number_of_books`, `course_code`\n- Bad variable names: `asfdstow`, `nounderscoreever`, `ur_stupid`, `CaPiTAlsANyWHErE`\n\n\n\n\n::divider\n","title":"2.1 — Variables, Arithmetic \u0026 String operations","date":"2024-01-06","published":true},{"slug":"Lecture-2.2.md","content":"\n## Function calls\nFunction take zero or more input values, perform an action or computation, and return the result value. \n\nInput values passed to a function are called :sc[arguments]. \n\nA :sc[Function Call] is an expression that looks like below:\n\n\n:::div{.center .code .my3}\n:span[function_name(argument1, argument2, ..., argumentN)]{.bgblue .p1 .br5}\n:::\n \nHow do we say it? — function \"takes\" argument(s) and \"returns\" a result. The result is also called the :sc[return value].\n\n## \nThe number of arguments required by a function depends on how that function is defined. \n\nFollowing are some built-in functions available in Python:\n```python\n# min() function takes 2 or more numbers and returns the minimum \nx = min(1, -4, 6)\nprint(x) # -4\n\n# abs() function takes a number and returns absolute value of the number\ny = abs(-6)\nprint(y) # 6\n\n# Gives an error if we do not give exactly one number\nz = abs(-1, 4)\n# TypeError: abs() takes exactly one argument (2 given)\n```\n\n## Expressions vs Statements\nAn :sc[Expression] is any valid combination of values, variables, operators, function calls. \n\nWhen executed, it always evaluates to a single object.\n\n```python\nx = 3\ny = 4\nz = x ** 2 + y ** 2 # this expression evaluates to an int object\nprint(z) # 25\n\ns = \"hello\"\ns2 = s * len(s) # this expression evaluates to str value\nprint(s2) # hellohellohellohellohello\n\n```\n\n## \nA statement is one or more lines of code that performs a task but does not evaluate to any value. \n\nSo, statements cannot be used as a part of an expression.\n\n```python\n\u003e\u003e\u003e x = 123 # Does not evaluate to anything so nothing shows below\n\u003e\u003e\u003e x # This is an trivially an expression\n123\n\u003e\u003e\u003e 10 + (x = 123) # Trying to use assignment statement in an expression\n 10 + (x = 123)\n ^\nSyntaxError: invalid syntax\n```\n\n## Function composition\nFunction composition is calling a function with the result(s) of another function(s).\n\nIt is a very useful thing to do especially when we do not need to store intermediate results.\n\n::::div{.hgrid}\n:::div\n::div[Using intermediate variables]{.b}\n```python\nx = -5\ny = -8\na = abs(x)\nb = abs(y)\nz = min(a, b)\nprint(x, y, z)\n```\n:::\n\n:::div\n::div[Using composition]{.b}\n```python\nx = -5\ny = -8\nz = min(abs(x), abs(y))\nprint(x, y, z)\n```\n:::\n::::\n\n## \n\nCheck :i[2.2 (B) — Built-in Functions] on Ed Lessons.\n\n\n## `print()` displays a space between arguments\n\n```python place=\"start\"\nnum = 1.5e3\ncity = \"New York City\"\nyear = 2023\nprint(num, city, year)\n```\n\nFirst, the arguments of `print()` are evaluated as:\n\n```python place=\"start\"\nprint(1500.0, \"New York City\", 2023)\n```\n\nThen, `print()` function is executed, which would display:\n\n:::div{.code style=\"background-color: white; border-radius:5px; padding: 0.25rem 1rem;\"}\n1500.0:span[\u0026nbsp;]{style=\"background-color: red;\"}New York City:span[\u0026nbsp;]{style=\"background-color: red;\"}2023\n:::\n\n::div[(Highlighted in red are spaces added by `print()`)]{.ppt-f90 style=\"margin-top: 0.5em;\"}\n\n\n##\n\n```python place=\"start\"\nx1 = 1.5\ny1 = 2\n\nprint(\"Point:\", \"(\", x1, \",\", y1, \")\")\n```\n\nFirst, the arguments of `print()` are evaluated as:\n\n```python place=\"start\"\nprint(\"Point:\", \"(\", 1.5, \",\", 2, \")\")\n```\n\nThen, `print()` function is executed, which would display:\n\n:::div{.code style=\"background-color: white; border-radius:5px; padding: 0.25rem 1rem;\"}\n:span[Point:]:span[\u0026nbsp;]{style=\"background-color: red;\"}(:span[\u0026nbsp;]{style=\"background-color: red;\"}1.5:span[\u0026nbsp;]{style=\"background-color: red;\"},:span[\u0026nbsp;]{style=\"background-color: red;\"}2:span[\u0026nbsp;]{style=\"background-color: red;\"})\n:::\n\n::div[(Highlighted in red are spaces added by `print()`)]{.ppt-f90 style=\"margin-top: 0.5em;\"}\n\n## \n\n```python place=\"start\"\nx1 = 1.5\ny1 = 2\n\npoint1 = \"(\" + str(x1) + \", \" + str(y1) + \")\" \nprint(\"Point:\", point1)\n```\n\nFirst, the arguments of `print()` are evaluated as:\n\n```python place=\"start\"\nprint(\"Point:\", \"(1.5, 2)\")\n```\n\nThen, `print()` function is executed, which would display:\n\n:::div{.code style=\"background-color: white; border-radius:5px; padding: 0.25rem 1rem;\"}\n:span[Point:]:span[\u0026nbsp;]{style=\"background-color: red;\"}(1.5, 2)\n:::\n\n::div[(Highlighted in red are spaces added by `print()`)]{.ppt-f90 style=\"margin-top: 0.5em;\"}\n\n\n## Defining a function\n\nA function is a :i[named] block of code that performs a task.\n\nSo far we have been using (calling) functions to do specific tasks — `print()`, `input()`, etc.\n\nWe can also define/create our own function.\n\n\n## Defining a function that takes no arguments\n\n:span[Such functions always do the same thing each time they are executed.]{.ppt-f94}\n\n::::div{.hgrid}\n:::div\n```python\n# Function definition\ndef display_greeting():\n print(\"+------------+\")\n print(\"| Welcome! |\")\n print(\"+------------+\")\n\n# Function call\ndisplay_greeting()\n\n# Call it again\ndisplay_greeting()\n```\n:::\n\n:::div\n```output\n+------------+\n| Welcome! |\n+------------+\n+------------+\n| Welcome! |\n+------------+\n```\n:::\n::::\n\n\n## :span[Functions with arguments and return value]{.ppt-f90}\nA function can return a value using `return` statement.\n\n::::div{.hgrid}\n:::div\n```python\ndef f(x):\n result = x * x - x - 1\n return result\n # OR: return x * x - x - 1\n\n# Call the function f\ny = f(5)\nprint(y) # 19\n\n# Call again\ny = f(10)\nprint(y) # 89\n```\n:::\n:::div\n```python\n# two parameters\ndef mean(x, y):\n return (x + y) / 2\n\n\nprint(mean(3, 4)) # 3.5\n```\n:::\n::::\n\n##\n\n:::div{.bgred .px2 .py025 .br5}\nParentheses `()` are required to call a function. Omitting them is a common mistake.\n\nWhen a function is called, correct number of arguments must be passed. It is an error to pass too many or too few arguments than what a function definition expects.\n:::\n\n## Creating a function — general form/syntax\n\n```python\ndef function_name(param1, param2, ..., paramN): # function header\n # function body\n statement1\n statement2\n .\n .\n statementN\n```\n\n- `def` is a Python keyword used to define functions\n- Notice how statements are indented by spaces, typically 4 spaces. In Thonny, we can just use tab key once to indent by 4 spaces.\n\n##\n\n- When we define a function using `def` keyword:\n - it is not executed. \n - Only the function name is created, which refers to the code block inside the function.\n- When we call a function, the code block inside the function is :i[actually] executed.\n\n\n## Why create our own functions?\n\n- Functions allow code re-use; duplication of code can be avoided.\n- They help organize code into sections, which makes programs easier to read and understand.\n- They make programs easier to fix.\n\n\n## Docstrings\n\nA :sc[docstring] (documentation string) is a multiline (triple-quoted) string that we write after the header of a function to explain how the function works.\n\nIt is an important part of programming to write such documentation. \nYou will be expected do so in your assignments.\n\n##\n\n```python\ndef euclidean_distance(x1, y1, x2, y2):\n \"\"\"\n Computes Euclidean distance between two 2D points.\n\n Parameters:\n x1 (float): x-coordinate of first point \n y1 (float): y-coordinate of first point\n x2 (float): x-coordinate of second point\n y2 (float): y-coordinate of second point\n\n Returns: the euclidean distance as a float\n \"\"\"\n d = (x1 - x2) ** 2 + (y1 - y2) ** 2\n return d ** 0.5\n```\n\n## Types of Errors\n\n:b[Syntax Errors]: When syntax is incorrect such as wrong punctuations, invalid characters, missing quotes or parentheses etc. \nProgram does not run at all in the case of syntax errors.\n```python\n# The following code has Syntax error due to missing double-quotes:\nx = 5\nprint(\"Square of x is)\nprint(x ** 2)\n```\n\n##\n\n:::div{.ppt-f94}\n:b[Runtime Errors], also called :sc[Exceptions], occur when there is a problem in the program during execution. \nAll code executes until an exception occurs.\n```python\n# The following code produces NameError because \n# variable y was not created before it is used.\nx = 5\nprint(\"Value of x is\", x)\nprint(\"Square of x is\", y ** 2)\n```\n\n:b[Semantic] or Logic errors are said to occur when a program executes without a problem but does not produce correct output as expected.\n\n:sc[Debugging] is the process of finding and removing errors in a program.\n:::\n\n::divider\n","title":"2.2 — Function calls, Defining functions, Types of Errors","date":"2024-01-07","published":true},{"slug":"Lecture-3.1.md","content":"\n## :span[Debugging in Thonny to understand expression evaluation]{.ppt-f80}\n\n:::div{.ppt-f87}\nIn Thonny, we can use debugging features to understand how expressions are evaluated:\n\n- To show variables and their values, go to menu \"View -\u003e Variables\"\n- First, run program in :i[debug mode] by clicking the \"Debug current script\" button (located next to the \"Run current script\" button and looks like a bug)\n- Then, we have two options:\n - Run the program line-by-line using \"Step over\" button next to the \"Debug\" button\n - Run program going inside each expression using \"Step into\" button (located next to \"Step over\" button)\n:::\n\n## \n\nTry the following examples in Thonny and use debug:\n\n```python\nx = 7\n\n# Increment value of variable x by 1\nx = x + 1\n\n#\ny = x * x + 2 * (x + 1) + max(x + 1, 5)\n\n# Calling print() with 4 arguments\nprint(\"x =\", x, \"y =\", y)\n```\n\n## \n\n```python\nx1 = 1.5\ny1 = 2\n\nprint(\"Given points:\", \"(\", x1, \",\", y1, \")\")\n\npoint1 = \"(\" + str(x1) + \", \" + str(y1) + \")\" \nprint(\"Given points:\", point1)\n```\n\n\n## Boolean Values\n\nPython has two values `True` and `False` of type `bool`, which are useful for expressing and storing yes/no or true/false kind of data.\n\n```python\n\u003e\u003e\u003e True\nTrue\n\u003e\u003e\u003e False\nFalse\n\u003e\u003e\u003e type(True)\n\u003cclass 'bool'\u003e\n\u003e\u003e\u003e type(False)\n\u003cclass 'bool'\u003e\n```\n\n## Comparison Operators\n\n:sc[comparison operators], also known as :i[relational operators], are used to compare two values, such as numbers or string. \nThe result of such comparison is always a `bool` value i.e. `True` or `False`.\n\n:::div{.hgrid}\n```python\n# are these numbers equal?\n\u003e\u003e\u003e 10 == 10 \nTrue\n\u003e\u003e\u003e 10 == 20\nFalse\n```\n```python\n\u003e\u003e\u003e x = 5\n\u003e\u003e\u003e y = 10\n\u003e\u003e\u003e x == y\nFalse\n\u003e\u003e\u003e x \u003c y\nTrue\n\u003e\u003e\u003e x \u003e y\nFalse\n```\n:::\n\n##\n\n```python\n# A variable can store the result of a boolean expression \n# (just like we did for arithmetic expressions)\n\u003e\u003e\u003e x = 3\n\u003e\u003e\u003e is_positive = (x \u003e 0)\n\u003e\u003e\u003e is_positive\nTrue\n\n\u003e\u003e\u003e x = 5\n\u003e\u003e\u003e y = 5\n\u003e\u003e\u003e is_equal = (x == y)\n\u003e\u003e\u003e is_equal\nTrue\n```\n\n## Boolean Expressions\nA :sc[boolean expression] is an expression that evaluates to either `True` or `False`.\nExamples above show how boolean expressions are created using comparison operators.\n\n:::div{.p1 .br5 .bgred}\n- Common error is using `=` (single equals sign) instead of `==` (double equals sign)\n- `=` is the assignment operator, used to create variable and assign it a value\n- `==` is a comparison operator used to check for equality between two values\n:::\n\n\n## List of comparison operators\n- `x == y` — `True` if `x` is equal to `y`, otherwise `False`\n- `x != y` — `True` if `x` is not equal to `y`, otherwise `False`\n- `x \u003c y` — `True` if `x` is less than `y`, otherwise `False`\n- `x \u003e y` — `True` if `x` is greater than `y`, otherwise `False`\n- `x \u003c= y` — `True` if `x` is less than or equal to `y`, otherwise `False`\n- `x \u003e= y` — `True` if `x` is greater than or equal to `y`, otherwise `False`\n \n\n## Order of operations\n\nAll comparison operators (e.g. `==`, `!=`, etc.) have same priority and are evaluated from left to right.\n\nAll arithmetic and string operators have higher priority than comparison operators.\n\n```python\n\u003e\u003e\u003e x = 5\n# + operator will be evaluated before ==\n\u003e\u003e\u003e x + 1 == 6\nTrue\n```\n\n##\n\n::::div{.p1 .br5 .bggreen}\nWrite a program that takes an integer as input from the user and displays on your screen whether it is true or false that such integer is even.\n\n:::div{.hgrid}\n```output\nEnter a number: 5\n5 is an even number: False\n```\n```output\nEnter a number: 8\n8 is an even number: True\n```\n:::\n\n```python\nnum = int(input(\"Enter a number: \"))\n# Write code below\n```\n::::\n\n##\n\n:::solution\n```python\nnum = int(input(\"Enter a number: \"))\n\n# a number is even if remainder is 0 when it is divided by 2\nis_even = (num % 2 == 0)\n\nprint(num, \"is an even number:\", is_even)\n\n# without using extra variable:\n# print(num, \"is an even number:\", num % 2 == 0)\n```\n:::\n\n\n## Comparing strings\nComparison operators work for strings as well. \nThe comparison is done :i[alphabetically] i.e. following a dictionary order\n\n:::div{.hgrid}\n```python\n\u003e\u003e\u003e \"cat\" == \"cat\"\nTrue\n\u003e\u003e\u003e \"cat\" == \"dog\"\nFalse\n\u003e\u003e\u003e \"cat\" != \"Cat\"\nTrue\n```\n```python\n# \"c\" appears before \"d\" alphabetically\n\u003e\u003e\u003e \"cat\" \u003c \"dog\"\nTrue\n\n# A-Z appear before a-z alphabetically\n\u003e\u003e\u003e \"cat\" \u003c \"Dog\"\nFalse\n```\n:::\n\n##\n\n```python\n# Objects of different types are always not equal\n\u003e\u003e\u003e \"cat\" == 123\nFalse\n\n# inequality is not allowed \n# between a number and str\n\u003e\u003e\u003e \"cat\" \u003c 123\nTypeError: '\u003c' not supported between instances of 'str' and 'int'\n\n# All of the above examples work the same when using variables\n\u003e\u003e\u003e s1 = \"cat\"\n\u003e\u003e\u003e s2 = \"dog\"\n\u003e\u003e\u003e s1 == s2\nFalse\n```\n\n\n## Equality and floating point numbers\n\nConsider following example:\n\n```python\n\u003e\u003e\u003e x = 1.1 + 2.2\n\u003e\u003e\u003e x == 3.3 # why is this False?\nFalse\n```\n\n- As we saw earlier, a floating-point number is stored with 64-bit :i[finite precision].\n- This means that a number may not be stored as precisely as we would like.\n\n## Correct way to check if two `float` values are equal\n\nWe should check if they are \"close enough\".\n\n```python\n\u003e\u003e\u003e epsilon = 0.000001 # define how close two numbers need to be\n\n\u003e\u003e\u003e x = 1.1 + 2.2\n\u003e\u003e\u003e x\n3.3000000000000003\n\n# Check if x and 3.3 are within epsilon distance\n\u003e\u003e\u003e abs(x - 3.3) \u003c epsilon\nTrue\n```\n\nThe epsilon value depends on the application and how much error we are willing tolerate.\n\n## Logical Operators\nLogical operators are useful to combine multiple conditions.\n\nLogical operators take boolean expressions as operands and produce a result of type `bool` when evaluated.\n\nPython has 3 boolean operators:\n- `not` — a unary operator\n- `and` — binary operator\n- `or` — binary operator\n\n##\n\nSuppose `x` is a variable of type `bool`:\n:::div{.code .p1 style=\"font-size: 0.75em;\"}\n| x | not x |\n|-------|---------|\n| False | True |\n| True | False |\n:::\n\n`not x` evaluates to the opposite value of `x`.\n\n## \n\nSuppose `x` and `y` are variables of type `bool`:\n::::div{.hgrid}\n:::div{.code .ppt-py2 .ppt-f80}\n| x | y | x and y |\n|-------|-------|---------|\n| True | True | True |\n| True | False | False |\n| False | True | False |\n| False | False | False |\n:::\n:::div{.code .ppt-py2 .ppt-f80}\n| x | y | x or y |\n|-------|-------|---------|\n| True | True | True |\n| True | False | True |\n| False | True | True |\n| False | False | False |\n:::\n::::\n\n`x and y` evaluates to `True` if and only if both `x` and `y` are `True`.\n\n`x or y` evaluates to `False` if and only if both `x` and `y` are `False`.\n\n## Order of operations\n\nIn order of higher to lower priority: `not`, `and`, `or`\n\nAs usual, we can use parentheses in order to change the priority.\n\n:::::div{.hgrid}\n::::div{style=\"border-right: solid 1px black;\"}\nWhat does `b and not a or b` evaluate to if `a = False` and `b = True` ?\n:::div{.code}\n- b and not a or b\n- True and not False or True\n- True and True or True\n- True or True\n- True\n:::\n::::\n::::div\nWhat does `a and not (a or b)` evaluate to if `a = True` and `b = False` ? \n:::div{.code}\n- a and not (a or b)\n- True and not (True or False)\n- True and not True\n- True and False\n- False\n:::\n::::\n:::::\n\n## Updated operator precedence table\n\n::::div{.hgrid}\n:::div{.ppt-f80}\n| Operator | Associativity |\n|----------------------------------|---------------|\n| `()` (parentheses) | - |\n| `**` | Right |\n| Unary `-` | - |\n| `*`, `/`, `//`, `%` | Left |\n| Binary `+`, `-` | Left |\n| `==`, `!=`, `\u003c`, `\u003e`, `\u003c=`, `\u003e=` | Left |\n| `not` | - |\n| `and` | Left |\n| `or` | Left |\n| `=` (assignment) | Right |\n:::\n\nYou don't need to memorize all this, use parenthesis when in doubt!\n\n::::\n\n\n\n## Try these examples in Thonny\n\nChange the value of x and see results of boolean expressions.\n\n```python\nx = 30\n# Is an even number greater than 20?\nprint(x % 2 == 0 and x \u003e 20)\n\nx = 10\n# Is an even number or a multiple of 5 greater than 20?\nprint(x % 2 == 0 or x % 5 == 0 and x \u003e 20)\n\n# Is a multiple of 2 or 5, greater than 20? \nprint((x % 2 == 0 or x % 5 == 0) and x \u003e 20)\n```\n\n##\n\n:::div{.px1 .py025 .bggreen}\n:b[Try it!]\n\nWrite a program that takes 3 integers $x, y, z$ as inputs and prints out `True` if $y$ is an even number between $x$ and $z$, `False` otherwise. Assume all 3 numbers will be different.\n\n```python\n# Retrieve inputs from the user\nx = int(input(\"Enter the x: \"))\ny = int(input(\"Enter the y: \"))\nz = int(input(\"Enter the z: \"))\n\n# Write code below\n```\n:::\n\n##\n\n:::solution\n```python\n# Retrieve inputs from the user\nx = int(input(\"Enter the x: \"))\ny = int(input(\"Enter the y: \"))\nz = int(input(\"Enter the z: \"))\n\n# check if y is even\nis_even = y % 2 == 0\n\n# check if y is between x and z\nis_between = (x \u003c y and y \u003c z) or (z \u003c y and y \u003c x)\n\nprint(is_even and is_between)\n```\n:::\n\n\n## Flow of execution\n\n- Flow of execution refers to order in which statements (lines of code) in our program are executed.\n- So far in our programs, each line was executed unconditionally.\n- For most programs, it is not enough as we need to make choices or run code repeatedly.\n\nWe need to control the flow of execution in our programs.\n\n## Control flow\n\nThe control flow of a program determines:\n- Which parts of the code should always be executed\n- Which parts should be executed only under certain conditions\n- Which parts should be executed repeatedly\n\nAll of these can be achieved using control flow statements:\n- `if` statement for conditional execution\n- `for` and `while` loops for repeated execution\n\n\n## :code[if] statement — to execute or not to execute\n\n:::::hgrid{cols=\"1fr 4fr\" margin=\"0 0\" gap=\"2em\"}\n::::hgrid\n:::div{.px1 .py025 .br5 style=\"background-color: white; white-space: pre;\"}\n:code[if]{.bgred} :code[condition]{.bggreen} :code[:]{.bgyellow}\n` `{code}:code[code block]{.bgblue}\n:::\n::::\n:::div\n- :code[condition]{.bggreen} must be a boolean expression\n- :code[code block]{.bgblue} is one of more Python statements\n- :code[code block]{.bgblue} is executed only if the condition is True, otherwise it is skipped.\n:::\n:::::\n:::div{style=\"margin-top: -1em;\"}\nNotice space before code block. It is called :sc[indentation]. \n\n:div[Indentation is required to tell Python that the code belongs inside `if` statement.]{.bgred .px1 .py025}\n\nTypically, 4 spaces are used for indentation. We can use :i[tab] key to indent.\n:::\n\n##\n\nTry the following examples with different values for variables.\n\n```python place=\"start\"\nx = 10 \nif x \u003e 0:\n print(x, \"is positive\")\n```\n\n```python place=\"start\"\nnum = -5.2\n\nabsolute_num = num\n\nif num \u003c 0:\n absolute_num = -num\n \nprint(\"Absolute value of\", num, \"is\", absolute_num)\n```\n\n##\n\n```python\nx = 1000\ny = 123\n\nmin_value = x\n\nif y \u003c min_value:\n min_value = y\n \nprint(\"Minimum of\", x, \"and\", y, \"is\", min_value)\n```\n\n\n## `if` statement with `else` part\n\n`if` statements can have `else` part to make a choice between two code blocks.\n\n:::::hgrid{cols=\"1fr 4fr\" margin=\"0 0\" gap=\"2em\"}\n::::hgrid\n:::div{.px1 .py025 .br5 style=\"background-color: white; white-space: pre;\"}\n:code[if]{.bgred} :code[condition]{.bggreen} :code[:]{.bgyellow}\n` `{code}:code[code block:sub[1]]{.bggreen}\n:code[else]{.bgred} :code[:]{.bgyellow}\n` `{code}:code[code block:sub[2]]{.bgblue}\n:::\n::::\n\n:::div\n- When :code[condition]{.bggreen} is `True`, :code[code block:sub[1]]{.bggreen} is executed\n- Otherwise (:code[condition]{.bggreen} is `False`) and :code[code block:sub[2]]{.bgblue} is executed \n- The code blocks are also called :sc[branches] of the if-statement.\n:::\n:::::\n##\n\n```python\nx = 10 # change this to -5 and run\n\nif x \u003e 0:\n print(\"x is positive.\")\nelse:\n print(\"x is not positive.\")\n```\n\n## \n\n::::div{.p2 .bggreen}\nWrite a program that takes an integer as input from the user and displays whether the number is even or odd.\n\n:::hgrid\n```output\nPlease enter a number: 5\nThe number 5 is odd\n```\n```output\nPlease enter a number: 8\nThe number 8 is even\n```\n:::\n::::\n\n##\n\n:::solution\n```python\nnum = int(input(\"Please enter a number: \"))\n\n# a number is even if remainder is zero when divided by 2\nif num % 2 == 0:\n print(\"The number\", num, \"is even\")\nelse:\n print(\"The number\", num, \"is odd\")\n```\n:::\n\n\n::divider\n","title":"3.1 — Order of Expression Evaluation, Comparing Values, Conditional Execution","date":"2024-01-15","published":true},{"slug":"Lecture-3.2.md","content":"\n## Flow of execution — functions\n\nTry debugging in thonny for the following examples. (Also try :i[Step out] button, when the code inside a function is being executed.)\n\n:::hgrid\n```python\n# Function definition\ndef display_greeting():\n print(\"+------------+\")\n print(\"| Welcome! |\")\n print(\"+------------+\")\n\ndisplay_greeting()\n\ndisplay_greeting()\n```\n\n```python\ndef f(x):\n result = x * x - x - 1\n return result\n # OR: return x * x - x - 1\n\ny = f(5)\nprint(y)\n\ny = f(10)\nprint(y)\n```\n:::\n\n## \n\n```python\ndef f():\n return 2\n\n\ndef g():\n return 3\n\n\ndef h():\n return f() * g()\n\n\nprint(h())\n```\n\n## Variables and `if` statement\n\nVariables can be created inside the branches of `if` statement. \n\nMake sure that all branches have same variable names!\n\n```python\nincome = 15000\n\nif income \u003c 12000:\n tax = 0.0\nelse:\n taxes = income * 15.5 / 100 # Change variable name to tax\n \nprint(\"Your tax is\", tax)\n```\n\n```output\nNameError: name 'tax' is not defined\n```\n\n\n## :span[Mutually exclusive conditions — chained `if-elif-else` statement]{style=\"margin: 0 -2em;\"} \n\n::::hgrid{margin=\"0 -2em\"}\n```python place=\"center\"\nincome = 20000\n\nif income \u003c 12000:\n tax = 0.0\nelif income \u003c 30000:\n tax = income * 15.0 / 100\nelif income \u003c 100000:\n tax = income * 20.0 / 100\nelse:\n tax = income * 25.0 / 100\n \nprint(\"Your tax is\", tax)\n```\n:::div\n- Mutually exclusive — only one of these blocks will get executed. \n- Order matters! If first of the conditions is `True`, later conditions are not checked.\n- We can have as many `elif`'s as you want.\n- The final `else` part is not required so you may omit it if not needed.\n:::\n::::\n\n\n## Example\n\nIs there anything wrong in code below?\n\n:::hgrid\n```python\ntemperature = 25\n\nif temperature \u003e 0:\n print(\"Cold\")\nelif temperature \u003e 20:\n print(\"Warm\")\nelif temperature \u003e 30:\n print(\"Hot\")\nelse: \n print(\"Freezing\")\n```\n```output\nCold\n```\n:::\n\n## Order of conditions matters!\n\n::::solution\n:::hgrid\n```python\ntemperature = 25\n\nif temperature \u003e 30:\n print(\"Hot\")\nelif temperature \u003e 20:\n print(\"Warm\")\nelif temperature \u003e 0:\n print(\"Cold\")\nelse: \n print(\"Freezing\")\n```\n```python\ntemperature = 25\n\nif temperature \u003e 0 and temperature \u003c= 20:\n print(\"Cold\")\nelif temperature \u003e 20 and temperature \u003c= 30:\n print(\"Warm\")\nelif temperature \u003e 30:\n print(\"Hot\")\nelse: \n print(\"Freezing\")\n\n```\n:::\n::::\n\n##\n\nTry \"Blood Pressure\" problem on Ed Lessons.\n\n## `if` statements can be nested\n\nExamples below are logically equivalent.\n\n::::hgrid{gap=\"3em\" margin=\"0 0\"}\n:::div\n:b[Nested `if` statements]\n```python place=\"start\"\nx = 10\nif x \u003e 0:\n print(\"Positive\")\nelse:\n if x \u003c 0:\n print(\"Negative\")\n else:\n print(\"Zero\")\n```\n:::\n:::div\n:b[Chained `if` statement]\n```python place=\"start\"\nx = 10\nif x \u003e 0:\n print(\"Positive\")\nelif x \u003c 0:\n print(\"Negative\")\nelse:\n print(\"Zero\")\n```\n:::\n::::\n\n::div[You can use either one, but nested statements can easily become difficult to read.]{.ppt-95}\n\n\n## Correct indentation is essential!\n\nSometimes, incorrect indentation may not give an error but it may lead to an unexpected program.\n\n```python\nincome = 1000\n\nif income \u003c 12000:\n print(\"You don't have to pay tax.\")\n tax = 0.0\nelse:\n print(\"You have to pay tax.\")\ntax = income * 15.0 / 100 # this line should be indented\n \nprint(\"Your tax is\", tax)\n```\n\n## Iteration using `for` loop\n\n`for` loop can be used to repeatedly execute a block of code.\n\n::::hgrid{gap=\"4em\"}\n:::div\n```python\nfor i in range(5):\n print(\"Hello\")\n```\n```output\nHello\nHello\nHello\nHello\nHello\n```\n:::\n:::div\n```python\nfor i in range(5):\n print(i)\n```\n\n```output\n0\n1\n2\n3\n4\n```\n:::\n::::\n\n## What happens when the `for` loop is executed?\n\n::::hgrid{cols=\"1fr 3fr\" margin=\"0 0\"}\n:::hgrid\n```python\nfor i in range(5):\n print(i)\n```\n:::\n:::div\n- `range(5)` will produce a sequence of integers $0, 1, 2, 3, 4$ in steps.\n- `for` loop allows us to iterate i.e. \"go over\" that sequence, a number at a time\n - In each step of the loop, variable `i` gets a value from the sequence\n- We can have any valid variable name, other than `i` if we want.\n:::\n::::\n\nTry step-by-step execution of the examples above!\n\n## `range()` function takes up to 3 arguments\n\n`range(end)`: produces sequence `0, 1, 2, ..., end-1`\n\n##\n\n`range(start, end)`: produces sequence `start, start+1, ..., end-1`\n\n##\n\n`range(start, end, step)`:\n- if `step \u003e 0`, produces sequence `start, start+step, ..., N` where `N \u003c end`\n- if `step \u003c 0`, produces sequence `start, start+step, ..., N` where `N \u003e end`\n\n## Examples of range()\n\n:::hgrid\n```python\n# 0, 1, 2, ..., 9\nfor i in range(10):\n print(i)\n\n# 1, 2, ..., 10\nfor i in range(1, 11):\n print(i)\n \n# 0, 2, 4, ..., 18 \nfor i in range(0, 20, 2):\n print(i)\n```\n```python\n# 10, 15, 20, 25, ..., 95\nfor i in range(10, 100, 5):\n print(i)\n\n# 10, 9, 8, ..., 1\nfor i in range(10, 0, -1):\n print(i)\n```\n:::\n\n\n## Exercise\n\nCompute sum of first N numbers.\n\n##\n\n:::solution\n```python\nN = 50\n\ntotal = 0\nfor num in range(1, N+1):\n total = total + num\n \nprint(total)\n```\n:::\n\n##\n\nTry \"Harmonic sum\" problem on Ed Lessons.\n\n## Indexing \u0026 Slicing Strings\n\nRecall that a string is a sequence of characters.\n\nEach character, therefore, has a position or an :sc[index]. \n\nIndex starts with zero. For example, for the string `\"Hello\"`:\n\n:::::div{style=\"display:grid;margin: 0.5rem 0;\"}\n::::div{style=\"display:grid;grid-auto-flow: row;grid-auto-rows: min-content;gap:0;width: min-content;justify-self:center;\"}\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[0]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;border: 1px solid black; width: min-content;\"}\n::div[H]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[o]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n:::\n\n::::\n:::::\n\n\n## \nIndices must be integers and cannot be float.\n\nPython also allows negative indices, which go from right to left:\n\n:::::div{style=\"display:grid;margin: 0.5rem 0;\"}\n::::div{style=\"display:grid;grid-auto-flow: row;grid-auto-rows: min-content;gap:0;width: min-content;justify-self:center;\"}\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[0]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;border: 1px solid black; width: min-content;\"}\n::div[H]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[o]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[-5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n:::\n\n::::\n:::::\n\nFor any string `s`, \n- valid positive index values are from `0` to `len(s)-1`.\n- valid negative index values are from `-len(s)` to `-1`.\n\n##\nSquare brackets `[]` are used to get the letter in a string at a given index.\n\n```python\n\u003e\u003e\u003e message = \"Hello\"\n\u003e\u003e\u003e message[0] # first letter\n'H'\n\u003e\u003e\u003e message[1] # second letter\n'e'\n\u003e\u003e\u003e message[4] # fifth letter, the last one in the string\n'o'\n```\n\n##\n\n:::::div{style=\"display:grid;margin: 0.5rem 0;\"}\n::::div{style=\"display:grid;grid-auto-flow: row;grid-auto-rows: min-content;gap:0;width: min-content;justify-self:center;\"}\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[0]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;border: 1px solid black; width: min-content;\"}\n::div[H]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[o]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[-5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n:::\n\n::::\n:::::\n\n```python\n\u003e\u003e\u003e message = \"Hello\"\n\u003e\u003e\u003e message[5] # there is no letter at this index\nIndexError: string index out of range\n\u003e\u003e\u003e message[-1]\n'o'\n\u003e\u003e\u003e message[-5]\n'H'\n\u003e\u003e\u003e message[-6] # there is no letter at this index\nIndexError: string index out of range\n\u003e\u003e\u003e message[1.0]\nTypeError: string indices must be integers\n```\n\n## Traversing a string\n\nWe can use `for` loop with `range()` function to go over a string letter-by-letter.\n\n:::hgrid\n```python\nmessage = \"Hello\"\n\nfor i in range(len(message)):\n print(i, message[i])\n```\n\n```output\n0 H\n1 e\n2 l\n3 l\n4 o\n```\n:::\n\n## \n\nAnother example:\n\n```python\nletters = \"bcmrst\"\n\nfor i in range(len(letters)):\n print(letters[i] + \"ake\")\n```\n\n##\n\nTry \"Remove spaces from a string\" problem on Ed Lessons.\n\n\n## Using slice to get substrings \n\n:::div{.ppt-m-2}\nUsing slice notation we can get parts of a string: `string[start:end:step]`. \n`start`, `end`, `step` values must be integers and work similar to `range()` function.\n:::\n\n:::::div{style=\"margin: 0.5rem 0;\" .grid}\n::::div{style=\"display:grid;grid-auto-flow: row;grid-auto-rows: min-content;gap:0;width: min-content;justify-self:center;\"}\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[0]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[6]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[7]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[8]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;border: 1px solid black; width: min-content;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[i]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[n]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[a]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[-9]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-8]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-7]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-6]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n:::\n\n::::\n:::::\n\n```python\n\u003e\u003e\u003e fruit = \"pineapple\"\n\n\u003e\u003e\u003e fruit[4:7] # letters at indices 4, 5, 6\n'app'\n\u003e\u003e\u003e fruit[2:7:2] # letters at indices 2, 4, 6\n'nap'\n```\n\n##\n\n\n:::::div{style=\"margin: 0.5rem 0;\" .grid .ppt-only}\n::::div{style=\"display:grid;grid-auto-flow: row;grid-auto-rows: min-content;gap:0;width: min-content;justify-self:center;\"}\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[0]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[6]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[7]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[8]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;border: 1px solid black; width: min-content;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[i]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[n]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[a]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[-9]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-8]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-7]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-6]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n:::\n\n::::\n:::::\n\n\n```python\n\u003e\u003e\u003e fruit[:4] # same as fruit[0:4]\n'pine'\n\u003e\u003e\u003e fruit[4:] # same as fruit[4:len(fruit)]\n'apple'\n\n\u003e\u003e\u003e fruit[-5:] # from index -5 to the end of string\n'apple'\n\u003e\u003e\u003e fruit[-5:-2] # letters at indices -5, -4, -3\n'app'\n```\n\n##\n\n:::::div{style=\"margin: 0.5rem 0;\" .grid .ppt-only}\n::::div{style=\"display:grid;grid-auto-flow: row;grid-auto-rows: min-content;gap:0;width: min-content;justify-self:center;\"}\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[0]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[6]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[7]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n::div[8]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:end center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;border: 1px solid black; width: min-content;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[i]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[n]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[a]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[p]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[l]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n::div[e]{style=\"border: 1px solid black;width:calc(var(--unit)*2);height:calc(var(--unit)*1.7);display:grid;place-content:center;\"}\n:::\n\n:::div{style=\"display:grid;grid-auto-flow: column;grid-auto-columns: min-content;gap:0;font-family: Hack;width: min-content; font-size: 0.7em;\"}\n::div[-9]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-8]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-7]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-6]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-5]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-4]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-3]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-2]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n::div[-1]{style=\"width:calc(var(--unit)*2);height:calc(var(--unit)*1.2);display:grid;place-content:start center;\"}\n:::\n\n::::\n:::::\n\n```python\n# Negative step size of -1 means go from \n# right to left, i.e. in reverse order\n\u003e\u003e\u003e fruit[-4:-8:-1] # letters at -4, -5, -6, -7\n'paen'\n\n# Omitting start and end mean select whole string,\n# but step size -1 means right to left i.e. reverse order \n\u003e\u003e\u003e fruit[::-1]\n'elppaenip'\n```\n\n::divider\n","title":"3.2 — if statement, for loop, Indexing \u0026 Slicing Strings","date":"2024-01-16","published":true},{"slug":"Lecture-4.1.md","content":"\n## Assignment 1\n\n- Read the instruction pages\n- Ask questions on Ed Discussion — posts can be made anonymous\n - Make the post private when you want to include code\n- It is not enough that your solution passes all the given \"public\" tests\n - Make sure your solution will work for other values not given in the examples\n - Q1: use the blood type compatibility table to test your code with different values\n - Q2: Try different strings of lengths 1–10\n - Q3: For small `n`, you can use the given formula to verify your solution \n\n## \n\n:::div{.ppt-scale-1_25}\n:b[What does this mystery function do?]{.ppt-f80}\n\n\u003ciframe width=\"800\" height=\"420\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=def%20mystery%28s%29%3A%20%20%20%20%20%0A%20%20%20%20result%20%3D%20%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28s%29%29%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20s%5Bi%5D%20%2B%20result%0A%0A%20%20%20%20return%20result%0A%0Anew_string%20%3D%20mystery%28%22hello%22%29%0Aprint%28new_string%29\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n## Functions and return value `None`\n\nFunctions that do not have an explicit `return` statement, return a special value `None`.\n\n##\n\nThe following 3 functions are equivalent because\n- Python implicitly returns `None` for a function that does not use a return statement\n- if the `return` statement is used without a value, `None` is returned.\n\n```python\ndef greeting():\n print(\"Welcome!\")\n```\n\n:::hgrid\n```python\ndef greeting():\n print(\"Welcome!\")\n return\n```\n```python\ndef greeting():\n print(\"Welcome!\")\n return None\n```\n:::\n\n## \n\n::::hgrid\n:::div\n:b[Function that prints]\n```python\ndef f(x):\n result = x * x - x - 1\n print(result)\n\nf(5) # No print here\n\ny = f(10) + 10 # TypeError\n```\nLess flexible to use; cannot be used with other expressions\n:::\n\n:::div\n:b[Function that returns a value]\n```python\ndef f(x): \n result = x * x - x - 1\n return result\n\nprint(f(5)) # print here\n\ny = f(10) + 10 # works\n```\nMore flexible to use; can be used with other expressions\n:::\n::::\n\nIt is usually advisable to keep functions \"pure\" and do `print()`, `input()` outside the functions when possible.\n\n##\n\nTry the problem \"Max of three numbers\" on Ed Lessons.\n\n\n## Short Circuit Evaluation\n\nThe evaluation of a boolean expression with `and` and `or` stops as soon as the end result can be inferred.\n\nFor example, in the expression below evaluates to `False` no matter what `not (x \u003e= 1 or y == 3)` evaluates to.\n\n```python lineno=false\n\u003e\u003e\u003e 2 \u003c 1 and not (x \u003e= 1 or y == 3)\nFalse\n```\n\n##\n\nIn general, for any expression with `and` operator:\n\n```python lineno=false\nleft_operand and right_operand\n```\n\nif `left_operand` is `False`, Python does not evaluate `right_operand`.\n\n##\n\nSimilarly, for any expression with `or` operator:\n\n```python lineno=false\nleft_operand or right_operand\n```\n\nif `left_operand` is `True`, Python does not evaluate `right_operand`.\n\nFor example,\n```python lineno=false\n# Evaluates to True no matter what (x \u003c 5) evaluates to\n\u003e\u003e\u003e 1 == 1 or (x \u003c 5)\nTrue\n```\n\n## Why is Short Circuit Evaluation useful?\n\n- It can save time, e.g. when `right_operand` has a computationally expensive function call.\n- It can avoid unnecessary errors as show below.\n\n```python\n\u003e\u003e\u003e x = 0\n\u003e\u003e\u003e 1 / x \u003c 0.5 # cannot divide a number by zero\nZeroDivisionError: division by zero\n\n\u003e\u003e\u003e x != 0 and 1 / x \u003c 0.5\nFalse\n\u003e\u003e\u003e x = 3\n\u003e\u003e\u003e x != 0 and 1 / x \u003c 0.5\nTrue\n```\n\n## \n\n:b[Common mistake when using logical operators]\n\n:::hgrid\n```python lineno=false\nx == \"a\" or \"b\" # Incorrect\n\nx == \"a\" or x == \"b\" # Correct\n```\n\n```python lineno=false\nx == \"a\" and \"b\" # Incorrect\n\nx == \"a\" and x == \"b\" # Correct\n```\n:::\n\n## `while` statement\n\n`while` statement is another way to repeatedly execute a block of code.\n\nGeneral format of a while loop:\n\n:::div{.px1 .py025 .br5 style=\"background-color: white; width: 90%; white-space: pre;\"}\n:code[Initialize variables so that ] :code[condition]{.bggreen .ibox} :code[is True]\n:code[while]{.b} :code[condition]{.bggreen .ibox} :code[:]{.b}\n` `{code}:code[code block]{.bgblue .ibox}\n` `{code}:code[update variables that affect ] :code[condition]{.bggreen .ibox}\n:::\n\n##\n\nWhat `while` loop does:\n1. Evaluate the :code[condition]{.bggreen .ibox}\n2. If :code[condition]{.bggreen .ibox} evaluates to `False`, loop body is not executed.\n3. If :code[condition]{.bggreen .ibox} evaluates to `True`, run the loop body (all indented lines of code) \n a) In loop body we perform some task, :code[code block]{.bgblue .ibox}, and update variables that may change the :code[condition]{.bggreen .ibox} value \n b) Go back to step 1 \n\n##\n\n```python\n# a program to compute sum of first N numbers\nN = 10\n\ntotal = 0\n\ni = 1 # Set value so that condition below is True \nwhile i \u003c= N: # Check if condition is True\n # main task of summing numbers:\n total = total + i \n \n # update i, affects value of condition i \u003c= N \n i = i + 1\n\n# print result outside the loop \nprint(total)\n```\n\n##\n\nIt is a common mistake to forget updating the condition inside loop body. \n\nSee what happens when you remove/comment out the line `i = i + 1` in the previous example. \n\nThe loop will never end — an infinite loop!\n\n\n## Augmented assignment statements\nAugmented assignment is the combination, in a single statement, of a arithmetic operation and an assignment statement:\n\n::::hgrid\n\n```python\nx = 3\ny = 5\n\nx += 1 # same as: x = x + 1\nx += y # x = x + y\nx += x * y # x = x + x * y\n\nx -= 5 # x = x - 5\n\nx *= 2 # x = x * 2\n```\n:::div\n- Similarly, other operators exist: `/=`, `//=`, `%=`, `**=`.\n- These are very useful, especially when updating the condition in while loop.\n:::\n::::\n\n## Loops with indefinite number of steps\n\nSo far we have seen loops that work with fixed number of steps. \n\nBut while loop can be used for repeating code for unknown number of steps.\n\n:::greenbox\nWrite a program to keep asking for password until correct password is entered.\n\nAssume that correct (secret) password is `abcd1234`.\n:::\n\n\n##\n\n```python\npassword = input(\"Enter password: \")\n\nwhile password != \"abcd1234\":\n print(\"Incorrect password, try again!\")\n \n password = input(\"Enter password: \")\n \n# Below line executes only after the above loop ends,\n# i.e. when the correct password was entered.\nprint(\"Login successful!\")\n```\n\n## `for` vs `while` loops\n\n- `for` loops are better when we want to go over a fixed sequence such as a string or a sequence of numbers\n- `while` loop is more flexible as it allows arbitrary conditions and number of steps. e.g. do something until user enters correct data\n\n\n## Importing modules\nA :sc[module] is a Python file (typically a `.py` file) containing function definitions, statements, etc.\n\nMany modules such as `math` and `random` are already installed with Python.\n\n## \n\nUsing `import` statement, we can use functions, variables etc. from a module in our program:\n\n```python\nimport math\n\n# Call a function defined in a module using dot operator\nx = math.sqrt(16)\nprint(x)\n\ny = math.sin(math.pi / 2)\nprint(y)\n```\n\n##\n\nAnother way to import functions, variables from the module:\n```python\nfrom math import sqrt, sin, pi\n\n# Now, we can call sqrt and sin without the \"math.\" prefix\nx = sqrt(16)\nprint(x)\n\ny = sin(pi / 2)\nprint(y)\n```\n\n## \n\nUse `help()` function in Python Shell to see list of all function contained in `math` module:\n```python lineno=false\n\u003e\u003e\u003e import math\n\u003e\u003e\u003e help(math) # will display a long doc, not showing here\n\n\u003e\u003e\u003e help(math.sqrt) # show help on a specific function\nHelp on built-in function sqrt in module math:\n\nsqrt(x, /)\n Return the square root of x.\n```\n\n\n## `random` module\nIn Python, we can generate random numbers using the `random` module.\n\nThe module provides us with a lot of different functions but for the moment we’ll focus on the following:\n- `random()` – It returns a random float value between $0.0$ (inclusive) and $1.0$ (exclusive)\n- `randint(x, y)` – It returns a random integer between x and y, both included.\n\n##\n\nEach time we execute these functions, we will get a different value, try it!\n\n```python\nimport random\n\nprint(random.random()) # 0.12826246225939641\n\nprint(random.randint(1, 10)) # 9\n```\n\n\n##\n\n:::greenbox\nTry the problem \"Guessing Game\" on Ed Lessons.\n:::\n\n::divider\n","title":"4.1 — return vs. print, while statement, Modules","date":"2024-01-22","published":true},{"slug":"Lecture-4.2.md","content":"\n## `in` operator (membership operator)\n\n- In Python `in` is a keyword.\n- The `in` and `not in` operators test for membership.\n- We can use them with strings to test if one string is a substring of another. This operation is case-sensitive.\n\n```python\ns = \"Pineapple\"\n\nprint(\"app\" in s) # True\nprint(\"pine\" in s) # False\n\nprint(\"x\" not in s) # True\nprint(\"P\" not in s) # False\n``` \n\n##\n\n:::greenbox\nTry the problem \"Remove Duplicates\" on Ed Lessons.\n:::\n\n##\n\n:::greenbox\nTry the problem \"Benchpress\" on Ed Lessons.\n:::\n\n## String methods\n\nA :sc[method] is similar to a function except that a method is :i[always] called on an object:\n\n:::div{.px1 .py025 .code .br5 style=\"background-color: white; width: auto; white-space: pre;\"}\nobject.method_name(argument1, argument2, ...)\n:::\n\n##\n\n`str` type has several methods that we can call on a string object:\n```python\nprint(\"hello\".upper()) # calling method upper() on the string \"hello\"\n# HELLO\n\nmessage = \"hello\"\nprint(message.upper()) # using variable that refers to string\n# HELLO\n\nmessage = 10\nprint(message.upper()) # upper() only available for str objects\n# AttributeError: 'int' object has no attribute 'upper'\n```\n\n## Useful string methods\n\n```python\ns = \"Luke, I am your father\"\n\n# s.lower() : returns a copy of s, but with all lower case letters.\nprint(s.lower())\n# luke, i am your father\n\n# s.upper() : returns a copy of s, but with all upper case letters.\nprint(s.upper())\n# LUKE, I AM YOUR FATHER\n```\n\n##\n\n```python\ns = \"Luke, I am your father\"\n# s.replace(old, new) : returns a copy of s with all occurrences of \n# the substring old replaced by new.\nprint(s.replace(\"am\", \"am not\"))\n# Luke, I am not your father\n\n# replace space with empty string\nprint(s.replace(\" \", \"\"))\n# Luke,Iamyourfather\n```\n\n## \n\n```python\ns = \"banana\"\n# s.count(c) : returns the number of non-overlapping \n# occurrences of substring c in s.\nprint(s.count(\"na\"))\n# 2\n\n# s.find(c) : returns the index where the substring begins in s begins. \n# If c is not a substring of s, then -1 is returned.\nprint(s.find(\"an\"))\n# 1\n\nprint(s.find(\"naa\"))\n# -1\n```\n\n## :style{.ppt-f90}\n\n```python\nx = 1\ny = 2.5\nz = 3.14\nname = \"Reza\"\n\n# fmt.format(a1, a2, ...):\n# returns a string where the placeholders {} in format string fmt\n# are replaced by args a1, a2, etc.\n\nprint(\"x = {}, y = {}\".format(x, y))\n# x = 1, y = 2.5\n\nprint(\"Point: ({}, {}, {})\".format(x, y, z))\n# Point: (1, 2.5, 3.14)\n\nprint(\"Welcome {}!\".format(name))\n# Welcome Reza!\n```\n\n## Formatted Strings\n\n```python\nx = 1\ny = 2.5\nz = 3.14\nname = \"Reza\"\n\nprint(f\"x = {x}, y = {y}\")\n# x = 1, y = 2.5\n\nprint(f\"Point: ({x}, {y}, {z})\")\n# Point: (1, 2.5, 3.14)\n\nprint(f\"Welcome {name}!\")\n# Welcome Reza!\n```\n\n## Example\n\nIn just one expression, compare if two strings `s1` and `s2` are equal in a case-insensitive manner.\n\n```python\ns1 = \"Hello Everyone\"\ns2 = \"hello everyone\"\n\nis_equal = s1.upper() == s2.upper()\n\n# OR\n# is_equal = s1.lower() == s2.lower()\n\nprint(is_equal) # prints True\n```\n\n## `break` statement\n\n`break` statement can be used to terminate a loop before it normally ends. \nAfter a `break` statement is executed, no other code inside the loop is executed.\n\n:::hgrid\n```python\nfor i in range(10):\n if i \u003e 5:\n break\n print(i)\n \nprint(\"Bye!\")\n```\n```output\n0\n1\n2\n3\n4\n5\nBye!\n```\n:::\n\n\n## \n\n:::greenbox\nWrite a function `is_prime` that takes an integer as argument and returns `True` if the number is prime, otherwise returns `False`.\nTo check if a number `n` is prime:\n- Assume `n` is prime\n- Divide `n` by each number `i` from `2` to `n-1`\n - if `n` is divisible by any `i` then `n` cannot be not prime\n\nIn other words, if `n` is not divisible by all `i`'s then `n` is prime.\n:::\n\n##\n\n:::solution\n```python\ndef is_prime(num):\n prime = True\n\n if num \u003c 2:\n prime = False\n else:\n for i in range(2, num):\n if num % i == 0:\n prime = False\n break\n\n return prime\n\nprint(is_prime(7)) # True\nprint(is_prime(21)) # False\n```\n:::\n\n## \n\n```python\npassword = input(\"Enter password: \")\n\nwhile password != \"abcd1234\":\n print(\"Incorrect password, try again!\")\n password = input(\"Enter password: \")\n\nprint(\"Login successful!\")\n```\n\n:::greenbox\nChange above program so that it keeps asking email and password until both are correct. \nPassword comparison must be case-sensitive, while email comparison should be case-insensitive. \nFor comparison, just choose any email, password that you like.\n:::\n\n##\n\n:::solution\n```python\nemail = input(\"Enter email: \")\npassword = input(\"Enter password: \")\n\nwhile email.lower() != \"abcd@gmail.com\" or password != \"1234\":\n print(\"Incorrect email or password, try again!\")\n \n email = input(\"Enter email: \")\n password = input(\"Enter password: \")\n \n# If we reach this line it means both email and password were correct\nprint(\"Login successful!\")\n\n```\n:::\n\n## Using `break` in a `while` loop\n\nWe can simplify the login example using a `break` statement:\n\n```python\nwhile True:\n email = input(\"Enter email: \")\n password = input(\"Enter password: \")\n if email.lower() == \"abcd@gmail.com\" and password == \"1234\":\n break\n print(\"Incorrect email or password, try again!\")\n\nprint(\"Login successful!\")\n```\n\n## `continue` statement\n\n`continue` statement is useful to skip some steps in a loop.\n\nAfter a `continue` statement is executed, code that follows the statement is skipped and execution continues from the next step of the loop.\n\n```python\nfor i in range(1, 50):\n if i % 2 == 0 or i % 3 == 0:\n continue\n print(i)\n```\n```output\n1 5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49\n```\n\n::divider","title":"4.2 — String methods, break \u0026 continue statements","date":"2024-01-23","published":true},{"slug":"Lecture-5.1.md","content":"\n\n## Controlling the flow of execution with `return` statement\n\nAs we have seen, `return` statement allows us to return a value from a function back to the code that calls the function.\n\nBut at the same time return statement also ends execution of the function. \n\n##\n\nWhen return statement is executed, no further code in the function gets executed.\n\n:::hgrid\n```python\ndef display(message):\n print(\"*** \" + message + \" ***\")\n return\n print(\"This will never be displayed\")\n \n \ndisplay(\"hello\")\n```\n\n```output\n*** hello ***\n```\n:::\n\n`return` can be very useful when placed inside an `if` statement if we want to exit from the function under certain conditions.\n\n\n## \n \nUsing return statement, we can simplify the prime number example:\n\n:::hgrid\n```python\n# We saw this before\ndef is_prime(num):\n prime = True\n\n if num \u003c 2:\n prime = False\n else:\n for i in range(2, num):\n if num % i == 0:\n prime = False\n break\n\n return prime\n```\n\n```python\n# Simplified version\ndef is_prime(num):\n if num \u003c 2:\n return False\n\n for i in range(2, num):\n if num % i == 0:\n return False\n\n return True\n```\n:::\n\n##\n\n:::redbox\n:b[Important]: we must make sure that all branches/cases in the function return the correct values. \n\nIn previous example, if we forget the last return statement in the simplified `is_prime` function, `return None` will happen implicitly, which would be incorrect.\n:::\n\n## Be careful — incorrect indentation changes logic (1)\n\n:::hgrid{.ppt-f90 margin=\"0 0\"}\n```python\ndef remove_spaces(s):\n result = \"\" \n for i in range(len(s)):\n letter = s[i]\n if letter != \" \":\n result = result + letter\n\n return result\n```\n```python\ndef remove_spaces(s):\n result = \"\" \n for i in range(len(s)):\n letter = s[i]\n if letter != \" \":\n result = result + letter\n\n return result\n```\n:::\n\n## Be careful — incorrect indentation changes logic (2)\n\n:::hgrid{.ppt-f90 margin=\"0 0\"}\n```python\ndef remove_spaces(s):\n result = \"\" \n for i in range(len(s)):\n letter = s[i]\n if letter != \" \":\n result = result + letter\n\n return result\n```\n```python\ndef remove_spaces(s):\n result = \"\" \n for i in range(len(s)):\n letter = s[i]\n if letter != \" \":\n result = result + letter\n\n return result # what will this do?\n```\n:::\n\n## ASCII code and special characters\n\n::div[Recall ASCII table from a previous lecture:]{style=\"margin: 0 auto;\" .ppt-f80}\n\n::image{style=\"margin: 0 auto; width: 70%;\" src=\"module-1/ASCII-Table.png\"} \n\n##\nPython has built-in functions to convert ASCII code (decimal) to/from a single character.\n\n```python\nprint(ord(\"a\")) # 97, the ASCII code for letter \"a\"\nprint(ord(\"A\")) # 65\nprint(ord(\"$\")) # 36\n\nprint(ord(\"hi\")) # doesn't work for more than one character\n# TypeError: ord() expected a character, but string of length 2 found\n\nprint(chr(70)) # F, the character for ASCII code 70\n\nprint(chr(103)) # g\n```\n\n##\n\n:::greenbox\nWrite a program that shifts each letter in a string to the left by 3 steps according to ASCII table. \ni.e. :code[A → \u003e, B → ?, C → @, D → A, E → B], etc.\n:::\n\n\n```python\nword = \"Python\"\n\nresult = \"\"\nfor i in range(len(word)):\n code = ord(word[i]) # Get ASCII code for the letter\n code = code - 3 # Shift code by 3\n # Get letter for the code and add it to result:\n result = result + chr(code) \n \nprint(result) # Mvqelk\n\n```\n\n## \n\nText is stored in a file as a sequence of character codes.\n\n:::hgrid{margin=\"1em 0\" gap=\"1em\"}\n```python lineno=false \ncat dog\n```\n::seqbox{text=\"99,97,116,32,100,111,103\" margin=\"0\" w=\"2.5\" h=\"2\"}\n:::\n\nMultiple lines in text:\n:::hgrid{margin=\"1em 0\" gap=\"1em\"}\n```python lineno=false\ncat\ndog\n```\n::seqbox{text=\"99,97,116,10,100,111,103\" margin=\"0\" w=\"2.5\" h=\"2\"}\n:::\n\nThe :em[newline character], which represents \"enter\" or \"return\" key, is also stored when text contains multiple lines.\n\n## Escape characters\n\nThere are special characters, which we may not directly include in a string, e.g.:\n- :i[newline character]: This is the character representing \"enter\" or \"return\" key.\n- :i[tab character]: This is the character representing \"tab\" key.\n\nSuch special characters can be used in a string using escape characters.\n\n## \n\n```python\n# Trying to enter a newline character directly fails:\nmessage = \"Hello\nworld\"\n```\n\n```output\n message = \"Hello\n ^\nSyntaxError: EOL while scanning string literal\n```\n\n## \n\nTo include a newline character in a string we can use the escape character `\\n` in the string:\n\n:::hgrid\n```python\nmessage = \"hello\\nworld\"\nprint(message)\n```\n\n```output\nhello\nworld\n```\n:::\n\n`\\n` is stored as a single character even though it looks like two.\n\n```python lineno=false\nprint(ord(\"\\n\")) # 10\n```\n\n##\n\nAnother escape character is `\\t` which represents the tab character. \nIt is useful as a separator when displaying values:\n\n:::hgrid\n```python\n# print uses space as separator by default\nprint(\"Khalid\", 85)\nprint(\"Reza\", 90)\n\n# Using tab as separator\nprint(\"Khalid\", 85, sep=\"\\t\")\nprint(\"Reza\", 90, sep=\"\\t\")\n```\n\n```output\nKhalid 85\nReza 90\nKhalid\t85\nReza\t90\n```\n:::\n\n## Controlling print() function\n\nIn previous example, we used a :sc[keyword argument] `sep=` to tell print which separator to use between values. \n\nUnlike the usual arguments, keyword arguments are given in the form `name=value`; in the example `sep` is the name of argument and `\"\\t\"` is the value.\n\n```python\n# separator can be any string\nprint(\"Alice\", 90, 3.14, sep=\",\")\n# Alice,90,3.14\n```\n\n##\n\n```python\nprint(\"Alice\", 90, 3.14, sep=\"|\")\n# Alice|90|3.14\n\n# even longer than one character\nprint(\"Alice\", 90, 3.14, sep=\"-----\")\n# Alice-----90-----3.14\n\nprint(\"Alice\", 90, 3.14, sep=\"\") # No separator!\n# Alice903.14\n```\n\n##\n\nBy default, `print()` function displays a newline character `\\n` at end of line. \n\n:::hgrid\n```python\nprint(\"Good\", \"morning\")\nprint() # no arguments, just prints \"\\n\"\nprint(123, 3.14)\n```\n\n```output margin=\"-1em 0 0 0\"\nGood morning\n\n123 3.14\n```\n:::\n\n##\n\nWe can change the `end` character using another keyword argument to `print()` function, `end=`. \n\n:::hgrid\n```python\nprint(\"A sequence of numbers:\")\nprint(1, end=\",\")\nprint(4, end=\",\")\nprint(9, end=\",\")\n```\n\n```output\nA sequence of numbers:\n1,4,9,\n```\n:::\n\n##\n\n`end=` is useful in a loop:\n\n```python\nN = 10\nfor i in range(N):\n print(i*i, end=\", \") # comma and a space\n```\n\n```output\n0, 1, 4, 9, 16, 25, 36, 49, 64, 81, \n```\n\n:::greenbox\nChange the above example to not print the last comma. E.g.,\n\n```output\n0, 1, 4, 9, 16, 25, 36, 49, 64, 81\n```\n:::\n\n##\n\n:::solution\n```python\nN = 10\nfor i in range(N):\n if i == N - 1:\n print(i * i)\n else:\n print(i * i, end=\", \")\n```\n:::\n\n## Multiline strings\n\nUsing `\\n`, we can create a single string that contains all of the following lines:\n\n```python\nshopping_list = \"Shopping list\\n- Milk\\n- Eggs\\n- Apples\\n\"\nprint(shopping_list)\n```\n\n```output\nShopping list\n- Milk\n- Eggs\n- Apples\n```\n\n##\n\nPython provides a better create multiline strings using :i[triple quotes]: `'''` or `\"\"\"`.\n\n```python\n\u003e\u003e\u003e shopping_list = \"\"\"Shopping list\n- Milk\n- Eggs\n- Apples\n\"\"\"\n\u003e\u003e\u003e shopping_list\n'Shopping list\\n- Milk\\n- Eggs\\n- Apples\\n'\n```\n\n## Nested Loops\n\nWe can have a for/while loop inside other for/while loops.\n\nThis is useful when we have two sequences and we need all combinations/pairs of items from the sequences.\n\n##\n\n:::greenbox\nWrite a program that prints all pairs of numbers that sum to $7$ when two six-sided dice are rolled.\n:::\n\n:::hgrid\n```python\n# outer loop for first die d1:\nfor d1 in range(1, 7): \n # inner loop for second die d2\n for d2 in range(1, 7):\n if d1 + d2 == 7:\n print(d1, d2)\n```\n\n```output\n1 6\n2 5\n3 4\n4 3\n5 2\n6 1\n```\n:::\n\n\n## \n\n:::greenbox\nWrite a program that takes two string—one with consonants and other with vowels—and combines each consonant with every vowel to print a syllable.\n:::\n\n:::hgrid{margin=\"0\"}\n```python\nconsonants = \"bdfghjklmn\"\nvowels = \"aeiou\"\n```\n```output\nba be bi bo bu \nda de di do du \nfa fe fi fo fu \nga ge gi go gu \nha he hi ho hu \nja je ji jo ju \nka ke ki ko ku \nla le li lo lu \nma me mi mo mu \nna ne ni no nu\n```\n:::\n\n##\n\n:::solution\n```python\nconsonants = \"bdfghjklmn\"\nvowels = \"aeiou\"\n\nfor i in range(len(consonants)):\n for j in range(len(vowels)):\n syllable = consonants[i] + vowels[j]\n print(syllable, end=\" \")\n print() # to start printing from next line\n\n```\n:::\n\n## Lists\n\n- A list is like a container that holds a sequence of objects.\n- Objects contained in a list are called :sc[elements] or :sc[items].\n- Lists are ordered! The order in which the items are stored in the list matters.\n- Each item is associated with an index (index $0$: first item, index $1$: second item, etc.)\n\n## Creating a list\n\nA list is created using square brackets, with each item separated by a comma.\n\n```python\nprime_numbers = [2, 3, 5, 7, 11, 13]\nprint(prime_numbers)\n# [2, 3, 5, 7, 11, 13]\n\nprint(type(prime_numbers))\n# \u003cclass 'list'\u003e\n```\n\n##\nA list can contain items of any type. For example we can have a list of strings:\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n\n# Number of items in the list\nprint(len(days)) # 7\n\nempty_list = []\nprint(len(empty_list)) # 0\n```\n\nA list can contain any number of items, from zero to as many as the computer's memory allows.\n\n##\nA list can contains objects of different types.\n\n```python\n# list with mixed types\nnumbers = [1, 'two', 3.75]\n```\n\nItems of a list don't need to be unique.\n\n```python\n# list with duplicate values\nnumbers = [5, \"five\", 5]\n```\n\n## Why use a list?\n\n```python\n# Suppose we want to store grades for multiple students\n\ngrades1 = 80\ngrades2 = 100\ngrades3 = 65\n# ...\n# How many variables?!!\n\n# Use just one variable name \"grades\"\ngrades = [80, 100, 65]\n```\n\n## Indexing a list\n\nWe can access an item inside a list using indexing (square brackets), just as we did for strings.\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\nfirst_day = days[0]\nsecond_day = days[1]\nlast_day = days[6]\nprint(first_day, second_day, last_day) # Mon Tue Sun\n\n# No item at index 7\nprint(days[7]) \n# IndexError: list index out of range\n```\n\n##\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n\n# Negative indexing can be used as well\nprint(days[-1], days[-2]) # Sun Sat\n\nnumbers = [1, 'two', 3.75]\nprint(numbers[0] + numbers[2]) # 4.75\n\nprint(numbers[0] + numbers[1])\n# TypeError: unsupported operand type(s) for +: 'int' and 'str'\n```\n\n## Modifying the content of a list\n\nWe can modify the content of a list after it has been created.\n\nWe can change a single item using its index and the assignment operator.\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\ndays[0] = \"Sun\"\nprint(days)\n# ['Sun', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n\ndays[7] = \"No such day\"\n# IndexError: list assignment index out of range\n```\n\n\n\n## \n\n`for` loops are very useful for looping through all the items in a list.\n\n```python\n# compute an average of grades\ngrades = [85, 78.5, 98, 75, 100]\ntotal = grades[0] + grades[1] + grades[2] + grades[3] + grades[4] \navg = total / 5\nprint(avg) # prints 87.3\n```\n:::hgrid{margin=\"0.5rem 0\"}\nFor instance, we could use a for loop to compute the average of grades.\n```python\ngrades = [85, 78.5, 98, 75, 100]\n\ntotal = 0\nfor i in range(5):\n total += grades[i]\n\navg = total / 5\nprint(avg) # 87.3\n```\n:::\n\n##\n\nGeneralized version:\n```python\ngrades = [85, 78.5, 98, 75, 100]\n\ntotal = 0\nN = len(grades) \nfor i in range(N):\n total += grades[i]\n\navg = total / N\nprint(avg) # 87.3\n```\n\n##\n\n:::greenbox\nTry the problem \"Dot product\" on Ed Lessons.\n:::\n\n\n##\n\n:::greenbox\nTry the problem \"DNA Cut-site\" on Ed Lessons.\n:::\n\n##\n\n:::greenbox\nTry the problem \"Palindrome\" on Ed Lessons (from last week).\n:::\n\n:::hgrid{margin=\"1em 0\" gap=\"4em\"}\n::seqbox{text=\"a,b,b,a\" margin=\"1em 0\"}\n::seqbox{text=\"a,b,c,b,a\" margin=\"2em 0\"}\n:::\n\n:::hgrid{margin=\"0\" gap=\"4em\"}\n::seqbox{text=\"a,b,a,a\" margin=\"1em 0\"}\n::seqbox{text=\"a,b,c,c,a\" margin=\"2em 0\"}\n:::\n\n##\n\n:::greenbox\nTry the problem \"Smallest and largest divisors\" on Ed Lessons (from last week)..\n:::\n\n\n::divider\n","title":"5.1 — Controlling print(), Nested Loops, Lists","date":"2024-01-28","published":true},{"slug":"Lecture-5.2.md","content":"\n## Scope of variables\n\n- A variable name only exists inside the body of the function in which it is created.\n - It does not exist outside the function or in any other functions.\n\n:::hgrid\n```python\ndef f():\n x = 3\n print(\"In f(), x =\", x)\n \nf()\nprint(x)\n```\n```output\nIn f(), x = 3\nNameError: name 'x' is not defined\n```\n:::\n\n##\n\n- The :sc[scope] of a variable consists of parts of the program where the variable name exists and can be used.\n- Each function has its own :sc[local scope], which other functions cannot access.\n- :sc[global scope] consists of names accessible by the entire module (Python file).\n\nA variable created inside a function is called a :sc[local variable]. \n\nA variable created outside any function is called a :sc[global variable].\n\n##\n\n:::hgrid\n```python\ndef f():\n x = 3 # local variable\n # local x is used below!\n print(\"In f(), x =\", x) \n\n\nx = 100 # global variable\nf()\nprint(x) # global x is used\n```\n```output\nIn f(), x = 3\n100\n```\n:::\n\n- As we saw above, it is possible to create a local variable with the same name as a global variable. \n- These are considered two different variables, and inside the function only the local one will be used.\n\n## \n\n:::hgrid\n```python\ndef f():\n # global x is used below!\n print(\"Inside f(), x =\", x)\n\n\nx = 100 # global variable\nf()\nprint(x)\n```\n```output\nInside f(), x = 100\n100\n```\n:::\n\nWhat will happen if the global variable `x` is created after the function call `f()`?\n\n\n## How variable name is looked up?\n- Inside a function, when a name is used:\n - First, name is searched within the function (local scope) to see if it exists.\n - If name is not found in the function, it is searched globally\n- Outside a function, name is simply searched globally\n- If a name cannot be found anywhere (local or global scope), we get `NameError` complaining that the name is not defined.\n\n##\n\nWhat will be printed in each case?\n\n:::hgrid{margin=\"0\" gap=\"5em\"}\n```python\ndef f():\n y = 5\n print(x)\n\nx = 10\nf()\n```\n```python\ndef f():\n x = 5 \n print(x)\n\nx = 10\nf()\nprint(x)\n```\n:::\n\n##\n\n::::solution\n:::hgrid{gap=\"5em\"}\n```output\n10\n```\n```output\n5\n10\n```\n:::\n::::\n\n\n##\n\n:::div{.ppt-scale-1_25}\n:b[Function parameters are also local to the function.]{.ppt-f80}\n\u003ciframe width=\"800\" height=\"380\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=def%20f%28x%29%3A%0A%20%20%20%20print%28%22In%20f%28%29,%20x%20%3D%22,%20x%29%0A%20%20%20%20return%20x%20*%20x%0A%0Ax%20%3D%2010%0Ay%20%3D%20f%28x%29%0Aprint%28f%22Global,%20x%20%3D%20%7Bx%7D,%20y%20%3D%20%7By%7D%22%29\u0026codeDivHeight=400\u0026codeDivWidth=360\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n##\n\n:::div{.ppt-scale-1_25}\n\u003ciframe width=\"840\" height=\"420\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=def%20f%28x%29%3A%0A%20%20%20%20x%20%3D%203%0A%20%20%20%20print%28f%22In%20f%28%29,%20x%20%3D%20%7Bx%7D,%20y%20%3D%20%7By%7D%22%29%0A%20%20%20%20return%20x%20*%20y%0A%0Ax%20%3D%2010%0Ay%20%3D%205%0Az%20%3D%20f%28x%29%0Aprint%28f%22Global,%20x%20%3D%20%7Bx%7D,%20y%20%3D%20%7By%7D,%20z%20%3D%20%7Bz%7D%22%29\u0026codeDivHeight=400\u0026codeDivWidth=440\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n\n## Best Practice\n- Try to avoid using global variables within functions when possible.\n - It is okay to use variables that don't change (e.g. constants such as $\\pi$)\n - It is also okay to use modules inside functions \n\n\n## Lists — Concatenation `+` and Replication `*`\n\n```python\n# lists a and b are joined to produce a third list c:\na = [1, 2]\nb = [10, 11, 12]\nc = a + b\nprint(c)\n# [1, 2, 10, 11, 12]\n\na = [1, 2]\n# resulting list consists of repeated items of list a:\nc = a * 3 \nprint(c)\n# [1, 2, 1, 2, 1, 2]\n```\n\n\n## membership operators: `in` and `not in`\n\nWe can use them to test if an object is present in a list.\n\n```python\na = [1, 2]\nb = [10, 11, 12]\n\nprint(1 in a) # True\nprint(11 in a) # False\nprint(5 not in b) # True\n\nx = 3.14\nprint(x in a or x in b) # False\n```\n\n##\n\n`in`/`not in` operators are very useful in simplifying code:\n\n:::hgrid\n```python\n# Instead of long conditions like this:\nif x == 5 or x == 7 or x == 10:\n # do something\n```\n```python\n# Now we can do:\nif x in [5, 7, 10]:\n # do something\n```\n:::\n\n\n## Slicing a list\n\nSimilar to strings, we can also get a sub-list — parts of a list — using slice notation. Slicing creates a new list.\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n\nprint(days[2:5])\n# ['Wed', 'Thu', 'Fri']\n\nprint(days[:6:2])\n# ['Mon', 'Wed', 'Fri']\n```\n\n##\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n\n# Make a copy of the whole list\nprint(days[:])\n# ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n\n# Makes a reversed copy of the list\nprint(days[::-1])\n# ['Sun', 'Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon']\n```\n\n## Iterating through a list\n\nWe can either use an index or loop directly over items in a list:\n\n:::hgrid{margin=\"0\"}\n```python\ndef average(nums):\n total = 0\n\n for i in range(len(nums)):\n total += nums[i]\n\n return total / len(nums)\n\ngrades = [85, 100, 98, 75]\nprint(average(grades))\n```\n```python\ndef average(nums):\n total = 0\n\n for x in nums:\n total += x\n\n return total / len(nums)\n\ngrades = [85, 100, 98, 75]\nprint(average(grades))\n```\n:::\n\n##\n\nBut when we need to modify a list inside a loop we have to use an index:\n\n```python\ndef add_bonus(grades, bonus):\n \"\"\" Add bonus to each grade in grades list\n (grade should not exceed 100) \n Returns: None\n \"\"\"\n for i in range(len(grades)):\n grades[i] = min(grades[i] + bonus, 100)\n \n \nassignment_grades = [55, 60, 67, 97]\nadd_bonus(assignment_grades, 5)\nprint(assignment_grades) # [60, 65, 72, 100]\n```\n\n## Built-in functions that work with lists\n\n```python\ngrades = [90, 70, 60.5, 70, 80]\n\n# len(x): \n# Returns the number of items in the list x.\nprint(len(grades)) # 5\n\n# sum(x): \n# Returns the sum of all the numbers in list x.\n# A TypeError occurs when some item is not a number.\nprint(sum(grades)) # 370.5\n```\n\n##\n\n```python\ngrades = [90, 70, 60.5, 70, 80]\n\n# min(x) / max(x) : \n# Returns the smallest/largest item in the list x.\n# A TypeError occurs if the items cannot be compared.\n\nprint(min(grades)) # 60.5\n\nprint(max(grades)) # 90\n\nprint(min([\"90\", 70, 60.5, 70, 80]))\n# TypeError: '\u003c' not supported between instances of 'int' and 'str'\n```\n\n## List methods\nPython has several methods that we can call on a list object:\n\n```python\n# list.append(x): Adds the item x to the end of the list\n\ngrades = [90, 70, 60.5, 70, 80]\ngrades.append(100)\nprint(grades) # [90, 70, 60.5, 70, 80, 100]\n\ngrades.append(\"30\")\nprint(grades) # [90, 70, 60.5, 70, 80, 100, '30']\n\ngrades.append(False)\nprint(grades) # [90, 70, 60.5, 70, 80, 100, '30', False]\n```\n\n## \n\n:b[Example:]\n\n```python\ndef filter_values(nums, threshold):\n ''' Return a new list to include numbers from \n the list nums that are above threshold\n '''\n new_list = []\n \n for n in nums:\n if n \u003e threshold:\n new_list.append(n)\n\n return new_list\n\nprint(filter_values([3, 1, 2, 5, 4], 3)) # [5, 4]\nprint(filter_values([3, 1, 2, 5, 4], 5)) # []\n```\n\n##\n\n```python\n# list.insert(i, x): Adds the item x to the list at index i\n\ngrades = [90, 70, 60.5, 70, 80]\ngrades.insert(0, 100)\nprint(grades) # [100, 90, 70, 60.5, 70, 80]\n\n# insert() works even when index is greater than length of list\ngrades = [90, 70, 60.5, 70, 80]\ngrades.insert(10, \"B+\")\nprint(grades) # [90, 70, 60.5, 70, 80, 'B+']\n```\n\n##\n\n```python\n# list.remove(x): \n# Removes the first occurrence of the item x in the list. \n# A ValueError occurs if there is no such item.\n\ngrades = [90, 70, 60.5, 70, 80, 'B+']\ngrades.remove(70)\nprint(grades) # [90, 60.5, 70, 80, 'B+']\n\ngrades.remove(\"B+\")\nprint(grades) # [90, 60.5, 70, 80]\n\ngrades.remove(60)\n# ValueError: list.remove(x): x not in list\n```\n\n##\n\n```python\n# list.pop(i): \n# Removes and returns item at index i \n# list.pop(): \n# Removes and returns the last item from the list\n\ngrades = [90, 70, 60.5, 70, 80, 'B+']\nlast_item = grades.pop()\nprint(last_item) # B+\nprint(grades) # [90, 70, 60.5, 70, 80]\n\nsecond_item = grades.pop(1)\nprint(second_item) # 70\nprint(grades) # [90, 60.5, 70, 80]\n```\n\n##\n\n```python\n# list.count(x) : \n# Returns the number of occurrences of the item x. \n\ngrades = [90, 70, 60.5, 70, 80, 'B+']\nprint(grades.count(70)) # 2\nprint(grades.count(\"B+\")) # 1\nprint(grades.count(60)) # 0\n```\n\n##\n\n```python\n# list.index(x) : \n# Returns the index of the first occurrence of item x in list. \n# A ValueError occurs if item x is not found in list.\n\ngrades = [90, 70, 60.5, 70, 80, 'B+']\n\nprint(grades.index(70)) # 1\nprint(grades.index(60)) # ValueError: 60 is not in list\n```\n\n##\n\n```python\n# list.extend(sequence) : \n# Extend list by appending items from the sequence.\n\ngrades = [90, 70, 60.5, 70, 80]\n\ngrades.extend([100, 95])\nprint(grades) # [90, 70, 60.5, 70, 80, 100, 95]\n\ngrades.extend(\"cat\")\nprint(grades)\n# [90, 70, 60.5, 70, 80, 100, 95, 'c', 'a', 't']\n```\n\n## Lists and Strings\n\nThere are built-in functions and string methods that allows us to transform strings to/from lists.\n\n```python\n# list(seq): built-in function which converts a sequence (such as\n# a string or a list) into a list\n\nfruit = \"apple\"\nletters = list(fruit)\nprint(letters) # ['a', 'p', 'p', 'l', 'e']\n```\n##\n\n```python\n# s.split(): Breaks the string s using whitespace\n# (spaces, tab character and newline) as the separator and\n# returns a list of strings containing the separated parts\n\ndata = \"Red Green Blue\"\nnames = data.split()\nprint(names) # ['Red', 'Green', 'Blue']\n\n# Multiple spaces are also removed\ndata = \" Red Green Blue \"\nnames = data.split()\nprint(names) # ['Red', 'Green', 'Blue']\n```\n\n##\n\n```python\ndata = \"Red\\tGreen\\tBlue\" # separated by tab\nnames = data.split()\nprint(names) # ['Red', 'Green', 'Blue']\n\ndata = \"\"\"Red\nGreen\nNavy Blue\"\"\"\nnames = data.split()\nprint(names) # ['Red', 'Green', 'Navy', 'Blue']\n```\n\n##\n\n```python\n# s.splitlines(): Breaks a multi-lines strings into separate lines\n# and returns a list containing those lines.\n\ndata = \"\"\"Red\n Green\nNavy Blue\n\"\"\"\nnames = data.splitlines()\nprint(names) # ['Red', ' Green', 'Navy Blue']\n```\n\n##\n\n:::hgrid{margin=\"0 0 0 -1em\"}\n```python\n# s.strip(): Return a copy of the string with \n# leading and trailing whitespace removed.\n# s.lstrip(): removes leading whitespace only \n# s.rstrip(): removes trailing whitespace only \n\nname = \" Green \"\nprint(\"|\" + name + \"|\") \nprint(\"|\" + name.strip() + \"|\")\nprint(\"|\" + name.lstrip() + \"|\")\nprint(\"|\" + name.rstrip() + \"|\")\n```\n\n```output\n| Green |\n|Green|\n|Green |\n| Green|\n```\n:::\n\n##\n\n:::hgrid{margin=\"0 0 0 -2em\" .ppt-f95}\n```python\n# s.strip(chars): Return a copy of the string \n# with leading and trailing chars removed.\n# s.lstrip(chars): removes leading chars only \n# s.rstrip(chars): removes trailing chars only \n\ntext = \"...#some . text #...\"\n\nprint(text.strip(\".\"))\nprint(text.lstrip(\".\"))\nprint(text.rstrip(\".\"))\n\n# multiple chars to remove\nprint(text.strip(\". #\"))\n```\n\n```output\n#some . text #\n#some . text #...\n...#some . text #\nsome . text\n```\n:::\n\n\n##\n\n```python\n# s.split(sep): Breaks the string s using the separator string sep\n# and returns a list of strings containing the separated parts\n\ndata = \"Red,Green,Blue\"\nnames = data.split(\",\")\nprint(names) # ['Red', 'Green', 'Blue']\n\ndata = \"Red, Green, Blue\"\nnames = data.split(\",\")\nprint(names) # ['Red', ' Green', ' Blue'] \n# notice space in strings above\n```\n\n##\n\n```python\n# sep.join(L): joins all the strings in the list L using \n# the string sep and returns the joined string.\n\nnames = ['Red', 'Green', 'Blue']\njoined = \" \".join(names)\nprint(joined) # Red Green Blue\n\n# a comma\njoined = \",\".join(names)\nprint(joined) # Red,Green,Blue\n```\n\n##\n\n```python\n# a comma and a space\nnames = ['Red', 'Green', 'Blue']\njoined = \", \".join(names)\nprint(joined) # Red, Green, Blue\n\n# empty string, no separator\nletters = [\"a\", \"p\", \"p\", \"l\", \"e\"]\njoined = \"\".join(letters)\nprint(joined) # apple\n```\n\n##\n\n:::greenbox\nTry the problem \"Parsing a string\" on Ed Lessons.\n:::\n\n##\n\n:::greenbox\nTry the problem \"Puzzle: Switching 100 light bulbs\" on Ed Lessons.\n:::\n\n\n::divider\n","title":"5.2 — Scope of variables, List operations","date":"2024-01-30","published":true},{"slug":"Lecture-6.1.md","content":"\n## Tuples\n\n- A tuple is an ordered collection of objects, like lists.\n- A tuple is :i[immutable]. A tuple object cannot be modified after it is created.\n- We create a tuple using parentheses `()`.\n\n```python\ntup = (1, 2, 3)\nprint(type(tup)) # \u003cclass 'tuple'\u003e\n\n# tuple with only one item\ntup = (10,) # comma is required!\nprint(tup, type(tup)) # (10,) \u003cclass 'tuple'\u003e\n```\n\n##\n\nWe can use `tuple` function to convert other sequences such as lists and strings into a tuple.\n\n```python\nword = \"apple\"\ntup = tuple(word)\nprint(tup)\n# ('a', 'p', 'p', 'l', 'e')\n\nprimes = [2, 3, 5, 7]\nprimes = tuple(primes)\nprint(primes)\n# (2, 3, 5, 7)\n```\n\n## Tuples are immutable\n\nItems cannot be added, removed or changed in a tuple.\n\nTherefore, unlike lists, none of the operations that modify a tuple are allowed.\n\n```python\nx = (1, 1, 2, 3, 5)\nx[3] = 100\n# TypeError: 'tuple' object does not support item assignment\n```\n\n## \n\nAssigning a new object to a variable does not affect/modify the current object the variable refers to.\n\n```python\nx = (1, 1, 2, 3, 5)\n\n# This does not modify the above tuple object so\n# it is allowed\nx = (1, 2, 3) \n```\n\n##\n\nIn general, operations that do not modify a tuple are available.\n\n```python\ntup = (45, 23, 'abc') \n\n# Indexing and slicing work the same way as lists.\nprint(tup[1])\n# 23\nprint(tup[1:])\n# (23, 'abc')\n\nprint(len(tup)) # number of items in a tuple\n# 3\n```\n\nSimilary `min()`, `max()`, and `sum()` functions work with tuples.\n\n##\n\n```python\ntup = (45, 23, 'abc') \n\n# Following methods are available for tuples\nprint(tup.index(\"abc\"))\n# 2\n\nprint(tup.count(23))\n# 1\n```\n\n##\n\nSince a tuple is a sequence, we can use it in a `for` loop just like a list:\n:::hgrid\n```python\nnumbers = (10, 20, 30)\n\nfor n in numbers:\n print(n)\n```\n```output\n10\n20\n30\n```\n:::\n\n## Why use tuples?\n\nIf lists are more flexible than tuples, why should we use tuples?\n\n- Immutability is useful to avoid changing data by mistake.\n- We can use tuples as elements of sets and as keys in a dictionary.\n- Programs are a bit faster when working with tuples.\n\n##\n\n:::greenbox\nTry the problem \"Euclidean distance using list/tuple\" on Ed Lessons.\n:::\n\n## Object Identity\n\n- Each object is assigned an :sc[ID] at its creation (think of a memory address).\n- This ID is unique and constant for this object as long as the object exists.\n- The built-in function `id()` can be used to retrieve the ID of an object.\n\n\n```python\nx = 1234\ny = x\n# x and y both refer to the same object, \n# therefore the IDs are the same.\nprint(id(x) == id(y)) # True\n```\n\n##\n\n```python\n# x and y point to two different objects, \n# therefore we expect x and y to have different IDs.\nx = 1234\ny = 5678\nprint(id(x) == id(y)) # False\n\n# x and y point to two different objects (with the same value),\n# therefore we expect x and y to have different IDs.\nx = int(\"1234\") # integer 1234\ny = int(\"12\" + \"34\") # integer 1234\n\nprint(id(x) == id(y)) # False\n```\n\n\n## Identity operators :style{.ppt-f95}\n\n- `is` and `is not` are comparison operators used to check if the two operands refer to the same object.\n- Using `is` operator means: are two variables referring to one and the same object?\n- Using `==` operator means: are two variables referring to objects that contain same data?\n\n:::hgrid\n```python\nx = int(\"1234\")\ny = int(\"12\" + \"34\")\nz = x \nprint(x == y) # True\nprint(x == z) # True\n```\n```python\nx = int(\"1234\")\ny = int(\"12\" + \"34\")\nz = x \nprint(x is y) # False\nprint(x is z) # True\n```\n:::\n\n\n## Mutable vs Immutable objects\n\n:sc[Immutable]: the content of the object cannot be changed after the object has been created. \n- e.g. `str`, `int`, `float`, `tuple`\n\n:sc[Mutable]: the content of the object can be changed after its creation without changing its identity.\n- e.g. `list`, `dict`, `set`\n\n\n## Strings are immutable\n\nUnlike lists, we cannot use the square brackets to modify a character in the string. \n\n```python lineno=false\ns = \"cats\"\ns[0] = \"r\" # TypeError: 'str' object does not support item assignment\n```\n\n##\n\nAll strings operations that seem to change a string actually :i[create] a new string.\n\n:::hgrid\n```python\ns = \"cat\"\nt = s\nprint(\"Before:\", s is t)\n\ns = s.replace('c','r')\nprint(\"After:\", s is t)\n\nprint(\"s:\", s)\nprint(\"t:\", t)\n```\n```output\nBefore: True\nAfter: False\ns: rat\nt: cat\n```\n:::\n\n## Lists are mutable\n\nThe following code does not create a copy of the list `x`. \nIt simply create a new variable name for the same list.\n\n```python\nx = [1, 2, 3]\ny = x # new name y for same list\n\nprint(x is y) # True\n```\n\nLet us see some implications of this.\n\n##\n\nIn the following illustrations, think about what is modified.\n- Whether a variable changes its value i.e. the variable refers to a different value\n- Whether a list object is modified i.e. some element of the list is changed.\n\n##\n\n:::div{.ppt-scale-1_25}\n\u003ciframe width=\"800\" height=\"310\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=x%20%3D%205%0Ay%20%3D%20x%0Ay%20%3D%20y%20%2B%203%20%0Aprint%28x,%20y%29\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n##\n\n:::div{.ppt-scale-1_25}\n\u003ciframe width=\"800\" height=\"400\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=x%20%3D%20%5B5,%206,%207%5D%0Ay%20%3D%20x%0Ay%5B0%5D%20%3D%20y%5B0%5D%20%2B%203%0Aprint%28x%5B0%5D,%20y%5B0%5D%29\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n##\n\n:::div{.ppt-scale-1_25}\n\u003ciframe width=\"800\" height=\"400\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=def%20example%28x%29%3A%0A%20%20%20%20x%20%3D%20x%20*%205%0A%0Ax%20%3D%205%0Aexample%28x%29%20%0Aprint%28x%29\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n##\n\n:::div{.ppt-scale-1_25}\n\u003ciframe width=\"800\" height=\"500\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=def%20example%28x%29%3A%0A%20%20%20%20x%5B0%5D%20%3D%20x%5B0%5D%20*%205%0A%0Ax%20%3D%20%5B5,%206,%207%5D%0Aexample%28x%29%0Aprint%28x%5B0%5D%29\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=true\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n\n## Sets\n\n- A set is a an :i[unordered] collection of :i[immutable] objects.\n- A set always contains unique elements, unlike lists and tuples which allow duplicates.\n- A set is unordered i.e. we cannot use indexing or slicing on a set object\n\n:::hgrid\n```python lineno=false\nnumbers = {1, 2, 3}\nprint(numbers) # {1, 2, 3}\nprint(type(numbers)) # \u003cclass 'set'\u003e\n```\n```python lineno=false\n# only unique values are kept\nnumbers = {1, 2, 3, 1, 3}\nprint(numbers) # {1, 2, 3}\nprint(len(numbers)) # 3\n```\n:::\n\n## Other ways to create a set\n\n```python\n# a set can be created from any sequence\n# such as list, tuple or a string\nthings = set([10, 42, \"apple\", 42])\nprint(things) # {'apple', 10, 42}\n\nword = \"pineapple\"\nletters = set(word)\nprint(letters) \n# {'p', 'n', 'l', 'i', 'e', 'a'}\n```\n\n##\n\n```python\n# create an empty set\nempty_set = set()\nprint(len(empty_set)) # 0\n\n# This does not create an empty set!\nempty_dictionary = {}\nprint(type(empty_dictionary)) # \u003cclass 'dict'\u003e\n```\n\n## Set elements must be immutable\n\nA set can contain `int`, `float`, `str`, `bool` and `tuple` objects because they are all immutable.\n\nBut a set cannot contain a list because lists are mutable.\n\n```python\n# tuples are immutable so allowed in set\npoints = {(1, 1), (3, 10), (3, 10)}\nprint(points) \n# {(3, 10), (1, 1)}\n\n# lists are mutable so not allowed\npoints = {[1, 1], [3, 10], [3, 10]} \n# TypeError: unhashable type: 'list'\n```\n\n## Set operators and methods\n\n```python\n# set.add(x):\n# Adds an element x to the set if x does not exist in the set. \n# Does not return anything.\n\nnumbers = {1, 2, 3}\nnumbers.add(20)\nprint(numbers) # {1, 2, 3, 20}\n\nnumbers.add(3)\nprint(numbers) # {1, 2, 3, 20}\n```\n\n##\n\n```python\n# set.remove(x):\n# Remove an element x from the set. Does not return anything.\n# Throws KeyError if element x is not present in the set.\n\nnumbers = {1, 2, 3}\nnumbers.remove(2)\nprint(numbers) # {1, 3}\n\nnumbers.remove(5) # KeyError: 5\n```\n\n##\n\n`in`, `not in` operators can be used to check if an element exists in a set.\n\n```python\nnumbers = {1, 2, 3}\nprint(2 in numbers) # True\nprint(5 not in numbers) # True\n\nshapes = {'circle', 'square'}\nprint(\"circle\" in shapes) # True\n```\n\n## \n\n:b[Sets cannot be indexed or sliced because they are not ordered.]\n\n:::hgrid\n```python\nprimes = {2, 3, 5, 7, 11}\nprimes[4] # TypeError: 'set' object is not subscriptable\n```\n:::\n\nBut we can use for loop to iterate over the items:\n\n:::hgrid\n```python\n# The order in which items will be printed \n# is not defined because sets are not ordered\n\nnumbers = {10, 1, 5, 20}\nfor n in numbers:\n print(n)\n```\n```output\n1\n10\n20\n5\n```\n:::\n\n## Why use sets?\n\n- Set are faster than lists and tuples, when inserting, removing and searching elements.\n- When order of elements is important or when elements are mutable, use lists or tuples\n- When only unique immutable elements need to be stored, use sets.\n\n\n##\n\n:::greenbox\nTry the problem \"Word count\" on Ed Lessons.\n:::\n\n##\n\n:::greenbox\nTry the problem \"List duplicates\" on Ed Lessons.\n:::\n\n\n::divider\n","title":"6.1 — Tuples, Immutable objects, Sets","date":"2024-02-01","published":true},{"slug":"Lecture-6.2.md","content":"\n## Updating multiple items in a list using slice assignment\n\nThe syntax of the slice assignment is as follows:\n\n```python lineno=false\nlist_object[start:stop:step] = iterable\n```\n\nFor now, think of `iterable` as a sequence/container such as a list, string, tuple etc.\n\n##\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\nprint(days[1:3])\n# ['Tue', 'Wed']\n\ndays[1:3] = [1, 2]\nprint(days)\n# ['Mon', 1, 2, 'Thu', 'Fri', 'Sat', 'Sun']\n```\n\n##\n\n```python\ndays = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\nprint(days[1::2])\n# ['Tue', 'Thu', 'Sat']\n\ndays[1::2] = [\"\", \"\", \"\"]\nprint(days)\n# ['Mon', '', 'Wed', '', 'Fri', '', 'Sun']\n\ndays[:3] = \"MTW\"\nprint(days)\n# ['M', 'T', 'W', '', 'Fri', '', 'Sun']\n```\n\n## Other list methods\n\n```python\n# list.reverse() : Reverse the list \"in place\"\ngrades = [90, 70, 60.5, 70, 80]\ngrades.reverse()\nprint(grades) # [80, 70, 60.5, 70, 90]\n\n# list.clear() : Remove all items from list.\ngrades = [90, 70, 60.5, 70, 80]\ngrades.clear()\nprint(grades) # []\n```\n\n## What modifies a list?\n\n- Assigning a value to an element using its index. \n ```python\n a = [1, 2, 3]\n a[0] = 5\n ```\n- Using the slice assignment to modify the elements of a list. \n ```python\n a = [1, 2, 3]\n a[:2] = [4, 5]\n ```\n- Using methods like :code[append(), insert(), remove() , pop(), clear()], etc.\n\n\n## What does not modify a list?\n- Slicing! It is a useful tool to create new lists out of an existing lists.\n ```python\n a = [1, 2, 3]\n b = a[:] # makes a copy of a\n print(a is b) # False\n ```\n- The `+` and `*` operators create a new list\n ```python\n a = [1, 2, 3]\n b = a # does not copy, just a new name for same list\n b = b + [4] # b now refers to a new list [1, 2, 3, 4]\n # a still refers to [1, 2, 3]\n print(a is b) # False\n ```\n\n## Set methods\n\n```python\n# set.update(iterable):\n# Adds all items from the iterable to the set.\n\nnumbers = {1, 2, 3}\nnumbers.update([10, 2, 2, 3, 20])\nprint(numbers) # {1, 2, 3, 10, 20}\n\nnumbers.update((\"a\", \"b\")) \nprint(numbers) # {1, 2, 3, 'a', 10, 20, 'b'}\n\nprimes = {2, 3, 5}\nprimes.update({5, 7, 11})\nprint(primes) # {2, 3, 5, 7, 11}\n```\n\n##\n\n```python\n# set.clear(): Remove all elements from this set.\n\nnumbers = {1, 2, 3}\nnumbers.clear()\nprint(numbers) # set()\n```\n\n##\n\n```python\n# For the following methods, suppose A and B are sets.\n\n# A.intersection(B):\n# Returns a new set that contains elements that are\n# present in both A and B \n\nodd = {3, 5, 7, 9, 25}\nsquares = {4, 9, 25, 36}\nodd_squares = odd.intersection(squares)\nprint(odd_squares) # {9, 25}\n\n# Intersection can also be done using operator \u0026\nodd_squares = odd \u0026 squares\nprint(odd_squares) # {9, 25}\n```\n\n##\n\n```python\n# A.union(B):\n# Returns a new set that contains elements that are\n# present in A or B or both\n\nx = {1, 2, 3}\ny = {2, 3, 5}\nall_numbers = x.union(y)\nprint(all_numbers) # {1, 2, 3, 5}\n\n# Same above but using an operator |\nall_numbers = x | y\nprint(all_numbers) # {1, 2, 3, 5}\n```\n\n##\n\n```python\n# A.difference(B):\n# Returns a new set that contains elements that are\n# present only in A but not in B\nx = {1, 2, 3}\ny = {2, 3, 5}\ndiff = x.difference(y)\nprint(diff) # {1}\n\n# Same as above but using operator -\ndiff = x - y\nprint(diff) # {1}\n```\n\nAll of the `set` methods work the same when elements are of other types such as strings.\n\n## Dictionaries :style{.ppt-f95}\n\nSuppose we would like to store the following enrollment data:\n\n:::hgrid{.ppt-f95 margin=\"0\"}\n| semester | no. of students |\n|----------|-----------------|\n| F2017 | 816 |\n| W2018 | 613 |\n| F2018 | 709 |\n| W2019 | 590 |\n:::\n\nWe can do this using two lists for the two columns:\n```python\nsemesters = ['F2017', 'W2018', 'F2018', 'W2019']\nstudents = [816, 613, 709, 590]\n```\n\n\n\n##\n\nWhat should we do if we want to add new data?\n\n```python\nsemesters = ['F2017', 'W2018', 'F2018', 'W2019']\nstudents = [816, 613, 709, 590]\n\nsemesters.append(\"F2020\")\nstudents.append(550)\n# ['F2017', 'W2018', 'F2018', 'W2019', 'F2020']\n# [816, 613, 709, 590, 550]\n``` \n\n##\n\nWhat if we want to modify the value for a specific semester?\n\n```python\nsemesters = ['F2017', 'W2018', 'F2018', 'W2019']\nstudents = [816, 613, 709, 590]\n\nidx = semesters.index(\"W2018\")\nstudents[idx] = 600\n```\n\n##\n\nWhat we if try to add an entry for a semester that already exists? \n\nList allows duplicates so it does not check if a semester already exists.\n\n```python\nsemesters = ['F2017', 'W2018', 'F2018', 'W2019']\nstudents = [816, 613, 709, 590]\n\nsemesters.append(\"F2018\")\nstudents.append(500)\n# ['F2017', 'W2018', 'F2018', 'W2019', 'F2018']\n# [816, 613, 709, 590, 500]\n```\n\n## Use a dictionary!\n\n- You can think of an item of a dictionary as a pair of objects:\n - The first object of the pair is called a :sc[key].\n - The second object is referred to as the :sc[value].\n- A dictionary is called a :i[mapping] type because it maps key objects to value objects.\n\n```python\n# A dictionary is created using a sequence of key-value pairs\nenrollment = {'F2017': 816, 'W2018': 613,\n 'F2018': 709, 'W2019': 590}\n\nprint(type(enrollment)) # \u003cclass 'dict'\u003e\n```\n\n##\n\n```python\n# Number of key-value pairs\nprint(len(enrollment)) # 4\n\n# This is an empty dictionary, not a set!\nempty_dict = {}\nprint(len(empty_dict)) # 0\n```\n\n## Dictionary Examples\n\n```python\n# Key: a number, Value: True if number is prime, else False\nis_prime = {2: True, 3: True, 4: False, 5: True,\n 7: True, 10: False}\n\n# Key: inventory items, Value: count of items in inventory\ninventory = {\"sofa\": 5, \"table\": 10, \"chair\": 20, \"mattress\": 5}\n\n# Key: city name, Value: area of city\npopulation = {\"Montreal\": 431.50, \"Toronto\": 630.20}\n\n# Key: country name, Value: capital city\ncapitals = {\"Canada\": \"Ottawa\", \"United States\": \"Washington, D.C.\",\n \"France\": \"Paris\", \"Germany\": \"Berlin\"}\n```\n\n## Note on keys and values\n\n- Keys\n - Have to be immutable objects.\n - Have to be unique in a dictionary. A dictionary cannot contain two items with the same key.\n- Values\n - Values can be of any type; both mutable and immutable values are allowed.\n - Many keys can map to the same value. i.e. values need not be unique.\n\n## Dictionary Lookup\n\nWith lists, we can access an item of the list through its index.\n\nWith dictionaries, we can access a value stored in the dictionary through the key associated with it.\n\n```python\nenrollment = {'F2017': 816, 'W2018': 613, 'F2018': 709,\n 'W2019': 590, 'F2019': 744}\n\nnum_students = enrollment[\"F2018\"]\nprint(num_students) # 709\n\n# Key must exist in the dictionary if we want to access its value\nprint(enrollment[\"F2020\"]) # KeyError: 'F2020'\n```\n\n## Adding an item\n\nWe can add a new item by specifying a key and a value: `dictionary[key] = value`\n\n```python\nenrollment = {'F2018': 709, 'W2019': 590}\n\nenrollment[\"F2020\"] = 800 # add an item\nenrollment[\"W2020\"] = 900 # add another item\n\n# {'F2018': 709, 'W2019': 590, 'F2020': 800, 'W2020': 900}\n\nenrollment[\"F2018\"] = 700 # change an existing item\n\n# {'F2018': 700, 'W2019': 590, 'F2020': 800, 'W2020': 900}\n```\n\n## Removing an item\n\nWe can delete an item using the following syntax: `del dictionary[key]`\n\n```python\nenrollment = {'F2018': 709, 'W2019': 590, 'F2019': 744}\n\ndel enrollment[\"F2019\"]\n\n# {'F2018': 709, 'W2019': 590}\n\ndel enrollment[\"F2020\"]\n# KeyError: 'F2020'\n```\n\n## \n\nWhat will be printed in the following examples?\n\n:::hgrid\n```python\nd = {'x' : 0, 'y' : 1, 'z' : 2} \nx = d['y']\nprint(x)\n```\n:::\n:::hgrid\n```python\nd = {'x' : 0, 'y' : 1, 'z' : 2} \nx = d[0]\nprint(x)\n```\n:::\n\n## Check for membership\n\nWe can check if a key is part of a dictionary using the `in` and `not in` operators.\n\n```python\nd = {'x' : 0, 'y' : 1, 'z' : 2} \nprint('x' in d) # True \nprint(0 in d) # False\nprint(0 not in d) # True\n```\n\n## Iterating through a dictionary\n\nWe can use a for loop to iterate through all the keys in a dictionary.\n\n```python\nenrollment = {'F2018': 709, 'W2019': 590, 'F2019': 744}\n\nfor key in enrollment:\n print(key, \"-\u003e\", enrollment[key])\n```\n\n```output\nF2018 -\u003e 709\nW2019 -\u003e 590\nF2019 -\u003e 744\n```\n\n## Functions and methods for dictionaries\n\n```python\n# dict(L): creates and returns a dictionary using a list L of tuples, \n# where each tuple is of length 2 in form of (key, value).\n\npairs = [(\"Montreal\", 1.78), (\"Rome\", 2.87), (\"Tokyo\", 9.27)]\npopulation_data = dict(pairs)\n\nprint(population_data)\n# {'Montreal': 1.78, 'Rome': 2.87, 'Tokyo': 9.27}\n\nprint(population_data[\"Rome\"])\n# 2.87\n```\n\n##\n\n```python\npopulation_data = {'Montreal': 1.78, 'Rome': 2.87, 'Tokyo': 9.27}\n\n# dict.keys(): returns a iterable (sequence) of all keys\n\ncities = list(population_data.keys())\nprint(cities) # ['Montreal', 'Rome', 'Tokyo']\n\n# dict.values(): returns a iterable (sequence) of all values\n\npopulation = list(population_data.values())\nprint(population) # [1.78, 2.87, 9.27]\n```\n\n##\n\n```python\n# dict.items(): returns a iterable (sequence) of tuples (key, value) \n# for all items in the dictionary\n\npopulation_data = {'Montreal': 1.78, 'Rome': 2.87, 'Tokyo': 9.27}\npairs = list(population_data.items())\n\nprint(pairs)\n# [('Montreal', 1.78), ('Rome', 2.87), ('Tokyo', 9.27)]\n```\n\nFor more methods, use `help(dict)`.\n\n##\n\nUsing the `dict` methods in `for` loop:\n\n```python\npopulation_data = {'Montreal': 1.78, 'Rome': 2.87, 'Tokyo': 9.27}\n\ntotal = 0\nfor population in population_data.values():\n total += population\n\nprint(total) # 13.92\n```\n\n##\n\n```python\npopulation_data = {'Montreal': 1.78, 'Rome': 2.87, 'Tokyo': 9.27}\n\n# dict.items() returns an iterable of key-value tuples\n\nfor tup in population_data.items():\n city = tup[0]\n population = tup[1]\n print(city, \"-\u003e\", population)\n\n# Montreal -\u003e 1.78\n# Rome -\u003e 2.87\n# Tokyo -\u003e 9.27\n```\n\n##\n\nMaking use of tuple unpacking in the for loop:\n\n```python\npopulation_data = {'Montreal': 1.78, 'Rome': 2.87, 'Tokyo': 9.27}\n\nfor city, population in population_data.items():\n print(city, \"-\u003e\", population)\n\n# Montreal -\u003e 1.78\n# Rome -\u003e 2.87\n# Tokyo -\u003e 9.27\n```\n\n##\n\n:::greenbox\nTime for some problems on Ed Lessons.\n:::\n\n\n::divider","title":"6.2 — More list \u0026 set operations, Dictionaries","date":"2024-02-03","published":true},{"slug":"Lecture-7.1.md","content":"\n## What's wrong in this example?\n\n```python\ndef average(nums):\n total = 0\n\n for x in grades:\n total += x\n\n return total / len(grades)\n\n\ngrades = [85, 100, 98, 75]\nprint(average(grades)) # 89.5\n```\n\n##\n\n- Try to avoid using global variables within functions when possible.\n- Assignment autograder will fail if you use global variable(s) instead of function parameter(s) inside a function.\n\n## Iterable\n\nAn :sc[iterable] is a kind of object that can produce a sequence of other objects and hence can be used in a `for` loop.\n\n- range: sequence of integers\n- strings: sequence of characters\n- tuples: (immutable) sequence of any object\n- lists: sequence of any object\n- dictionaries: sequence of tuples (key, value)\n- sets: collection of immutable objects (but order is not defined)\n\n\n##\n\nIterables can be used as arguments of the functions `list()`, `set()`, `tuple()`, `dict()`, etc.\n\n```python\n# range object only stores start, end and step size.\nprint(range(10, 101, 10)) # range(10, 101, 10)\n\n# list stores all objects in memory \nl = list(range(10, 101, 10))\nprint(l)\n# [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]\n\ns = set(range(10, 101, 10))\nprint(s)\n# {100, 70, 40, 10, 80, 50, 20, 90, 60, 30}\n```\n\n##\n\n:::greenbox\n\"Performance of searching a list, a set and dictionary\" from last week.\n:::\n\n## Sorting a list using `sort` method\n\n```python\n# list.sort() : \n# Sorts the list \"in place\" i.e. list will be modified.\n# Returns None.\n# The list is sorted in ascending order.\n\ngrades = [90, 70, 60.5, 70, 80]\ngrades.sort()\nprint(grades) # [60.5, 70, 70, 80, 90]\n\n\ngrades.sort(reverse=True) # descending order\nprint(grades) # [90, 80, 70, 70, 60.5]\n```\n\n##\n\n```python\ngrades = [90, 70, 60.5, 70, 80, \"A\"]\ngrades.sort()\n# TypeError: '\u003c' not supported between instances of 'str' and 'int'\n\n\n# Works with strings as well\nfruits = [\"banana\", \"orange\", \"apple\"]\nfruits.sort()\nprint(fruits) # ['apple', 'banana', 'orange']\n```\n\n## `sorted` function\n\n```python\n# sorted(iterable): \n# Return a new list containing all items from \n# the iterable in ascending order.\n# If any items cannnot be compared to each other, TypeError occurs.\n\ngrades = [90, 70, 60.5, 70, 80]\nsorted_grades = sorted(grades)\nprint(sorted_grades) # [60.5, 70, 70, 80, 90]\nprint(grades) # [90, 70, 60.5, 70, 80]\n\n# Sort in descending order\nsorted_grades = sorted(grades, reverse=True)\nprint(sorted_grades) # [90, 80, 70, 70, 60.5]\n```\n\n##\n\n```python\n# string is iterable\nword = \"pineapple\"\nsorted_letters = sorted(word)\nprint(sorted_letters)\n# ['a', 'e', 'e', 'i', 'l', 'n', 'p', 'p', 'p']\n\n\n# set is iterable\nfruits = {\"banana\", \"orange\", \"apple\"}\nsorted_fruits = sorted(fruits)\nprint(sorted_fruits) # ['apple', 'banana', 'orange']\n```\n\n##\n\n```python\n# dictionary is considered as an iterable of its keys\ninventory = {\"sofa\": 5, \"table\": 10, \"chair\": 20, \"mattress\": 5}\nsorted_names = sorted(inventory)\nprint(sorted_names) # ['chair', 'mattress', 'sofa', 'table']\n\n# same as above\nsorted_names = sorted(inventory.keys())\nprint(sorted_names) # ['chair', 'mattress', 'sofa', 'table']\n\n# iterable of values in the dictionary\nsorted_counts = sorted(inventory.values())\nprint(sorted_counts) # [5, 5, 10, 20]\n```\n\n## Packing vs Unpacking\n\nWhen we create a string, a list, or a tuple, we are packing several elements into a single object.\n\n```python\ns = \"cat\"\nmy_list = [5, 'a']\nmy_tuple = (0, 3, 7)\n```\n\n##\n\n:sc[Unpacking] allows us to assign values in a string/list/tuple to multiple variables. \nWe must know the exact length of the string/list/tuple.\n\n```python\ns = \"cat\"\na, b, c = s\n# a, b and c are all strings\nprint(a) # c\nprint(b) # a\nprint(c) # t\n\nmy_list = [5, 'cat']\nx, y = my_list\nprint(x) # 5\nprint(y) # cat\n```\n\n##\n\n```python\n# Parentheses are optional in this context.\nmy_tuple = 0, 3, 7\nx, y, z = my_tuple\nprint(x) # 0\nprint(y) # 3\nprint(z) # 7\n\n# Variables must match number of elements\ntup = 1, 2, 3 \nx, y = tup\n# ValueError: too many values to unpack (expected 2)\n```\n\n## Multiple assignment using packing/unpacking on same line\n\n```python\n# We are creating a tuple on the right side and \n# unpacking it into 3 variables.\ncity, population, area = 'Montreal', 1704694, 431.5\nprint(city) # Montreal\nprint(population) # 1704694\nprint(area) # 431.5\n```\n\n## Returning a tuple from a function and unpacking\n\n```python\ndef min_max(mylist):\n # Return a tuple of two elements\n return min(mylist), max(mylist)\n \n \n# Unpack the returned tuple into 2 variables\nx, y = min_max([2, -3, 10, 20])\nprint(x) # -3\nprint(y) # 20\n```\n\n\n## Nested Lists\n\nAn element of a list can be another list! \nSuch lists are called :sc[nested] lists.\n\n```python\nnested_list = [[1], [1, 2, 3], [1, 2]]\nprint(type(nested_list))\n# \u003cclass 'list'\u003e\n\nprint(nested_list[0])\n# [1]\nprint(nested_list[1])\n# [1, 2, 3]\nprint(nested_list[2])\n# [1, 2]\n```\n\n##\n\nNested lists are useful to store data which come in form of a table or spreadsheet.\n\n```python\n# Name, A1, A2, A3\nstudent_grades = [[\"Student-A\", 90, 95, 100], \n [\"Student-B\", 85, 90, 98], \n [\"Student-C\", 70, 75, 80]] \n```\n\n##\n\nWe can perform same operations on nested lists as we saw earlier: indexing, slicing, etc.\n\n```python\nstudent_grades = [[\"Student-A\", 90, 95, 100],\n [\"Student-B\", 85, 90, 98],\n [\"Student-C\", 70, 75, 80]]\n\n# Print name of 2nd student\nprint(student_grades[1][0]) \n# Student-B\n\n# Change A2 grade for Student-B\nstudent_grades[1][2] = 100\nprint(student_grades)\n# [['Student-A', 90, 95, 100], ['Student-B', 85, 100, 98], \n# ['Student-C', 70, 75, 80]]\n```\n\n## Iterating in a :i[row-first] order\n\n:::hgrid\n```python\nmatrix = [[81, 75, 90, 60], \n [80, 70, 85, 55],\n [40, 50, 45, 85]]\n\nnum_rows = len(matrix) \nnum_cols = len(matrix[0]) \n\nprint(\"Row-first order:\")\nfor r in range(num_rows):\n for c in range(num_cols):\n print(matrix[r][c], end=\" \")\n print()\n```\n```output\nRow-first order:\n81 75 90 60 \n80 70 85 55 \n40 50 45 85 \n```\n:::\n\n## Iterating in a :i[column-first] order\n\n:::hgrid\n```python\nmatrix = [[81, 75, 90, 60], \n [80, 70, 85, 55],\n [40, 50, 45, 85]]\n\nnum_rows = len(matrix) \nnum_cols = len(matrix[0])\n\nprint(\"\\nColumn-first order:\")\nfor c in range(num_cols):\n for r in range(num_rows):\n print(matrix[r][c], end=\" \")\n print()\n```\n```output\nColumn-first order:\n81 80 40 \n75 70 50 \n90 85 45 \n60 55 85\n```\n:::\n\n## List of tuples\n\n```python\npoints = [(1, 1, 3), (4, 10.5, 9), (7, 4.4, 9.7)]\n\n# List element can be modified:\npoints[1] = (4, 12, 10) # Assign new point\nprint(points) # [(1, 1, 3), (4, 12, 10), (7, 4.4, 9.7)]\n\n# Trying to change the second points's z-coordinate\npoints[1][2] = 20 \n# TypeError: 'tuple' object does not support item assignment\n```\n\n##\n\nIterating over list of tuples:\n\n:::hgrid\n```python\npoints = [(1, 1, 3), (4, 10.5, 9), \n (7, 4.4, 9.7)]\n\nfor p in points: # p is a tuple\n print(p)\n\n# Unpack a tuple into 3 variables\nfor x, y, z in points: \n print(x, y, z)\n```\n\n```output\n(1, 1, 3)\n(4, 10.5, 9)\n(7, 4.4, 9.7)\n1 1 3\n4 10.5 9\n7 4.4 9.7\n```\n:::\n\n##\n\n:::greenbox\nTime for some problems on Ed Lessons.\n:::\n\n::divider\n","title":"7.1 — Iterables, Unpacking, Nested Lists","date":"2024-02-10","published":true},{"slug":"Lecture-7.2.md","content":"\n## List of dictionaries\n\nA dictionary can be used to represent a data record such as student record in a course in the following example.\n\nA list of such dictionaries can store multiple data records.\n\n```python\nstudent_records = [\n {\"name\": \"Student-A\", \"ID\": 2601234, \"grades\": [90, 95, 100]},\n {\"name\": \"Student-B\", \"ID\": 2601000, \"grades\": [95, 95, 97]},\n {\"name\": \"Student-C\", \"ID\": 2605000, \"grades\": [80, 85, 90]}\n]\n\n# Each \"grades\" list has grades for 3 assignments.\n```\n\n##\n\nWhat are assignment 1 grades for \"Student-B\" ?\n\n```python\nstudent_records = [\n {\"name\": \"Student-A\", \"ID\": 2601234, \"grades\": [90, 95, 100]},\n {\"name\": \"Student-B\", \"ID\": 2601000, \"grades\": [95, 95, 97]},\n {\"name\": \"Student-C\", \"ID\": 2605000, \"grades\": [80, 85, 90]}\n]\n\nprint(student_records[1][\"grades\"][0]) # 95\n```\n\n##\n\nDisplay total grade for each student.\n\n```python\nstudent_records = [\n {\"name\": \"Student-A\", \"ID\": 2601234, \"grades\": [90, 95, 100]},\n {\"name\": \"Student-B\", \"ID\": 2601000, \"grades\": [95, 95, 97]},\n {\"name\": \"Student-C\", \"ID\": 2605000, \"grades\": [80, 85, 90]}\n]\n\nfor record in student_records:\n name, total_grade = record[\"name\"], sum(record[\"grades\"])\n print(f\"Total grade for {name} is {total_grade}\")\n```\n```output\nTotal grade for Student-A is 285\nTotal grade for Student-B is 287\nTotal grade for Student-C is 255\n```\n\n\n## Comparing data structures\n\nData structures — list, tuples, sets and dictionaries can be compared for equality using `==` and `!=` operators.\n\n```python\n# Lists, order matters\ngrades1 = [85, 80, 100]\ngrades2 = [85, 80, 100]\ngrades3 = [85, 100, 80]\n\n# True only when all elements are equal in order\nprint(grades1 == grades2) # True\nprint(grades2 == grades3) # False\n```\n\n##\n\n```python\n# Comparing tuples, order matters\npoint1 = (1, 1, 2)\npoint2 = (1, 2, 1)\nprint(point1 != point2) # True\n\n\n# Comparing sets, order does not matter\nfruits1 = {\"apple\", \"orange\", \"banana\"}\nfruits2 = {\"orange\", \"apple\", \"banana\"}\n\n# True only when sets are of equal length and \n# both sets contain same elements\nprint(fruits1 == fruits2) # True\nprint(fruits1 == {\"apple\", \"orange\", \"banana\", \"grapes\"}) # False\n```\n\n##\n\n```python\n# Comparing dictionaries, order does not matter\nphonebook1 = {\"A\": 5140001000, \"B\": 5140002000, \"C\": 5140003000 }\nphonebook2 = { \"B\": 5140002000, \"A\": 5140001000, \"C\": 5140003000 }\n\n# True only when dictionaries are of equal length and \n# both contain same key-value pairs\nprint(phonebook1 == phonebook2) # True\n\nprint(phonebook1 == {\"A\": 4381001000, \"B\": 5140002000, \n \"C\": 5140003000 }) # False\n```\n\n## Comparison works for nested structures as well\n\n```python\npoints1 = [(1, 1), (2, 10)]\npoints2 = [(1, 1), (2, 10)]\nprint(points1 == points2) # True\n\nprint(points1 == [(1, 1), (2, 5)]) # False\nprint(points1 == [(1, 1), [2, 10]]) # False\n\nstudent1 = {\"name\": \"Student-B\", \"grades\": [90, 100, 100]}\nstudent2 = {\"name\": \"Student-B\", \"grades\": [90, 100, 90]}\nprint(student1 == student2) # False\n```\n\n## `enumerate` function\n\n```python\nmylist = [10, 50, -3.14, 5]\nprint(enumerate(mylist))\n# \u003cenumerate object at 0x10e327100\u003e\n\n# enumerate creates an iterable of tuples (index, element), \n# which we convert to list\nlist_of_tuples = list(enumerate(mylist))\nprint(list_of_tuples)\n# [(0, 10), (1, 50), (2, -3.14), (3, 5)]\n```\n\n##\n\n:::hgrid\n```python\nmylist = [10, 50, -3.14, 5]\n\nfor i in range(len(mylist)):\n num = mylist[i]\n print(i, num)\n```\n```python\nmylist = [10, 50, -3.14, 5]\n\nfor i, num in enumerate(mylist):\n print(i, num)\n```\n:::\n\n## List, set and dictionary comprehensions\n\nWe often find ourselves repeating the following pattern to create a list. \n\nPython provides a simpler way to create a list using list comprehension.\n\n```python\nsome_list = []\nfor i in some_iterable:\n some_list.append(some_expression)\n\n# Using list comprehension\nsome_list = [some_expression for i in some_iterable]\n```\n\n##\n\n```python\neven_nums = [i for i in range(2, 20, 2)]\nprint(even_nums)\n# [2, 4, 6, 8, 10, 12, 14, 16, 18]\n\n\n# Need not use loop variable i in the expression\nzeros = [0 for i in range(7)]\nprint(zeros)\n# [0, 0, 0, 0, 0, 0, 0]\n```\n\n##\n\n```python\nimport math\n\nsine_values = [math.sin(x) for x in [-math.pi/2, 0, math.pi/2]]\nprint(sine_values)\n# [-1.0, 0.0, 1.0]\n\nstring = \"10.0,20.5,100.123\"\nnumbers = [float(word) for word in string.split(\",\")]\nprint(numbers)\n# [10.0, 20.5, 100.123]\n```\n\n## Using if-statement in list comprehension\n\n```python\nsquares_of_odds = []\nfor x in range(1, 20):\n if x % 2 != 0:\n squares_of_odds.append(x * x)\n\nprint(squares_of_odds)\n# [1, 9, 25, 49, 81, 121, 169, 225, 289, 361]\n\n# same as above but using list comprehension\nsquares_of_odds = [x * x for x in range(1, 20) if x % 2 != 0]\nprint(squares_of_odds)\n# [1, 9, 25, 49, 81, 121, 169, 225, 289, 361]\n\n```\n\n## Set and dictionary comprehension\n\n```python\nodd_squares = {i*i for i in range(1, 20, 2)}\nprint(odd_squares)\n# {1, 121, 225, 289, 9, 169, 361, 81, 49, 25}\n\n\nnames = [\"A\", \"B\", \"C\", \"D\"]\nnames_to_index = {name: i for i, name in enumerate(names)}\nprint(names_to_index)\n# {'A': 0, 'B': 1, 'C': 2, 'D': 3}\n```\n\n## Writing \u0026 importing modules\n\n- What are modules exactly?\n - A module is simply a Python file containing definitions and statements.\n - Every `.py` file is a module. The name of the module is the name of the file.\n- Name of a Python file (module) must follow same rules as variable names.\n - Module names can only start with letters a-z, A-Z or an underscore and must only contain these letters, digits and underscores.\n\n##\n\nDownload files-7.2.zip from Ed Lessons. It contains `geometry.py` and `geometry_tester.py`\n\n##\n\nIn `geometry_tester.py` file, we import and use the functions defined in the module `geometry`:\n\n```python\n# Import functions from the module\nfrom geometry import euclidean_distance, sine\n\n# Call the sine function\nprint(sine(90))\n\ndist = euclidean_distance((1, 1), (2, 3)) # tuples\nprint(dist) # 2.23606797749979\n\n```\n\n## Some observations\n\n- When we import a module, all code inside that module is executed.\n- Add some statement such as `print(\"hello\")` in `geometry` module outside the functions. Then,\n - Run the `geometry.py` as the main program\n - Run another program which imports the module `geometry`.\n\n## Running a file as main program vs importing it\n\n- Sometimes, we may want to run some code only when a Python file is executed directly as main program but not when it is imported as a module.\n- For example, suppose we want the following test cases in `geometry.py`\n ```python\n print(sine(-90))\n print(sine(180))\n ```\n- How to make sure the test cases do not execute when `geometry` is imported as a module?\n\n## `__name__`\n\n- `__name__` is a special variable that the interpreter initializes whenever it executes a file.\n- When a module is executed, the interpreter does the following: \n - sets the value of `__name__` for that module using the filename\n - executes all the code in the module.\n- Each module has its own `__name__` variable.\n- Add `print(__name__)` in `geometry.py` and import `geometry` module in another program.\n\n## `\"__main__\"`\n\n- When we execute a file as the main program, then the variable `__name__` is set to be `\"__main__\"`\n- Run `geometry.py` directly and see what value of `__name__` is printed.\n\n## How to :i[not execute] code in a module when it is imported\n\nAdd the following at the end of the file `geometry.py`:\n\n```python\nif __name__ == \"__main__\":\n # Run the following code only when this file is \n # executed as main program but not when it is imported\n print(\"hello from geometry!\")\n print(sine(-90))\n print(sine(180))\n```\n\n##\n\n:::greenbox\nTime for some problems on Ed Lessons.\n:::\n\n::divider\n\n","title":"7.2 — Nested data structures, Comprehensions, Modules","date":"2024-02-12","published":true},{"slug":"Lecture-8.1.md","content":"\n##\n\n:b[How list is stored]{.sans}\n\n:::hgrid{gap=\"1em\" margin=\"0.5em 0\"}\n```python lineno=false\nmylist = [3, 17, 42]\n```\n\n::image{style=\"width: 100%\" src=\"lecture-8.1/mylist_actual.png\"} \n:::\n\n:::hgrid{cols=\"1fr 4fr\" margin=\"0.5em 0\"}\n:b[Simplified: ]{.sans}\n::image{style=\"width: 70%\" src=\"lecture-8.1/mylist_simplified.png\"} \n:::\n\n##\n\n:b[How :i[nested list] is stored]{.sans}\n\n```python lineno=false margin=\"0.5em 0\"\nnested_list = [[3, 17, 42], [10, 20, 30]]\n```\n\n::image{style=\"width: 65%; margin: 1em auto;\" src=\"lecture-8.1/nested_list.png\"} \n\n##\n\n:::hgrid{margin=\"0\"}\n```python lineno=false margin=\"0.5em 0\"\nzeros = [0] * 5\nC = [zeros] * 3\nC[1][3] = 100 \nprint(C) \n```\n```python lineno=false margin=\"0.5em 0\"\nzeros = [0] * 5\nC = []\nfor i in range(3):\n C.append(zeros)\nC[1][3] = 100 \nprint(C) \n```\n```output\n[[0, 0, 0, 100, 0], \n [0, 0, 0, 100, 0], \n [0, 0, 0, 100, 0]]\n```\n:::\n\n::image{style=\"width: 80%; margin: 0.5em auto;\" src=\"lecture-8.1/nested_zeros.png\"} \n\n##\n\n:::hgrid{margin=\"0\" cols=\"1fr 4fr\"}\n```python lineno=false margin=\"0.5em 0\"\nC = []\nfor i in range(3):\n C.append([0] * 5)\nC[1][3] = 100 \nprint(C)\n```\n\n::image{style=\"width: 100%; margin: 0.5em auto;\" src=\"lecture-8.1/nested_zeros_correct.png\"} \n:::\n\n## Nested list comprehensions\n\n```python\nmatrix = [[10, 20, 30],\n [40, 50, 60],\n [70, 80, 90]]\n\nsquared = [[x ** 2 for x in row] for row in matrix]\n\nprint(squared)\n# [[100, 400, 900],\n# [1600, 2500, 3600],\n# [4900, 6400, 8100]]\n\n```\n\n##\n\n:::greenbox\nTime for list comprehension problems on Ed Lessons.\n:::\n\n## How to work with a file in Python\n\nA file is a sequence of characters or bytes stored on a storage device such as a hard drive.\n\n- Open the file using the built-in function `open()`\n- Read data from the file or write data into the file\n- Close the file\n\n\n## Opening a file with :code[open()]\n\nBuilt-in function `open(filename, mode)`:\n- `filename` (str): name of the file to read (if the file is in the current directory) or full path to the file.\n- `mode` (str): `'r'` for reading, `'w'` for writing, `'a'` for appending. If this argument is omitted, it defaults to `'r'`\n- Returns: a :sc[file object] which allows reading from/writing to the file.\n\n```python\nfilename = \"quotes.txt\"\nfobj = open(filename, \"r\") # mode \"r\" for reading\n```\n\n## Reading a file with :code[read()] method of file object\n\nFile objects have a method `read(size)` that takes one optional argument:\n- `size`: the number of characters to read from the file\n - If omitted, the entire file will be read.\n- Returns: a string containing the characters in the file\n\n## Closing a file with :code[close()] method of file object\n\n- `close()` method takes no argument and returns nothing. It closes the file associated with the file object\n\n##\n\nFor the following example, download the files from Ed Lessons and keep them in the same directory as the python program.\n\n```python\nfilename = \"quotes.txt\"\nfobj = open(filename, \"r\") # mode \"r\" for reading \n\nfile_content = fobj.read() # read whole file as a string\nprint(file_content) # print the string \n\nfobj.close() # close the file\n```\n\n## Why close a file? \n\nClosing the file is important for many reasons \n- Operating System (e.g Windows, Mac OS) may lock the file until it is closed (Other programs may not use the file as long as it is open)\n- Too many open files may cause your program/computer to slow down\n\n\n## Reading a file line by line\n\n- The file object returned by `open()` is an iterable that can produce a sequence of lines in the file. So we can use it in a `for` loop.\n- Each line will have a trailing newline (`\"\\n\"`) character.\n\n```python\nfilename = \"quotes.txt\"\nfobj = open(filename, \"r\")\n\nfor line in fobj: # file object is iterable\n line = line.rstrip(\"\\n\") # Remove trailing \"\\n\" character\n print(line) \n\nfobj.close()\n```\n\n## Reading a file as list of lines\n\n```python\nfilename = \"quotes.txt\"\nfobj = open(filename, \"r\")\n\n# Read whole file, split into lines and return a list of lines\nall_lines = fobj.readlines() \n\n# Each line will have a trailing newline character \"\\n\"\n\nprint(all_lines)\n\nfobj.close()\n```\n\n## Writing text to a file in Python\n\nTo write to a file in Python:\n- Open the file with open() using mode `'w'` for \"write.\"\n - If the file does not exist, it will be created.\n - :span[If the file exists, it will be deleted and replaced with an empty file.]{.bgred .px05}\n- Call `write(s)` method on the file object to write the string `s` into the file.\n- Close the file.\n\n##\n\n```python\nfilename = \"grades.txt\"\nfobj = open(filename, \"w\") # mode \"w\" for writing \n\nfobj.write(\"Name,A1,A2,A3\\n\") # write line to file\nfobj.write(\"Student-A,90,80,100\\n\") # write another line\nfobj.write(\"Student-B,100,90,100\\n\") # write another line\n\nfobj.close()\n```\n\n## Appending text to a file in Python\n\nAppending means adding text to the end of a file without changing/deleting text already present in the file.\n\n- Open the file with open() using mode `'a'` for \"append.\"\n - If the file does not exist, it will be created.\n - If the file exists, it is NOT deleted.\n- Call `write(s)` method on the file object, to write the string `s` at the end of the file.\n- Close the file.\n\n##\n\n```python\nfilename = \"grades.txt\"\nfobj = open(filename, \"a\") # mode \"a\" for appending \n\nfobj.write(\"Student-C,85,90,97\\n\")\nfobj.write(\"Student-D,95,90,97\\n\") \n\nfobj.close()\n```\n\n##\n\n:::greenbox\nTime for some problems on Ed Lessons.\n:::\n\n::divider\n\n","title":"8.1 — Nested list comprehensions, Reading \u0026 Writing Files","date":"2024-02-20","published":true},{"slug":"Lecture-8.2.md","content":"\n## `with` statement to open files\n\n```python\nwith open(\"myfile.txt\", \"r\") as fobj:\n # file remains open here inside with-statement\n file_content = fobj.read()\n\n# the file is automatically closed when\n# the with-statement is done\n\nprint(file_content)\n```\n\n##\n\nAny valid operations/methods can be performed on the file object inside the `with` statement.\n\n```python\nwith open(\"myfile.txt\", \"r\") as fobj:\n for line in fobj:\n line = line.rstrip(\"\\n\")\n print(line)\n```\n\nFor the Write and Append modes, the `write` method can be used in the `with` statement.\n\n##\n\n```python\nwith open(\"myfile.txt\", \"r\") as fobj:\n print(\"hello\")\n\nprint(fobj.read())\n```\n\n```output\nhello\nTraceback (most recent call last):\n File \"lecture_8.py\", line 4, in \u003cmodule\u003e\n print(fobj.read())\nValueError: I/O operation on closed file.\n```\n\n\n## Reading \u0026 writing files using pathlib\n\nTo read a file entirely into a string:\n\n```python\nfrom pathlib import Path\n\nfile_content = Path(\"myfile.txt\").read_text()\nprint(file_content)\n```\n\n##\n\nTo write text into a file:\n\n```python\nfrom pathlib import Path\n\ndata = \"\"\"Name,A1,A2,A3\nStudent-A,90,80,100\nStudent-B,100,90,100\n\"\"\"\n\n# this will delete existing contents of the file\nPath(\"grades.txt\").write_text(data)\n```\n\n## Shallow copy\n\nWhen we create a new list using slicing or `list()` function on a nested list, inner lists are not copied but they are shared. Such a copy is called :sc[shallow copy].\n\n```python\nnested_list = [[3, 17, 42], [10, 20, 30]]\n\nnew_copy = nested_list[:] # shallow copy\n\nnew_copy = list(nested_list) # shallow copy\n```\n\n##\n\n```python lineno=false\nnested_list = [[3, 17, 42], \n [10, 20, 30]]\n\nnew_copy = nested_list[:]\n```\n\n::image{style=\"width: 70%; margin: 1em auto\" src=\"lecture-8.2/shallow_copy.png\"} \n\n##\n\nChanging values in a shallow copy affects the original nested list because inner lists are shared.\n\n:::hgrid\n```python\nnested_list = [[3, 17, 42], \n [10, 20, 30]]\n\nnew_copy = nested_list[:] \n# new_copy = list(nested_list)\n\nnew_copy[0][2] = 123\n\nprint(new_copy)\nprint(nested_list)\n```\n```output\n[[3, 17, 123], [10, 20, 30]]\n[[3, 17, 123], [10, 20, 30]]\n```\n:::\n\n##\n\nSimilary `dict()`, `tuple()`, `set()` functions will make a shallow copy.\n\n```python lineno=false\nstudent = {\"name\": \"Reza\", \"ID\": 2601000, \"grades\": [95, 95, 97]}\nstudent_copy = dict(student)\n```\n\n::image{style=\"width: 100%; margin: 1em auto\" src=\"lecture-8.2/shallow_copy_dict.png\"} \n\n##\n\nChanging values via the shallow copy affects the original dictionary because grades list is shared.\n\n```python\nstudent = {\"name\": \"Reza\", \"ID\": 2601000, \"grades\": [95, 95, 97]}\n\nstudent_copy = dict(student)\nstudent_copy[\"grades\"][0] = 100\n\nprint(student_copy)\nprint(student)\n```\n```output\n{'name': 'Reza', 'ID': 2601000, 'grades': [100, 95, 97]}\n{'name': 'Reza', 'ID': 2601000, 'grades': [100, 95, 97]}\n```\n\n## Deep copy :style{.ppt-f95}\n\n`deepcopy` function from `copy` module can copy a nested structure recursively (all inner lists etc. are copied as well). Such a copy is called :sc[deep copy].\n\n:::hgrid\n```python\nimport copy\n\nnested_list = [[3, 17, 42], \n [10, 20, 30]]\nnew_copy = copy.deepcopy(nested_list)\nnew_copy[0][2] = 123\n\nprint(new_copy)\nprint(nested_list)\n```\n```output\n[[3, 17, 123], [10, 20, 30]]\n[[3, 17, 42], [10, 20, 30]]\n```\n:::\n\n##\n\n```python\nimport copy\n\nstudent = {\"name\": \"Reza\", \"ID\": 2601000, \"grades\": [95, 95, 97]}\n\nstudent_copy = copy.deepcopy(student)\nstudent_copy[\"grades\"][0] = 100\n\nprint(student_copy)\nprint(student)\n```\n\n```output\n{'name': 'Reza', 'ID': 2601000, 'grades': [100, 95, 97]}\n{'name': 'Reza', 'ID': 2601000, 'grades': [95, 95, 97]}\n```\n\n##\n\n:::greenbox\nTime for problems about file reading/writing on Ed Lessons.\n:::\n\n## Types of Errors\n\n- Syntax Errors: When syntax is incorrect such as wrong punctuations, invalid characters, indentation etc.\n - Program does not even run in this case.\n- Runtime Errors, also called :sc[Exceptions], occur when there is a problem in the program during execution.\n - All code executes until an exception occurs.\n- Semantic or Logic errors are said to occur when a program executes without a problem but does not produce correct output as expected.\n\n## :style{.ppt-f90}\n\nIn Python, all exceptions are objects of some exception type. \nCommon exceptions are:\n\n:::div\n| Exception Type | Meaning |\n|-------------------|-----------------------------------------------------------------------|\n| IndexError | Index is out of range in a list or tuple |\n| KeyError | Specified key does not appear in a dictionary |\n| NameError | Specified local or global name does not exist |\n| TypeError | Operation or function applied to an inappropriate type |\n| ValueError | Operation or function applied to correct type but inappropriate value |\n| ZeroDivisionError | Second operand of division or remainder operation is zero |\n:::\n\n## Exception Traceback\n\nTraceback is an error message that allows tracing an exception back to its origin\n\n:::hgrid{cols=\"1fr 2fr\"}\n```python\ndef func():\n return 5 / 0\n\ndef main():\n return func()\n\nmain()\n```\n```output\nTraceback (most recent call last):\n File \"myprogram.py\", line 7, in \u003cmodule\u003e\n main()\n File \"myprogram.py\", line 5, in main\n return func()\n File \"myprogram.py\", line 2, in func\n return 5 / 0\nZeroDivisionError: division by zero\n```\n:::\n\n## Using `try` statement to handle errors\n\n```python\ntry:\n # try-block: code that may cause runtime error\nexcept:\n # except-block: handle the error here\n```\n\n- Identify a code that can potentially produce errors\n- Put that code in `try-block`. \n- Write code in `except-block` to handle the case when error occurs\n\n## Example\n\nSuppose we want to take a number from user input:\n\n```python\nnumber = float(input('Please enter a number: '))\n```\n\n```output\nValueError: could not convert string to float: 'abcd'\n```\n\nSince we know that `float()` function throws the `ValueError`, we can handle that error using `try` statement.\n\n##\n\n```python\ntry:\n number = float(input('Please enter a number: '))\n # the following line only executes when float() function\n # worked i.e. it did not throw ValueError\n print(\"You entered: \", number) \nexcept ValueError:\n print(\"Please enter valid number!\")\n```\n\nThere are two cases when we run the code above:\n\n::::div{.flex .ml-2}\n:::div{.flexc}\n\n:div[No error occurs in try-block]{.sans}\nexcept-block is not executed.\n\n```output\nPlease enter a number: -3.1415\nYou entered: -3.1415\n```\n:::\n\n:::div{.flexc}\n\n:div[Error occurs in try-block]{.sans}\nexcept-block is executed.\n\n```output\nPlease enter a number: abcd\nPlease enter valid number!\n```\n:::\n::::\n\n## \n\n:::greenbox\nUsing try statement in a loop, ask user to input a number (float) until they enter a valid number. \nYou can use a break statement in the try-block.\n:::\n\n```output\nPlease enter a number: abcd\nInvalid number!\nPlease enter a number: -1.61\nCorrect number entered: -1.61\n```\n\n##\n\n:::solution\n```python\nwhile True: # Loop forever\n try:\n number = float(input('Please enter a number: '))\n break # Get out of the loop\n except ValueError:\n print('Invalid number!')\n\nprint(\"Correct number entered:\", number)\n```\n:::\n\n## Unhandled exceptions are thrown as usual\n\nIf an exception occurs in try-block but it is not the same type as in the except part, the exception occurs as usual i.e. except-block is not executed and program crashes.\n\n:::hgrid{.ppt-f80 margin=\"1em 0 1em -2em\"}\n```python\nstudent_grades = {\"Reza\": 90.0}\n\ntry:\n name = input('Enter name: ')\n grade = float(input(\"Enter grade to add: \"))\n student_grades[name] += grade\n print(student_grades)\nexcept ValueError:\n print('Grade should be a number!')\n```\n\n```output\nEnter name: Dev\nEnter grade to add: 5\nTraceback (most recent call last):\n File \"myprogram.py\", line 20\n student_grades[name] += grade\nKeyError: 'Dev'\n```\n:::\n\n## Catching multiple exceptions\n\nExcept blocks can be chained to handle multiple exceptions that may occur in try-block.\n\nDepending on the exception that occurs, only one of the except-blocks executes. Others are skipped.\n\n```python\ntry:\n # try-block: code that may cause runtime error(s)\nexcept Error1:\n # handle the Error1 here\nexcept Error2:\n # handle the Error2 here\n```\n\n## :style{.ppt-f90}\n\n```python\nstudent_grades = {\"Reza\": 90.0}\n\ntry:\n name = input('Enter name: ')\n grade = float(input(\"Enter grade to add: \"))\n student_grades[name] += grade\n print(student_grades)\nexcept ValueError:\n print('Grade should be a number!')\nexcept KeyError:\n print(name, \"was not found.\")\n```\n\n::::div{.flex style=\"width: 110%; margin-left: -2em;\"}\n:::div{.flexc}\n:span[No error]{.sans}\n```output\nEnter name: Reza\nEnter grade to add: 5\n{'Reza': 95.0}\n```\n:::\n\n:::div{.flexc}\n:span[ValueError]{.sans}\n```output\nEnter name: Reza\nEnter grade to add: 10x\nGrade should be a number!\n```\n:::\n\n:::div{.flexc}\n:span[KeyError]{.sans}\n```output\nEnter name: Dev\nEnter grade to add: 5\nDev was not found.\n```\n:::\n::::\n\n## :style{.ppt-f95}\n\nWe can have a :b[default except block] without any exception type to handle exception of any kind. \n\n::::hgrid{cols=\"3fr 1fr\" margin=\"0.5em 0 1em -2em\"}\n```python\nstudent_grades = {\"Reza\": 90.0}\n\ntry:\n name = input('Enter name: ')\n grade = float(input(\"Enter grade to add: \"))\n student_grades[name] += grade\n print(no_such_variable) # error here\nexcept ValueError:\n print('Grade should be a number!')\nexcept KeyError:\n print(name, \"was not found.\")\nexcept:\n print(\"Some error occured.\")\n```\n:::div\n```output\nEnter name: Reza\nEnter grade to add: 5\nSome error occured.\n```\n(Again, only one of the except-blocks will execute.)\n:::\n::::\n\n## \n\n:::redbox\nIn general it is :em[not a good practice] to catch all errors using a default block. \nInstead, specific errors should be handled explicitly by using an exception type.\n:::\n\n## `finally` block :style{.ppt-f95}\n\n- We can have an optional `finally` block in a `try` statement; it is always executed whether an exception occurs in `try` block or not. \n- It is useful to clean up resources (e.g. closing a file), which needs to be done even when exceptions occur.\n\n```python\ntry:\n # try-block: code that may cause runtime error(s)\nexcept Error1:\n # handle the Error1 here\nexcept Error2:\n # handle the Error2 here\nfinally:\n # this block always executes\n```\n\n## \n\nFor this example, download the files `read_matrix.py` and `matrixdata.txt` from Ed Lesson and keep it in the same folder as the program.\n\n## When to use try statement \n\n- It is a bad practice to use a try/except to \"hide\" bugs in the program!\n- try/except should be used when we know that a specific error may occurs and there is no other way to handle it\n\n## :style{.ppt-f90}\n\n::::hgrid{margin=\"0\"}\n:::div\n:span[Good practice]{.sans}\n```python\ntry:\n number = float(input(\"Enter a number: \"))\nexcept ValueError:\n print('Number is not valid!')\n```\n:::\nbecause there is no other better way to check if a string contains a valid number.\n::::\n\n::::hgrid{margin=\"0\"}\n:::div\n:span[Not a good practice]{.sans}\n```python\nstudent_grades = {\"Reza\": 90.0}\ntry:\n name = input('Enter name: ')\n student_grades[name] += 10\nexcept KeyError:\n print(name, \"was not found.\")\n```\nbecause there is another way to do this (shown on right -\u003e)\n:::\n```python place=\"center\"\nstudent_grades = {\"Reza\": 90.0}\n\nname = input('Enter name: ')\n\nif name in student_grades:\n student_grades[name] += 10\nelse:\n print(name, \"was not found.\")\n```\n::::\n\n## Where do Exceptions come from? \n\n`raise` statement is used to throw an exception from our code to tell Python that an unexpected case or error has occurred.\n\n```python lineno=false\nraise SomeException(\"Some message\")\n```\n\nCheck `euclidean_distance` function in `distance.py` and `distance2.py`.\n\nAn exception raised in this way must be handled using `try` statements, otherwise Python will stop execution with the error as usual.\n\n## Example\n\nHow exceptions are raised in Python modules? \nOpen the following link and search for `raise`: \nhttps://github.com/python/cpython/blob/main/Lib/random.py\n\n## Checking type of an object\n\n```python\n# isinstance(obj, class):\n# Return whether an object is an instance of a class\n\nx = 123\nprint(isinstance(x, int)) # True\nprint(isinstance(x, float)) # False\n\nx = \"apple\"\nprint(isinstance(x, str)) # True\n\nx = [1, 5, 9]\nprint(isinstance(x, list)) # True\nprint(isinstance(x, tuple)) # False\n```\n\n##\n\n```python\n# isinstance(obj, tuple_of_classes):\n# A tuple of classes, e.g. isinstance(x, (A, B, ...)), may be given.\n# Equivalent to isinstance(x, A) or isinstance(x, B) or ...\n\nx = 3.14\nprint(isinstance(x, (int, float))) # True\n\nx = [1, 5, 9]\nprint(isinstance(x, (list, tuple))) # True\n\nx = (11, 51, 4)\nprint(isinstance(x, (list, tuple))) # True\n```\n\n##\n\n:::greenbox\nTime for some problems on Ed Lessons.\n:::\n\n::divider\n\n","title":"8.2 — Shallow vs. deep copy, Handling exceptions","date":"2024-02-22","published":true},{"slug":"Lecture-9.1.md","content":"\n## Objects\n\n- An object consists of data and a set of methods can be provided to work with it. \n- For example, a string is a collection of characters and methods like :code[isupper] or :code[split] can be called :i[on it].\n- Python is an object-oriented language. This means that it uses objects to represent data and provides methods related to them.\n\n## Object-oriented programming (OOP)\n\n- Up to now, we have been using functions to organize our code, and built-in types :code[(list, str, list, or dict)] to organize our data.\n- OOP is a way to use programmer-defined data classes to organize code and data.\n\n\n## Class and objects\n\n- A class is like a :i[blueprint/template] for creating objects. It specifies what data the objects have and what methods can operate on the data.\n- An object is an :sc[instance] of some class. The terms :i[instance] and :i[object] are used interchangeably.\n\n## Example — Student\n\nWe want to define a class that would be a good template for objects representing students.\n\n::::hgrid{margin=\"0\"}\n:::div\nUseful data: \n- Name\n- Student ID\n- Current courses \n- Past grades\n:::\n:::div\nUseful methods: \n- compute_GPA \n- add_course\n- drop_course\n:::\n::::\n\nEach instance of the class (i.e., each object) would represent one particular student.\n\n## Defining a class\n\n```python\n# This class does not contain any useful code yet\nclass MyClass:\n \"\"\" a new data type \"\"\"\n pass\n```\n\n- Class names should follow the UpperCamelCase convention. \n- In a Python file, we can define as many classes as we want. \n\n## Instantiating a class\n\n```python\nclass Student:\n \"\"\" Represents a student \"\"\"\n pass\n\n# We can now create an object using the constructor Student()\nstudent1 = Student() \nprint(student1) # \u003c__main__.Student object at 0x7fa7806c9310\u003e\n\nstudent2 = Student() \nprint(student2) # \u003c__main__.Student object at 0x7fa7806c9290\u003e\n```\n\nThe variables `student1` and `student2`refer to two different objects of class `Student`. \n\n## Attributes\n\n- We can create a variable that belongs to a specific object. These variables are called :sc[attributes]. \n- We create an attribute by assigning it a value using the dot notation: `object.attribute = value`\n- Attributes can be accessed only through the object they belong to, using dot notation: `object.attribute`\n\n##\n\n```python\nclass Student:\n \"\"\" Represents a student \"\"\"\n pass\n\nstudent1 = Student()\n# Create an attribute inside the student1 object\nstudent1.name = \"Reza\"\n\n# Use the attribute inside student1 object:\nprint(student1.name) # Reza\n\nstudent2 = Student()\nprint(student2.name)\n# AttributeError: 'Student' object has no attribute 'name'\n\nprint(name) # NameError: name 'name' is not defined\n```\n\n## Visualization\n\n:::div{.ppt-scale-1_25}\n\u003ciframe width=\"800\" height=\"400\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=class%20Student%3A%0A%20%20%20%22%22%22%20Represents%20a%20student%0A%20%20%20%22%22%22%0A%0Astudent1%20%3D%20Student%28%29%0Astudent1.name%20%3D%20%22Deven%22%0Aname%20%3D%20student1.name%0Aprint%28name%29%0A\u0026codeDivHeight=400\u0026codeDivWidth=350\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=nevernest\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n## Try it!\n\n- Define a class `Student`.\n- Write a function that takes as arguments a string `name` and an integer `id_num` and returns a `Student` object with two attributes `name` and `id_num`.\n- Write a function that takes as arguments two Student objects and returns the `name` of the student with the larger `id_num`.\n- Test the above functions by creating two objects of Student class.\n\nCode available in the file `student_example1.py`.\n\n##\n\n:::redbox\nSo far, we saw how to create attributes in an object, from outside a class. That is not how we usually create attributes. It was done for demonstration purposes to understand what attributes are.\n:::\n\n## Constructor and `__init__` method\n\n- A constructor in an expression of form `MyClass(arg1, arg2, ...)` which creates an object of class `MyClass`. For example, `Student()` or `Student(\"Reza\", 1234)`\n- In Python, we define a special method named `__init__` (known as initializer method). It is invoked automatically whenever a new object is created using a contructor.\n\n```python\nclass MyClass:\n def __init__(self):\n # do something when the object is being created\n```\n\n##\n\nLet's write an `__init__` method for the `Student` class that takes no arguments (besides `self`) and prints out \"Creating a new student\".\n\n```python\nclass Student:\n \"\"\" Represents a student \"\"\"\n \n def __init__(self):\n print(\"Creating a new student\")\n\n\n# constructor without arguments:\nstudent1 = Student() # __init__ will be called\n```\n```output\nCreating a new student\n```\n\n## Constructor with arguments\n\nThe constructor can have arguments which are typically used to create the attributes and set their initial values.\n\nNow, let's modify the `__init__` method to add more arguments:\n- `name` (string) of the student and their `id_num` (int)\n- Create attributes `name` and `id_num` using `self` and set their values to the respective arguments.\n\nCode available in the file `student_example2.py`.\n\nWhat happens in example above if we do not create attributes in `__init__` ?\n\n## Defining Methods\n\nWe can define methods inside a class using `def` keyword.\n\n:sc[Instance methods] – methods that are associated or bound to instances of a class. \n\n- These methods are called on an instance (object) and they can access attributes specific to that instance.\n\n##\n\n```python\nclass MyClass:\n def my_method(self, argument1, argument2, ..., argumentN):\n # do something \n```\n\nThe first argument of every instance method is always refers to the object on which we are calling the method.\n```python\nobj = MyClass() # Create an instance\n\n# call my_method on obj\nobj.my_method(argument1, argument2, ..., argumentN) \n```\n\nBy convention, the first argument is always named `self`. (`self` is not a keyword! If we use any other name instead of `self`, it would not be an error.)\n\n## Example continued\n\nLet's go back to the Student class:\n\n- Add a method `display_info()` that displays the information of a student i.e. prints the attributes of the instance.\n\nCode available in the file `student_example3.py`.\n\n## \n\n:::div{.ppt-scale-1_25}\n:span[Understanding `self`]{.sans .ppt-f70}\n\u003ciframe width=\"930\" height=\"500\" scrolling=\"no\" style=\"overflow: hidden;\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=class%20Student%3A%0A%20%20%20%20def%20__init__%28self,%20student_name,%20id_num%29%3A%0A%20%20%20%20%20%20%20%20self.name%20%3D%20student_name%0A%20%20%20%20%20%20%20%20self.id_num%20%3D%20id_num%0A%20%20%20%20%0A%20%20%20%20def%20display_info%28self%29%3A%0A%20%20%20%20%20%20%20%20print%28%22Name%20of%20student%3A%22,%20self.name%29%0A%20%20%20%20%20%20%20%20print%28%22Student%20ID%3A%22,%20self.id_num%29%0A%0As1%20%3D%20Student%28%22Reza%22,%2026000%29%0As1.display_info%28%29%0As2%20%3D%20Student%28%22Jane%22,%2026001%29%0As2.display_info%28%29\u0026codeDivHeight=400\u0026codeDivWidth=450\u0026cumulative=false\u0026curInstr=0\u0026heapPrimitives=nevernest\u0026origin=opt-frontend.js\u0026py=3\u0026rawInputLstJSON=%5B%5D\u0026textReferences=false\"\u003e \u003c/iframe\u003e\n:::\n\n\n::divider\n","title":"9.1 — Object Oriented Programming (OOP)","date":"2024-02-26","published":true},{"slug":"Lecture-9.2.md","content":"\n\n## Defining functions/methods with keyword arguments\n\nA keywords argument has a default value in function or method definition.\n\n```python lineno=false margin=\"1em 0\"\ndef func(pos1, pos2, ..., name1=value1, name2=value2, ...):\n```\n\nHere, `pos1`, `pos2`, etc are positional arguments and \n`name1`, `name2`, etc are keyword arguments with default values `value1`, `value2`, respectively.\n\nKeyword arguments cannot appear before positional arguments.\n\n##\n\nIf the function is called without passing a keyword argument, that argument gets its default value.\n\n```python\ndef greet(name, greeting=\"Hello\", num_of_times=1): \n for i in range(num_of_times):\n print(greeting, name)\n\n# try the following one at a time:\n# greet(\"Dev\")\n# greet(\"Dev\", greeting=\"Hi\")\n# greet(\"Dev\", num_of_times=3)\n# greet(\"Dev\", greeting=\"Hi\", num_of_times=3)\n# greet(\"Dev\", num_of_times=3, greeting=\"Hi\")\n```\n\n\n## OOP continued :style{.ppt-f90}\n\n```python\nclass Student:\n \"\"\" Represents a student. \"\"\"\n \n def __init__(self, student_name, id_num):\n self.name = student_name\n self.id_num = id_num\n \n def display_info(self):\n print(\"Name of student:\", self.name)\n print(\"Student ID:\", self.id_num)\n\n\nnew_student = Student(\"Bob\", 260000000)\nnew_student.display_info()\n# Name of student: Bob\n# Student ID: 260000000\n```\n\n##\n\nLet's add a new attribute to store courses and a new method which allows adding a course.\n\n```python\ns1 = Student(\"Robin\", 26005)\ns1.add_course(\"COMP 208\")\ns1.add_course(\"POLI 220\", pass_fail=True)\ns1.add_course(\"MATH 250\")\ns1.display_info()\n```\n\n```output\nName of student: Robin\nStudent ID: 26005\nCourses: COMP 208, POLI 220 (pass/fail), MATH 250\n```\n\n##\n\n- Add an attribute `courses`, initializing it to empty dictionary. This dictionary will store a course name as a key and a boolean value to indicate whether the course is registered as pass/fail.\n- Update `display_info` method to also display a comma-separate list of course names. If there are no courses in the `courses` dictionary, do not display any line for it.\n- Add a method `add_course` that takes as a course name (`str`) and a keyword argument `pass_fail` (default value: `False`) and adds them to the attribute `courses`.\n\nCode available in the file `student_methods.py`.\n\n## Displaying objects: `__str__` method\n\nWhen we display `student1` we see what class the object belongs to, and the identity of the object.\n\n```python\ns1 = Student(\"Dev\", 26001)\nprint(s1)\n```\n```output\n\u003c__main__.Student object at 0x7f8cd66aa890\u003e\n```\n\nWouldn't it be nice to display `name`, `id_num` and other attributes when we do `print(student1)`?\n\n\n## `__str__` method \n\n- We can change the string representation of our class objects by implementing a method called `__str__` in our class.\n\n ```python\n def __str__(self):\n # must return a string\n ```\n\n- If we do that, then when we call `print(obj)` or `str(obj)` with an instance `obj` of our class, `__str__` method is called automatically and the returned string is used.\n\n## Try it!\nIn the `Student` class, add a `__str__` method that returns a string in the following format:\n```\nName: \u003cname attribute\u003e\nStudent ID: \u003cid_num attribute\u003e\nCourses: \u003ccomma-separated courses\u003e\n```\n\nThen, try to use print with an object of Student class.\n\nCode available in the file `student_str.py`.\n\n## Example — List of Student objects\n\n```python\nstudents = [Student(\"Dev\", 260001),\n Student(\"Reza\", 260005)]\n\n# Create a student object and append it to the list\nstudents.append(Student(\"Alice\", 260011))\n\nprint(students[2]) # uses __str__ of Student class\n# Name: Alice\n# Student ID: 260011\n# Courses: None registered.\n```\n\n##\n\n```python\n# Continued from previous slide:\n\nprint(students) # Does not use __str__ of Student class\n# [\u003c__main__.Student object at 0x10ad16100\u003e,\n# \u003c__main__.Student object at 0x10ad169d0\u003e,\n# \u003c__main__.Student object at 0x10ad16a00\u003e]\n\n\nfor s in students:\n print(s) # uses __str__ of Student class\n```\n\n## \n\nTry questions on Ed Lessons.\n\n## More on special methods\n\nhttps://docs.python.org/3/reference/datamodel.html#special-method-names\n\n\"A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names.\"\n\nSee the files `point.py` and `point_tester.py`. \n\n## `zip` function\n\n`zip(x, y)` function creates an iterable of tuples $(x_i, y_i)$ where $x_i$ is element from `x` and $y_i$ is element from `y`.\n\n```python\nx_values = [0.5, -2, 5, 10]\ny_values = [-1.5, 3, -3.5, 20]\n\npoints = list(zip(x_values, y_values))\nprint(points)\n# [(0.5, -1.5), (-2, 3), (5, -3.5), (10, 20)]\n```\n\n##\n\n:::div{.flex}\n```python\nx = [1, 2.5, 5]\ny = [2, 4, 10.5]\n\ntotal = 0\nfor i in range(len(x)):\n total += x[i] * y[i]\n \nprint(total)\n```\n```python\nx = [1, 2.5, 5]\ny = [2, 4, 10.5]\n\ntotal = 0\nfor x, y in zip(x, y):\n total += x * y\n \nprint(total)\n```\n:::\n\n\nWhat happens when one of the argument lists of `zip` is shorter than the other?\n\n## Truth Value Testing\n\nAny object can be tested for truth value, e.g. when used in an if or while condition.\n\n```python\nx = [1, 2, 3]\n\nif x:\n print(\"do something\")\nelse:\n print(\"do other thing\")\n```\n\n##\n\nBy default, an object is considered true \n- unless its class defines either a special `__bool__()` method that returns False or a `__len__()` method that returns zero, when called with the object. \n\nHere are most of the built-in objects considered false:\n\n- Constants defined to be false: `None` and `False`\n- Zeros: `0`, `0.0`\n- empty sequences and collections: `\"\"`, `tuple()`, `[]`, `{}`, `set()`, `range(0)`\n\n##\n\nIncorrect:\n\n```python\nans = input('Are you sure? ')\n\nif ans == 'y' or 'yes':\n print('Installing...')\n```\n\nCorrect:\n\n```python\nans = input('Are you sure? ')\n\nif ans == 'y' or ans == 'yes':\n print('Installing...')\n```\n\n::divider","title":"9.2 — Keyword arguments, More on OOP","date":"2024-02-28","published":true},{"slug":"Lecture-10.1.md","content":"\n## What is NumPy?\n- The core library for scientific computing\n- Provides a new data structure called an \"array\"\n - It is like a list, but more efficient\n - Provides many functions that work with NumPy arrays\n- https://numpy.org\n\n## Installing NumPy\n\n- If you use Thonny, go to Tools -\u003e Manage packages. Type `numpy` in the\nsearch bar and click \"Search on PyPI\". Then click Install.\n- If you do not have Thonny, you can do so by typing the following commands in the terminal:\n ```\n python -m pip install -U pip \n python -m pip install -U numpy \n ```\n\n## What is a NumPy array?\n- A NumPy array is a multidimensional collection/grid of items of same type .\n - Multidimensional: it could be a linear array (like a list e.g. vector) or a 2D array (like a list of lists e.g. matrix), 3D array etc.\n- Unlike a regular Python list,\n - All the values in a NumPy array must have the same type.\n - A NumPy array's size cannot be changed after creation.\n - All such operations that change the size (e.g. adding/removing items) result in a copy of the array.\n\n## Creating an array\n\n```python\nimport numpy as np\n\n# Create a NumPy array from a Python list\narr = np.array([1, 2, 3])\nprint(arr) \n# [1 2 3] \n# note the lack of commas in the output\n\nprint(type(arr)) # \u003cclass 'numpy.ndarray'\u003e\n\n# convert a tuple object to numpy array\nx = np.array((1, 10, 100))\nprint(x) # [ 1 10 100]\n```\n\n##\n\nWe can specify the data type of elements when creating an array.\n\n```python\nimport numpy as np\n\n# these floats will be converted to ints (by truncation)\nx = np.array([1.2, 3.14, 10.65], dtype=int)\nprint(x) # [ 1 3 10]\nprint(x.dtype) # int64\n\n# We can also specify a NumPy-defined data type\nx = np.array([10, 20, 30], dtype=np.float64)\nprint(x) # [10. 20. 30.]\nprint(x.dtype) # float64\n```\n\n## Shape and dimensions of a NumPy array\n\n- The number of dimensions is how many levels of nested arrays there are. e.g. 1D array, 2D array, etc. It can be obtained by accessing the `ndim` attribute.\n- `shape` attribute of a NumPy array is a tuple containing size/length in each dimension.\n\n```python\nimport numpy as np\n\nx = np.array([10, 20, 30])\n\nprint(x.ndim) # 1\nprint(x.shape) # (3,)\n```\n\n##\n\n```python\nimport numpy as np\n\nx = np.array([[10, 20, 30], [40, 50, 60]])\nprint(x)\n# [[10 20 30]\n# [40 50 60]]\n\nprint(x.ndim) # 2\nprint(x.shape) # (2, 3) \u003c-- row, col\n```\n\n## Indexing a NumPy array \n\n```python\nimport numpy as np\n\narr = np.array([2, 4, 8])\nprint(arr[0], arr[1], arr[2]) # 2 4 8\n\n# we can modify existing elements.\narr[0] = 1\nprint(arr) # [1 4 8]\n```\n\n## Other ways to create arrays\n\n```python\nimport numpy as np\n\n# create an array of 0's, with shape (2,).\nx = np.zeros(2)\nprint(x)\n# [0. 0.] \n# dots above mean float values\n\n# create an array of all 1's, with shape (3,),\n# of integer type.\ny = np.ones(3, dtype=int)\nprint(y) # [1 1 1]\n```\n\n##\n\n```python\n# create an array of shape (5,) filled with one value\nx = np.full(5, 7)\nprint(x)\n# [7 7 7 7 7]\n\n# create an array of 4 random values in the interval [0.0, 1.0).\ny = np.random.random(4)\nprint(y)\n[0.70260439 0.68529032 0.59847495 0.88655089]\n```\n\n## Some useful numpy functions\n\n```python\nimport numpy as np\n\n# Similar to the built-in range() function, \n# we can use arguments start, stop and step.\nx = np.arange(10)\nprint(x)\n# [0 1 2 3 4 5 6 7 8 9]\n\n# Unlike range(), float numbers are allowed.\nx = np.arange(10.0, 20.0, 2.5)\nprint(x)\n# [10. 12.5 15. 17.5]\n```\n\n##\n\nWhen we want to create a list of evenly-spaced numbers, it is better to use `np.linspace()` than `np.arange()`.\n\n```python\nimport numpy as np\n\n# Create an array of 5 evenly spaced numbers in interval [0, 1]\nx = np.linspace(0, 1, 5)\nprint(x)\n# [0. 0.25 0.5 0.75 1. ]\n\n# 7 evenly spaced numbers in interval [10, 100]\nx = np.linspace(10, 100, 7)\nprint(x)\n# [ 10. 25. 40. 55. 70. 85. 100.]\n```\n\n## Broadcasting operations\n\n- An arithmetic operation between an array and a scalar (number) is applied to all elements of the array. It is known as :sc[broadcasting].\n- It does not modify the given array; instead a new copy is created.\n\n```python\nimport numpy as np\narr = np.linspace(-1.0, 5.0, 7)\nprint(arr) # [-1. 0. 1. 2. 3. 4. 5.]\n\n# Multiplication is broadcasted to each element.\nprint(arr * 6) # [-6. 0. 6. 12. 18. 24. 30.]\n```\n\n##\n\n```python\n# Unary minus\nprint(-arr)\n# [ 1. -0. -1. -2. -3. -4. -5.]\n\n# Other operators work in same way\nprint(arr / 5)\n# [-0.2 0. 0.2 0.4 0.6 0.8 1. ]\n\nprint(arr + 4)\n# [3. 4. 5. 6. 7. 8. 9.]\n\nprint((arr + 3) * 2)\n# [ 4. 6. 8. 10. 12. 14. 16.]\n```\n\n##\n\nUnlike Python lists, NumPy arrays implement operators such as `*` to perform arithmetic operations.\n\n```python\nimport numpy as np\n\nx = [1, 2, 3] # Python list\ny = x * 5\nprint(y)\n# [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]\n\nx = np.array([1, 2, 3])\ny = x * 5\nprint(y)\n# [ 5 10 15]\n```\n\n##\n\nNumPy also defines math functions that broadcast to all elements in the array.\n\n```python\nimport numpy as np\n\narr = np.linspace(-1.0, 5.0, 7)\n\nprint(np.sin(arr))\n# [-0.841 0. 0.841 0.909 0.141 -0.757 -0.959]\n\nprint(np.exp(arr)) # e^x function\n# [ 0.368 1. 2.718 7.389 20.086 54.598 148.413]\n```\n\n## Vector operations\n\nAn arithmetic operation between arrays is done element-wise. It is also called a :sc[vectorized] operation. \n\n```python\nimport numpy as np\n\na = np.array([34.0, -12.0, 5.0])\nb = np.array([68.0, 5.0, 20.0])\nprint(a + b) # [102. -7. 25.]\nprint(a / b) # [ 0.5 -2.4 0.25]\n```\n\n## Example\n\nWrite code to evaluate the expression below using NumPy: \n$$\ns = \\sum_{k=0}^{100} \\sqrt{\\frac{k \\pi}{100}} sin \\frac{k \\pi}{100}\n$$\n\n##\n\n:::solution\n```python\nimport numpy as np\n\nk = np.arange(0, 101)\nx = k * (np.pi / 100)\ns = np.sum(np.sqrt(x) * np.sin(x))\nprint(s) # 77.51389798916512\n```\n:::\n\n## Comparing the performance of numpy vs pure python solution\n\n:::hgrid\n```python\nimport numpy as np\nimport time\n\nstart = time.time()\n\nN = 10000000\nk = np.arange(0, N+1)\nx = k * (np.pi / N)\ns = np.sum(np.sqrt(x) * np.sin(x))\nprint(s) \n\nprint(\"Time:\", time.time() - start)\n```\n\n```python\nimport math\nimport time\n\nstart = time.time()\n\nN = 10000000\ns = 0\nfor k in range(0, N+1):\n x = k * (math.pi / N)\n s += math.sqrt(x) * math.sin(x)\nprint(s) \n\nprint(\"Time:\", time.time() - start)\n```\n:::\n\n## Slicing NumPy arrays\nSimilar to lists, we can slice a 1D NumPy array.\n```python\nimport numpy as np\n\ny = np.array([0.0, 1.3, 5.0 , 10.9, 18.9, 28.7, 40.0])\nprint(y[1:4]) # print from index 1 until but not including index 4\n# [ 1.3 5. 10.9]\n\nprint(y[::-1]) # reversed copy\n# [40. 28.7 18.9 10.9 5. 1.3 0. ]\n```\n\n## Copying NumPy arrays\n- We can use the `array.copy()` method to get a copy of an array.\n- Changes to the copy will not affect the original array.\n\n:::hgrid\n```python\nimport numpy as np\n\na = np.array([[1, 2, 3], [4, 5, 6]])\n\nb = a.copy()\nb[1][2] = 100\n\nprint(a)\nprint(b)\n```\n\n```output\n[[1 2 3]\n [4 5 6]]\n[[ 1 2 3]\n [ 4 5 100]]\n```\n:::\n\n## Matrix in form of a 2D NumPy array\n\n```python\nimport numpy as np\n\n# convert a list of lists into 2D NumPy array\nm = np.array([[1, 2, 3], [4, 5, 6]])\nprint(m)\n# [[1 2 3]\n# [4 5 6]]\n\n# create an array of all 0's of shape (2, 2).\n# By default, dtype is float64.\nprint(np.zeros((2, 2))) # (2, 2) is a tuple.\n# [[0. 0.]\n# [0. 0.]]\n```\n\n##\n\n```python\nimport numpy as np\n# create an array full of 7's, of shape (2, 3).\nprint(np.full((2, 3), 7))\n# [[7 7 7]\n# [7 7 7]]\n\n# create an array of random values in interval [0, 1).\nprint(np.random.random((2, 2)))\n# [[0.1782372 0.35920979]\n# [0.9368368 0.9005017 ]]\n\n# create an identity matrix of shape (3, 3).\nprint(np.eye(3))\n# [[1. 0. 0.]\n# [0. 1. 0.]\n# [0. 0. 1.]]\n```\n\n## Reshaping arrays\n```python\nimport numpy as np\n\n# We can also create a matrix from a 1D array, using np.reshape()\nc = np.arange(6)\nprint(c) # [0 1 2 3 4 5]\n\n# change the shape to (2, 3).\nd = np.reshape(c, (2, 3))\nprint(d)\n# [[0 1 2]\n# [3 4 5]]\n```\n\n## Indexing a 2D array\nWe can index into a multi-dimensional NumPy array by providing a comma-separated list of the indices.\n\n```python\nimport numpy as np\n\nm = np.array([[1, 2, 3], [4, 5, 6]])\nprint(m.shape) # (2, 3)\n\nprint(m[0, 0]) # 1\nprint(m[0, 1]) # 2\nprint(m[1, 0]) # 4\nprint(m[1, 2]) # 6\n```\n\n## Matrix (2D array) operations\n\n```python\nimport numpy as np\n\n# Broadcasting\nb = np.array([[1, 4, 5], [9, 7, 4]])\nprint(2 + b)\n# [[ 3 6 7]\n# [11 9 6]]\n\nprint(np.sin(b))\n# [[ 0.841 -0.757 -0.959]\n# [ 0.412 0.657 -0.757]]\n```\n\n##\n\n```python\nimport numpy as np\n\n# Element-wise product of matrices\nb = np.array([[1, 4, 5], [9, 7, 4]])\nc = np.array([[0, 1, 2], [3, 4, 5]])\n\n# Note: both matrices must have the same shape\nprint(b * c)\n# [[ 0 4 10]\n# [27 28 20]]\n```\n\n##\n\nTo perform matrix multiplication, we use the dot() function.\n\n```python\nimport numpy as np\n\nb = np.array([[1, 4, 5], [9, 7, 4]])\nd = np.array([[ 4, 2], [ 9, 8], [-3, 6]])\n\n# shapes must be (M, N) dot (N, P) --\u003e (M, P)\nprint(np.dot(b, d))\n# [[25 64]\n# [87 98]]\n```\n\n## Slicing a 2D array\n\n::img{src=\"week10/slicing.png\" style=\"width: 85%; margin: 0.5em auto 0 auto\"}\n:span[(source: https://github.com/ContinuumIO/tutorials/blob/master/NumPy.pdf)]{.ppt-f80}\n\n##\n\n```python\nimport numpy as np\n\na = np.array([[ 1, 2, 3, 4, 5, 6],\n [11, 12, 13, 14, 15, 16],\n [31, 32, 33, 34, 35, 36],\n [41, 42, 43, 44, 45, 46],\n [51, 52, 53, 54, 55, 56],\n [61, 62, 63, 64, 65, 66]])\n\nprint(a[:, 1])\n# [ 2 12 32 42 52 62]\n\nprint(a[::2, ::3])\n# [[ 1 4]\n# [31 34]\n# [51 54]]\n```\n\n##\n\n```python\nimport numpy as np\n\na = np.array([[ 1, 2, 3, 4, 5, 6],\n [11, 12, 13, 14, 15, 16],\n [31, 32, 33, 34, 35, 36],\n [41, 42, 43, 44, 45, 46],\n [51, 52, 53, 54, 55, 56],\n [61, 62, 63, 64, 65, 66]])\n\nprint(a[1, 2:5])\n# [13 14 15]\n\nprint(a[3:5, 4:6])\n# [[45 46]\n# [55 56]]\n```\n\n::divider\n\n","title":"10.1 — NumPy","date":"2024-03-10","published":true},{"slug":"Lecture-10.2.md","content":"\n\n## Copies and views of Numpy array \n\nWhen manipulating arrays, their data may or may not be copied into a new array. Let’s look at different cases.\n\n```python\nx = np.arange(12)\n\n# Just a new name, no data is copied\ny = x\n\nprint(x) # [ 0 1 2 3 4 5 6 7 8 9 10 11]\nprint(y) # [ 0 1 2 3 4 5 6 7 8 9 10 11]\nprint(x is y) # True, as they are same objects\n```\n\n##\n\nWhen we :b[index or slice] a numpy array, the resulting array :b[shares same data] i.e. the result array is a :sc[view] for the original array.\n\n:::hgrid\n```python\nx = np.arange(1, 13).reshape((3, 4))\nprint(x)\n\ny = x[:, 1:3] # x and y share same data\nprint(y)\n\ny[0, 0] = 123 # Changing the shared data\nprint(x)\nprint(y)\n```\n```output\n[[ 1 2 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n[[ 2 3]\n [ 6 7]\n [10 11]]\n[[ 1 123 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n[[123 3]\n [ 6 7]\n [ 10 11]]\n```\n:::\n\n##\n\n`copy()` method of a numpy array can be used to create a new array which :b[does not share data] with the original array. \n\n:::hgrid\n```python\nx = np.arange(1, 13).reshape((3, 4))\nprint(x)\n\ny = x[:, 1:3].copy()\nprint(y)\n\ny[0, 0] = 123 # Changes the copy only\nprint(x)\nprint(y)\n```\n```output\n[[ 1 2 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n[[ 2 3]\n [ 6 7]\n [10 11]]\n[[ 1 2 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n[[123 3]\n [ 6 7]\n [ 10 11]]\n```\n:::\n\n##\n\n`np.array` infers `dtype` based on types of numbers in the argument.\n\n```python\nimport numpy as np\n\nx = np.array([10, 20, 30])\nprint(x) #[10 20 30]\nprint(x.dtype) # int64\n\nx = np.array([1.2, 3.14, 10.15])\nprint(x) #[ 1.2 3.14 10.15]\nprint(x.dtype) # float64\n\nx = np.array([10.0, 20, 30])\nprint(x) #[10. 20. 30.]\nprint(x.dtype) # float64\n```\n\n##\n\n```python\nimport numpy as np\n\n# we can explicity specify what dtype we want:\nx = np.array([10, 20, 30], dtype=float)\nprint(x) #[10. 20. 30.]\nprint(x.dtype) # float64\n\n# float is truncated to int (not rounded)\nx = np.array([1.2, 3.14, 10.65], dtype=int)\nprint(x) #[ 1 3 10]\nprint(x.dtype) # int64\n```\n\n## Different shapes for different purposes \n\n:::hgrid{margin=\"0\" gap=\"1em\"}\n```\n[1.0 5.0 3.5 4.0 5.0 1.2]\n```\nOne point in 6D euclidean space\n:::\n\n:::hgrid{margin=\"0\" gap=\"1em\"}\n```\n[[1.0 5.0 3.5]\n [4.0 5.0 1.2]]\n```\nTwo points in 3D euclidean space\n:::\n\n:::hgrid{margin=\"0\" gap=\"1em\"}\n```\n[[1.0 5.0]\n [3.5 4.0]\n [5.0 1.2]]\n```\nThree points in 2D euclidean space\n:::\n\n##\n\n:::hgrid\n```python\nimport numpy as np\n\nx = np.arange(1, 13) # 1D array\nprint(x)\n\n# 2D array, a 3x4 matrix\nmatrix1 = x.reshape((3, 4))\nprint(matrix1)\n\n# 2D array, a 2x6 matrix\nmatrix2 = matrix1.reshape((2, 6))\nprint(matrix2)\n\n # 1D array\ny = matrix2.reshape((12,))\nprint(y)\n```\n\n```output\n[ 1 2 3 4 5 6 7 \n 8 9 10 11 12]\n\n[[ 1 2 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n\n[[ 1 2 3 4 5 6]\n [ 7 8 9 10 11 12]]\n\n[ 1 2 3 4 5 6 7 \n 8 9 10 11 12]\n```\n:::\n\n## Special values — `numpy.nan`, `numpy.inf`\nNumpy has special values to represent invalid or extreme values that result from numerical computation\n- `numpy.nan` (not a number)\n- `numpy.inf` (infinity)\n\n```python\nimport numpy as np\n\narr = np.array([-1, 0, 1, 2, 3, 4])\n\nprint(np.log(arr))\n#[ nan -inf 0. 0.69314718 1.09861229 1.38629436]\n```\n\n##\n\n```python\n# Any operation on np.nan results in np.nan\nprint(np.nan * 10, 0 / np.nan)\n# nan nan\n\n# Some operations are allowed on np.inf\n\nprint(np.inf, -np.inf)\n# inf -inf\n\nprint(0 / np.inf, 1 / np.inf)\n# 0.0 0.0\n\nprint(np.inf * np.inf, 100 + np.inf)\n# inf inf\n```\n\n## Slicing 2D array\n\n::img{src=\"week10/course_array.png\" style=\"width: 90%; margin: 0 auto\"}\n\n##\n\n```python\nimport numpy as np\n\ngrades = np.array([[94, 84, 97, 79],\n [81, 88, 93, 85],\n [87, 95, 86, 77]])\n\nprint('Grades for student 1, course 2:')\nprint(grades[1, 2]) # 93\n\n\nprint('All grades for student 2:')\nprint(grades[2, :]) # [87 95 86 77]\n\nprint('All grades for course 0:')\nprint(grades[:, 0]) # [94 81 87]\n```\n\n## Assigning values using indices and slices\n\n```python\nimport numpy as np\n\ngrades = np.array([[94, 84, 97, 79],\n [81, 88, 93, 85],\n [87, 95, 86, 77]])\n\n# Setting same value to all indices in the slice\ngrades[0:2, 1:3] = 100\nprint(grades)\n# [[ 94 100 100 79]\n# [ 81 100 100 85]\n# [ 87 95 86 77]]\n```\n\n##\n\n```python\nimport numpy as np\n\ngrades = np.array([[94, 84, 97, 79],\n [81, 88, 93, 85],\n [87, 95, 86, 77]])\n\n# Setting values from array of same shape\ngrades[0:2, 1:3] = np.array([[10, 20], [30, 40]])\nprint(grades)\n# [[94 10 20 79]\n# [81 30 40 85]\n# [87 95 86 77]]\n```\n\n\n## Matplotlib\n\n- Matplotlib is an extensive Python library commonly used to generate different types of plots.\n- To install Matplotlib: if you use Thonny, go to Tools -\u003e Manage packages. Type `matplotlib` on the\nsearch bar and click \"Search on PyPI\". Then click Install.\n- If you do not have Thonny, you can do so by typing the following commands in the terminal:\n ```\n python -m pip install -U pip \n python -m pip install -U matplotlib \n ```\n\n## matplotlib.pyplot\n\n- `matplotlib.pyplot` is a module in the package Matplotlib. \n- This is the module we'll be using to create plots.\n- To use it, we first need to import it\n ```\n import matplotlib.pyplot as plt\n ```\n- For more details: https://matplotlib.org/devdocs/api/pyplot_summary.html\n\n## Example – A Line plot\n\nWe can use the function plot to create a line plot between the points in the input sequence.\n\n:::hgrid\n```python\nimport matplotlib.pyplot as plt\n\nsome_numbers = [3, 1, 5, 2, 9, 3] \nplt.plot(some_numbers)\nplt.show() # display figure\n```\n\n::img{src=\"week10/line1.png\" style=\"width: 100%;\"}\n:::\n\n##\n\nIn the previous example:\n- We provided only one input to the function `plot`.\n- If we do that, then the input values are going to be considered as the y-coordinates. Their corresponding x-coordinates are the indices of the list.\n- In the example, we plot the following points: $(0, 3), (1, 1), (2, 5), (3, 2), (4, 9), (5, 3)$\n\n\n## Example - two inputs\n\n:::div{.flex}\n```python\nimport matplotlib.pyplot as plt\n\nx_coord = range(0, 10, 2)\ny_coord = [0, 0, 9, 8, 2]\nplt.plot(x_coord, y_coord)\nplt.show()\n```\n\n::img{src=\"week10/line2.png\" style=\"width: 50%; margin-left: 2em;\"}\n\n:::\n\n## Example - a linear function\nUsing pyplot, we can plot the graph of the linear function $y = x + 5$.\n\n:::div{.flex}\n```python\nimport matplotlib.pyplot as plt\n\nx_coord = range(15)\ny_coord = [x + 5 for x in x_coord]\n\nplt.plot(x_coord, y_coord)\nplt.show()\n```\n\n::img{src=\"week10/linear.png\" style=\"width: 50%; margin-left: 2em;\"}\n\n:::\n\n## Plot title and axis labels\n\n`plt.title(label)`: takes as argument a string and adds the title label to the figure.\n\n`plt.xlabel(label)`: takes as argument a string and sets the label for the x-axis. \n\n`plt.ylabel(label)`: takes as argument a string and sets the label for the y-axis.\n\nWe can choose the font size of the labels using the keyword argument: `plt.title(\"First plot\", fontsize=22)`\n\n##\n\n:::div{.flex}\n```python\nimport matplotlib.pyplot as plt\n\nx_coord = range(15)\ny_coord = [x + 5 for x in x_coord]\n \nplt.plot(x_coord, y_coord)\n\nplt.title(\"First plot\", fontsize=20)\nplt.xlabel(\"x\", fontsize=14)\nplt.ylabel(\"y = x + 5\", fontsize=14)\n\nplt.show()\n```\n\n::img{src=\"week10/linear2.png\" style=\"width: 50%; margin-left: 1em;\"}\n:::\n\n## Colors, markers and line styles\n\n- We can chose the style/color of the plots, the style/size of the markers, etc. Here is just a taste:\n\n:::hgrid{cols=\"1fr 1fr 1fr\" margin=\"0.5em -2em\" gap=\"0.5em\"}\n::img{src=\"week10/colors.png\" style=\"width: 100%;\" }\n::img{src=\"week10/linestyles.png\" style=\"width: 100%; \"}\n::img{src=\"week10/markers.png\" style=\"width: 100%;\"}\n:::\n\n:span[More info: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html]{.ppt-f80}\n\n\n## Using color, marker and line style\n\n- The function plot can take as arguments one or two lists (for x and y coordinates) and a format string. \n- The format string consists of three parts: `[marker][line][color]`. Each part is optional.\n\n:::div{.flex}\n```python\nimport matplotlib.pyplot as plt\n\nsome_numbers = [3, 1, 5, 2, 9, 3]\n\n# circle marker, dashed line, green\nplt.plot(some_numbers, \"o--g\") \nplt.show() # display figure\n```\n\n::img{src=\"week10/colorplot.png\" style=\"width: 40%; margin-left: 2em;\"}\n\n:::\n\n## \n\n:b[Multiple plots in same figure]{.sans}\n\n:::div{.flex}\n```python\nimport matplotlib.pyplot as plt\nfrom math import sin, cos, radians\n\nx_coord = range(0, 540, 20)\ny_sin = [sin(radians(x)) for x in x_coord]\ny_cos = [cos(radians(x)) for x in x_coord]\n \n# + marker, blue color and use label for legend\nplt.plot(x_coord, y_sin, '+b', label=\"sin(x)\")\n\n# magenta color and use label for legend\nplt.plot(x_coord, y_cos, 'm', label=\"cos(x)\")\nplt.legend() # show legend\nplt.show()\n```\n:::\n\n##\n\n::img{src=\"week10/twoplots.png\" style=\"width: 110%;\"}\n\n## Saving a figure\n\n- `plt.savefig(filename)`: Save the figure in a file (.jpg, .png, etc.)\n\n```python\nimport matplotlib.pyplot as plt\nfrom math import sin, radians\n\nx_coord = range(0, 540, 20)\ny_sin = [sin(radians(x)) for x in x_coord]\n\nplt.plot(x_coord, y_sin, 'm')\n\n# the figure won't be displayed, but saved in y_sin.png \nplt.savefig(\"myplot.png\")\n```\n\n## Bar Plots\n\nWhen working with data that can be broken down into categories, it might be useful for us to use a bar plot instead. \n\n```python\nimport matplotlib.pyplot as plt\n\nmtl_pop = [1293992, 1080545, 1015420, 1016376, 1620693, 1704694]\nyears = ['1966', '1976', '1986', '1996', '2006', '2016']\n\nplt.bar(years, mtl_pop)\n\nplt.title(\"Population of Montreal\")\nplt.show()\n```\n\n##\n\n::img{src=\"week10/barplot.png\" style=\"width: 110%;\"}\n\n## NumPy and Matplotlib\nMatplotlib functions work with NumPy arrays as well.\n\n```python\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nx_values = np.linspace(0, 8, 501)\ny_values = np.sin(x_values ** 2) # computes sin(x^2)\nplt.plot(x_values, y_values)\nplt.show()\n```\n\n::divider\n\n","title":"10.2 — More Numpy, Plotting using Matplotlib","date":"2024-03-11","published":true},{"slug":"Lecture-11.1.md","content":"\n## Numpy arrays and `for` loop\n\n:::hgrid\n```python\nimport numpy as np\n\nx = np.arange(1, 13)\nprint(x)\nprint(x.shape)\n\nfor i in range(x.shape[0]):\n x[i] = x[i] ** 2\n \nprint(x)\nprint(x.shape)\n```\n\n```output\n[ 1 2 3 4 5 6 \n7 8 9 10 11 12]\n(12,)\n[ 1 4 9 16 25 36 \n49 64 81 100 121 144]\n(12,)\n```\n:::\n\n##\n\n:::hgrid\n```python\nimport numpy as np\n\nmatrix = np.arange(1, 13).reshape((3, 4))\nprint(matrix)\nprint(matrix.shape)\n\nfor i in range(matrix.shape[0]):\n for j in range(matrix.shape[1]):\n matrix[i, j] = matrix[i, j] ** 2\n \nprint(matrix)\nprint(matrix.shape)\n```\n\n```output\n[[ 1 2 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n(3, 4)\n[[ 1 4 9 16]\n [ 25 36 49 64]\n [ 81 100 121 144]]\n(3, 4)\n```\n:::\n\n## Comparison operation with NumPy array\n\nWhen comparison operators are used with NumPy arrays, result is an array of boolean values.\n\n```python\nimport numpy as np\n\narr = np.array([10, 11, 12, 13, 14, 15, 16, 17])\nprint(arr % 2 == 0) # produces numpy array of booleans\n# [ True False True False True False True False]\n\narr = np.array([-10, -20, 0, 24, -50, 33, 10])\nprint(arr \u003e 0)\n# [False False False True False True True]\n```\n\n## Using a list/array of booleans as index\n\n```python\nimport numpy as np\n\narr = np.array([10, 11, 12, 13])\nindices = [True, False, False, True]\n# For each i, select arr[i] if indices[i] is True:\nprint(arr[indices]) # [10 13]\n\narr = np.array([10, 11, 12, 13, 14, 15, 16, 17])\nprint(arr[arr % 2 == 0]) # [10 12 14 16]\n\narr = np.array([-10, -20, 0, 24, -50, 33, 10])\nprint(arr[arr \u003e 0]) # [24 33 10]\n```\n\n## Using a list/array of integers as index\n\nMultiple elements of a numpy array can be selected using a list or an array of indices.\n\n```python\nnums = np.arange(10, 100, 10)\n# [10 20 30 40 50 60 70 80 90]\n\nindices = [0, 1, 5]\nprint(nums[indices]) # [10 20 60]\n\n# Output will be in same order as indices\nindices = np.array([2, 8, 5, 1])\nprint(nums[indices]) # [30 90 60 20]\n```\n\n##\n\nIndexing 2D arrays with list/array of indices:\n\n:::hgrid{margin=\"0\"}\n```python\nimport numpy as np\nmatrix = np.arange(1, 13).reshape((3,4))\nprint(matrix)\n\n# Select rows 0 \u0026 2\nrow_indices = [0, 2] \nprint(matrix[row_indices, :])\n\n# Select columns 1 \u0026 2\ncol_indices = [1, 2]\nprint(matrix[:, col_indices])\n\n# Select numbers at (0, 1) and (2, 2)\nprint(matrix[row_indices, col_indices])\n```\n```output\n[[ 1 2 3 4]\n [ 5 6 7 8]\n [ 9 10 11 12]]\n\n[[ 1 2 3 4]\n [ 9 10 11 12]]\n\n[[ 2 3]\n [ 6 7]\n [10 11]]\n\n[ 2 11]\n```\n:::\n\n## Vector and Matrix operations in NumPy\n\n```python\nimport numpy as np\nv = np.arange(10, 70, 10) # 1D array as vector\nprint(v) #[10 20 30 40 50 60]\n\nM = np.arange(10, 100, 10).reshape((3,3)) # 2D array as matrix\nprint(M)\n# [[10 20 30]\n# [40 50 60]\n# [70 80 90]]\n\n# ndarray.size attribute — total number of elements in ndarray\nprint(v.size) # 6\nprint(M.size) # 9\n```\n\n## Stacking new rows/columns into a matrix\n\nSize of new row/col must match that of the matrix row/col.\n\n:::hgrid\n```python\nimport numpy as np\nM = np.arange(1, 7).reshape((2, 3))\nprint(M)\n\nv = np.array([-1, -2])\nM2 = np.column_stack([M, v])\nprint(M2)\n\nw = np.array([5, 7, -1])\nM3 = np.row_stack([M, w])\nprint(M3)\n```\n```output\n[[1 2 3]\n [4 5 6]]\n[[ 1 2 3 -1]\n [ 4 5 6 -2]]\n[[ 1 2 3]\n [ 4 5 6]\n [ 5 7 -1]]\n```\n:::\n\n## Matrix Multiplication\n\n- Interactive explanation for matrix multiplication: https://observablehq.com/@meetamit/matrix-multiplication\n\n- The `@` operator performs matrix multiplication with NumPy arrays. \n - `np.dot` can also be used to do matrix multiplication.\n\n- Recall that `*` operator performs element-wise multiplication.\n\n##\n\n```python\nimport numpy as np\nA = np.arange(10, 70, 10).reshape((3, 2))\nB = np.arange(10, 70, 10).reshape((2, 3))\nprint(A)\n#[[10 20]\n# [30 40]\n# [50 60]]\nprint(B)\n#[[10 20 30]\n# [40 50 60]]\n\nC = A @ B\nprint(C)\n#[[ 900 1200 1500]\n# [1900 2600 3300]\n# [2900 4000 5100]]\n```\n\n##\n\n```python\nimport numpy as np\n\nA = np.array([[83, 10, 39],\n [29, 67, 81]])\nprint(A @ A)\n# ValueError: matmul: Input operand 1 has a mismatch in \n# its core dimension 0, (size 2 is different from 3)\n```\n\n## Example\n\nLet's compute the following\n$$\n \\small\n 2A + AB - 3I\n$$\nwhere given matrices A and B (I is the identity matrix). Shapes of all matrices must match.\n\n##\n\n:::solution\n```python\nimport numpy as np\n\nA = np.array([[1, 5], [-2, 9]])\nB = np.array([[2, 5], [1, -2]])\nI = np.eye(2) # 2x2 identity matrix\n\nresult = 2 * A + A @ B - 3 * I\nprint(result)\n```\n:::\n\n## Powers of a matrix\n\n```python\nimport numpy as np\nfrom numpy.linalg import matrix_power\n\nA = np.array([[1, 5], [-2, 9]])\n\n# Cube of the matrix A\nprint(A @ A @ A)\n#[[-109 405]\n# [-162 539]]\n\n# Another way, using library function:\nprint(matrix_power(A, 3))\n```\n\n\n##\n\n:h1[Random numbers and Monte Carlo methods]{.sans}\n\n\n## Python random module\n\n```python\nimport random\n\nnums = [5, 4, 3, 2, 100, 1]\n\n# Randomly choose one item from mylist\nprint(random.choice(nums)) # 5\n\n# Randomly choose a list of 3 items from the list\n# An element in the list is chosen only once.\nprint(random.sample(nums, 3)) # [4, 5, 100]\n```\n\n\n## Random numbers in NumPy\n\n```python\nimport numpy as np\n\n# All output numbers below will change each time your run the code.\n# But shape of arrays should remain same.\n\nx = np.random.random() # a random float in [0, 1)\nprint(x) # 0.2894424639826647\n\n# array of 5 floats, each value in [0, 1)\nnums = np.random.random(5)\nprint(nums)\n# [0.564407 0.36047169 0.68956636 0.11457356 0.26594372]\n```\n\n##\n\n```python\nimport numpy as np\n\n# 3x2 matrix, each float in [0, 1)\nmatrix = np.random.random((3, 2))\nprint(matrix)\n# [[0.573776 0.37419411]\n# [0.23126045 0.32425979]\n# [0.81373111 0.33039031]]\n\n# random integer in [1, 5)\nx = np.random.randint(1, 5)\nprint(x) # 2\n```\n\n##\n\n```python\nimport numpy as np\n\n# random array of size 7, each integer in [1, 5)\nnums = np.random.randint(1, 5, 7)\nprint(nums) # [4 4 1 3 3 1 4]\n\n# 3x2 matrix, each integer in [1, 10)\nmatrix = np.random.randint(1, 10, (3, 2))\nprint(matrix)\n# [[4 7]\n# [4 6]\n# [3 9]]\n```\n\n##\n\n```python\nimport numpy as np\n\nnums = [5, 4, 3, 2, 100, 1]\n# choose a single number from list\nx = np.random.choice(nums)\nprint(x) # 4\n\n# Choose 10 numbers from the list.\n# Each number is chosen independently, so numbers may repeat.\nmany = np.random.choice(nums, 10)\nprint(many)\n# [3 4 3 3 3 1 5 2 4 2]\n```\n\n\n## Example: throwing an unbiased die \n\n- Let's throw an unbiased six-sided die and count the number of occurrences of 4.\n\n- Unbiased or unweighted means that every integer from 1 to 6 is equally likely to roll. That is, the die is \"fair\".\n\n##\n\n```python\nimport random\n\noptions = [1, 2, 3, 4, 5, 6] # six faces of the die\nnum_rolls = 1000\n\nnum_fours = 0\nfor i in range(num_rolls):\n roll = random.choice(options)\n if roll == 4:\n num_fours += 1\n\nprint(\"Frequency of 4:\", num_fours / num_rolls)\n```\n\n##\n\nLet's do the same using NumPy:\n\n```python\nimport numpy as np\n\noptions = np.arange(1, 7) # six faces of the die\nnum_rolls = 1000\n\nrolls = np.random.choice(options, num_rolls)\nfreq = rolls[rolls == 4].size / num_rolls\n\nprint(\"Frequency of 4:\", freq)\n```\n\n## Example: throwing a biased or weighted die \n\n- Let's throw a biased, or weighted, six-sided die where we favor number 3 over others by giving it more \"weight\" or probability. \n - Such a die would be called \"unfair.\"\n\n##\n\n```python\nimport numpy as np\n\noptions = [1, 2, 3, 4, 5, 6]\n\n# Assign unequal weights or probabilties to each choices\n# Weights must sum to 1.0\nweights = [.1, .1, .5, .1, .1, .1 ]\n\nnum_rolls = 1000\n\nrolls = np.random.choice(options, num_rolls, p=weights)\nfreq = rolls[rolls == 3].size / num_rolls\n\nprint(\"Frequency of 3:\", freq)\n```\n\n## Monte Carlo methods\n\n- A type of algorithm that uses random sampling to solve problems.\n\n- There are Monte Carlo methods for many different problems in physics, chemistry, AI and engineering.\n - https://en.wikipedia.org/wiki/Monte_Carlo_method#Engineering\n\n- We will see a simple case here with a Monte Carlo method for integration.\n\n\n## Finding the area of a complex shape\n\n- Let's say we have a complex shape and we want to find its area.\n\n- We will draw a rectangle around this shape, then sample random points inside the rectangle.\n\n- Then we can estimate the area of the inner shape by multiplying the rectangle's area by the fraction of points that land inside the shape.\n\n$$\n\\small\n\\textup{area}_\\textup{shape} \\approx \\textup{area}_\\textup{rectangle} * \\frac{\\textup{points}_\\textup{inside}}{\\textup{points}_\\textup{inside} + \\textup{points}_\\textup{outside}}\n$$\n\n\n## Monte Carlo integration\n\n- We can use the above idea of finding area to numerically integrate a function.\n\n- For a given function $f(x)$, we define a rectangle that bounds $f(x)$ along X-axis between $[a, b]$ and along Y-axis between $[0, c]$, where \t\t\t\t\t \n $$\n \\small\n c \u003e= \\max_{x \\epsilon (a, b)} f(x)\n $$\n\n- Then we can estimate the integral by randomly choosing points inside the rectangle, and using the formula of area from the previous slide.\n\n##\n\nExample:\n\n- Consider the following function:\n $$\n \\small\n f(x) = \\left | sin(2\\pi x)^5 - 2cos(3cos( \\frac{x}{\\pi} )^2)^3 \\right |\n $$\n\n- We can’t integrate this analytically (i.e. representing it in terms of standard math functions).\n\n- So let's use Monte Carlo integration instead.\n\n##\n\n```python\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n\ndef f(x): # x is numpy array\n y = np.sin(2 * np.pi * x) ** 5\n y -= 2 * np.cos(3 * np.cos(x / np.pi) ** 2) ** 3\n return np.abs(y)\n\n\nx = np.linspace(0, 10, num=1001)\nplt.figure()\nplt.plot(x, f(x))\nplt.show()\n```\n\n##\n\n::img{src=\"week13/function.png\" }\n\n##\n\n- Suppose we want to calculate the definite integral of $f(x)$ in interval $[0, 10]$\n\n- We then choose the rectangle bounds as:\n - $[0, 10]$ along X-axis\n - $[0, 3]$ along Y-axis because from the plot\n $$\n \\small\n 3 \u003e= \\max_{x \\epsilon (0, 10)} f(x)\n $$\n\n- Then, we compute number of points below the function f(x) in the above defined rectangle\n\n- Check `montecarlo_integration.py` for the implementation.\n\n##\n\n::img{src=\"week13/mc_integration.png\"}\n\n##\n\n- There are several methods that can perform Monte Carlo integration.\n- The one we've seen is called the rectangle method, or dart method. \n- It can only be used for simple functions and its accuracy is not very high.\n\n\n::divider\n\n","title":"11.1 — More Numpy, Linear algebra, Random numbers","date":"2024-03-17","published":true},{"slug":"Lecture-11.2.md","content":"\n\n## Motivation \n\n- We often want to analyze data points in the form shown below, where we have some dependent variables $y_i$ corresponding to some independent variables $x_i$.\n\n::::hgrid{margin=\"0\"}\n:::div{style=\"display: inline-grid; grid-template-columns: repeat(5, 2.5em); grid-template-rows: repeat(2, 1fr);border: 1px solid #666666; margin-left: 3em;\"}\n::div[$x_0$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$x_1$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$x_2$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$...$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$x_n$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n\n::div[$y_0$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$y_1$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$y_2$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$...$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n::div[$y_n$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:2.5em; width: 2.5em;\"}\n:::\n::::\n\n- The goal is to find a function $f(x)$ that either\n - passes through all the points exactly (:i[interpolation])\n - approximates data in a \"best\" possible way but not exactly (:i[curve fitting])\n\n##\n\n::img{src=\"week11/interpolation_curve_fitting.png\" style=\"width: 50%; margin: 1.5em 0 0 1em;\"}\n:div[Image from Kiusalaas, chap. 3.]{style=\"font-size: 0.6em; margin: -1em 0 0 1em;\"}\n\n## How interpolation and curve-fitting are useful?\n\n- If we can find a function $f(x)$ that interpolates or approximates the data well,\n - We can try to predict new data points $(x_k, y_k)$.\n - We can better understand the underlying process that might have generated the data.\n\n\n## Polynomial interpolation\n\n- The simplest form of interpolation function is a polynomial function. \n- It is always possible to construct a polynomial $P(x)$ of degree $n$ that passes through $n+1$ data points.\n\n::img{src=\"week11/poly_interpolation.png\" style=\"width: 50%; margin: 1em 0 0 1em;\"}\n\n\n## Lagrange's method\n\n- Suppose we have a dataset of $n+1$ points:\n $$\n (x_0, y_0), (x_1, y_1), (x_2, y_2), ..., (x_n, y_n)\n $$\n\n- We construct an interpolation polynomial of degree $n$, called the Lagrange polynomial, using the formula:\n $$\n P(x) = c_0(x)y_0 + c_1(x)y_1 + ... + c_n(x)y_n\n $$\n\n where $c_i(x)$ are called the :sc[cardinal] functions.\n\n##\n\nThe cardinal functions are given by:\n$$\n \\begin{aligned}\n c_i\\left( x \\right) \u0026= \\frac{(x-x_0)}{(x_i-x_0)} \\cdots \\frac{(x-x_{i-1})}{(x_i-x_{i-1})} \\cdot \\frac{(x-x_{i+1})}{(x_i-x_{i+1})} \\cdots \\frac{(x-x_{n})}{(x_i-x_{n})}\\\\[1em]\n \u0026= \\prod_{\\substack{j = 0 \\\\[.1em] j \\neq i}}^{n} \\frac{(x-x_j)}{(x_i-x_j)}\n \\end{aligned}\n$$\n\n- Numerator contains all terms except $(x - x_i)$\n- Denominator contains all terms except $(x_i - x_i)$\n\n\n## Claim: $P(x)$ passes through each data point\n\nIf $P(x)$ passes through all points $(x_i, y_i)$, then we must have:\n\n$$\n P(x_i) = y_i \n$$\n\nLet's see if this is true for the Lagrange polynomial we just found.\n\n##\n\nIn the above equation for $c_i(x)$, setting $x=x_i$ and $x=x_j$, $i \\ne j$, we have \n\n:::hgrid\n$$\n c_i\\left( x_i \\right) = \\prod_{\\substack{j = 0 \\\\[.1em] j \\neq i}}^{n} \\frac{(x_i-x_j)}{(x_i-x_j)} = 1\n$$\n\nand\n\n$$\n c_i\\left( x_j \\right) = \\prod_{\\substack{j = 0 \\\\[.1em] j \\neq i}}^{n} \\frac{(x_j-x_j)}{(x_i-x_j)} = 0\\ ,\\ j \\neq i\n$$\n:::\n\nNow, using the equation of $P(x)$ for $x=x_i$: \n\n$$\n\\begin{aligned}\nP(x_i) \u0026= c_0(x_i)y_0 \u0026 \u0026+ ... + c_i(x_i)y_i \u0026 \u0026+ ... + c_n(x_i)y_n\n\\\\ \u0026= 0 \u0026 \u0026+ ... + 1\\cdot y_i \u0026 \u0026+ ... + 0\n\\\\ \u0026= y_i\n\\end{aligned}\n$$\n\n\n## Lagrange polynomial — Example\n\nConsider the following dataset. We have $4$ data points so the degree of the Lagrange polynomial $P(x)$ will be $n=3$.\n\n::::hgrid\n:::div{style=\"display: inline-grid; grid-template-columns: repeat(5, 3em); grid-template-rows: repeat(2, 1fr);border: 1px solid #666666; margin-left: 3em;\"}\n::div[$x_i$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em; background-color: #d0d0d0;\"}\n::div[$0$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n::div[$10$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n::div[$20$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n::div[$30$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n\n::div[$y_i$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em; background-color: #d0d0d0;\"}\n::div[$-250$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n::div[$0$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n::div[$50$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n::div[$-100$]{style=\"border: 1px solid #666666; display: flex; align-items: center; justify-content: center; height:3em; width: 3em;\"}\n:::\n::::\n\n##\n\nWe first write $4$ cardinal functions corresponding to these $4$ data points using x-values.\n\n:::div{style=\"font-size: 0.6em\"}\n$$\n\\begin{aligned}\nc_0(x) \\ \u0026= \\ \\frac{(x-10)}{(0-10)} \\cdot \\frac{(x-20)}{(0-20)} \\cdot \\frac{(x-30)}{(0-30)} \\ = \\ \\frac{-1}{6000}x^3 + \\frac{1}{100}x^2 - \\frac{11}{60} x + 1\\\\\nc_1(x) \\ \u0026= \\ \\frac{(x-0)}{(10-0)} \\cdot \\frac{(x-20)}{(10-20)} \\cdot \\frac{(x-30)}{(10-30)} \\ = \\ \\frac{1}{2000}x^3 - \\frac{1}{40}x^2 + \\frac{3}{10} x\\\\\nc_2(x) \\ \u0026= \\ \\frac{(x-0)}{(20-0)} \\cdot \\frac{(x-10)}{(20-10)} \\cdot \\frac{(x-30)}{(20-30)} \\ = \\ \\frac{-1}{2000}x^3 + \\frac{1}{50}x^2 - \\frac{3}{20} x\\\\\nc_3(x) \\ \u0026= \\ \\frac{(x-0)}{(30-0)} \\cdot \\frac{(x-10)}{(30-10)} \\cdot \\frac{(x-20)}{(30-20)} \\ = \\ \\frac{1}{6000}x^3 - \\frac{1}{200}x^2 + \\frac{1}{30} x\n\\end{aligned}\n$$\n:::\n\nWe then use these four cardinal functions along with the y-values $y_i$ in the equation of $P(x)$:\n\n:::div{style=\"font-size: 0.9em\"}\n$$\n\\begin{aligned}\nP(x) \u0026= c_0(x)y_0 + c_1(x)y_1 + ... + c_n(x)y_n \\\\\n \u0026= -x^2 + 35x -250\n\\end{aligned}\n$$\n:::\n\n##\n\nDownload file `lagrange_polynomial.py` from Ed to plot these points and the interpolation function to check if it works.\n\n\n## Curve fitting\n\n- If data is obtained experimentally, there could be random noise present due to measurement errors.\n - Interpolation would not be a good choice as it would fit all data points including noise as well. \n- Instead, we use :i[curve fitting], to find a smooth curve that fits the \"average\" of the data points, and that is less affected by the noise or \"outlier\" points.\n\n\n## \n\n:::hgrid{cols=\"2fr 1fr\"}\n- :b[Linear regression] is a simpler case of curve fitting where a straight line is found that fits a set of data points. \n- Unlike interpolation, the line need not pass through the points.\n\n::img{src=\"week11/linear_regression.png\" style=\"width: 100%\"}\n:::\n\n- Linear regression line is given by $f(x) = ax + b$,\n where $a$ is the slope of the line, and $b$ is the y-intercept.\n- The main task will be to find $a$ and $b$ such that the line \"fits\" the data points in the \"best\" possible way.\n\n\n## How to determine the \"best\" fit?\n\n:::hgrid{cols=\"4fr 3fr\"}\n- To know how good a line fits the given data points, we compute error for each data point.\n\n- :i[Error] is defined as the squared difference between the actual y-value $y_i$ and the y-value given by the regression line, $f(x_i)$.\n\n::img{src=\"week11/best_fit_error.png\" style=\"width: 100%;\"}\n:::\n\n##\n\nThe total error $E$ for all data points is given by\n$$\n \\begin{aligned}\n E(a, b) \u0026= \\sum_{i=0}^{n} \\left ( y_i - f\\left ( x_i \\right ) \\right ) ^ 2 \\\\\n \u0026= \\sum_{i=0}^{n} \\left ( y_i - \\left ( ax_i + b \\right ) \\right ) ^ 2\n \\end{aligned}\n$$\n\n##\n\nOne way to define \"best\" fit:\n- Given all the data points $(x_i, y_i)$, we want to find a line (i.e. find $a$ and $b$) such that the error $E(a, b)$ is minimized.\n\nTo find $a$ and $b$ that minimize $E(a, b)$, we calculate the partial derivatives of $E$ with respect to $a$ and $b$ and solve for a and b when derivates are zero:\n$$\n \\frac{\\partial E}{\\partial a} = 0,\\, \\, \\, \\, \\, \\frac{\\partial E}{\\partial b} = 0\n$$\n\n\n## Calculating the partial derivatives\n\n:::div{style=\"font-size: 0.8em\"}\n$$\n\\frac{\\partial E}{\\partial a} = \\sum_{i=0}^{n} 2 (y_i - (ax_i + b)) (-x_i)\n$$\n\n$$\n\\frac{\\partial E}{\\partial b} = \\sum_{i=0}^{n} 2 (y_i - (ax_i + b))\n$$\n:::\n\n\nSetting above derivatives to $0$ and solving for $a$ and $b$,\n\n::::div{style=\"font-size: 0.8em\"}\n:::hgrid{margin=\"0\"}\n$$\na = \\frac{\\sum (x_i - \\bar{x}) (y_i - \\bar{y}) } {\\sum (x_i - \\bar{x})^2 }\n$$\n\n$$\nb = \\bar{y} - a\\bar{x}\n$$\n:::\n::::\n\nwhere $\\bar{x}$ and $\\bar{y}$ are the averages of x-values and y-values of data points.\n\n##\n\nFinally, using these $a$ and $b$, we get the desired best-fit line as $f(x)=ax+b$. Here $a$ is slope of the line and $b$ is the y-intercept.\n\nDownload `linear_regression.py` from Ed, which plots data and regression line.\n\n\n## Interpolation examples using Numpy/SciPy\n\n- `numpy_linear_interpolation.py`: Piecewise linear interpolation.\n- `cubic_spline_interpolation.py`: Cubic spline interpolation — piecewise polynomial interpolation where each piece is a cubic polynomial ($a+ b x + c x^2 + d x^3$, $d \\ne 0$)\n\n\n## Definite Integral\n\n- The definite integral of a function of a single variable, $f(x)$, between two limits $a$ and $b$ can be viewed as the area $S$ under the curve.\n\n- Numerical integration algorithms try to estimate this area\n\n::img{src=\"week12/area_curve.png\" style=\"width: 30%; margin: 1em auto;\"}\n\n\n## Numerical Integration\n\nOur approach will be as follows:\n- Divide the region between $a$ and $b$ into $N$ segments\n- We then estimate the area under the curve in each segment \n- Finally, we sum these areas\n\nWe will consider three algorithms for estimating this area in each segment:\n- The Midpoint method\n- The Trapezoidal method\n- Simpson's method\n\n\n## Example\n\nWe will use the following function in the interval $[0, 5]$ to compare the three algorithms:\n\n::img{src=\"week12/example.png\" style=\"width: 50%; margin: 1em auto;\"}\n\n\n## The Midpoint Method\n\n::img{src=\"week12/midpoint_10.png\" style=\"width: 75%; margin: 1em 0 0 1em;\"}\n\n##\n\n- We first divide the region from $a$ to $b$ into $N$ equal rectangular segments \n\n- The width of each segment is $\\Delta x = \\frac{b-a}{N}$\n\n- The endpoint of the segments are\n $$\n \\small\n x_0 = a \u003c x_1 \u003c \\cdots \u003c x_{N-1} \u003c x_{N} = b\n $$\n\t\n where\n\n $$\n \\small\n x_i = a + i \\cdot \\Delta x, \\quad i \\in [0, N]\n $$ \n\n##\n\n- We estimate the area under the curve in each rectangular segment using the value of $f(x)$ at the midpoint of each segment.\n $$\n \\small\n area_k = \\Delta x \\cdot f(x_k + \\frac{\\Delta x}{2})\\ ,\\ k \\in [0, N)\n $$\n\n- To compute an approximation to the integral, we just have to sum these areas\n\n##\n\nApproximation improves as the number of segments is increased.\n\n::img{src=\"week12/midpoint_20.png\" style=\"width: 75%; margin: 1em auto;\"}\n\n\n## Implementation and Error analysis of Midpoint method\n\n```python\ndef midpoint_integrate(f, a, b, N):\n area = 0.0\n dx = (b - a) / N\n x = a\n \n for k in range(N):\n area += dx * f(x + dx / 2)\n x += dx\n \n return area\n```\n\n##\n\nWe know the integral of the example function is given by:\n$$\n \\small\n \\int_{0}^{5} \\frac{1}{1+x^2} dx = arctan(5) = 1.37340076695\n$$\n \nSo, we can compute error in the result produced by our implementation `midpoint_integrate`.\n\n##\n\nError of Midpoint method for different values of $N$:\n\n```output\n N estimated_integral error\n----- -------------------- ----------------\n 10 1.3735434283e+00 1.4266136666e-04\n 20 1.3734392602e+00 3.8493263529e-05\n 40 1.3734103959e+00 9.6289197895e-06\n 80 1.3734031745e+00 2.4075766312e-06\n 160 1.3734013689e+00 6.0191232953e-07\n 320 1.3734009174e+00 1.5047571300e-07\n 640 1.3734008046e+00 3.7615273785e-08\n 1280 1.3734007764e+00 9.4000809359e-09\n 2560 1.3734007693e+00 2.3462840559e-09\n 5120 1.3734007675e+00 5.8283222693e-10\n10240 1.3734007671e+00 1.4196355203e-10\n```\n\n## Trapezoidal Method\n\n::img{src=\"week12/trapezoid_area.png\" style=\"width: 60%; margin: 1em auto;\"}\n\nTo approximate the area of each segment, use the area of trapezoid rather than the rectangle.\n\n##\n \n::img{src=\"week12/trapezoid_4.png\" style=\"width: 75%; margin: 1em auto;\"}\n\nThe area of each trapezoidal segment is given by\n$$\n \\small\n area_k = \\frac{\\Delta x}{2} \\left( f(x_k) + f(x_k + \\Delta x) \\right)\\ ,\\ k \\in [0, N)\n$$\n\n##\n\n::img{src=\"week12/trapezoid_10.png\" style=\"width: 100%; margin: 1em auto;\"}\n\n\n## \n\nImplementation and error analysis of the Trapezoidal method\n\n```python lineno=false\narea += dx * (f(x) + f(x + dx)) / 2\n```\n\n```output\n N estimated_integral error\n----- -------------------- ----------------\n 10 1.3731040812e+00 2.9668571989e-04\n 20 1.3733237548e+00 7.7012176612e-05\n 40 1.3733815075e+00 1.9259456542e-05\n 80 1.3733959517e+00 4.8152683754e-06\n 160 1.3733995631e+00 1.2038458712e-06\n 320 1.3734004660e+00 3.0096677106e-07\n 640 1.3734006917e+00 7.5245528253e-08\n 1280 1.3734007481e+00 1.8815126790e-08\n 2560 1.3734007622e+00 4.7075219278e-09\n 5120 1.3734007658e+00 1.1806169375e-09\n10240 1.3734007667e+00 2.9889868358e-10\n```\n\n## Simpson's method\n\nGiven any three points there is a unique polynomial (parabola), called the interpolating polynomial, that passes through these points\n\n::img{src=\"week12/simpsons.png\" style=\"width: 40%; margin: 0.5em auto;\"}\n\n##\n\n- Simpson's method fits a parabola $P(x)$ through the curve at three points — the value of the function at the two endpoints, and at the midpoint of the interval:\n $$\n \\small\n f(x_k),\\ f(x_k + \\frac{\\Delta x}{2}),\\ f(x_k + \\Delta x)\n $$\n\n\n- The area under the parabola in the interval $[x_k, x_k + \\Delta x]$ is given by:\n $$\n \\small\n \\frac{\\Delta x}{6} \\left[f(x_k) + 4f(x_k + \\frac{\\Delta x}{2}) + f(x_k + \\Delta x)\\right]\n $$\n\n- Adding areas of all the $N$ segments gives an approximation to the integral.\n\n##\n\n- Simpson's method generally finds a better approximation to the area under the curve in each segment than trapezoidal method which uses a line instead of parabola.\n\n- For more details on where does the formula above come from: https://en.wikipedia.org/wiki/Simpson%27s_rule\n\n\n## \n\nImplementation and error analysis of the Simpson's method\n\n```python lineno=false\narea += dx * (f(x) + 4 * f(x + dx / 2) + f(x + dx)) / 6\n```\n\n```output\n N estimated_integral error\n----- -------------------- ----------------\n 10 1.3733969793e+00 3.7876621872e-06\n 20 1.3734007584e+00 8.5498519375e-09\n 40 1.3734007664e+00 5.3898707719e-10\n 80 1.3734007669e+00 3.8371528177e-11\n 160 1.3734007669e+00 7.0714545330e-12\n 320 1.3734007669e+00 5.1121329392e-12\n 640 1.3734007669e+00 4.9893422727e-12\n 1280 1.3734007669e+00 4.9857895590e-12\n 2560 1.3734007669e+00 4.9855675144e-12\n 5120 1.3734007669e+00 4.9795723100e-12\n10240 1.3734007669e+00 4.9829029791e-12\n```\n\n## \n\nCheck implementation and example in `numerical_integration.py` and `integration_examples.py`\n\n## Accuracy of the three methods\n\n- For all the three methods we saw, as the number of subintervals $N$ approaches infinity, approximation improves and approaches actual integral.\n\n- However, the speed of improvement differs:\n - Midpoint method — $error \\propto \\frac{1}{N^2}$\n - Trapezoidal method — $error \\propto \\frac{1}{N^2}$\n - Simpson’s method — $error \\propto \\frac{1}{N^4}$\n\n- Simpson's method approaches the integral faster than other methods.\n\n\n## Integration using scipy.integrate \n\nCheck `scipy_integrate.py` file on Ed.\n\n\n::divider\n\n","title":"11.2 — Interpolation, Curve fitting, Numerical Integration","date":"2024-03-19","published":true},{"slug":"Lecture-12.1.md","content":"\n##\n\nA :b[system of linear equations] is a collection of one or more linear equations: \n\n$$\n\\small\n\\begin{aligned}\na_{0,0}x_0 + a_{0,1}x_2 + \\cdots + a_{0,n}x_n \u0026 = b_0 \\\\\na_{1,0}x_0 + a_{1,1}x_2 + \\cdots + a_{1,n}x_n \u0026 = b_1 \\\\\n\u0026 \\vdots \\\\\na_{m,0}x_0 + a_{m,1}x_2 + \\cdots + a_{m,n}x_n \u0026 = b_m \\\\\n\\end{aligned}\n$$\n\nIn matrix notation, the linear system is given by $\\mathbf{Ax = b}$, where\n\n$$\n\\small\nA = \\begin{bmatrix}\na_{0,0} \u0026 a_{0,1} \u0026 \\cdots \u0026 a_{0,n} \\\\\na_{1,0} \u0026 a_{1,1} \u0026 \\cdots \u0026 a_{1,n} \\\\\n\\vdots \u0026 \u0026 \u0026 \\vdots \\\\\na_{m,0} \u0026 a_{m,1} \u0026 \\cdots \u0026 a_{m,n} \\\\\n\\end{bmatrix}\n \\ \\ , \\ \\\n\\mathbf{x} = \\begin{bmatrix}\nx_0 \\\\ x_1 \\\\ \\vdots \\\\ x_n\n\\end{bmatrix}\n \\ \\ , \\ \\\n\\mathbf{b} = \\begin{bmatrix}\nb_0 \\\\ b_1 \\\\ \\vdots \\\\ b_m\n\\end{bmatrix}\n$$\n\n\n## Gauss-Jordan elimination to solve linear system\n\nHigh-level overview of Gauss-Jordan elimination:\n\n1. Given the matrices $\\mathbf{A}$ and $\\mathbf{b}$ of the linear system $\\mathbf{Ax = b}$ we obtain augment matrix $\\mathbf{M = [A | b]}$ by stacking $\\mathbf{b}$ as the last column to the matrix $\\mathbf{A}$.\n $$\n \\small\n M = \\left[\n \\begin{array}{cccc|c}\n a_{0,0} \u0026 a_{0,1} \u0026 \\cdots \u0026 a_{0,n} \u0026 b_0 \\\\\n a_{1,0} \u0026 a_{1,1} \u0026 \\cdots \u0026 a_{1,n} \u0026 b_1 \\\\\n \\vdots \u0026 \u0026 \u0026 \\vdots \u0026 \\vdots \\\\\n a_{m,0} \u0026 a_{m,1} \u0026 \\cdots \u0026 a_{m,n} \u0026 b_m \\\\\n \\end{array}\n \\right]\n $$\n\n##\n\n2. Using elementary row operations, convert the augmented matrix $M$ into reduced row-echelon form (RREF). \n - In this form, the solution is simply the last column of the matrix $M$ (if there is a unique solution). For example,\n :::div{.flex style=\"align-items: center;\"}\n $$\n \\small\n \\left[\n \\begin{array}{ccc|c}\n 0 \u0026 8 \u0026 4 \u0026 2 \\\\\n 2 \u0026 5 \u0026 1 \u0026 5 \\\\\n 4 \u0026 10 \u0026 -1 \u0026 1 \\\\\n \\end{array}\n \\right]\n $$\n $ \\implies $\n $$\n \\small\n \\left[\n \\begin{array}{ccc|c}\n 1 \u0026 0 \u0026 0 \u0026 4.125 \\\\\n 0 \u0026 1 \u0026 0 \u0026 -1.25 \\\\\n 0 \u0026 0 \u0026 1 \u0026 3. \\\\\n \\end{array}\n \\right]\n $$\n :::\n\n\n## Reduced row-echelon form\n\nA matrix is in reduced row-echelon form (RREF) if it satisfies all of the following conditions.\n\n- In every row the leftmost non-zero entry is $1$ (called the leading $1$).\n\n- If a column contains a leading $1$ (i.e. first non-zero entry in the column is $1$), then all other entries in that column is $0$.\n\n- The leading $1$ of any given row is always to the right of the leading $1$ of the row above it.\n\n- Any rows consisting entirely of zeroes are placed at the bottom of the matrix.\n\n##\n\nExamples:\n\n$$\n\\small\nA = \\begin{bmatrix} 1 \u0026 0 \u0026 1 \u0026 0\\\\ 0 \u0026 1 \u0026 0 \u0026 2 \\end{bmatrix} \\quad , \\quad B = \\begin{bmatrix} 1 \u0026 0 \u0026 0\\\\ 0 \u0026 1 \u0026 0 \\\\ 0 \u0026 0 \u0026 1 \\end{bmatrix}\n$$\n\n$$\n\\small\nC = \\begin{bmatrix} 1 \u00260 \\\\ 0 \u0026 1\\\\ 0 \u0026 0\\\\ 0 \u0026 0\\\\ 0 \u0026 0 \\end{bmatrix} \\quad , \\quad D = \\begin{bmatrix} 1 \u0026 0 \u0026 0 \u0026 4 \u0026 0 \u0026 0 \\\\ 0 \u0026 1 \u0026 0 \u0026 3 \u0026 0 \u0026 0\\\\ 0 \u0026 0 \u0026 1 \u0026 3 \u0026 1 \u0026 0 \\\\ 0 \u0026 0 \u0026 0 \u0026 0 \u0026 0 \u0026 1 \\end{bmatrix}\n$$\n\n\n## Elementary row operations\n\nWe use the following operations to convert a matrix M into RREF:\n\n1. :b[Row Swap.] Exchange any two rows: $M[i] \\leftrightarrow M[j]$\n\n2. :b[Scalar Multiplication.] Multiply any row by a non-zero constant: \n $M[i] \\gets k \\cdot M[i] , k \\neq 0$\n\n3. :b[Row Addition.] Add multiple of one row to another row: \n $M[i] \\gets M[i] + k \\cdot M[j] , i \\neq j$\n\n## \n\nDownload file `elementary_row_ops.py` from Ed.\n\n## Gauss-Jordan Elimination algorithm using NumPy\n\nCheck [this link](https://en.wikipedia.org/wiki/Gaussian_elimination#Example_of_the_algorithm) to understand how the algorithm works.\n\nCheck the program `gaussjordan.py` for an implementation of the algorithm. (Just for understanding; not for exam). \n- Based roughly on the pseudocode for ToReducedRowEchelonForm in https://rosettacode.org/wiki/Reduced_row_echelon_form\n\n## Using scipy to solve linear systems\n\n```python\nimport numpy as np\nimport scipy.linalg as la\n\nA = np.array([[0, 8, 4], [2, 5, 1], [4, 10, -1]], dtype=float)\nb = np.array([2, 5, 1], dtype=float)\n\nresult = la.solve(A, b)\nprint(result)\n# [ 4.125 -1.25 3. ]\n```\n\n","title":"12.1 — Using SciPy, System of Linear Equations","date":"2024-03-25","published":true},{"slug":"Lecture-12.2.md","content":"\n\n## Roots of a function\n- A root or zero of a function $f(x)$ is a solution of the equation $f(x)=0$\n\n- Some functions can be solved in closed form i.e., their solutions can be represented in terms of functions that we know.\n - $f(x) = x^2 - 1$ can be factored to $(x-1)(x+1)$, with the solution of $f(x) = 0$ being $x = -1, 1$\n\n- For the functions that cannot be factored or represented in closed form, we must use numerical methods to find their roots. \n - See [this post](https://math.stackexchange.com/questions/9199/what-does-closed-form-solution-usually-mean/9203#9203) for a discussion on \"closed form\".\n\n\n##\n\n- In general, finding roots numerically involves a procedure which generates a sequence of approximations $x_1, x_2, ..., x_n$ until we obtain a value close to the actual root.\n\n- Note that we will focus on finding the real roots to functions, not the complex roots\n\n\n## \n\n:b[Single and double roots]{.sans}\n\n::img{src=\"week11/single_double_roots.png\" style=\"width: 80%; background-color: white; margin: 0.7em auto;\"}\n\n- The equation $f(x)=0$ has a single root for each x-intercept i.e. each time the function $f(x)$ crosses X-axis\n- Double roots (two equal roots) occur when $f(x)$ touches but does not cross the X-axis\n\n##\n\nFor example, $f(x) = x^2$ has a double root at $x=0$\n\n::img{src=\"week11/square_func.png\" style=\"width: 70%; margin: 0.7em auto;\"}\n\n## Methods of finding roots\n\n- All the methods we will see require an initial estimate of the root — either a single number as an estimate or an estimated interval containing the root.\n\n- Few different ways of estimating an initial root value or interval:\n - If the equation is associated with a physical problem its physical context may suggest the estimate.\n - A search can be carried out for estimated root values.\n - The function could be plotted (a visual procedure).\n\n##\n\nWe will see the following methods in this lecture:\n- Bisection method\n- Secant method\n- Newton-Raphson method\n\n\n## Intermediate value theorem\n\n:::hgrid\n::img{src=\"week11/ivt.png\" style=\"width: 100%; background-color: white;\"}\n\n(We will assume that all functions are continuous for root finding algorithms.)\n:::\n\n- Let $f(x)$ be a continuous function on the interval $[a, b]$ and let $s$ be a number where $f(a) \u003c s \u003c f(b)$, then there exists at least one $x$ with $f(x) = s$.\n\n##\n\n:::hgrid\n::img{src=\"week11/ivt.png\" style=\"width: 100%; background-color: white;\"}\n\nWe will use Intermediate value theorem in some of the root-finding methods.\n:::\n\n- The main idea is that \n - If $f(a)$ and $f(b)$ have opposite signs, e.g. $f(a) \u003c 0 \u003c f(b)$, then there is at least one $x$ in the interval $[a, b]$ such that $f(x) = 0$ i.e. there exists a root in $[a, b]$\n- Note that since a double root does not lead to a change in sign, it won't be detectable using this method.\n\n\n## Checking if an interval contains a root\n\nGiven a function $f(x)$ which is continuous in the interval $[a, b]$, if $f(a)$ and $f(b)$ have opposite signs, we have a root in $[a, b]$\n\n```python\ndef f(x):\n return x ** 2 - 25\n\na1, b1 = [-4.0, 3.0]\nprint('Is root in [-4.0, 3.0]?', f(a1) * f(b1) \u003c 0)\n# False\n\na2, b2 = [3.0, 10.0]\nprint('Is root in [3.0, 10.0]?', f(a2) * f(b2) \u003c 0)\n# True\n```\n\n\n## Example\n\n::img{src=\"week11/tan_func.png\" style=\"width: 90%; margin: 0 auto;\"}\n\n##\n\nIn the previous graph,\n- Since we have $tan$ function, which is not continuous, there are sign changes at discontinuities, e.g. in interval $[1, 2]$. \n- So the theorem doesn't work here and we can't use the root finding methods in the interval $[1, 2]$.\n- But we can find root in the interval $[6, 7]$.\n\n\n## Bisection method\n\n- It is the simplest root finding algorithm which can be applied to any continuous function $f(x)$ on an interval $[a, b]$ where a root exists i.e. $f(a)$ and $f(b)$ have different signs.\n\n- Therefore, the method relies on Intermediate value theorem.\n\n- The general idea is to repeat the following\n - Divide the interval in two subintervals, one of which must contain the root.\n - Select the subinterval $[a_n, b_n]$ where $f(x)$ changes sign i.e. $f(a_n)·f(b_n) \u003c 0$\n\n##\n\n:sc[Algorithm]\n\n1. Choose an interval $[a_0, b_0]$ where sign of $f(x)$ changes i.e. $f(a_0) \\cdot f(b_0) \u003c 0$\n\n2. Compute $f(m_0)$ for the midpoint $m_0 = \\frac{a_0 + b_0}{2}$\n3. Determine the next subinterval $[a_1, b_1]$ as follows:\n - If $f(a_0) \\cdot f(m_0) \u003c 0$, then $[a_1, b_1] = [a_0, m_0]$\n - If $f(m_0) \\cdot f(b_0) \u003c 0$, then $[a_1, b_1] = [m_0, b_0]$\n4. Repeat steps (2) and (3) until the interval $[a_n, b_n]$ is small enough, i.e. its size is less than some value $\\epsilon$\n5. Return the midpoint value $m_n = \\frac{a_n + b_n}{2}$\n\n##\n\nTo visualize this algorithm download `visualize_bisection.py` from Ed and run it.\n\n##\n\n:sc[Python implementation]\n\nCheck `bisection` function in `rootfinding.py` file.\n\n\n## \n\n:sc[Example]\n\n$$\nf(x) = x^4 - 6.4x^3 + 6.45x^2 + 20.538x - 31.752\n$$\n\n::img{src=\"week11/example_poly.png\" style=\"width: 60%; margin: 0.5em auto;\"}\n\nThere is a double root at $x=2$ and a single root at $x=4$.\n\n##\n\nCheck Bisection method example in `rootfinding_example.py` file.\n\n\n## Secant Method\n\n- A :sc[secant line] is a straight line joining at least two points on a function.\n\n::img{src=\"week11/secant.png\" style=\"width: 40%; background-color: white; padding: 1em; margin: 1em auto;\"}\n\n##\n\n- The [secant method](https://en.wikipedia.org/wiki/Secant_method) is very similar to the bisection method.\n\n- Instead of choosing the midpoint, the secant method divides each interval $[a, b]$ by the x-intercept of the secant line connecting the endpoints $(a, f(a))$ and $(b, f(b))$. \n\n- However, unlike Bisection method, Secant method does not require that a root be present in the interval $[a, b]$. So the Secant method may not converge to any number.\n\n\n## Formula for Secant line\n\n- Let $f(x)$ be a continuous function on a closed interval $[a, b]$ such that $f(a) \\cdot f(b) \u003c 0$\n\n- The secant line connecting the endpoints $(a, f(a))$ and $(b, f(b))$ is given by\n $$\n y = \\frac{f(b) - f(a)}{b - a}(x - a) + f(a)\n $$\n\n- By setting $y=0$ in above equation, we get the x-intercept of secant line as follows:\n $$\n x = a - f(a) \\frac{b - a}{f(b) - f(a)}\n $$\n\n\n## :style{.ppt-f90}\n\n:b[Algorithm for Secant method]{.sans}\n\n1. Choose an interval $[a_0, b_0]$ where sign of $f(x)$ changes i.e. $f(a_0) \\cdot f(b_0) \u003c 0$.\n2. Compute $f(x_0)$ where $x_0$ is the x-intercept of secant line between $(a_0, f(a_0))$ and $(b_0, f(b_0))$ given by\n $$\n x_0 = a_0 - f(a_0) \\frac{b_0 - a_0}{f(b_0) - f(a_0)}\n $$\n3. Determine the next subinterval $[a_1, b_1]$ as follows:\n - If $f(a_0) \\cdot f(x_0) \u003c 0$, then $[a_1, b_1] = [a_0, x_0]$\n - If $f(x_0) \\cdot f(b_0) \u003c 0$, then $[a_1, b_1] = [x_0, b_0]$\n4. Repeat steps (2) and (3) until the interval $[a_n, b_n]$ is small enough or some fixed number of steps have been taken.\n5. Return the value $x_n$, the x-intercept of $n^{th}$ subinterval.\n\n##\n\nTo visualize this algorithm download `visualize_secant.py` from Ed and run it.\n\n##\n\n:sc[Python implementation]\n\nCheck `secant` function in `rootfinding.py` file.\n\nCheck Secant method example in `rootfinding_example.py` file.\n\n## Newton-Raphson method\n- Simple, fast, and best-known method of finding roots.\n\n- It uses derivative of the function $f(x)$ i.e. tangent line.\n - So, it can only be used if the function is differentiable in the given interval.\n\n - If it is costly to compute the derivative, Newton's method will be slow.\n\n- It doesn't need an interval as an initial estimate, but only one estimate value $x_0$. \n\n##\n\n- We start with an approximation of the root, $x_0$ and compute successive approximations as follows:\n $$\n x_n = x_{n-1} - \\frac{f(x_{n-1})}{f'(x_{n-1})}\n $$\n\n- The above formula comes from [Taylor series approximation](https://en.wikipedia.org/wiki/Taylor_series) of $f(x_n)$ given by:\n $$\n f(x_n) \\approx f(x_{n-1}) + f'(x_{n-1})(x_n - x_{n-1})\n $$\n - Setting $f(x_n)$ to $0$ and solving for $x_n$ would give the above formula for $x_n$.\n\n- This method may not converge. To prevent an infinite loop, we use a fixed number of iterations, and stop if we do not find a root.\n\n##\n\nTo visualize this algorithm download `visualize_newton.py` from Ed and run it.\n\n##\n\n:sc[Python implementation]\n\nCheck `newton_raphson` function in `rootfinding.py` file.\n\nFor Newton-Raphson we also need the first derivative of the function. For the exampl we have,\n\n$$\nf(x) = x^4 - 6.4x^3 + 6.45x^2 + 20.538x - 31.752\n$$\n\n$$\nf'(x) = 4 x^3 - 19.2 x^2 + 12.9 x + 20.538\n$$\n\nCheck the example in `rootfinding_example.py` file.\n\n## Newton-Raphson divergence examples\n\n::img{src=\"week11/newton_divergence.png\" style=\"width: 90%; margin: 1.5em 0 0 1em;\"}\n\nAlthough the Newton–Raphson method converges fast near the root, its global convergence characteristics are poor.\n\n\n## :style{.ppt-f90}\n\n:::div{style=\"margin: 0 -3em;\"}\n| Method | Assumptions | Convergence | Can find Double roots? |\n|----------------|----------------------------------------------------------------|-------------------------------------------------------------------------|------------------------------|\n| Bisection | $f$ is continuous, root exists in the initial interval | Yes | No |\n| Secant | $f$ is continuous, root need not exist in the initial interval | Not guaranteed but will converge if root is close to initial interval. | Not guaranteed. |\n| Newton-Raphson | $f$ is continuous and differentiable | Not guaranteed, but will converge if initial estimate is close to root. | Yes but may converge slowly. |\n\nSpeed of convergence (single root): Newton-Raphson \u003e Secant \u003e Bisection\n:::\n\n\n\n\n## Root-finding using Scipy\n\nCheck the file `scipy_rootfinding.py`.\n\n\n\n::divider\n\n","title":"12.2 — Root Finding, Sorting algorithms","date":"2024-04-01","published":true},{"slug":"Lecture-13.1.md","content":"\n\n## Binary search algorithm\n\nWe have seen examples which perform linear search using a loop:\n\n```python\ndef linear_search(sequence, target):\n for index in range(len(sequence)):\n if sequence[index] == target:\n return index\n\n return -1 # Not found\n```\n\n##\n\n- Binary search is a faster algorithm to search an item in a sequence, provided the sequence is :i[sorted].\n- Binary search is similar to looking up a word in an English dictionary. Suppose we are looking for the word \"doctor\"\n - We flip pages in the dictionary to find the \"d\" section but we may end up a little further to the right, say at \"f\" section.\n - Then we flip pages to the left and may end up at \"da\" section.\n - Then we flip pages to the right towards \"do\" and so on...\n - At each step, we decrease the number of pages to search.\n - The process works because the dictionary is sorted in alphabetical order.\n\n\n##\n\n:b[Visualize the binary search algorithm]{.sans}\n\n\u003ciframe width=\"900\" height=\"600\" frameborder=\"0\" style=\"background-color: white;\"\n src=\"https://observablehq.com/embed/@d3vp/binary-search-algorithm?cells=view%2Cviewof+sequence%2Cviewof+item_to_search%2Cbuttons\"\u003e\u003c/iframe\u003e\n\n\n## \n\n:b[Implementation]{.sans}\n\n```python\ndef binary_search(sequence, target):\n low = 0\n high = len(sequence) - 1\n\n while low \u003c= high:\n middle = (low + high) // 2 # floor division\n\n if sequence[middle] \u003c target:\n low = middle + 1\n elif sequence[middle] \u003e target:\n high = middle -1\n else:\n return middle\n\n return -1 # Not found\n```\n\n##\n\n- In general, if the length of sequence is $N$\n - Linear search takes time proportional to $N$\n - Binary search takes time proportional to $log(N)$\n\n\n## Sorting algorithms\n\n- Sorting algorithms sort a sequence into ascending or descending order.\n\n ::::pre{.code}\n [1, 3, 2, 0] → [0, 1, 2, 3]\n [‘a’, ‘c’, ‘b’, ‘d’] → [‘a’, ‘b’, ‘c’, ‘d’]\n ::::\n\n- There are many sorting algorithms which have different speed and computer memory requirements.\n\n- We will only cover two — Selection sort and Insertion sort\n\n\n## Selection sort algorithm and implementation\n\nVisualize algorithm: https://visualgo.net/en/sorting\n\n\n##\n\n```python\ndef selection_sort(seq):\n N = len(seq)\n \n for i in range(N):\n # Assume that element at index i is minimum\n min_index = i\n \n # Find minimum of unsorted elements on right of i\n for k in range(i+1, N):\n if seq[k] \u003c= seq[min_index]:\n min_index = k\n\n # Swap elements at i and min_index\n temp = seq[i]\n seq[i] = seq[min_index]\n seq[min_index] = temp\n```\n\n##\n\nRun `selectionsort.py` to see the sequence after each iteration.\n\n\n## Insertion sort algorithm and implementation\n\nVisualize algorithm: https://visualgo.net/en/sorting\n\n##\n\nFirst let's look at a single step when we have a partially sorted list of size $k$ and want to move an element to have a bigger partially sorted list of size $k+1$.\n\n##\n\n```python\nsequence = [9, 22, 51, 63, 10, 79, 60, 75] # partially sorted list\n\ni = 4\nkey = sequence[i] # 10\n\n# Elements on the left of key are sorted.\n# We want to insert key on the left to keep the partial list sorted.\n# Shift elements one place to the right if they are greater than key. \nj = i - 1\nwhile(j \u003e= 0 and sequence[j] \u003e key):\n sequence[j+1] = sequence[j]\n j = j - 1\n\n# Moving key to index j+1\nsequence[j+1] = key\nprint(sequence) # [9, 10, 22, 51, 63, 79, 60, 75]\n```\n\n##\n\nNow, we can look at the final insertion sort implementation:\n\n```python\ndef insertion_sort(seq):\n N = len(seq)\n \n for i in range(1, N):\n key = seq[i]\n \n # Elements on the left of key are already sorted.\n # Shift them one place to the right if they are greater than key.\n j = i - 1\n while(j \u003e= 0 and seq[j] \u003e key):\n seq[j+1] = seq[j]\n j = j - 1\n \n # After shifting right, index j+1 is now available for key\n seq[j+1] = key\n```\n\n##\n\nRun `insertionsort.py` to see the sequence after each iteration.\n\n## Iterables\n\nhttps://docs.python.org/3/glossary.html#term-iterable\n\nAn object capable of returning its members one at a time. \n- all sequence types (such as list, str, and tuple) \n- some non-sequence types like dict, file objects\n- objects of any class that implements `__iter__()` or `__getitem__()` method.\n\nIterables can be used in a for loop and in many other places where a sequence is needed (`zip()`, `map()`, etc.). \n\n##\n\n- When an iterable object is passed as an argument to the built-in function `iter()`, it returns an :sc[iterator] for the object. \n- This iterator is good for one pass over the set of values.\n- Repeated calls to the iterator's `__next__()` method (or passing it to the built-in function next()) return successive items in the stream.\n- When no more data are available a `StopIteration` exception is raised instead by `__next__()` method.\n\n##\n\n```python\n\u003e\u003e\u003e rng = range(1, 6, 2)\n\u003e\u003e\u003e it = iter(rng) # create iterator\n\u003e\u003e\u003e next(it)\n1\n\u003e\u003e\u003e next(it)\n3\n\u003e\u003e\u003e next(it)\n5\n\u003e\u003e\u003e next(it)\nTraceback (most recent call last):\n File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\nStopIteration\n```\n\n##\n\n- When using iterables, it is usually not necessary to call `iter()` or deal with iterator objects ourselves. \n- The `for` statement does that automatically for us, creating a temporary variable to hold the iterator for the duration of the loop.\n\n:::hgrid\n```python\nfor x in some_iterable:\n do_something(x)\n```\n\n```python\nit = iter(some_iterable)\nwhile True:\n try:\n x = next(it)\n do_something(x)\n except StopIteration:\n break\n```\n:::\n\n## Our simple `range` implementation\n\n:::hgrid\n```python\nclass MyRange: \n # assume step will be positive\n def __init__(self, start, stop, step):\n self.start = start\n self.stop = stop\n self.step = step\n self.value = start\n \n def nextValue(self):\n value = self.value\n if value \u003c self.stop:\n self.value += self.step\n return value\n```\n```python\nrng = MyRange(1, 8, 2)\nwhile True:\n val = rng.nextValue()\n if not val:\n break\n \n print(val)\n```\n:::\n\n##\n\n:::hgrid\n```python\nclass MyRange: \n # assume step will be positive\n def __init__(self, start, stop, step):\n self.start = start\n self.stop = stop\n self.step = step\n self.value = start\n \n def nextValue(self):\n value = self.value\n if value \u003c self.stop:\n self.value += self.step\n return value\n raise StopIteration()\n```\n```python \nrng = MyRange(1, 8, 2)\nwhile True:\n try:\n val = rng.nextValue()\n print(val)\n except StopIteration:\n break\n```\n:::\n\n##\n\n:::hgrid\n```python\nclass MyRange: \n # assume step will be positive\n def __init__(self, start, stop, step):\n self.start = start\n self.stop = stop\n self.step = step\n self.value = start\n \n def __iter__(self):\n return self\n \n def __next__(self):\n value = self.value\n if value \u003c self.stop:\n self.value += self.step\n return value\n raise StopIteration()\n```\n```python \nrng = MyRange(1, 8, 2)\nfor val in rng:\n print(val)\n```\n:::\n\n##\n\n```python\nimport random\n\nclass RandomIter:\n def __init__(self, num):\n self.num = num\n self.index = 0\n \n def __iter__(self):\n return self\n \n def __next__(self):\n if self.index \u003c self.num:\n self.index += 1\n return random.random()\n raise StopIteration()\n \n \nfor val in RandomIter(4):\n print(val)\n\n```\n\n\n## Generators using `yield` statement\n\n```python\ndef myrange(start, stop, step):\n value = start\n \n while True:\n if value \u003e= stop:\n break\n \n yield value\n value += step\n\n\nfor x in myrange(1, 8, 2):\n print(x)\n```\n\n##\n\n```python\nimport random\n\n\ndef random_iter(num):\n for i in range(num):\n yield random.random()\n\n\nfor x in random_iter(5):\n print(x)\n \n\nprint(list(random_iter(4)))\n```\n\n## Functions are first-class citizens\n\nFunctions can be used in same way as any other Python objects:\n\n- Functions can be passed function as arguments\n- Functions can be created inside other functions\n- A function can be returned from other function.\n\n## Useful examples of passing functions as arguments\n\n```python\npoints = [(1, 1, 3), (4, 10, 9), (7, 4, 11)]\n\n\ndef key_func(p):\n return p[1], p[0], p[2]\n\n\nprint(max(points, key=key_func))\n\nprint(sorted(points, key=key_func))\n```\n\n## lambda\n\nAn anonymous inline function consisting of a single expression which is evaluated when the function is called. \n\nThe syntax to create a lambda function is \n```python\nf = lambda param1, param2, ...: some_expression\n```\n\nEquivalent to\n\n```python\ndef f(param1, param2, ...):\n return some_expression\n```\n\n##\n\n```python\ndiagnostic_frequencies = {'pharyngitis': 1,\n 'meningitis': 1,\n 'food_poisoning': 2}\n\nmaxKey = max(diagnostic_frequencies,\n key=lambda k: diagnostic_frequencies[k])\nprint(maxKey)\n\nmaxKey = max(diagnostic_frequencies,\n key=diagnostic_frequencies.get)\nprint(maxKey)\n```\n\n##\n\n```python\nwords = [(\"happy\", 0.7), (\"sad\", -0.9),\n (\"fun\", 0.58), (\"enemy\", -0.4)]\n\nwords_sorted = sorted(words, key=lambda tup: tup[1])\nprint(words_sorted)\n```\n\n## Built-in `map` function\n\n`map(func, iterable)` applies a function `func` to every item of an iterable, yielding the results.\n\nIn very simplified terms, `map()` does something like:\n```python\ndef map(func, iterable):\n for item in iterable:\n yield func(item)\n```\n\nExamples:\n```python\nx = [\"23\", \"3.14\", \"1.61\"]\ny = list(map(float, x))\nprint(y)\n```\n\n##\n```python\ninventory = {\"sofa\": 10, \"chair\": 5, \"lamp\": 3}\nnew_inventory = dict(map(lambda item: (item[0].upper(), item[1]+10),\n inventory.items()))\nprint(new_inventory)\n```\n\n```python\npersons = [['music', 'running', 'reading'],\n ['movies', 'boardgames'],\n ['boardgames', 'running', 'hiking']]\n\nnewlist = list(map(set, persons))\nprint(newlist)\n```\n\n## Other built-in functions that work with iterables\n\n`filter(func, iterable)`: yields items from `iterable` for which `func(item)` is `True`. Equivalent to:\n```python\ndef filter(func, iterable):\n for item in iterable:\n if func(item):\n yield item\n```\n\n```python\nnums = [-2, 10, -5, 7]\npositive = list(filter(lambda x: x \u003e 0, nums))\nprint(positive) # [10, 7]\n```\n##\n\n`all(iterable)`: Return `True` if all items of the `iterable` are true (or if the iterable is empty). Equivalent to:\n```python\ndef all(iterable):\n for item in iterable:\n if not item:\n return False\n return True\n```\n\n```python\nnums = [2, 4, 6, 8]\nprint(all([x % 2 == 0 for x in nums])) # True\n\nnums = [1, 4, 6, 8]\nprint(all([x % 2 == 0 for x in nums])) # False\n```\n\n##\n\n`any(iterable)`: Return `True` if any item of the iterable is true. If the iterable is empty, return `False`. Equivalent to:\n```python\ndef any(iterable):\n for item in iterable:\n if item:\n return True\n return False\n```\n```python\nnums = [2, 3, 5, 7]\nprint(any([x % 2 == 0 for x in nums])) # True\n\nnums = [1, 3, 5, 7]\nprint(any([x % 2 == 0 for x in nums])) # False\n```\n\n## Inner/Nested functions\n\n- Functions can be defined inside other functions. \n- This is useful to create helper functions that are not used outside of a function — Encapsulates (hides) inner functions.\n\n```python\ndef main_function():\n def helper(params):\n print(\"do something\", params)\n\n helper(\"hello\")\n helper(123)\n\n\nmain_function()\nhelper()\n```\n\n## Closure\n\nAn enclosed (i.e. inner/nested) function has access to local variables and parameters of outer (enclosing) function.\n\n:::hgrid\n```python\ndef create_quadratic(a, b, c):\n def quadratic(x):\n return a * x ** 2 + b * x + c\n\n return quadratic\n\n# q1 and q2 are functions:\nq1 = create_quadratic(1, 3, 10)\nq2 = create_quadratic(-5, -1, 0)\n```\n```python\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nx = np.linspace(-20, 20, 1000)\nplt.plot(x, q1(x), \"r\")\nplt.plot(x, q2(x), \"b\")\nplt.show()\n```\n:::\n\n## Last lecture next week\n\n- Examples using SciPy\n- Final exam overview\n- Practice problems from past exams\n\n\n\n::divider\n\n","title":"13.1 — Sorting \u0026 Misc. topics","date":"2024-04-02","published":true}]},"__N_SSG":true},"page":"/comp208-notes","query":{},"buildId":"-JQHz_cEzCXhwJZEcxZPK","isFallback":false,"gsp":true,"scriptLoader":[]}</script></body></html>