#set document(title: "8.4 String formatting", author: "OpenStax / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 8.4#h(0.6em)String formatting === Learning objectives By the end of this section you should be able to - Format a string template using input arguments. - Use format() to generate numerical formats based on a given template. === String format specification Python provides string substitutions syntax for formatting strings with input arguments. #strong[Formatting string] includes specifying string pattern rules and modifying the string according to the formatting specification. Examples of formatting strings include using patterns for building different string values and specifying modification rules for the string's length and alignment. === String formatting with replacement fields #strong[Replacement fields] are used to define a pattern for creating multiple string values that comply with a given format. The example below shows two string values that use the same template for making requests to different individuals for taking different courses. #examplebox("Example 1")[String values from the same template][ Dear John, I'd like to take a programming course with Prof. Potter. Dear Kishwar, I'd like to take a math course with Prof. Robinson. ] In the example above, replacement fields are 1) the name of the individual the request is being made to, 2) title of the course, and 3) the name of the instructor. To create a template, replacement fields can be added with {} to show a placeholder for user input. The #strong[format()] method is used to pass inputs for replacement fields in a string template. #examplebox("Example 2")[String template formatting for course enrollment requests][ A string template with replacement fields is defined below to create string values with different input arguments. The format() method is used to pass inputs to the template in the same order. s = "Dear {}, I'd like to take a {} course with Prof. {}." print(s) print(s.format("John", "programming", "Potter")) print(s.format("Kishwar", "math", "Robinson")) The above code's output is: Dear {}, I'd like to take a {} course with Prof. {}. Dear John, I'd like to take a programming course with Prof. Potter. Dear Kishwar, I'd like to take a math course with Prof. Robinson. ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[String template and formatting] ] === Named replacement fields Replacement fields can be tagged with a label, called #strong[named replacement fields], for ease of access and code readability. The example below illustrates how named replacement fields can be used in string templates. #examplebox("Example 3")[Season weather template using named replacement fields][ A named replacement argument is a convenient way of assigning name tags to replacement fields and passing values associated with replacement fields using corresponding names (instead of passing values in order). s = "Weather in {season} is {temperature}." print(s) print(s.format(season = "summer", temperature = "hot")) print(s.format(season = "winter", temperature = "cold")) The above code's output is: Weather in {season} is {temperature}. Weather in summer is hot. Weather in winter is cold. ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Multiple use of a named argument] Since named replacement fields are referred to using a name key, a named replacement field can appear and be used more than once in the template. Also, positional ordering is not necessary when named replacement fields are used. s = "Weather in {season} is {temperature}; very very {temperature}." print(s) print(s.format(season = "summer", temperature = "hot")) print(s.format(temperature = "cold", season = "winter")) The above code's output is: Weather in {season} is {temperature}; very very {temperature}. Weather in summer is hot; very very hot. Weather in winter is cold; very very cold. ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Named replacement field examples] ] === Numbered replacement fields Python's string format() method can use positional ordering to match the numbered arguments. The replacement fields that use the positional ordering of arguments are called #strong[numbered replacement fields]. The indexing of the arguments starts from 0. Ex: print("{1}{0}".format("Home", "Welcome")) outputs the string value "Welcome Home" as the first argument. "Home" is at index 0, and the second argument, "Welcome", is at index 1. Replacing these arguments in the order of "{1}{0}" creates the string "Welcome Home". Numbered replacement fields can use argument's values for multiple replacement fields by using the same argument index. The example below illustrates how an argument is used for more than one numbered replacement field. #examplebox("Example 4")[Numbered replacement field to build a phrase][ Numbered replacement fields are used in this example to build phrases like "very very cold" or "very hot". template1 = "{0} {0} {1}" template2 = "{0} {1}" print(template1.format("very", "cold")) print(template2.format("very", "hot")) The above code's output is: very very cold very hot ] === String length and alignment formatting Formatting the string length may be needed for standardizing the output style when multiple string values of the same context are being created and printed. The example below shows a use case of string formatting in printing a table with minimum-length columns and specific alignment. #examplebox("Example 5")[A formatted table of a class roster][ A formatted table of a class roster Student Name              Major          Grade ---------------------------------------------- Manoj Sara          Computer Science        A- Gabriel Wang     Electrical Engineering      A Alex Narayanan       Social Sciences        A+ ] In the example above, the table is formatted into three columns. The first column takes up 15 characters and is left-aligned. The second column uses 25 characters and is center-aligned, and the last column uses two characters and is right aligned. Alignment and length format specifications controls are used to create the formatted table. The field width in string format specification is used to specify the minimum length of the given string. If the string is shorter than the given minimum length, the string will be padded by space characters. A #strong[field width] is included in the format specification field using an integer after a colon. Ex: {name:15} specifies that the minimum length of the string values that are passed to the name field is 15. Since the field width can be used to specify the minimum length of a string, the string can be padded with space characters from right, left, or both to be left-aligned, right-aligned, and centered, respectively. The #strong[string alignment type] is specified using \<, \>, or ^characters after the colon when field length is specified. Ex: {name:^20} specifies a named replacement field with the minimum length of 20 characters that is center-aligned. #figure(table( columns: 4, align: left, inset: 6pt, table.header([Alignment Type], [Symbol], [Example], [Output]), [Left-aligned], [\<], [template = "{hex:\<7}{name:\<10}" print(template.format(hex = "\#FF0000", name = "Red")) print(template.format(hex = "\#00FF00", name = "green"))], [\#FF0000Red \#00FF00green], [Right-aligned], [\>], [template = "{hex:\>7}{name:\>10}" print(template.format(hex = "\#FF0000", name = "Red")) print(template.format(hex = "\#00FF00", name = "green"))], [\#FF0000          Red \#00FF00        green], [Centered], [^], [template = "{hex:^7}{name:^10}" print(template.format(hex = "\#FF0000", name = "Red")) print(template.format(hex = "\#00FF00", name = "green"))], [\#FF0000   Red \#00FF00  green], )) #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Specifying field width and alignment] ] === Formatting numbers The format() method can be used to format numerical values. Numerical values can be padded to have a given minimum length, precision, and sign character. The syntax for modifying numeric values follows the {\[index\]:\[width\]\[.precision\]\[type\]} structure. In the given syntax, - The index field refers to the index of the argument. - The width field refers to the minimum length of the string. - The precision field refers to the floating-point precision of the given number. - The type field shows the type of the input that is passed to the format() method. Floating-point and decimal inputs are identified by "f" and "d", respectively. String values are also identified by "s". The table below summarizes formatting options for modifying numeric values. #figure(table( columns: 3, align: left, inset: 6pt, table.header([Example], [Output], [Explanation]), [print("{:.7f}".format(0.9795))], [0.9795000], [The format specification .7 shows the output must have seven decimal places. The f specification is an identifier of floating-point formatting.], [print("{:.3f}".format(12))], [12.000], [The format specification .3 shows the output must have three decimal places. The f specification is an identifier of floating-point formatting.], [print("{:+.2f}".format(4))], [+4.00], [The format specification .2 shows the output must have two decimal places. The f specification is an identifier of floating-point formatting. The + sign before the precision specification adds a sign character to the output.], [print("{:0\>5d}".format(5))], [00005], [The format specification 0\>5 defines the width field as 5, and thus the output must have a minimum length of 5. And, if the number has fewer than five digits, the number must be padded with 0's from the left side. The d specification is an identifier of a decimal number formatting.], [print("{:.3s}".format("12.50"))], [12.], [The format specification .3 shows the output will have three characters. The s specification is an identifier of string formatting.], )) #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Numeric value formatting examples] ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Formatting a list of numbers] Given a list of numbers (floating-point or integer), print numbers with two decimal place precision and at least six characters. Input: \[12.5, 2\] Prints: 012.50 002:00 numbers = \[10, 10.0, 1, 12.5\] ]