Recall: Last time we disucssed writing your own functions and storing them in your own modules. This leads to the creation of multiple files for a single application as we saw in the geometry.py and heron_test.py examples.
The Newton-Raphson method is a widely used approach for solving for roots of a function. This method relies on the differentiability of our function f and that the derivative is nonzero close to the root. We start with a guess. We evaluate the function at the guess and then use the derivative there to come up with a better next guess. More formally, suppose our first guess is a, then the equation of a line that is tangent to f at f(a) may be derived as $$y = f^\prime(a)(x-a)+f(a)$$ We use the x-intercept of this line as a better approximation for a nearby root of f. We can find the x-intercept by setting y = 0 in the above equations and solving for x. We end up witht the more general equation for our next guess: $$ x_{n+1} = x_n - \frac{f(x_n)}{f^\prime(x_n)}$$
To use this to solve for the nth-root of any number we simply let $$f(x)=x^n$$ in the equation above. We implement this method in Python below.
def nth_root(a, n, tolerance):
'''uses Newton-Raphson to find nth root'''
oldGuess = 1.0
newGuess = oldGuess - (oldGuess ** n - a) / (n * oldGuess**(n - 1))
while abs(oldGuess - newGuess) > tolerance:
oldGuess = newGuess
newGuess = oldGuess - (oldGuess ** n - a) / (n * oldGuess**(n - 1))
return newGuess
# We can break the same function up into smaller functions if we want
def next_guess(a, n, oldGuess):
'''part of Newton-Raphson approach to finding nth root'''
nextGuess = oldGuess - (oldGuess ** n - a) / (n * oldGuess**(n - 1))
return nextGuess
def has_next_guess(a, n, tolerance, oldGuess):
'''predicate function for Newton-Raphson method for finding nth root'''
newGuess = next_guess(a, n, oldGuess)
return (abs(oldGuess - newGuess)>tolerance)
def nth_root2(a, n, tolerance):
'''Uses Newton-Raphson to find nth root of a'''
guess=1.0
while has_next_guess(a, n, tolerance, guess):
guess = next_guess(a, n, guess)
return next_guess(a, n, guess)
import newton_raphson as nr
def main():
TEST = 29
EPSILON = .01
n=5
print( nr.nth_root(TEST,n,EPSILON))
main()
What just happened?
In the first file, newton_raphson.py we created a module to store our root approximating function. We wrote the functin in two different ways, just for fun. The second file, nr_test.py is used to define the main function where we test out our implementation of the Newton-Raphson method and use it to calculate the 5th root of 29. Notice we imported newton_raphson.py as nr. This allows us to shorten the longer module name to nr whenever we use it in main.
This is not too different from what you need to do for the projectile problem you have for homework!
When defining a function we use the term parameter to refer to the variable names in parenthesis. These are basically placeholders for the input values that the function requires to execute. When invoking a function we provide actual values inside the paranthesis and we call these arguments. Notice in the newton-raphson.py file above where we define the function nth_root2 we have three parameters: a, n, and tolerance. Then in the while loop we call the function next_guess and feed it the arguments a, n, and guess. So in this one function a and n are used as parameters for the nth_root2 function and then as arguments for the next_guess function.
We have become somewhat familiar with this important data-type. Let's explore the str type in Python some more.
Stuff we know:
str function or by just using the assignment operator.s='happy', then s[1] is a.: inside of the brackets when accessing them, just like lists.+ to concatenate strings and * to get repetiion.Stuff we may know:
\n is the special character sequence for new line.\t is the special character sequence for tab.'''like this''' preserves some formatting.print('first\n\nsecond')
print('first\t\tsecond')
print('''first
and then
second''')
More on slicing:
: when slicing to indicate step size[:] just like lists.s='Columbia University'
s[0:8]
s[0:8:2]
s[7::-1]
The last example may seem tricky. Note that it means begin at index 7 and work backwards for the rest of the string.
t=s[:]
t
More stuff we may not know:
We can use the in operator on strings to see if one string is a substring of another.
'adam' in 'madam'
'madam' in 'adam'
A method is a function defined as part of a class. So it's like a function that is specifically used for the data-type that a class is defining. The string class has many methods that may be used with string objects. For a complete list see the Python documentation linked above. For an abbreviated list of useful methods see your text page 200. One important difference in the use of methods versus functions is in the syntax.
s='Columbia University'
type(s)
s.upper() # upper is a method in the string class that returns a similar string in all caps.
s='joe'
s = s.upper()
s
Notice the format in calling a string method. First the string object reference, s, then dot, then the method name. Methods may or may not require arguments in the parenthesis but they always require the object reference preceding the dot.
Some particularly useful string methods that you should investigate include: isdigit, split, rstrip, lstrip, replace, count. Be sure to check these out!
Question:What makes a text file different from any other file?
Answer: Context!
Files on your computer are just units of information stored as binary digits in a manner that allows them to persist from one session to the next. That is, they are all just sequences of bytes. A byte is 8 binary digits. We call binary digits bits for short. So really all files are just sequences of zeros and ones that come in 8 bit packages. What distinguishes them is simply how all those bits are interpreted. An MP3 player interprets them one way and a text editor interprets them another. At the end of the day the many different kinds of stuff (programs, documents, pictures, sounds) on your computers are all represented as sequences of bits.
So how do we represent numbers in binary? There are many ways. One way is to use the same positional scheme you use in the familiar base-10. Remember from grade school the number 153 is is just 1 x 100 + 5 x 10 + 3 x 1. That is, $$ 153 = 1 \times 10^2 + 5 \times 10^1 + 3 \times 10^0 . $$ It's the same in binary, just replace the tens with twos which allows us to only need the two symbols 0 and 1. So the binary pattern 10011001 can be converted to base-10 like this:
$$ 10011001 = 1 \times 2^7 + 0 \times 2^6 + 0 \times 2^5 + 1 \times 2^4 + 1 \times 2^3 + 0 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 = 153 $$Remember, numbers are ideas. The symbols we use to represent them are just that, symbols. Their utility is in accurately conveying the idea behind them. The reason binary is so useful is because we can reprsent numbers using only two symbols and that makes building physical systems much easier. Lots of things in the real world come in pairs, on/off, charged/uncharged, magnetized/not magnetized. It is easier to build stable physical systems to represent numbers that require only two states than systems that require ten states. Once we can represent numbers, we can design encoding schemes to represent any of the kind of information you find on a computer from text to video as a sequence of numbers. So that's why binary is such a big deal!
Text files are very simple creatures. Plain text, that is a no frills sequence of characters, is all a text file is. Typically (but not always) one character is encoded using a 1-byte bit pattern. There are no bold, no italics, no formatting of any kind beyond blank spaces, tabs, and line breaks. The .py files you have been using for your source code are plain text files. They are given the .py extension so that the Python interpreter will know that they contain text written in a very specific way, a way that may be interpreted as a Python program. Plain text is also used to store data that may be read or written by a Python program. It's easy to do.
One way to think about a program is as a process that takes in information, does stuff with it, and then sends out information. Let's think of the information as streams coming in and out of the program. So far our input streams have always originated from the keyboard and our output streams alwyas gone to the console. The built-in functions input and print are used for getting and sending information along these streams. What kind of information? Strings. Remember, input takes in information as a string and print sends it to the console as string.
open is a built-in function in Python that creates a stream to a text file using a file object. It works like this
input_file = open('example.txt','r')
where example.txt would be an existing text file that we wish to read from. The second argument 'r' indicates that the file is for reading only. The open function will return a file object which we have named input_file. We can now use the methods that are associated with file objects to read from the file. Namely
contents = input_file.read()
Will return the entire contents of the file to be stored in the variable contents.
Here is a simple of example of reading a file
file_read.py
# This program reads and displays the contents
# of the philosophers.txt file.
def main():
# Open a file named philosophers.txt.
infile = open('philosophers.txt', 'r')
# Read the file's contents.
file_contents = infile.read()
# Close the file.
infile.close()
# Print the data that was read into
# memory.
print(file_contents)
# Call the main function.
main()
What just happened?
The file philosophers.txt was opened in read mode and it's contents were returned to the string variable file_contents. We then closed the stream (streams take up resources so we close them when we're done) and printed the contents of the file to the monitor.
We can also use the built-in function open to create an output stream to a new or existing text file. To do this we simply open the file in the write mode by using 'w' instead of 'r' in the second argument. Here is an example.
# This program writes four lines of data
# to a file.
def main():
# Open a file named philosophers.txt.
outfile = open('philosophers.txt', 'w')
# Write the names of three philosphers
# to the file.
outfile.write('Mickey Mouse\n')
outfile.write('David Hume\n')
outfile.write('Edmund Burke\n')
outfile.write('Joe Lion\n')
# Close the file.
outfile.close()
# Call the main function.
main()
What just happened?
This time when we called open we gave it the name of an output file philosophers.txt and used the w mode to indicate that we would be writing to the file. If the file doesn't already exist then our program will create a new file with this name. If it does already exist then our program will overwrite it erasing the previous contents. We then use the write method that is assoicated with file objects to write strings to the file. Notice we include the \n newline characters to indicate end of lines in the text file. If we use a text editor to read the file now we see that each philosopher is on its own line of the file.
Sometimes files are big. So big that reading the whole thing at once would be a mistake. It could crash our program. For this reason there is an alternative method to read that reads only a line at a time. This method is called, you guessed it, readline. Here's an example of using it
# This program reads the contents of the
# philosophers.txt file one line at a time.
def main():
# Open a file named philosophers.txt.
infile = open('philosophers.txt', 'r')
# Read three lines from the file
line1 = infile.readline()
line2 = infile.readline()
line3 = infile.readline()
line4 = infile.readline()
# Close the file.
infile.close()
# Print the data that was read into
# memory.
print (line1+line2+line3+line4)
# Call the main function.
main()