[Before working through this document, everyone should have read "Notes on cognitive computing" (in lecture materials) by now!]
This document is a Jupyter Notebook. It is a combination of text (like this sentence you are currently reading), Python code, and Python code output.
Basically, text and code (+ output) are organized into 'cells' which can be 'run' or executed independently.
In [2]:
print("Hello world!")
Hey, you just ran your first line of code, and learned your first -- and possibly most important! -- programming function, print
!
The homework will also be conducted in a Jupyter Notebook. (If you really get interested in programming, we can talk off-line about what tools and programming environments besides Jupyter Notebooks are good to learn and use.)
In [3]:
( (9 + 10) * (3 + 4) ) / 2
Out[3]:
Remember in algebra how you defined variables and then wrote expressions with those variables? That's key to programming as well:
In [4]:
x = 9
y = 3
z = 2
a = x * y / z
print(a)
This is really all programming is -- writing mathematical and math-like expressions, and letting a computer do the work of evaluating such expressions.
Let's now talk a little more about...
In [5]:
x = 2
All values have a 'type'.
In [6]:
type(x)
Out[6]:
In [7]:
type(2)
Out[7]:
Note that the type is associated with the value, not the name.
In [8]:
x = 2.1
type(x)
Out[8]:
That is, x
was initially an int
but when we assigned it a new value, it became a float
. (More on what a float
is in a moment.)
If we try to use a variable that has not yet been defined we get a NameError
.
In [9]:
x + q
Trying to understand your error messages is key to understanding why your program isn't working as expected!!
Googling your error message will usually lead to helpful information. In fact, if you can google, you can learn to program. Any question you have about programming has been asked and answered online 100 times before!
Real quick: in case you haven't noticed...if the last line of code in a cell is simply an expression -- and not an assignment -- jupyter notebook prints the result of that expression to the screen. Contrast this...:
In [10]:
x
Out[10]:
...with this...:
In [11]:
x = 9
(This is not a terribly important concept in programming or Python -- I just mention it to preempt possible confusion about the notebook's behavior.)
Here, we'll just think of 'types' as the 'kinds of things' a programming language has. Python's fundamental types (some of which we've seen):
In [12]:
# integer
x = 1
type(x)
Out[12]:
In [13]:
# floating-point
x = 1.0
type(x)
Out[13]:
(In case it isn't clear #
introduces a 'comment'. It's basically a way of telling the computer not to interpret anything after it as Python code. Instead, you (the programmer) write this or that comment to help explain the program, so someone reading it later can understand it.)
int
is integer. A float
(short for 'floating point' number) is not really simply a number with a decimal point, but it will suffice for now to think of them that way.
The next data type we are already familiar with from our logic lectures!
In [14]:
# boolean
x = True
y = False
type(x)
Out[14]:
In a little I'll show how Python can calculate truth-values for complex statements. Later, I'll show how we use bool
instances for conditionals (e.g., if x == True: print("x is True!")
)
Finally, Python has a str
type for strings, which you can basically think of as plain text.
In [15]:
# string
name = 'Russell Richie'
type(name)
Out[15]:
You can also put strings inside double-quotes. This is necessary if you want to put a single quote inside a string!
In [19]:
"Russell Richie's a grad student."
Out[19]:
Having different data types is important because they let you do different things. That is, different types afford different actions, or operations (which we'll cover in a minute). (Quickly, int
and float
let you do math-y things, bool
logic-y things, and str
text-y/language-y thing.)
Questions so far? We do have time to field some questions throughout!
What happens if we try to add the integer 1
, and the string '1'
? Before we execute this next line, any guesses what the output will be?
In [22]:
1 + '1'
Try to read that error message. Why did this fail?
So we need to be able to convert between types.
In [23]:
x = '1'
type(x)
Out[23]:
In [24]:
y = int(x) # y is x converted to an integer
type(y)
Out[24]:
So again, this doesn't work:
In [25]:
y + x
But this does:
In [26]:
y + int(x)
Out[26]:
In [28]:
1 + 2
Out[28]:
In [29]:
1 - 2
Out[29]:
In [30]:
3 * 4
Out[30]:
In [31]:
3 / 4
Out[31]:
In [32]:
# Power, aka exponentiation
2**3
Out[32]:
In [26]:
# Modulus. Returns the remainder when you divide one number by another
9 % 4
Out[26]:
In [33]:
1 == 2
Out[33]:
ATTENTION!
In Python, =
is different from ==
.
=
is for assigning a value to a variable!
==
is for computing equivalence between two values!
Observe:
In [34]:
1 = 2
Why does that fail?
These work, though!
In [35]:
1 != 2
Out[35]:
In [36]:
1 > 2
Out[36]:
In [37]:
1 >= 2
Out[37]:
In [38]:
1 < 2
Out[38]:
In [39]:
1 <= 2
Out[39]:
And we have some of the statement logic connectives that you all are familiar with!
In [40]:
(1 != 2) and (2 > 1)
Out[40]:
In [41]:
(1 == 2) or (1 != 2)
Out[41]:
In [42]:
print("x is",x)
not (x == 2)
Out[42]:
Hopefully this reminds you of statement logic! We could also just define atomic letters with truth-values and compute the truth-value of complex propositions like this:
In [43]:
A = True
B = False
C = False
(A or B) and not (C)
Out[43]:
In [ ]:
#A->B
(not A) or B
#A<>B
(A and B) or (not A and not B)
There are no logical operators/connectives for the conditional or biconditional in base (ordinary) Python, but do you really need them? Why or why not?
You can concatenate strings:
In [48]:
first_name = 'Russell'
last_name = 'Richie'
full_name = first_name + ' ' + last_name # ' ' is a string with a space!
print(full_name)
I'll go over more of the things you can do with strings later, in the "Objects" section below.
Questions?
Above we covered data types like bool
, int
, float
, and str
. Sometimes, you want to put a whole bunch of instances of such types into a single container. This is where we could use:
They are exactly what they sound like! A list is an ordered sequence of any combination of data types.
Lists are defined by square brackets []
, with individual elements inside separated by commas.
In [51]:
the_numbers = [4, 8, 15, 16, 23, 42]
type(the_numbers)
Out[51]:
In [52]:
len(the_numbers)
Out[52]:
You also might want to get just certain values within the list. We can access values from lists using square brackets. This is called "indexing". Indexes start at 0 in Python.
In [53]:
the_numbers[0] # get the 0-th item from the list (intuitively, the 1st)
Out[53]:
In [54]:
the_numbers[3]
Out[54]:
So 0-based indexing means an index of n gets you the n+1th element.
If 0-based indexing seems weird, think of it like this:
Think of the indexes as 'fence posts' between the elements of the list.
Rest assured, this does become more intuitive as you use it.
Use [#:#]
to get a range of elements.
In [55]:
the_numbers[2:5] # get everything between the 2 and 5 fenceposts
Out[55]:
In [56]:
the_numbers[:2] # get everything up to the 2 fencepost
Out[56]:
In [57]:
the_numbers[2:] # get everything after the 2 fencepost
Out[57]:
Indexing can be used to change values, too.
In [58]:
the_numbers[0] = 2
the_numbers
Out[58]:
Note: indexing works similarly for strings.
In [59]:
print(name)
In [60]:
name[0]
Out[60]:
In [61]:
name[:7]
Out[61]:
But you actually can't use indexing to change a value of a string! (For reasons we won't go into.)
In [62]:
name[0] = 'F'
The range
function is useful for creating simple lists. Although it behaves like a list, it's technically not a list unless you convert it to one explicitly.
In [63]:
range(4)
Out[63]:
In [64]:
list( range(4) )
Out[64]:
More generally, any type of data structure that we can "iterate" over can be turned into a list. (More on the notion of 'iteration' in a minute.)
In [66]:
r = 'russell'
print(len(r))
r = 'richie'
print(len(r))
In [65]:
list(name)
Out[65]:
Pick up here on Friday Feburary 24th!.
In [54]:
the_numbers + [1, 2] # concatenate two lists
Out[54]:
In [55]:
n = 2
the_numbers * n # repeat a list n times
Out[55]:
But notice that neither of these operations directly modified the_numbers
!
In [56]:
the_numbers
Out[56]:
If we want to add [1, 2
] to the_numbers
and keep the result in the_numbers
, we could do this:
In [57]:
the_numbers = the_numbers + [1, 2]
the_numbers
Out[57]:
Another very important thing you do with lists -- sometimes you want to know if something (say, an integer) is or is not in a particular list:
In [58]:
23 in the_numbers # like asking "(is) 23 in the_numbers"?
Out[58]:
In [59]:
23 not in the_numbers
Out[59]:
There are certain operations to modify lists 'in place'. Meaning, you can modify the list without having to reassign the list to the same or a new variable.
In [60]:
the_numbers.append(101)
the_numbers
Out[60]:
In [61]:
the_numbers.extend([120, 142, 179])
the_numbers
Out[61]:
Those two operations are definitely very similar and easy to confuse. But observe:
In [62]:
the_numbers.append([1,2,3])
the_numbers
Out[62]:
In [63]:
the_numbers.extend([1,2,3])
the_numbers
Out[63]:
The key difference is that append
simply takes its 'argument' and makes it the last element in the_numbers
. extend
takes the argument and adds each of its elements to the end of the_numbers
.
You might be puzzled by the syntax of these operations: some_object.some_operation(some_inputs)
. Operations with this syntax are technically called methods, but for now you can think of them as functions just like len()
or print()
.
Questions??
I've already referred to 'functions' without much explanation, when I showed, e.g., print()
and len()
and even .append()
. The programming notion of 'function' more or less maps onto your everyday but also mathematical notions of 'function': functions do things, usually for some special purpose; functions map from inputs to outputs.
Commonly, functions are sequences of operations...
Quick: what technical term meaning 'sequence of operations' do we already know?
...sequences of operations that are called with parentheses, and the input to the function in parentheses.
As we've seen, Python provides many 'built-in' and useful functions. We already saw print()
, len()
, and range()
. In addition:
In [64]:
the_numbers = [4, 8, 15, 16, 23, 42, 101, 103]
the_numbers
Out[64]:
In [65]:
sum(the_numbers)
Out[65]:
In [66]:
min(the_numbers)
Out[66]:
That notation -- function_name(argument)
-- should remind you of how functions appeared in math classes, as in f(x)
.
We can define our own functions with the def
keyword (short for 'define').
In [67]:
def is_even(x):
# determines whether a number is an even number
return (x % 2) == 0
is_even(10)
Out[67]:
For the first three numbers in the_numbers
, determine whether each is even, and store the result in a list.
In [68]:
is_even_list = [is_even(the_numbers[0]),
is_even(the_numbers[1]),
is_even(the_numbers[2])]
is_even_list
Out[68]:
In [69]:
def square_is_even(x):
# this function tells you whether the square of x is even!
square = x ** 2
return is_even(square)
print(square_is_even(3))
print(square_is_even(4))
We've seen the word 'object' pop up here and there. In Python, pretty much everything is an object. Every instance of some broader class is an object. That includes literals (like 1
or 'a'
or ['a',2]
), variables, and more. But for the purposes of this class, don't fret terribly much about the precise definition of 'object'...
What's important is this -- objects have values and functions (technically called methods) attached to them that are accessed with a .
(dot). We already saw a couple instances of these functions with .append
and .extend
. Some other important ones:
For string objects:
In [70]:
name.endswith('ie')
Out[70]:
In [71]:
' '.join(['Russell','Richie'])
Out[71]:
In [72]:
name.upper()
Out[72]:
For list objects:
In [73]:
the_numbers.index(23)
Out[73]:
In [74]:
the_numbers.reverse()
the_numbers
Out[74]:
In [75]:
the_numbers.sort()
the_numbers
Out[75]:
If you put your cursor after the .
and press 'tab', you will see possible attributes and methods for an object. (And actually, pressing tab at many points will often give possible completions to a line.)
In [76]:
name.
In [77]:
name.capitalize?
The ?
trick is actually super useful for looking up what something is or does without having to open a new tab and google it.
In Python, whitespace -- spaces or tabs -- at the beginning of a line is significant. Some types of keywords (like if
below) require that they be followed by (1) a colon :
, and then (2) a group of lines indented with 4 spaces. The group of indented lines is called a block. For example, when we defined functions above, we deployed a block to indicate the content of the function.
Control flow is how we tell Python which block of code to execute.
In [78]:
first_and_last_name = name.split()
print(first_and_last_name)
In [79]:
first_name = first_and_last_name[0]
print(first_name)
In [80]:
if first_name == 'Russell':
print("That's a good name.")
else:
print("That name's okay, I guess...")
In [81]:
first_name = 'Garrett'
if first_name == 'Russell':
print("That's a good name.")
else:
print("That name's okay, I guess...")
If you screw up the indentation, Python will let you know.
In [82]:
x = 1
print(x)
In [83]:
if x == 1:
print('yep')
Much of the power of programming comes from...:
In [84]:
for x in [0,1,2,3,4]:
print(x)
Equivalent to:
In [85]:
for x in range(5): # also equivalent to `for x in list(range(5)):`
print(x)
Let's break this down a little bit...
The for x in some_sequence:
introduces a variable x
which will contain a different value in some_sequence
on every iteration of the loop. If some_sequence
is [0,1,2,3,4]
, on the first run of the loop, x
is 0, on the second run, x
is 1, and so forth. This allows you to write a block of code just once, but run it for every element in some_sequence
.
Let's try another loop:
In [86]:
the_numbers
Out[86]:
In [87]:
for number in the_numbers:
numb_digits = len(str(number))
print("The number is", number, "which has", numb_digits, "digits")
Let's try combining a for loop and a conditional:
In [88]:
for number in the_numbers:
numb_digits = len(str(number))
if numb_digits in [1,3]:
print("The number is", number, "which has", numb_digits, "digits")
A slight tangent:
If it isn't clear, the reason the loop gives computing systems such power is that it lets the system process a large amount of information with a small amount of code/instructions if the process is repetitive in some way.
This is practically important for the "real world" (e.g., automating boring clerical work), but we could argue that it also has implications for cognitive science. That is, much of our cognition involves applying the same process to a sequence of elements. The problem set will exemplify this a little bit.
Questions??
There is, without a doubt, much, much more to Python. I haven't even covered all of what I would consider "the basics". But using just what I've given you, you really can go quite far. Certainly it will suffice for the problem set.
If you are interested in learning more programming, you could talk to me or Garrett. And/or, you might consider taking CSE classes! Many professors, graduate students, researchers, etc. elsewhere in the cognitive science community also use programming for cognitive science applications, so they might provide good learning experiences, as well.
Questions about anything we covered today?
Like much of the material in this course, to solidify your understanding of and facility with the above material, you probably need to practice. So, you might try some...
For each exercise, there is a cell where you should try to fill in the function (delete the ...
). The following cell acts as a test, run it to see if your function is correct.
Each exercise can be solved with just a few lines, using almost exclusively the Python functionality we covered above (when you perhaps need more stuff, I tell you what it is).
Write a function distance
that takes two tuples, each containing two numbers (x, y)
. Compute the distance between them using the formula
As a hint I've imported the sqrt
function from the math
module.
In [89]:
from math import sqrt
sqrt(9)
Out[89]:
In [ ]:
def distance(a, b):
...
In [ ]:
assert distance((0, 0), (0, 0)) == 0
assert distance((0, 0), (1.5, 0)) == 1.5
assert distance((0, 0), (1, 1)) == sqrt(2)
Define a function max_of_three
that takes three numbers and returns the largest of them. One way to solve this is by using if
/else
. But probably easier: remember the min()
function? Given that that function exists, what other function probably exists?
In [ ]:
def max_of_three(a, b, c):
...
In [ ]:
assert max_of_three(1, 2, 3) == 3
assert max_of_three(3, 2, 1) == 3
assert max_of_three(1, -1, 1) == 1
assert max_of_three(100.25, 0, -1.5) == 100.25
Define a function is_palindrome
that returns True
for inputs that are the same backwards and forwards.
Might find the following trick helpful:
In [90]:
my_string = 'palindrome'
my_string[::-1]
Out[90]:
In [ ]:
def is_palindrome(word):
...
In [ ]:
assert is_palindrome('radar')
assert is_palindrome('x')
assert is_palindrome('xx')
assert is_palindrome('')
assert not is_palindrome('abc')
A pangram is a sentence that contains every letter of the English alphabet.
For example, The quick brown fox jumps over the lazy dog.
Write a function is_pangram
that checks whether a sentence is a pangram or not.
In [91]:
# This may be helpful.
from string import ascii_lowercase
ascii_lowercase
Out[91]:
There's many ways to solve this, but if you approach it like I did, you may also want to know that when you define a function, you can put return
in more than one place. Which return gets called might then depend on the argument given to the function...
In [ ]:
def is_pangram(sentence):
...
In [ ]:
assert is_pangram('The quick brown fox jumps over the lazy dog.')
assert not is_pangram('The quick brown fox leaps over the lazy cat.')
Write a function factorial
that computes the factorial of a number.
Factorial is the !
operation. For example $4! = 4 \cdot 3 \cdot 2 \cdot 1 = 24$.
Include the special case that $0! = 1$.
In [ ]:
def factorial(x):
...
In [ ]:
assert factorial(0) == 1
assert factorial(1) == 1
assert factorial(2) == 2
assert factorial(3) == 6
assert factorial(4) == 24
assert factorial(5) == 120
Write a function digits_are_even
that returns True
if all the digits of a number are even.
You can call the function we already defined is_even
inside your function.
In [ ]:
def digits_are_even(x):
...
In [ ]:
assert digits_are_even(22)
assert digits_are_even(2840)
assert not digits_are_even(24561)
assert not digits_are_even(52486)
assert digits_are_even(0)
Write a function char_freq
that returns a dictionary containing the number of times each character appears.
We didn't talk about dictionaries, but they are basically collections of key: value pairs, like this:
In [92]:
word_freqs = {'the':100, 'a':90, 'Russell':10, 'is':50}
word_freqs
Out[92]:
In [93]:
word_freqs['Russell']
Out[93]:
In [94]:
word_freqs['Richie'] = 9
word_freqs
Out[94]:
You can build an empty dictionary -- which you will fill in later in your program -- with:
In [96]:
new_dictionary = dict()
new_dictionary
Out[96]:
Note that a function like char_freq
already exists in Python, as collections.Counter
, but see if you can recreate it.
In [ ]:
def char_freq(sentence):
...
In [ ]:
from collections import Counter
sentence = 'The quick brown fox jumps over the lazy dog.'.lower()
assert char_freq(sentence) == Counter(sentence)
sentence = 'aksjdhjsdbvjbfajsdhbajfgsodig'
assert char_freq(sentence) == Counter(sentence)