<jupyter_start><jupyter_text>INST644: Intro to DH Spring 2022 Understanding Lists and Transforming  Lines  In Python, a list is an ordered sequence of items. While a list is its own unique data type, individual elements within the list can be of different types, such as letters, words, or numbers. Many combinatorial poems make extensive use of lists. You can slice a list, sort it, loop through it, add and remove items from it, and otherwise manipulate it. Understanding lists is therefore crucial for exploratory data analysis, advancing your practice as a computational poet, in addition to other applications. One of the things that can sometimes make working with lists challenging is that they can behave so much like strings, especially when the individual items are text of some kind. This is because strings and lists, while distinct, are nonetheless united in both being types of *sequences*: successions or chains of elements. As such, they share many similarities. As we shall see, lists and strings also work synergistically in different contexts, such as poetry generation. For these reasons, we'll begin this notebook with a quick review of the string data type that we learned about in our previous notebook before moving on to lists.This notebook is inspired by <NAME>'s excellent tutorial: https://github.com/aparrish/rwet/blob/master/understanding-lists-manipulating-lines.ipynb. I've specifically tried to capture and preserve Parrish's foregrounding of how lists and strings can interact when creating text generators. The organization thus adheres closely to hers, while the explanations and most (but not all) of the examples are mine. I've also made use of a fragment of code from <NAME>'s [reimplementation of Alison Knowles' "House of Dust"](https://nickm.com/memslam/a_house_of_dust.html). Finally, I've followed Parrish in using ["Sea Rose" by H.D.](https://www.poetryfoundation.org/poems/48188/sea-rose) to illustrate some key concepts.  List PreviewBefore taking a second look at strings, let's preview what a list looks like:<jupyter_code>ships = ["schooner", "barque", "xebec", "cutter", "clipper"]
letters = ["h", "o", "u", "s", "e", "o", "f", "d", "u", "s", "t"]
fictional_addresses = ["221B Baker St.", "Apartment 5A, 129 W. 81st St.", "4 Privet Drive, Little Whinging, Surrey", "890 Fifth Avenue"]
famous_numbers = [3.141592, 1.6180, 186282] #pi, golden ratio, speed of light<jupyter_output><empty_output><jupyter_text>Note that square brackets signal to both computer and human readers alike that we're dealing with a list.  Taken collectively, the above examples demonsrate that individual list items can vary by linguistic unit (e.g., letter, word, phrase) and by data type (e.g., string, integer, float). You can also mix more than one data type or linguistic unit within a single list:<jupyter_code>mixed_data = ["xebec", "211B Baker St.", 3.141592]
mixed_data<jupyter_output><empty_output><jupyter_text>Remember that you can apply the "type" function to an object to check its data type:<jupyter_code>type(ships)
type(letters)
type(fictional_addresses)
type(famous_numbers)<jupyter_output><empty_output><jupyter_text>The key thing to note is that despite the internal data variety, the Python interactive interpreter parses a collection of items between square brackets as a list.   Strings Again Let's refresh our memories with regards to strings. Recall that quotation marks--rather than square brackets--signal that the data we're dealing with is a string. Here's a string assigned to a variable:<jupyter_code>scent_of_rain = "petrichor"<jupyter_output><empty_output><jupyter_text>"Petrichor" is a word that refers to the wonderful smell of rain. You can learn more about it [here](https://www.merriam-webster.com/words-at-play/words-were-watching-petrichor-slang-definition) We can use indexing to retrieve individual characters:<jupyter_code>scent_of_rain[1] #petrichor
scent_of_rain[-2] #petrichor<jupyter_output><empty_output><jupyter_text>And of course we can check the data type using the "type" function:<jupyter_code>type(scent_of_rain)<jupyter_output><empty_output><jupyter_text>Recall that in addition to retrieving an individual character, we can extract a substring using positional indexing:<jupyter_code>scent_of_rain[-5:-2] #petrichor<jupyter_output><empty_output><jupyter_text>Here are two built-in Python functions with string applications that we haven't previously looked at:<jupyter_code>max(scent_of_rain) #petrichor
min(scent_of_rain)<jupyter_output><empty_output><jupyter_text>The max() function returns the "largest" value in a string, and the min() function returns the "smallest", whether in alphabetic or lexicographic terms. The letter "z", for example, would be considered the "largest" letter in the alphabet, while "a" would be considered the smallest. These functions can also be applied to numbers, returning the quantitatively largest and smallest values.   The Main Event: Lists There are techniques for converting data from one type into another. We can convert a string into a list of characters using the list() function:<jupyter_code>list(scent_of_rain)<jupyter_output><empty_output><jupyter_text>We can assign that expression to a variable:<jupyter_code>rain_letters = list(scent_of_rain)
rain_letters<jupyter_output><empty_output><jupyter_text>Let's check the data type:<jupyter_code>type(rain_letters)<jupyter_output><empty_output><jupyter_text>Positional indexing applies to lists as well as strings.<jupyter_code>rain_scent_string = "petrichor"
rain_scent_list = ["p", "e", "t", "r", "i", "c", "h", "o", "r"]
rain_scent_syllable_list = ['pe', 'tri', 'chor']
rain_scent_string[0]
rain_scent_list[0]
rain_scent_syllable_list[0]
rain_scent_string[-2]
rain_scent_list[-2]
rain_scent_syllable_list[-2]
rain_scent_string[0:3]
rain_scent_list[0:3]
rain_scent_syllable_list[0:3]<jupyter_output><empty_output><jupyter_text>The max() and min() functions apply to both strings and lists. We've already seen them in action with strings; here's how they work with lists:<jupyter_code>max(rain_scent_list)
min(rain_scent_list)<jupyter_output><empty_output><jupyter_text>You can use the sorted() function to order the characters in a string alphabetically:<jupyter_code>sorted(rain_scent_list)<jupyter_output><empty_output><jupyter_text>The default mode returns a list in ascending order. With an added "reverse" parameter, you can alternatively specify descending order:<jupyter_code>sorted(rain_scent_list, reverse = True)<jupyter_output><empty_output><jupyter_text>You can also apply the sorted() function to lists containing numbers to arrange them in ascending or descending order:<jupyter_code>numbers = [1, 2, 3, 4, 5]
sorted(numbers, reverse = True)<jupyter_output><empty_output><jupyter_text>Some functions apply to lists containing data of exclusively one type or another. The sum() function, for example, which performs addition, presupposes a list of numbers rather than letters or words. Note that you'll get an error message if you try to use it with a list containing strings:<jupyter_code>sum(numbers)
sum(rain_scent_list)<jupyter_output><empty_output><jupyter_text>The sum() function illustrates a fundamental duality at work in the organization of data types: while an ordered collection of items is a list, the individual elements within that list preserve separate identities as strings, integers, floats, etc. Those primary identities aren't effaced just because the items also participate in a secondary group identity. On the contrary, their duality is signaled at the level of syntax: a word within a list, for example, is enclosed within quotation marks to indicate its individual status as a string, while simultaneously being embedded in square brackets alongside other items to indicate its membership in a list. Numbers, in turn, differentiate themselves from letters by foregoing the quotation marks. The Python interactive interpreter is constantly dealing with list elements at both syntactical levels. In the case of the sum() function, for example, it first assesses the data type of individual elements to determine if they're numerical before subsequently adding them all together at the list level. This double structure is a hallmark of lists.  Splitting Strings into Lists of Lines and WordsWe've looked at how we can use the list() function to convert a string into a list of letters. But what if we want to convert a string into a list of some other unit, such as *words* or *lines*? For those we have the split() and splitlines() methods:<jupyter_code>harper = """I felt it when the glow of life
Was warm upon my cheek,
In mornful cadence to my heart,
It solemnly did speak."""
print(harper)
harper.splitlines()<jupyter_output><empty_output><jupyter_text>That gave us a list of lines. Now let's obtain a list of words:<jupyter_code>harper.split()<jupyter_output><empty_output><jupyter_text>This stanza is from a poem entitled "Presentiment" by <NAME> (1825-1911), a Black poet, abolitionist, suffragist, and teacher. *Forest Leaves* (1845), the collection in which "Presentiment" was originally published, was rediscovered by <NAME> a decade or so ago in Baltimore, Maryland, where Harper was born and raised. You can read more about the book's recovery [here](http://commonplace.online/article/lost-no-more-recovering-frances-ellen-watkins-harpers-forest-leaves/).  The split() method automatically splits a string into individual words by using the spaces between them as a separator. You can use other delimiters to split at alternative places within the text. In the following example, I've divided Harper's stanza into three parts by specifying a comma rather than whitespace as the separator. Every time the system encounters a comma, it splits the text:<jupyter_code>harper_comma = harper.split(",")
print(harper_comma)<jupyter_output><empty_output><jupyter_text>Note that the chosen delimiter is inserted between quotation marks in the parentheses following "split" in the code cell. We now have a list of three elements, each of which consists of one or two lines from the original poetic stanza. This example combined with the others collectively illustrate that the string units comprising elements in a list can vary from individual letters to words to longer textual fragments or structures. Note that a comma separates each list element. We can retrieve individual elements from this list using indexing:<jupyter_code>print(harper_comma[0])
print(harper_comma[1])
print(harper_comma[2])<jupyter_output><empty_output><jupyter_text>Let's use the type() function to verify the data types for harper_comma as well as the elements within harper_comma. Try to predict the data types before you run the code cells:<jupyter_code>type(harper_comma)
type(harper_comma[0])<jupyter_output><empty_output><jupyter_text>Converting Lists into Strings   When we're modifying existing poems to create new poems, we'll often want to convert the original poem from a string into a list, modify the list elements in some way, and then convert them back to a string. We've now seen how to do the first part of that sequence--convert a string into a list--but how do we move in the other direction, converting a list into a string? The solution is to use the join() method. Let's look at an example. First, we'll assign a list to a variable:<jupyter_code>autumn_harvest = ["pumpkins", "squash", "apples", "corn", "grapes"]<jupyter_output><empty_output><jupyter_text>Next we'll use the join method to convert that list back into a string, inserting the conjunction "and" between each element:<jupyter_code>" and ".join(autumn_harvest)<jupyter_output><empty_output><jupyter_text>What to notice: the join method involves gluing the different list elements together to create a continous string. The word, letters, or characters that appear in front of ".join" serve as the connective tissue between each of those elements. Let's take the same list and glue them together with a leaf emoji instead of the conjunction "and":<jupyter_code>"🍁".join(autumn_harvest)<jupyter_output><empty_output><jupyter_text>We can assign that expression to a variable:<jupyter_code>autumn_harvest_join = "🍁".join(autumn_harvest)
autumn_harvest_join<jupyter_output><empty_output><jupyter_text>We can also print the string, which will remove the quotation marks around it:<jupyter_code>print(autumn_harvest_join)<jupyter_output><empty_output><jupyter_text>Remember that the variable "autumn_harvest" holds the list of autumn fruits and vegetables. If we don't want to assign the list to a variable, we could alternatively write the expression that converts it into a string like this:<jupyter_code>"🍁".join(["pumpkins", "squash", "apples", "corn", "grapes"])<jupyter_output><empty_output><jupyter_text>We could also assign the leaf emoji to a variable and then plug in the variable rather than the emoji in the slot in front of .join in the above expression. Create a new cell and try it out.  EXERCISE: Create a new cell and glue together the autumn harvest list elements with [another emoji](https://getemoji.com/) of your choice (you can just copy-and-paste the emoji) You can also convert the list elements to a string by specifying a blank space as the connector:<jupyter_code>harvest = " ".join(["pumpkins", "squash", "apples", "corn", "grapes"])
harvest
print(harvest)<jupyter_output><empty_output><jupyter_text>Adding and Removing List Items  You can add an item to a list using the append() method. Let's add a new seasonal fruit to our autumn harvest:<jupyter_code>autumn_harvest = ["pumpkins", "squash", "apples", "corn", "grapes"]
autumn_harvest.append("figs")
autumn_harvest<jupyter_output><empty_output><jupyter_text>Note that after adding "figs" using append(), you need to run the variable name again to see the updated list (new items are always tacked on at the end). If you want to add more than one item at a time to a list, use the extend() method:<jupyter_code>autumn_harvest.extend(["beans", "broccoli"])
autumn_harvest<jupyter_output><empty_output><jupyter_text>The extend() method, as you can see above, differs syntactically from append(): your new items must be embedded inside square brackets, following familiar list conventions.  There are two approaches to removing list items. The first is the remove() method:<jupyter_code>autumn_harvest.remove("corn")
autumn_harvest<jupyter_output><empty_output><jupyter_text>The second method uses pop() to specify an index position for the item you wish to remove. The syntax can be generalized as follows: *list or variable name for list*.pop*(position of item to be removed)*  Let's remove "figs" from the list, whose current indexical position is "4" (counting from left to right, starting with zero, in conformance with the string indexing methods we've learned):<jupyter_code>autumn_harvest.pop(4)<jupyter_output><empty_output><jupyter_text>Note that the pop() expression evaluates to the list item specified by the index position, in this case "figs". You'll need to run the variable name "autumn_harvest" again in a code cell to see the updated list minus the expunged fruit:<jupyter_code>autumn_harvest<jupyter_output><empty_output><jupyter_text>Random Library<NAME> [articulates a helpful workflow](https://github.com/aparrish/rwet/blob/master/understanding-lists-manipulating-lines.ipynb) for writing code that creatively traffics back and forth between strings and lists. Here are her steps:>1. Split a string to get a list of units (usually words).>2. Use some of the list operations discussed above to modify or slice the list.>3. Join that list back together into a string.>4. Do something with that string (e.g., print it out).>5. With this in mind, here's a program that splits a string into words, randomizes the order of the words, then prints out the results:<jupyter_code>import random #from Allison Parrish's Lists and Lines tutorial
text = "it was a dark and stormy night"
words = text.split()
random.shuffle(words)
text2 = ' '.join(words) #I've altered Parrish's original variable name in this line
print(text2)<jupyter_output><empty_output><jupyter_text>Note that Parrish's code first converts the string "it was a dark and stormy night" to a list of words **before** applying shuffle() from the random library. Attempting to apply it to the original input string will raise a programming error:<jupyter_code>random.shuffle(text)<jupyter_output><empty_output><jupyter_text>random.shuffle() operates on lists not strings, which is why artful code that shuttles back and forth between strings and lists is so important for combinatorial poetry that includes chance operations. The penultimate line of Parrish's code knits the shuffled list elements back together using the join() method before printing out the new string. Take note, too, of how Parrish is making use of variables, first assinging the string "it was a dark and stormy night" to the "text" variable, subsequently using the split() method to generate a list of words, which are in turn assigned to the "words" variable. It is these list elements held by "word" that are randomly shuffled before converting everything back to a string. We know from <NAME>'s *Electronic Literature* that chance, randomization, and aleatory techniques are cornerstones of early combinatorial poetry. And we've already seen how <NAME>'s reimplementation of <NAME>' "House of Dust" draws on the Python random library to continously output new stanzas. Let's take a closer look at how randomization works in Python so that we can creatively leverage it in our own work:   Lists and randomnessPython's random library contains three helpful functions that can be used for text generators:1. random.shuffle() As we've already seen, this function will randomly reorganize the elements of a list2. random.choice() This function returns a random element from a list3. random.sample() This function returns a random sample of elements from a list  Here's how each works:<jupyter_code>import random
materials = ['SAND', 'DUST', 'LEAVES', 'PAPER', 'TIN', 'ROOTS', 'BRICK', 'STONE', 'DISCARDED CLOTHING', 'GLASS', 'STEEL', 'PLASTIC', 'MUD', 'BROKEN DISHES', 'WOOD', 'STRAW', 'WEEDS']
random.shuffle(materials)
materials
import random
random.choice(materials)
import random
random.sample(materials, 3)<jupyter_output><empty_output><jupyter_text>Note that random.sample() takes two arguments rather than one: the list itself (or a variable holding the elements of the list, in this case "materials"), followed by a number indicating the sample size. Try increasing and decreasing the number to get a feel for how it works.  List Comprehensions: Applying Transformations to Lists  When writing code for combinatorial poetry, it can be helpful to loop through each element in a list to either filter some elements or transform them in some way. List comprehensions provide a compact syntax for accomplishing that.  If we abstract away from the details, the component parts of the syntax can be translated as something like this:  **newlist = transformation of list items from original list if (optionally) some specified condition is met.** Since elements in our lists will customarily consist of strings, we can perform the various string methods we previously learned about in our "Strings" notebook. As one example, we might convert a poem (string) into a collection of lines (list), then use string methods to cycle through each line in search of a specific word. If that word is found, we can use the replace() method to change that word to something else, the upper() method to capitalize it, the lower() method to lower case it, and so forth.Let's work with the "Sea Rose" poem to illustrate list comprehensions. In the first example, we'll simply tell Python to open the file containing the poem and capture each line as a list element. We'll assign this list of lines to the variable "text":<jupyter_code>text = [line for line in open("sea_rose.txt")]
text<jupyter_output><empty_output><jupyter_text>Recall that in the Strings notebook we learned the following method for opening and reading a poem:<jupyter_code>poem = open("sea_rose.txt").read()
poem<jupyter_output><empty_output><jupyter_text>In both cases the newline code "\n" preserves the location of line breaks. But note the ways in which the output differs for each of our two variables: the "text" variable encloses the entire poem as a list of lines contained between square brackets. Each line is separated from the others with a comma. The "poem" variable, by contrast, encloses the entire poem in single quotation marks. There are no commas separating each line from the others. The "text" variable holds the poem as a list of lines, while the "poem" variable holds it as a string. We can check this using the "type" function:<jupyter_code>type(text)
type(poem)<jupyter_output><empty_output><jupyter_text>To get a better sense of why this data type distinction is important, let's try slicing "text" and "poem" using indexing methods and compare the differences in output:<jupyter_code>text[3:9]
poem[3:9]<jupyter_output><empty_output><jupyter_text>In the first instance, the units being indexed are a list of lines; in the second, the units are sequences of characters. Positional indexing and slicing work with both strings and lists, but the units being indexed may differ.  Because of the differences in data type, we'll also get different results applying random.sample() to the contents of each variable:<jupyter_code>random.sample(text, 3)
random.sample(poem, 3)<jupyter_output><empty_output><jupyter_text>**EXERCISE: Try applying the sorted() function to the contents of both "text" and "poem" and study the differences. Include a markdown cell and write a short explanation to account for those differences.**  Now let's try something a little more advanced with list comprehensions:<jupyter_code>[line[:5] for line in text]<jupyter_output><empty_output><jupyter_text>Can you see what's happening here? For each line of the poem, Python returns the first five characters as specified in the string indexing. Even though "text" holds the poem as a list, each element in the list is a string, allowing us to manipulate lines using string methods. Recall the previous discussion about the paradox of duality!<jupyter_code>["Overwrite rose poem" for line in text]<jupyter_output><empty_output><jupyter_text>The list comprehension, above, tells Python to replace each line of the poem with the string "Overwrite rose poem".  **EXERCISE: repeat the above list comprehension, but overwrite each line with something else (maybe emoji?)** Let's try some other string transformations using list comprehensions. Run each line of code to see what it does. Most of these string methods should be familiar to you from the "Strings" notebook, only now those methods are inserted in list comprehensions:<jupyter_code>[line.upper().strip() for line in text] #strip method to remove the unsightly newline code
[line.replace("rose", "tulip").strip() for line in text]<jupyter_output><empty_output><jupyter_text>Compare the list comprehension syntax, above, with the relevant syntax from the Strings notebook, which accomplishes the same thing:<jupyter_code>print(poem.replace("rose", "tulip"))<jupyter_output><empty_output><jupyter_text>Because the replace() method is case sensitive, the first "Rose" in the poem isn't being replaced. Let's fix that with the help of a variable and the lower() method:<jupyter_code>k = [line.lower().strip() for line in text]
k
k2 = [line.replace("rose", "tulip").strip() for line in k]
k2
[line.capitalize() if line == k2[0] else line for line in k2]<jupyter_output><empty_output><jupyter_text>As a refresher, here's what the syntax looks like when we're dealing with the poem as a string rather than a list of lines:<jupyter_code>print(poem.lower().replace("rose", "tulip").capitalize())<jupyter_output><empty_output><jupyter_text>Other things we can do with list comprehensions. In the next code cell, note how we're making use of the "+" operator to concatenate emoji and text to create a display where each line of the poem is flanked by roses:<jupyter_code>["🌹 " + line.strip() + " 🌹" for line in text]<jupyter_output><empty_output><jupyter_text>We can yoke multiple string methods together in a single list comprehension. The following code cell combines the strip() method with multiple instances of replace():<jupyter_code>[line.strip().lower().replace("leaf", "dog").replace("rose", "thorn").replace("sand", "bottle").replace("flower", "bug") for line in text]<jupyter_output><empty_output><jupyter_text>Since the variable "text" holds the poem as a list of lines, when we use the print function, we still get an ugly list display:<jupyter_code>print(text)<jupyter_output><empty_output><jupyter_text>Recall that we can convert a list back to a string using the join() method:<jupyter_code>print(" ".join(text))<jupyter_output><empty_output><jupyter_text>Filtering Lines  We don't have to necessarily apply a string transformation to every line. Using list comprehensions, we can select which lines we want to transform with filtering. Note how we're deploying the "if" statement in the following code cell:<jupyter_code>[line for line in text if len(line) < 18]<jupyter_output><empty_output><jupyter_text>Only lines with less than 18 characters are returned. Let's get rid of those newline codes:<jupyter_code>[line.strip() for line in text if len(line) < 18]<jupyter_output><empty_output><jupyter_text>Other ways to filter:<jupyter_code>[line for line in text if "rose" in line]<jupyter_output><empty_output><jupyter_text>List comprehensions utilize what are called "temporary variables" with a short shelf life that don't have to be defined in advance. Because their scope is local and limited, we don't run into problems with ambiguity; Python simply understands that whatever temporary variable name has been called upon, it evaluates to a list element. Thus far we've been using the temporary variable "line" because it's descriptive and intuitive, but we could opt for something else to achieve the same results. Here let's use "item":<jupyter_code>[item for item in text if len(item) > 7 if item.startswith("m")]
[item for item in text if item.startswith("R")]<jupyter_output><empty_output><jupyter_text>While list comprehensions have the virtue of being compact and economical, we can also use a more traditional "for" loop to iterate through each item in a list and apply string transformations. First let's open "Sea Rose" again and use the splitlines() method to convert the poem into a list of lines:<jupyter_code>poem = open("sea_rose.txt").read().lower()
poem_lines = poem.splitlines()<jupyter_output><empty_output><jupyter_text>Let's have Python evaluate the variable "poem-lines" just to verify for ourselves that we've got a list of lines:<jupyter_code>poem_lines<jupyter_output><empty_output><jupyter_text>Take a look at the "poem_lines" output. Note that every line in the poem is separated from the others by a comma and that all the lines as a collective unit are embedded in square brackets. These are the syntactic indicators that we're dealing with a list. Each element of that list, however, is a string, as signaled by the quotation marks enclosing individual lines. We're now ready to loop through each of them one at a time and apply a filter and/or transformation:<jupyter_code>for line in poem_lines: 
    if "rose" in line:  
        print(line)<jupyter_output><empty_output><jupyter_text>Here's a walkthrough of the above code cell: 1. The "for" loop announces that each line in the list of poem_lines will be considered in turn. 2. The "if" statement evaluates the current line to determine if the string "rose" appears in it. 3. If it does, the line is printed. Since this is a loop, the whole process is repeated until every line has been processed and evaluated.  The indents and the colons in the code block are required: if they are omitted or the indents are skewed, an error message will be raised when you run the cell. Likewise, the words "for", "if", and "in" are all syntactically important. If your indentation is off by just a space, your code won't parse. Fortunately, Jupyter notebook automatically supplies indents when it detects a "for" or "else" statement followed by a colon and return.  Let's look at one more "for" loop. This example duplicates the first, but additionally stipulates using an "else" statement that any line which does not include "rose" will be printed in all caps:<jupyter_code>for line in poem_lines:
    if "rose" in line:  
        print(line)
    else:
        print(line.upper())<jupyter_output><empty_output><jupyter_text>Alternative Method for Encoding Files in UTF-8 format  If you've had trouble converting your files into UTF-8 format using a text editor, try the following method (be sure your file extension is already .txt)<jupyter_code>x = open("sea_rose.txt", encoding = "utf-8").read()
x
print(x)<jupyter_output><empty_output><jupyter_text>Solutions to String Exercises from the Strings Notebook EXERCISE 1: Identify a poem you want to work with. Make sure you save it in "UTF-8" format and upload it to your Jupyter notebook so that it's available to you in the right directory on your computer. Create a variable and assign the text of your poem to that variable. Use the len() function to find out how many characters are in your poem. Then, use the count() method to find out how many times one or more specific strings occur within it.<jupyter_code>poem = open("tyger.txt", encoding = "utf-8").read()
print(poem)
len(poem)
poem.count("Tyger")<jupyter_output><empty_output><jupyter_text>EXERCISE 2: Transform your poem by 1.) using the "swapcase" string method we encountered during class; and 2.) replacing at least three distinct words with three new words using the replace method. Try achieving each of these transformations separately (one version of the poem that swaps the case, another version that replaces words), but as a more advanced and challenging step (optional), try creating output that combines both of them in a single transformation.<jupyter_code>new_poem = poem.replace("Tyger", "Zebra").replace("sinews", "entrails").replace("immortal", "ephemeral").swapcase()
print(new_poem)<jupyter_output><empty_output><jupyter_text>EXERCISE 3: Try concatenating the Sea Rose poem with your chosen poem to achieve a new poem that combines them together. Hint: assign a different variable to each poem using the open file method we saw earlier and then concatenate the two variables. Do you remember which operator you can use for concatenation? Second hint: you'll actually want to concatenate three things in your print statement: the variable holding your first poem, a new line symbol to separate your first poem from your second poem, and the variable holding your second poem. The new line character is "\n" (inclusive of the quotation marks in your print statement).<jupyter_code>tyger = open("tyger.txt").read()
sea_rose = open("sea_rose.txt").read()
print(tyger + "\n" + sea_rose)
tyger = open("tyger.txt").read()
sea_rose = open("sea_rose.txt").read()
combined = tyger + "\n" + sea_rose
print(combined)<jupyter_output><empty_output><jupyter_text>EXERCISE 4: Write an expression, or a series of expressions, that prints out "Sea Rose" from the first occurence of the string "sand" up until the end of the poem. (Hint: Use the .find() method, discussed in class in addition to string slicing methods). My code, which I'll share later, is three lines long and uses two variables: "poem" and another variable to identify and hold the location of the string "sand". Another hint: your first line should be "poem = open("sea_rose.txt").read()" minus the quotation marks. Third hint: remember that you can use variables in lieu of explicit numbers to slice strings! In other words, you can have a variable that holds a number, and that variable can subsequently be used in your string slicing brackets.)<jupyter_code>poem = open("sea_rose.txt").read()
sand = poem.find("sand")
print(poem[sand:])<jupyter_output><empty_output><jupyter_text>EXERCISE 5: Write an expression that evaluates to a string containing the first fifty characters of "Sea Rose" followed by the last fifty characters of "Sea Rose." (Hint: you'll be using string slicing methods and concatenation in this exercise. First line of code can again be "poem = open("sea_rose.txt").read()" minus the quotation marks. Then create two new variables, one to hold the first 50 characters, the second to hold the last 50, then concatenate those two variables and print them)<jupyter_code>poem = open("sea_rose.txt").read()
first_fifty = poem[:51]
last_fifty = poem[-50:]
print(first_fifty + " " + last_fifty)<jupyter_output><empty_output><jupyter_text>New Exercises  EXERCISE 1: Use the append() and remove() methods to try adding and subtracting list elements from the "House of Dust" Materials category:<jupyter_code>materials = ['SAND', 'DUST', 'LEAVES', 'PAPER', 'TIN', 'ROOTS', 'BRICK', 'STONE', 'DISCARDED CLOTHING', 'GLASS', 'STEEL', 'PLASTIC', 'MUD', 'BROKEN DISHES', 'WOOD', 'STRAW', 'WEEDS']<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07564442303059504, 'fraction_numerical': 0.004396530956396049, 'mean_word_length': 5.899854560565136, 'pattern_counts': {'":': 3, '<': 279, '<?xml version=': 0, '>': 283, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '67', 'dir': 'jupyter-structured-clean-dedup', 'id': '19771', 'license': 'Unlicense', 'n_tokens_mistral': 9560, 'n_tokens_neox': 9019, 'n_words': 4311, 'path': 'lists and lines spring april 2022.ipynb', 'repo_name': 'karikraus/lists-and-lines-2021', 'size': '33272'}