*[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!")

```
```

`print`

!

```
In [3]:
```( (9 + 10) * (3 + 4) ) / 2

```
Out[3]:
```

```
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]:
```

`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!

```
In [10]:
``````
x
```

```
Out[10]:
```

...with this...:

```
In [11]:
```x = 9

```
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]:
```

`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]:
```

```
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!

`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]:
```

```
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)

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]:
```

```
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'

```
```

`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]:
```

```
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]:
```

```
In [58]:
```23 in the_numbers # like asking "(is) 23 in the_numbers"?

```
Out[58]:
```

```
In [59]:
```23 not in the_numbers

```
Out[59]:
```

```
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]:
```

`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]:
```

`.`

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?

`?`

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)

`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')

*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]:
```

`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]:
```

`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)