[{"data":1,"prerenderedAt":1296},["ShallowReactive",2],{"article_list_educational_":3},[4,352],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"excerpt":11,"image":12,"publishDate":13,"tags":14,"body":17,"_type":343,"_id":344,"_source":345,"_file":346,"_stem":347,"_extension":348,"author":349},"/alalande/2023-2/heuristics","2023-2",false,"","Thinking Like a Programmer: Heuristics","We want to help you turbo-charge your decision making.","Hiring good people is hard.","/alalande/2023-2/img/heuristics.png","2024-03-15",[15,16],"series","educational",{"type":18,"children":19,"toc":332},"root",[20,27,40,45,50,55,62,74,79,86,91,96,101,106,111,117,122,127,132,137,149,154,160,165,177,182,202,207,212,218,223,228,233,238,250,263,268,273,282,295,300,306,311,316],{"type":21,"tag":22,"props":23,"children":24},"element","p",{},[25],{"type":26,"value":11},"text",{"type":21,"tag":22,"props":28,"children":29},{},[30,32,38],{"type":26,"value":31},"No, actually, hiring is easy. It's ",{"type":21,"tag":33,"props":34,"children":35},"em",{},[36],{"type":26,"value":37},"finding",{"type":26,"value":39}," the right people for the job that's hard.",{"type":21,"tag":22,"props":41,"children":42},{},[43],{"type":26,"value":44},"Suppose you are the hiring manager in charge of filling an open position. You have written an exciting and enticing job ad, posted it in all of the right places, and now, you sit in front of a stack of 50 resumes. In the early stage of the hiring process, your aim was to cast as wide a net as possible, in hopes of finding the absolute best fit. You've done that, and now you find yourself at the inflection point, where you need to work in the opposite direction, and narrow the field of consideration down to one.",{"type":21,"tag":22,"props":46,"children":47},{},[48],{"type":26,"value":49},"In an ideal world, you would hire each of these applicants for a year, and see who does the job best, like some reality show competition. Realistically, however, you don't have time to interview 50 people, let alone coach them through a first year on the job.",{"type":21,"tag":22,"props":51,"children":52},{},[53],{"type":26,"value":54},"What you -- and most people -- would do to speed up the process is to define some criteria that would let you start sorting the applicants into \"will\" and \"won't\" interview piles. What you need are heuristics.",{"type":21,"tag":56,"props":57,"children":59},"h2",{"id":58},"two-kinds-of-heuristics",[60],{"type":26,"value":61},"Two Kinds of Heuristics",{"type":21,"tag":22,"props":63,"children":64},{},[65,67,72],{"type":26,"value":66},"The word ",{"type":21,"tag":33,"props":68,"children":69},{},[70],{"type":26,"value":71},"heuristics",{"type":26,"value":73}," (hyoo-RIS-tics) refers to a kind of filter that comes before a difficult or laborious decision, and which reduces the options by quickly disqualifying those that seem like a waste of time or energy. Using a heuristic, you can efficiently narrow down the field of choices, and spend more time meeting with and getting to know the candidates who seem most promising.",{"type":21,"tag":22,"props":75,"children":76},{},[77],{"type":26,"value":78},"How can you tell which applicants to immediately discard? That question lies at the heart of this discussion, and to address it we need to talk about two different kinds of heuristics.",{"type":21,"tag":80,"props":81,"children":83},"h3",{"id":82},"loosecasual-heuristics",[84],{"type":26,"value":85},"Loose/Casual Heuristics",{"type":21,"tag":22,"props":87,"children":88},{},[89],{"type":26,"value":90},"Think of all of the decisions that consumers make at the grocery store. Few consumers make decisions based solely on objective product details (e.g. price, expiration dates, quantities, nutritional details, etc.). Most consumers also include subjective criteria, and might therefore opt for the brand of eggs with the cuddly mascot on the carton, or pay for the more expensive brand of razors because of the catchy jingle in their ads.",{"type":21,"tag":22,"props":92,"children":93},{},[94],{"type":26,"value":95},"Most of the time, humans make decisions using fuzzy, ill-defined emotional judgments, not clear, strictly-defined objective criteria. For that reason, sorting \"good\" choices from \"bad\" choices tends to be highly subjective, and that's why we talk first about casual heuristics.",{"type":21,"tag":22,"props":97,"children":98},{},[99],{"type":26,"value":100},"Returning to the interviewing scenario, there are countless heuristics you could use to quickly discard the \"bad\" candidates. You could start by tossing the obviously-unprofessional resumes: those with spelling mistakes, those with bad grammar or swearing, those written in fanciful fonts, or those written by hand, in pink, on the back of a Big Mac wrapper.",{"type":21,"tag":22,"props":102,"children":103},{},[104],{"type":26,"value":105},"This is your first heuristic, call it the Unprofessional Heuristic. You are effectively saying that the ideal candidate for the job will already be fully versed in the codes of professionalism that you expect from job-seekers.",{"type":21,"tag":22,"props":107,"children":108},{},[109],{"type":26,"value":110},"This is a \"loose\" heuristic, because you can see it as a way to cut corners. You may be right 99.9% of the time, that an unprofessional-looking resume is indicative of a candidate who would not fit the corporate culture, but you can probably also picture a Hollywood-worthy scenario where a prodigious but eccentric savant is disqualified from a job he could do in his sleep, because of the Unprofessional Heuristic. Hollywood notwithstanding, the Unprofessional Heuristic is a useful way of reducing the candidate pool to a manageable size.",{"type":21,"tag":80,"props":112,"children":114},{"id":113},"stricttechnical-heuristics",[115],{"type":26,"value":116},"Strict/Technical Heuristics",{"type":21,"tag":22,"props":118,"children":119},{},[120],{"type":26,"value":121},"On the other hand, certain problems -- especially in the quantitative and scientific disciplines -- come with an \"objective\" definition of what makes a bad or a good candidate.",{"type":21,"tag":22,"props":123,"children":124},{},[125],{"type":26,"value":126},"To illustrate this, consider the problem of verifying if a number is prime or not. Determining whether a small number is prime is easy, but as numbers get increasingly long, the difficulty of the problem increases. In cryptography, for instance, knowing whether very large numbers (containing hundreds of digits) are prime or not is critical to keeping information secure. Finding ways to make the prime/composite determination about large numbers faster and more efficient is essential to the widespread use of cryptography.",{"type":21,"tag":22,"props":128,"children":129},{},[130],{"type":26,"value":131},"Heuristics can help greatly accelerate the process. If you spot a number greater than 2 that ends in an even digit (0, 2, 4, 6, or 8), you can quickly tell that it is divisible by 2, and therefore could not possibly be prime. The beauty of this approach is that no matter how many digits a number has, looking to see if the last digit is even is always going to take the exact same amount of time.",{"type":21,"tag":22,"props":133,"children":134},{},[135],{"type":26,"value":136},"We'll call this the Obviously-Even Heuristic. It alone saves you from checking a whopping 50 percent of candidates!",{"type":21,"tag":22,"props":138,"children":139},{},[140,142,147],{"type":26,"value":141},"Unlike the Unprofessionalism Heuristic above (which was a casual/loose type of heuristic), this one will never be wrong. This is the key difference between the two types of heuristics: technical heuristics must never, ",{"type":21,"tag":33,"props":143,"children":144},{},[145],{"type":26,"value":146},"ever",{"type":26,"value":148}," disqualify a good candidate.",{"type":21,"tag":22,"props":150,"children":151},{},[152],{"type":26,"value":153},"Whereas a \"loose\" heuristic is like a \"rule of thumb\", a \"strict\" heuristic is more like a hint in a puzzle: the hint will never be wrong, but it won't tell you the answer either. A hint will narrow down your range of choices and provide a condition which must necessarily be true in order to find the answer.",{"type":21,"tag":80,"props":155,"children":157},{"id":156},"necessary-not-sufficient",[158],{"type":26,"value":159},"Necessary, not Sufficient",{"type":21,"tag":22,"props":161,"children":162},{},[163],{"type":26,"value":164},"Technical heuristics ensure that they never disqualify a good candidate by isolating a necessary but not solely-sufficient condition that can be easily tested (like the presence of an even number as the last digit for numbers greater than 2).",{"type":21,"tag":22,"props":166,"children":167},{},[168,170,175],{"type":26,"value":169},"Notice that a number ",{"type":21,"tag":33,"props":171,"children":172},{},[173],{"type":26,"value":174},"not",{"type":26,"value":176}," ending in an even digit does not guarantee it to be prime. A number like 25 does not end in an even digit, but is not prime, either.",{"type":21,"tag":22,"props":178,"children":179},{},[180],{"type":26,"value":181},"Like sifting pans with holes that get progressively smaller down the stack, you could pile heuristics on top of one another to reduce the number of candidates even further, e.g.:",{"type":21,"tag":183,"props":184,"children":185},"ol",{},[186,192,197],{"type":21,"tag":187,"props":188,"children":189},"li",{},[190],{"type":26,"value":191},"First, we apply our Obviously-Even Heuristic,",{"type":21,"tag":187,"props":193,"children":194},{},[195],{"type":26,"value":196},"Then, we apply the Obviously-Divisible-by-Five Heuristic, which will filter out any candidate greater than 5 that ends in 5 or 0.",{"type":21,"tag":187,"props":198,"children":199},{},[200],{"type":26,"value":201},"Finally, we'll apply the Obviously-Divisible-by-Three Heuristic, which will filter out any candidate greater than 3 whose digits add up to a multiple of 3.",{"type":21,"tag":22,"props":203,"children":204},{},[205],{"type":26,"value":206},"A number like 8 will get filtered out by the first heuristic, a number like 15 will be filtered out by the second one, and a number like 63 will be filtered out by the third.",{"type":21,"tag":22,"props":208,"children":209},{},[210],{"type":26,"value":211},"If a number manages to get through all three filters, it has managed to satisfy three necessary conditions of primacy, but that is still not enough, not sufficient. The number still needs to be tested for being prime or not. Heuristics don't help speed up the final test, they just ensure that you don't waste time running it on numbers like 9,375,292,394, which are clearly not prime.",{"type":21,"tag":56,"props":213,"children":215},{"id":214},"programming-with-costs",[216],{"type":26,"value":217},"Programming with Costs",{"type":21,"tag":22,"props":219,"children":220},{},[221],{"type":26,"value":222},"This entire discussion has been about the cost of making decisions. These costs come in the form of time, money, effort, or any other scarce resource (e.g. computing power, hard disk space, bandwidth).",{"type":21,"tag":22,"props":224,"children":225},{},[226],{"type":26,"value":227},"Casual Heuristics sacrifice some of the accuracy of the decision-making process to gain time. Technical Heuristics find necessary but insufficient conditions that can be tested at low cost.",{"type":21,"tag":22,"props":229,"children":230},{},[231],{"type":26,"value":232},"A skillful programmer should always bear the question of costs in mind while writing code. Sending a request out over the internet may take a lot of time, and should be considered much more expensive than retrieving the same information from the hard drive, or, even better, from an in-memory cache.",{"type":21,"tag":22,"props":234,"children":235},{},[236],{"type":26,"value":237},"When combined with a language feature called \"short circuiting\", it becomes very easy to implement technical heuristics in code.",{"type":21,"tag":239,"props":240,"children":244},"pre",{"className":241,"code":243,"language":26},[242],"language-text","#!/usr/bin/env python3\n\nfrom .heuristics import (\n   # Runs quickly, only checks the last digit\n   obviously_even_heuristic,\n\n   # Runs quickly, only checks the last digit\n   obviously_divisible_by_five_heuristic,\n\n   # Runs a tiny bit slower, requires summing up\n   # all of the digits in a number recursively\n   obviously_divisible_by_three_heuristic,\n)\n\nfrom .deciders import (\n   # Very expensive to run on large numbers\n   is_number_prime\n)\n\n\ndef determine_if_prime(n):\n   return (\n      not obviously_even_heuristic(n) and\n      not obviously_divisible_by_five_heuristic(n) and\n      not obviously_divisible_by_three_heuristic(n) and\n      is_number_prime(n)\n   )\n",[245],{"type":21,"tag":246,"props":247,"children":248},"code",{"__ignoreMap":8},[249],{"type":26,"value":243},{"type":21,"tag":22,"props":251,"children":252},{},[253,255,261],{"type":26,"value":254},"\"Short circuiting\" means that in the ",{"type":21,"tag":246,"props":256,"children":258},{"className":257},[],[259],{"type":26,"value":260},"return",{"type":26,"value":262}," statement at the end of the code above, the second condition will not be evaluated if the first one is false, the third will not be evaluated if the second one is false, and so on. In essence, with the \"and\" operator, Python is smart enough to know that it can stop evaluating the expression if the end result cannot possibly be true.",{"type":21,"tag":22,"props":264,"children":265},{},[266],{"type":26,"value":267},"(Short-circuiting is essentially a technical heuristic built into the programming language itself!)",{"type":21,"tag":22,"props":269,"children":270},{},[271],{"type":26,"value":272},"This behavior could also be used with unrelated tests using the \"or\" operator, as in this excerpt:",{"type":21,"tag":239,"props":274,"children":277},{"className":275,"code":276,"language":26},[242],"def transfer_money(amount, from_account, to_account):\n   if (\n      amount > 10000 or\n      from_account.is_frozen() or\n      to_account.is_frozen() or\n      account_is_on_government_watchlist(from_account) or\n      account_is_on_government_watchlist(to_account)\n    ):\n      raise TransferDeniedException\n    \n    # proceed with the rest of the transfer code here\n    ...\n",[278],{"type":21,"tag":246,"props":279,"children":280},{"__ignoreMap":8},[281],{"type":26,"value":276},{"type":21,"tag":22,"props":283,"children":284},{},[285,287,293],{"type":26,"value":286},"Before we proceed to the bulk of the work inside of this ",{"type":21,"tag":246,"props":288,"children":290},{"className":289},[],[291],{"type":26,"value":292},"transfer_money()",{"type":26,"value":294}," function, we first test whether this transfer raises any red flags. We do this with the \"or\" operator, which means that as soon as any one of these conditions is true, we stop testing the rest.",{"type":21,"tag":22,"props":296,"children":297},{},[298],{"type":26,"value":299},"This is why we start with a very inexpensive test (whether the amount being transferred exceeds $10,000), then proceed with a bit more expensive tests (we query our own database to see if the accounts are frozen), and finally, we perform the most expensive tests (querying a government system).",{"type":21,"tag":56,"props":301,"children":303},{"id":302},"conclusion",[304],{"type":26,"value":305},"Conclusion",{"type":21,"tag":22,"props":307,"children":308},{},[309],{"type":26,"value":310},"Technical heuristics are a very useful tool for any programmer who wishes to build at scale. A well-crafted technical heuristic may permit the same program to run faster, on smaller devices, across more users simultaneously, etc.",{"type":21,"tag":22,"props":312,"children":313},{},[314],{"type":26,"value":315},"More broadly, programming while keeping in mind the costs associated with each statement is a good habit to adopt. Higher resource usage implies higher fragility as well, as there is more that can go wrong. All other things being equal, code that uses fewer resources is necessarily less fragile.",{"type":21,"tag":22,"props":317,"children":318},{},[319,321,330],{"type":26,"value":320},"If you'd like to get started experimenting with heuristics in a hands-on way, there is no better place to start than ",{"type":21,"tag":322,"props":323,"children":327},"a",{"href":324,"rel":325},"https://projecteuler.net/",[326],"nofollow",[328],{"type":26,"value":329},"Project Euler",{"type":26,"value":331},". There, you will find hundreds of puzzles involving number theory and geometry. For each one, you must write a piece of code that, when run, will find the answer in under one minute. You will quickly discover that each puzzle has a \"naive\" solution that will require hours, days, or even months of computing time to find. The art in solving these puzzles lies in finding the right set of heuristics to speed up your code.",{"title":8,"searchDepth":333,"depth":333,"links":334},3,[335,341,342],{"id":58,"depth":336,"text":61,"children":337},2,[338,339,340],{"id":82,"depth":333,"text":85},{"id":113,"depth":333,"text":116},{"id":156,"depth":333,"text":159},{"id":214,"depth":336,"text":217},{"id":302,"depth":336,"text":305},"markdown","content:alalande:2023-2:heuristics.md","content","alalande/2023-2/heuristics.md","alalande/2023-2/heuristics","md",{"user":350,"name":351},"alalande","Anthony Lalande",{"_path":353,"_dir":354,"_draft":7,"_partial":7,"_locale":8,"title":355,"description":356,"excerpt":357,"image":358,"publishDate":359,"tags":360,"body":361,"_type":343,"_id":1292,"_source":345,"_file":1293,"_stem":1294,"_extension":348,"author":1295},"/alalande/2023-1/escaping_text","2023-1","Thinking Like a Programmer: Escaping Text","In this progressive and vivid explanation, we explore the HOW and WHY of quoting, nesting, and escaping text.","Spend enough time around a software engineer, and you might hear something about escaping text. This is not a wish to avoid text, or replace it with other media (images, animated GIFs, movies, etc.).","/alalande/2023-1/img/header.png","2024-02-15",[15,16],{"type":18,"children":362,"toc":1284},[363,375,394,400,419,440,445,501,506,514,540,552,574,587,599,604,626,632,637,642,653,658,663,668,674,686,698,710,729,734,743,748,766,771,776,781,787,792,815,820,832,841,846,893,905,917,929,934,941,946,952,962,967,977,995,1014,1019,1079,1084,1089,1096,1101,1117,1122,1141,1146,1155,1160,1220,1225,1231,1236,1254,1259,1273,1278],{"type":21,"tag":22,"props":364,"children":365},{},[366,368,373],{"type":26,"value":367},"Spend enough time around a software engineer, and you might hear something about ",{"type":21,"tag":33,"props":369,"children":370},{},[371],{"type":26,"value":372},"escaping text",{"type":26,"value":374},". This is not a wish to avoid text, or replace it with other media (images, animated GIFs, movies, etc.).",{"type":21,"tag":22,"props":376,"children":377},{},[378,380,385,387,392],{"type":26,"value":379},"\"Escaping text\", in the context of software engineering, refers to the practice of switching between the ",{"type":21,"tag":33,"props":381,"children":382},{},[383],{"type":26,"value":384},"literal",{"type":26,"value":386}," and ",{"type":21,"tag":33,"props":388,"children":389},{},[390],{"type":26,"value":391},"pragmatic",{"type":26,"value":393}," modes of interpreting text. This can be a daunting idea to wrap your head around, so allow me to illustrate it progressively.",{"type":21,"tag":56,"props":395,"children":397},{"id":396},"lets-start-with-strings",[398],{"type":26,"value":399},"Let's start with strings",{"type":21,"tag":22,"props":401,"children":402},{},[403,405,410,412,417],{"type":26,"value":404},"When a programmer writes code, he or she is usually making a list of statements in a text file. This text file gets transformed (either ",{"type":21,"tag":33,"props":406,"children":407},{},[408],{"type":26,"value":409},"compiled",{"type":26,"value":411}," or ",{"type":21,"tag":33,"props":413,"children":414},{},[415],{"type":26,"value":416},"interpreted",{"type":26,"value":418},") into language that the computer itself can read and carry out.",{"type":21,"tag":22,"props":420,"children":421},{},[422,424,431,433,438],{"type":26,"value":423},"Conventionally, when learning a new language, a programmer will be exposed to a program called ",{"type":21,"tag":322,"props":425,"children":428},{"href":426,"rel":427},"https://en.wikipedia.org/wiki/%22Hello,_World!%22_program",[326],[429],{"type":26,"value":430},"Hello World",{"type":26,"value":432},". This program -- no matter the language it is programmed in -- displays the phrase \"Hello, World!\" somewhere on the screen. (Confusingly, programmers refer to this as ",{"type":21,"tag":33,"props":434,"children":435},{},[436],{"type":26,"value":437},"printing",{"type":26,"value":439}," text to the screen).",{"type":21,"tag":22,"props":441,"children":442},{},[443],{"type":26,"value":444},"In Python, the \"Hello World\" program looks like this:",{"type":21,"tag":239,"props":446,"children":450},{"className":447,"code":448,"language":449,"meta":8,"style":8},"language-python shiki shiki-themes github-light github-dark","#!/usr/bin/python3\n\nprint(\"Hello World!\")\n","python",[451],{"type":21,"tag":246,"props":452,"children":453},{"__ignoreMap":8},[454,466,475],{"type":21,"tag":455,"props":456,"children":459},"span",{"class":457,"line":458},"line",1,[460],{"type":21,"tag":455,"props":461,"children":463},{"style":462},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[464],{"type":26,"value":465},"#!/usr/bin/python3\n",{"type":21,"tag":455,"props":467,"children":468},{"class":457,"line":336},[469],{"type":21,"tag":455,"props":470,"children":472},{"emptyLinePlaceholder":471},true,[473],{"type":26,"value":474},"\n",{"type":21,"tag":455,"props":476,"children":477},{"class":457,"line":333},[478,484,490,496],{"type":21,"tag":455,"props":479,"children":481},{"style":480},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[482],{"type":26,"value":483},"print",{"type":21,"tag":455,"props":485,"children":487},{"style":486},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[488],{"type":26,"value":489},"(",{"type":21,"tag":455,"props":491,"children":493},{"style":492},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[494],{"type":26,"value":495},"\"Hello World!\"",{"type":21,"tag":455,"props":497,"children":498},{"style":486},[499],{"type":26,"value":500},")\n",{"type":21,"tag":22,"props":502,"children":503},{},[504],{"type":26,"value":505},"As expected, running it produces the text \"Hello World!\" on screen.",{"type":21,"tag":22,"props":507,"children":508},{},[509],{"type":21,"tag":510,"props":511,"children":513},"img",{"alt":8,"src":512},"/alalande/2023-1/img/01_hello_world.gif",[],{"type":21,"tag":22,"props":515,"children":516},{},[517,519,524,526,531,533,538],{"type":26,"value":518},"The first important thing to notice, for our purposes here, is that the ",{"type":21,"tag":33,"props":520,"children":521},{},[522],{"type":26,"value":523},"text file",{"type":26,"value":525}," that we used to define the program is different than the ",{"type":21,"tag":33,"props":527,"children":528},{},[529],{"type":26,"value":530},"result",{"type":26,"value":532}," of running that program -- which, of course, is the whole fun of programming -- you make the computer actually ",{"type":21,"tag":33,"props":534,"children":535},{},[536],{"type":26,"value":537},"do",{"type":26,"value":539}," things!",{"type":21,"tag":22,"props":541,"children":542},{},[543,545,550],{"type":26,"value":544},"In particular, you'll notice that the text \"/usr/bin/python3\" and \"print\" do not appear in the program's output; only the phrase \"Hello World!\" appears in the output. This is because \"Hello World!\" is defined in the program as a ",{"type":21,"tag":33,"props":546,"children":547},{},[548],{"type":26,"value":549},"string",{"type":26,"value":551},".",{"type":21,"tag":22,"props":553,"children":554},{},[555,560,562,566,568,572],{"type":21,"tag":33,"props":556,"children":557},{},[558],{"type":26,"value":559},"Strings",{"type":26,"value":561}," are a special type of ",{"type":21,"tag":33,"props":563,"children":564},{},[565],{"type":26,"value":26},{"type":26,"value":567}," (or ",{"type":21,"tag":33,"props":569,"children":570},{},[571],{"type":26,"value":246},{"type":26,"value":573},") that gets passed around by computer programs. Strings can be sent by programs to a variety of places -- to a spreadsheet on your hard drive, to your monitor, to your printer, to a text-to-speech synthesizer, and so on.",{"type":21,"tag":22,"props":575,"children":576},{},[577,579,585],{"type":26,"value":578},"Strings generally contain text designed for a human to read, but not always. Website addresses, for instance (e.g. \"",{"type":21,"tag":322,"props":580,"children":583},{"href":581,"rel":582},"https://www.artandlogic.com/",[326],[584],{"type":26,"value":581},{"type":26,"value":586},"\") are usually treated as strings within computer code, even if these are meant to be interpretable by web browsers rather than people.",{"type":21,"tag":22,"props":588,"children":589},{},[590,592,597],{"type":26,"value":591},"The important take-away here is that strings are not code. They are used ",{"type":21,"tag":33,"props":593,"children":594},{},[595],{"type":26,"value":596},"by",{"type":26,"value":598}," computer code, but they are pieces of text that are not transformed into computer code. If programs are airports, strings are luggage. Strings get shuttled around by the airport workers, and although they contain an assortment of objects inside, they are treated as self-contained units.",{"type":21,"tag":22,"props":600,"children":601},{},[602],{"type":26,"value":603},"This analogy is actually quite useful, because it lets us make a further comparison: the quotes around the string \"Hello World!\" are the suitcase: the opening quotation mark says \"what follows are the contents of my string\", while the closing quotation mark says \"now we're going back to the code\". This is why quotes come in pairs. One opens the string, the other closes it.",{"type":21,"tag":22,"props":605,"children":606},{},[607,609,613,614,618,620,624],{"type":26,"value":608},"This is what I meant earlier by switching between the ",{"type":21,"tag":33,"props":610,"children":611},{},[612],{"type":26,"value":384},{"type":26,"value":386},{"type":21,"tag":33,"props":615,"children":616},{},[617],{"type":26,"value":391},{"type":26,"value":619}," modes of interpreting text. All the pieces of text in our program that make the computer ",{"type":21,"tag":33,"props":621,"children":622},{},[623],{"type":26,"value":537},{"type":26,"value":625}," things are pragmatic. This is the text that defines what the airport looks like and how it operates. All of the pieces of text in our program that get passed around like luggage are literal. Quotation marks let us switch back-and-forth between designing the airport and describing the luggage.",{"type":21,"tag":56,"props":627,"children":629},{"id":628},"what-does-charlie-say",[630],{"type":26,"value":631},"What does Charlie say?",{"type":21,"tag":22,"props":633,"children":634},{},[635],{"type":26,"value":636},"I started this discussion using double quotes (\"), because readers of English will recognize them from newspaper articles and books, where double quotes are often used to encapsulate what someone has said.",{"type":21,"tag":22,"props":638,"children":639},{},[640],{"type":26,"value":641},"Avid fiction readers may be intuitively familiar with one of the tricky aspects of quotes, which is reporting on what one person says another person says. If Alice asks Bob what Charlie said, the writer needs to find a way to communicate to the reader that within Bob's response are Charlie's words.",{"type":21,"tag":22,"props":643,"children":644},{},[645,647,651],{"type":26,"value":646},"\"What did Charlie say?\", asked Alice.",{"type":21,"tag":648,"props":649,"children":650},"br",{},[],{"type":26,"value":652},"\n\"He stared off into the distance and waxed poetically, 'If things were different they wouldn't be the same'\", replied Bob.",{"type":21,"tag":22,"props":654,"children":655},{},[656],{"type":26,"value":657},"Bob has just presented the reader with a suitcase within a suitcase. The writer has used two types of quotation marks to differentiate them: Bob's words (the outer suitcase) are encapsulated using double quotes (\"). Charlie's words (the inner suitcase) are encapsulated using single quotes (').",{"type":21,"tag":22,"props":659,"children":660},{},[661],{"type":26,"value":662},"This can quickly get messy. What would Bob's response be if Charlie were quoting Dennis?",{"type":21,"tag":22,"props":664,"children":665},{},[666],{"type":26,"value":667},"English punctuation doesn't comfortably handle multiple-layered scenarios, and so a resourceful author might prefer to find an alternative way to describe the interaction, perhaps using more context or even a literary device like a flashback or a parenthetical remark.",{"type":21,"tag":56,"props":669,"children":671},{"id":670},"poetry-and-code",[672],{"type":26,"value":673},"Poetry and code",{"type":21,"tag":22,"props":675,"children":676},{},[677,679,684],{"type":26,"value":678},"While literary authors have the luxury of being able to play with the conventions and form of the language (they have ",{"type":21,"tag":33,"props":680,"children":681},{},[682],{"type":26,"value":683},"poetic license",{"type":26,"value":685}," to bend the rules), that kind of freedom is much more restricted in programming. There are a lot of creative choices you can make when writing a computer program, but these don't extend to the syntax of the language.",{"type":21,"tag":22,"props":687,"children":688},{},[689,691,696],{"type":26,"value":690},"In other words, computer code has to be unambiguous -- it can only mean one thing and one thing only. The rules of ",{"type":21,"tag":33,"props":692,"children":693},{},[694],{"type":26,"value":695},"syntax",{"type":26,"value":697}," for any given programming language are generally very strict.",{"type":21,"tag":22,"props":699,"children":700},{},[701,703,708],{"type":26,"value":702},"(Even though we talk about computer languages being \"interpreted\", this is another term of art which -- unlike its everyday usage -- doesn't imply any choice on the part of the reader, i.e. the computer. My \"Hello World\" Python program above can be interpreted (i.e. ",{"type":21,"tag":33,"props":704,"children":705},{},[706],{"type":26,"value":707},"run",{"type":26,"value":709},") millions of times, and it will function the exact same way every time.)",{"type":21,"tag":22,"props":711,"children":712},{},[713,715,720,722,727],{"type":26,"value":714},"Programming, as a discipline, draws a lot of inspiration from mathematical notation. While math formulas don't report what people say, they do make heavy use of the \"suitcase inside a suitcase\" phenomenon, more generally called ",{"type":21,"tag":33,"props":716,"children":717},{},[718],{"type":26,"value":719},"nesting",{"type":26,"value":721},". The inner suitcase from our analogy can be said to be ",{"type":21,"tag":33,"props":723,"children":724},{},[725],{"type":26,"value":726},"nested",{"type":26,"value":728}," within the outer suitcase.",{"type":21,"tag":22,"props":730,"children":731},{},[732],{"type":26,"value":733},"Parentheses accomplish the same thing in math:",{"type":21,"tag":239,"props":735,"children":738},{"className":736,"code":737,"language":26},[242],"a = 12 × (b + 2.4 × (g + 2))\n",[739],{"type":21,"tag":246,"props":740,"children":741},{"__ignoreMap":8},[742],{"type":26,"value":737},{"type":21,"tag":22,"props":744,"children":745},{},[746],{"type":26,"value":747},"You can see here that we have three levels of nesting:",{"type":21,"tag":183,"props":749,"children":750},{},[751,756,761],{"type":21,"tag":187,"props":752,"children":753},{},[754],{"type":26,"value":755},"The entire right-hand-side of the equation provides the outermost suitcase,",{"type":21,"tag":187,"props":757,"children":758},{},[759],{"type":26,"value":760},"The first open parenthesis declares the start of the first inner suitcase, and",{"type":21,"tag":187,"props":762,"children":763},{},[764],{"type":26,"value":765},"The second open parenthesis declares the start of the innermost suitcase.",{"type":21,"tag":22,"props":767,"children":768},{},[769],{"type":26,"value":770},"The nesting you see here is quite easy to follow, because it is accomplished using just one pair of symbols: the open and closed parentheses.",{"type":21,"tag":22,"props":772,"children":773},{},[774],{"type":26,"value":775},"Nesting within text strings is a bit more complicated because of a key difference: parentheses in math formulas are always pragmatic, never literal.",{"type":21,"tag":22,"props":777,"children":778},{},[779],{"type":26,"value":780},"Quotation marks in the context of programming, on the other hand, may be either literal or pragmatic.",{"type":21,"tag":56,"props":782,"children":784},{"id":783},"bringing-it-all-together",[785],{"type":26,"value":786},"Bringing it all together",{"type":21,"tag":22,"props":788,"children":789},{},[790],{"type":26,"value":791},"Reviewing what we know so far, we've discussed three really important ideas:",{"type":21,"tag":183,"props":793,"children":794},{},[795,800,805],{"type":21,"tag":187,"props":796,"children":797},{},[798],{"type":26,"value":799},"Strings are text that computer programs treat as whole and distinct entities. They are like suitcases.",{"type":21,"tag":187,"props":801,"children":802},{},[803],{"type":26,"value":804},"The start of a string is declared when a double quote (\") is encountered within the computer code. The end of the string is declared when the exact same symbol (\") is encountered again. (Other symbols such as single quotes, backticks or slashes are sometimes used, but for simplicity, let's stick with double quotes).",{"type":21,"tag":187,"props":806,"children":807},{},[808,810,814],{"type":26,"value":809},"Strings, like suitcases, and like math formulas, may contain sub-objects. This is called ",{"type":21,"tag":33,"props":811,"children":812},{},[813],{"type":26,"value":719},{"type":26,"value":551},{"type":21,"tag":22,"props":816,"children":817},{},[818],{"type":26,"value":819},"Reflecting and meditating on these three ideas and their implications raises a difficulty: How can points #2 and #3 above co-exist?",{"type":21,"tag":22,"props":821,"children":822},{},[823,825,830],{"type":26,"value":824},"For instance, what if I wanted to modify my Hello World program above so that I have a nested string? Instead of \"Hello World\", suppose I wanted my program to give me a piece of advice inspired by the TV show ",{"type":21,"tag":33,"props":826,"children":827},{},[828],{"type":26,"value":829},"Arrested Development",{"type":26,"value":831},":",{"type":21,"tag":239,"props":833,"children":836},{"className":834,"code":835,"language":26},[242],"Just remember: \"There's always money in the banana stand!\"\n",[837],{"type":21,"tag":246,"props":838,"children":839},{"__ignoreMap":8},[840],{"type":26,"value":835},{"type":21,"tag":22,"props":842,"children":843},{},[844],{"type":26,"value":845},"I would have to replace the contents of the \"Hello World\" string with the above, to give me something like this:",{"type":21,"tag":239,"props":847,"children":849},{"className":447,"code":848,"language":449,"meta":8,"style":8},"#!/usr/bin/python3\n\nprint(\"Just remember: \"There's always money in the banana stand!\"\")\n",[850],{"type":21,"tag":246,"props":851,"children":852},{"__ignoreMap":8},[853,860,867],{"type":21,"tag":455,"props":854,"children":855},{"class":457,"line":458},[856],{"type":21,"tag":455,"props":857,"children":858},{"style":462},[859],{"type":26,"value":465},{"type":21,"tag":455,"props":861,"children":862},{"class":457,"line":336},[863],{"type":21,"tag":455,"props":864,"children":865},{"emptyLinePlaceholder":471},[866],{"type":26,"value":474},{"type":21,"tag":455,"props":868,"children":869},{"class":457,"line":333},[870,874,878,883,888],{"type":21,"tag":455,"props":871,"children":872},{"style":480},[873],{"type":26,"value":483},{"type":21,"tag":455,"props":875,"children":876},{"style":486},[877],{"type":26,"value":489},{"type":21,"tag":455,"props":879,"children":880},{"style":492},[881],{"type":26,"value":882},"\"Just remember: \"",{"type":21,"tag":455,"props":884,"children":885},{"style":486},[886],{"type":26,"value":887},"There",{"type":21,"tag":455,"props":889,"children":890},{"style":492},[891],{"type":26,"value":892},"'s always money in the banana stand!\"\")\n",{"type":21,"tag":22,"props":894,"children":895},{},[896,898,903],{"type":26,"value":897},"However, pairing up the quotes from left to right as dictated by principle #2 above, disappointingly results in two ",{"type":21,"tag":33,"props":899,"children":900},{},[901],{"type":26,"value":902},"non",{"type":26,"value":904},"-nested strings with some gibberish between them.",{"type":21,"tag":22,"props":906,"children":907},{},[908,910,915],{"type":26,"value":909},"Scanning left to right, the first string (between the first two double quotes) is ",{"type":21,"tag":246,"props":911,"children":913},{"className":912},[],[914],{"type":26,"value":882},{"type":26,"value":916},". Of course, we meant for both of these to be opening quotation marks (which would be unambiguous if we were using parentheses), but because we're forced to use the same character for both opening and closing quotes, we have to rely on left-to-right scanning to pair them up and decide which one is an opening quote and which one is a closing quote.",{"type":21,"tag":22,"props":918,"children":919},{},[920,922,928],{"type":26,"value":921},"The second string, not recognized until another pair of quotation marks is encountered, is empty, ",{"type":21,"tag":246,"props":923,"children":925},{"className":924},[],[926],{"type":26,"value":927},"\"\"",{"type":26,"value":551},{"type":21,"tag":22,"props":930,"children":931},{},[932],{"type":26,"value":933},"Between the two strings is the text (not a string!) \"There's always money in the banana stand!\", which the Python interpreter is not going to know how to deal with. We meant to put one suitcase inside another, but instead we've created two suitcases, one of which is empty, and between them, an armful of loose items which is just going to gum up the works of the baggage carousel.",{"type":21,"tag":22,"props":935,"children":936},{},[937],{"type":21,"tag":510,"props":938,"children":940},{"alt":8,"src":939},"/alalande/2023-1/img/02_unescaped.gif",[],{"type":21,"tag":22,"props":942,"children":943},{},[944],{"type":26,"value":945},"So, how can we have nesting (#3) when we must close one suitcase before creating a new one?",{"type":21,"tag":56,"props":947,"children":949},{"id":948},"escaping",[950],{"type":26,"value":951},"Escaping",{"type":21,"tag":22,"props":953,"children":954},{},[955,957,961],{"type":26,"value":956},"Now, finally, we arrive at the concept of ",{"type":21,"tag":33,"props":958,"children":959},{},[960],{"type":26,"value":948},{"type":26,"value":551},{"type":21,"tag":22,"props":963,"children":964},{},[965],{"type":26,"value":966},"When we use double quotes to mark the start and stop of a string, we are using quotes in their pragmatic sense. These quotes are telling Python something: while scanning text left to right, start then stop treating this text as a string.",{"type":21,"tag":22,"props":968,"children":969},{},[970,972,976],{"type":26,"value":971},"What we need, in order to reconcile points #2 and #3 above, is a method of turning a pragmatic double quote into a literal one. This is known as ",{"type":21,"tag":33,"props":973,"children":974},{},[975],{"type":26,"value":948},{"type":26,"value":551},{"type":21,"tag":22,"props":978,"children":979},{},[980,982,987,989,994],{"type":26,"value":981},"In our banana stand string, to accomplish nesting, we need to somehow tell Python to ignore the string-demarcating behaviour of the two inner double quotes, and to treat them like any other letter, number or character within a string. We need to switch those two quotes from ",{"type":21,"tag":33,"props":983,"children":984},{},[985],{"type":26,"value":986},"pragmatic quotes",{"type":26,"value":988}," to ",{"type":21,"tag":33,"props":990,"children":991},{},[992],{"type":26,"value":993},"literal quotes",{"type":26,"value":551},{"type":21,"tag":22,"props":996,"children":997},{},[998,1000,1005,1007,1013],{"type":26,"value":999},"We do this with an ",{"type":21,"tag":33,"props":1001,"children":1002},{},[1003],{"type":26,"value":1004},"escape character",{"type":26,"value":1006},". In Python, this is the backslash: ",{"type":21,"tag":246,"props":1008,"children":1010},{"className":1009},[],[1011],{"type":26,"value":1012},"\\",{"type":26,"value":551},{"type":21,"tag":22,"props":1015,"children":1016},{},[1017],{"type":26,"value":1018},"Our Python program, with the inner quotes escaped appropriately, now looks like this:",{"type":21,"tag":239,"props":1020,"children":1022},{"className":447,"code":1021,"language":449,"meta":8,"style":8},"#!/usr/bin/python3\n\nprint(\"Just remember: \\\"There's always money in the banana stand!\\\"\")\n",[1023],{"type":21,"tag":246,"props":1024,"children":1025},{"__ignoreMap":8},[1026,1033,1040],{"type":21,"tag":455,"props":1027,"children":1028},{"class":457,"line":458},[1029],{"type":21,"tag":455,"props":1030,"children":1031},{"style":462},[1032],{"type":26,"value":465},{"type":21,"tag":455,"props":1034,"children":1035},{"class":457,"line":336},[1036],{"type":21,"tag":455,"props":1037,"children":1038},{"emptyLinePlaceholder":471},[1039],{"type":26,"value":474},{"type":21,"tag":455,"props":1041,"children":1042},{"class":457,"line":333},[1043,1047,1051,1056,1061,1066,1070,1075],{"type":21,"tag":455,"props":1044,"children":1045},{"style":480},[1046],{"type":26,"value":483},{"type":21,"tag":455,"props":1048,"children":1049},{"style":486},[1050],{"type":26,"value":489},{"type":21,"tag":455,"props":1052,"children":1053},{"style":492},[1054],{"type":26,"value":1055},"\"Just remember: ",{"type":21,"tag":455,"props":1057,"children":1058},{"style":480},[1059],{"type":26,"value":1060},"\\\"",{"type":21,"tag":455,"props":1062,"children":1063},{"style":492},[1064],{"type":26,"value":1065},"There's always money in the banana stand!",{"type":21,"tag":455,"props":1067,"children":1068},{"style":480},[1069],{"type":26,"value":1060},{"type":21,"tag":455,"props":1071,"children":1072},{"style":492},[1073],{"type":26,"value":1074},"\"",{"type":21,"tag":455,"props":1076,"children":1077},{"style":486},[1078],{"type":26,"value":500},{"type":21,"tag":22,"props":1080,"children":1081},{},[1082],{"type":26,"value":1083},"While somewhat uglier and less readable to humans, this is unambiguous to the Python interpreter: the inner two quotes are part of the string, and you can safely ignore them when looking for the second double quote that closes the suitcase.",{"type":21,"tag":22,"props":1085,"children":1086},{},[1087],{"type":26,"value":1088},"Naturally, I don't want my final output to contain any ugly backslashes, which is why escape characters are removed before the string gets displayed, sent as a text message, read aloud, or otherwise.",{"type":21,"tag":22,"props":1090,"children":1091},{},[1092],{"type":21,"tag":510,"props":1093,"children":1095},{"alt":8,"src":1094},"/alalande/2023-1/img/03_escaped.gif",[],{"type":21,"tag":22,"props":1097,"children":1098},{},[1099],{"type":26,"value":1100},"Now that we understand what strings are, how strings are delimited in a left-to-right way, what nesting is, and how escape characters allow strings to be nested within one another, there's a final distinction to master:",{"type":21,"tag":22,"props":1102,"children":1103},{},[1104,1106,1110,1111,1115],{"type":26,"value":1105},"Are escape characters ",{"type":21,"tag":33,"props":1107,"children":1108},{},[1109],{"type":26,"value":391},{"type":26,"value":411},{"type":21,"tag":33,"props":1112,"children":1113},{},[1114],{"type":26,"value":384},{"type":26,"value":1116},"?",{"type":21,"tag":22,"props":1118,"children":1119},{},[1120],{"type":26,"value":1121},"Escape characters are pragmatic. We can see this in two ways:",{"type":21,"tag":183,"props":1123,"children":1124},{},[1125,1136],{"type":21,"tag":187,"props":1126,"children":1127},{},[1128,1130,1134],{"type":26,"value":1129},"They actually tell Python to ",{"type":21,"tag":33,"props":1131,"children":1132},{},[1133],{"type":26,"value":537},{"type":26,"value":1135}," something (in this case, \"ignore the quotation marks that directly follow\"), and",{"type":21,"tag":187,"props":1137,"children":1138},{},[1139],{"type":26,"value":1140},"They could not possibly be literal because they are removed from the string.",{"type":21,"tag":22,"props":1142,"children":1143},{},[1144],{"type":26,"value":1145},"So, what happens if I want my program to output something that contains a backslash?",{"type":21,"tag":239,"props":1147,"children":1150},{"className":1148,"code":1149,"language":26},[242],"The file you are looking for is found under C:\\Windows\\System32\n",[1151],{"type":21,"tag":246,"props":1152,"children":1153},{"__ignoreMap":8},[1154],{"type":26,"value":1149},{"type":21,"tag":22,"props":1156,"children":1157},{},[1158],{"type":26,"value":1159},"The answer is logical. Since the escape character tells Python to ignore the following character (treat it as a literal), we can use the escape character on itself, simply by doubling it!",{"type":21,"tag":239,"props":1161,"children":1163},{"className":447,"code":1162,"language":449,"meta":8,"style":8},"#!/usr/bin/python3\n\nprint(\"The file you are looking for is found under C:\\\\Windows\\\\System32\")\n",[1164],{"type":21,"tag":246,"props":1165,"children":1166},{"__ignoreMap":8},[1167,1174,1181],{"type":21,"tag":455,"props":1168,"children":1169},{"class":457,"line":458},[1170],{"type":21,"tag":455,"props":1171,"children":1172},{"style":462},[1173],{"type":26,"value":465},{"type":21,"tag":455,"props":1175,"children":1176},{"class":457,"line":336},[1177],{"type":21,"tag":455,"props":1178,"children":1179},{"emptyLinePlaceholder":471},[1180],{"type":26,"value":474},{"type":21,"tag":455,"props":1182,"children":1183},{"class":457,"line":333},[1184,1188,1192,1197,1202,1207,1211,1216],{"type":21,"tag":455,"props":1185,"children":1186},{"style":480},[1187],{"type":26,"value":483},{"type":21,"tag":455,"props":1189,"children":1190},{"style":486},[1191],{"type":26,"value":489},{"type":21,"tag":455,"props":1193,"children":1194},{"style":492},[1195],{"type":26,"value":1196},"\"The file you are looking for is found under C:",{"type":21,"tag":455,"props":1198,"children":1199},{"style":480},[1200],{"type":26,"value":1201},"\\\\",{"type":21,"tag":455,"props":1203,"children":1204},{"style":492},[1205],{"type":26,"value":1206},"Windows",{"type":21,"tag":455,"props":1208,"children":1209},{"style":480},[1210],{"type":26,"value":1201},{"type":21,"tag":455,"props":1212,"children":1213},{"style":492},[1214],{"type":26,"value":1215},"System32\"",{"type":21,"tag":455,"props":1217,"children":1218},{"style":486},[1219],{"type":26,"value":500},{"type":21,"tag":22,"props":1221,"children":1222},{},[1223],{"type":26,"value":1224},"When the backslashes are duplicated: the first in a pair tells Python to ignore the second in the pair, or at least to treat it literally.",{"type":21,"tag":56,"props":1226,"children":1228},{"id":1227},"closing-thoughts-why-this-is-important-to-programmers",[1229],{"type":26,"value":1230},"Closing thoughts: Why this is important to programmers",{"type":21,"tag":22,"props":1232,"children":1233},{},[1234],{"type":26,"value":1235},"Escaping text is important to understand because it is central to the art and craft of programming, just as luggage is central to the experience of traveling.",{"type":21,"tag":22,"props":1237,"children":1238},{},[1239,1241,1245,1247,1252],{"type":26,"value":1240},"More broadly, strings, suitcases, trunks, cans, enveloppes, cardboard boxes and many other types of containers are designed to shuttle contents through a system. Their outsides are meant to be handled ",{"type":21,"tag":33,"props":1242,"children":1243},{},[1244],{"type":26,"value":596},{"type":26,"value":1246}," the system, while their contents are meant to be kept separate ",{"type":21,"tag":33,"props":1248,"children":1249},{},[1250],{"type":26,"value":1251},"from",{"type":26,"value":1253}," the system. Both the system and the contents of the container benefit from a sturdy and leak-proof divider between them.",{"type":21,"tag":22,"props":1255,"children":1256},{},[1257],{"type":26,"value":1258},"Picture a malformed string as a suitcase missing one side, or a paint can with a leak, and it's easy to see the kind of havoc it could wreak making its way to where it needs to go.",{"type":21,"tag":22,"props":1260,"children":1261},{},[1262,1264,1271],{"type":26,"value":1263},"An improperly-escaped string can be just as bad. In fact, Apple's installer for iTunes 2 (released in 2001) contained a string which was not properly escaped, and this caused ",{"type":21,"tag":322,"props":1265,"children":1268},{"href":1266,"rel":1267},"https://macosx.com/threads/itunes-2-did-it-erase-your-hd-or-not.8733/post-42972",[326],[1269],{"type":26,"value":1270},"a bug which erased several users' hard drives",{"type":26,"value":1272},"!",{"type":21,"tag":22,"props":1274,"children":1275},{},[1276],{"type":26,"value":1277},"This is why escaping text matters. A programmer who doesn't craft strings carefully risks the introduction of bugs, errors, and even security vulnerabilities anywhere strings are used, which is to say almost anywhere.",{"type":21,"tag":1279,"props":1280,"children":1281},"style",{},[1282],{"type":26,"value":1283},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":333,"depth":333,"links":1285},[1286,1287,1288,1289,1290,1291],{"id":396,"depth":336,"text":399},{"id":628,"depth":336,"text":631},{"id":670,"depth":336,"text":673},{"id":783,"depth":336,"text":786},{"id":948,"depth":336,"text":951},{"id":1227,"depth":336,"text":1230},"content:alalande:2023-1:escaping_text.md","alalande/2023-1/escaping_text.md","alalande/2023-1/escaping_text",{"user":350,"name":351},1780330275596]