1

-5

print 2 + 10 # addition
print 5 - 3 # subtraction
print 6 * 4 # multiplication
print 10 / 5 # division
print 2**4 # exponents

"""
Explanation: Data types
Today we'll be taking a look at the standard data types that come with python and some examples of how we can use these data types to represent real world information. This list isn't comprehensive, there are many more types available but this is the majority of what you'll see day to day.
Why?
Data types are the building blocks of applications. They are the basic elements we can combine to form more complex structures.
Integers
Integers are whole numbers. They can be either positive or negative. Not much to say here, they do what you'd expect!
End of explanation
"""


2 / 3

"""
Explanation: There is something I should mention here. python 2 can trip people up when trying to do something like the following:
End of explanation
"""


2 / 3.0

"""
Explanation: This is because python defaults to returning an integer (rounded down) when you ask to divide two integers. Read more about this here: http://python-history.blogspot.com/2009/03/problem-with-integer-division.html
To get the answer you'd expect you'll need at least one of the numbers to be a float:
End of explanation
"""


1.5

type(1.5)

"""
Explanation: Floating point numbers
Floating point numbers can be a bit tricky. Let's take a look at some examples:
End of explanation
"""


0.1 + 0.2

"""
Explanation: So you might think that floats are simply numbers that have decimal parts but...
End of explanation
"""


from decimal import Decimal
Decimal('0.1') + Decimal('0.2')

"""
Explanation: The python docs discuss this behavior: (https://docs.python.org/2/tutorial/floatingpoint.html#representation-error):

Note that this is in the very nature of binary floating-point: this is not a bug in Python, and it is not a bug in your code either. You’ll see the same kind of thing in all languages that support your hardware’s floating-point arithmetic (although some languages may not display the difference by default, or in all output modes).

For more info:

http://en.wikipedia.org/wiki/IEEE_floating_point
http://cr.yp.to/2005-590/goldberg.pdf

In short: if you are doing work which requires numerical precision you'll want to use the decimal library. Note that we have to pass the Decimal class a string as an argument.
End of explanation
"""


'Hello python learners'

"""
Explanation: Strings
Now we get to the fun stuff. I say we're getting into the fun stuff because there aren't a lot of methods for numerical types but the rest of the types we'll discuss have plenty of methods available to them which can be very useful.
Strings are text, generally. A string is any collection of symbols surrounded by quotes:
End of explanation
"""


print 'Hello'
print "there"
print '''python'''
print """learners!"""

"""
Explanation: Strings can use single, double, and triple quotes:
End of explanation
"""


print "This string contains single quotes but that's ok since it's surrounded by double quotes"
print 'This string is surrounded by single quotes. The cow says: "mooo"'
print '''This string want's to mix both "types" of quotes and that's ok since we surrounded it with triple quotes! '''

"We can " + "concatenate strings " + "together using the + operator"

first = "Sometimes it's better "
middle = "to assign parts of a long string "
last = "to variables then concatenate the strings by the variable names"
sentence = first + middle + last
print sentence

("And sometimes "
"we can split a string "
"on seperate lines and they will be "
"put together since they are surrounded by parentheses!")

"""
Explanation: It's useful that we can use all types of quotes as it allows us to have strings with quote's inside them.
End of explanation
"""


name = "Eve"
len(name)

"""
Explanation: One of the most common built-in functions you'll use is len(), as you might imagine it returns the length of the argument you pass it:
End of explanation
"""


for i in dir('Hello'):
    if not i.startswith('_'):
        print i

"""
Explanation: These methods on strings allow us to modify and ask questions about a string.
End of explanation
"""


word = "hello"
print "capitalize:", word.capitalize() # capitalize the first letter of the string
print "count:", word.count('l') # count how many times the string we pass as an argument appear in 'word'
print "endswith:", word.endswith('o') # T/F if it ends with the string we pass as an argument
print "index:", word.index('o') # Returns index of the string we pass as an argument (remember indexes start at 0)
print "isalpha:", word.isalpha() # methods that start with 'is' give us a clue that the method returns True or False
print "upper:", word.upper() # changes all letters of the string to uppercase
word_two = "HeLlO"
print "swapcase:", word_two.swapcase() # for every letter in the string, swap between upper and lower case
name = "guido van rossum"
print "title:", name.title() # Assumes the string is a name and will change the first letter of each word to uppercase
sentence = "The quick brown fox"
print "split:", sentence.split() # Splits the string into individual words grouped into a list.

"""
Explanation: Here are some examples of what we can do with these methods:
End of explanation
"""


a = [1, 2, 3]
print a
print len(a)

b = [1, 'one', 1.0]
print b
print len(b)

c = [[1, 2, 3], ['one', 'two', 'three'], [1.0, 2.0, 3.0]]
print c
print len(c)

"""
Explanation: Lists
So far we've talked about data types that exist as singular objects. Now we can move on to data types that act as collections of items. The first we'll discuss is lists.
A list is an ordered series of things. A list can contain objects of any type, including other lists! We use square brackets [] around a comma seperated series of objects to define a list.
End of explanation
"""


for i in dir([]):
    if not i.startswith('_'):
        print i

"""
Explanation: Just like strings there are methods available to us to work with lists
End of explanation
"""


names = ['alice', 'bob']
names

"""
Explanation: Let's take a look at how these work. We'll start off with a list of two names, alice and bob. From there we'll use each of the methods to modify the 'names' list.
End of explanation
"""


names.append('eve') 
names

"""
Explanation: append() will add the argument to the end of the list
End of explanation
"""


names.append('bob') 
names

"""
Explanation: We'll append again to show off the next method
End of explanation
"""


print "The word 'bob' is seen:", names.count('bob') 

"""
Explanation: count() tells us how many times the argument occurs in the list
End of explanation
"""


names.extend(['bill', 'sally']) 
names

"""
Explanation: append() only adds a single item at a time, if we want to extend our original list by several items we can use the extend() method and pass in a list of things to add to the end.
End of explanation
"""


print "'sally' is at index:", names.index('sally') 

"""
Explanation: We can find the position of an item using index(), remember lists start counting at 0
End of explanation
"""


names.insert(2, 'mike')
names

"""
Explanation: We can use insert() to put an item at a specific position in the list
End of explanation
"""


names.pop()
names

"""
Explanation: pop() can be used for a couple of things, if we simply need to remove the last item from the list we can call it by itself
End of explanation
"""


last_person = names.pop()
print names
print last_person

"""
Explanation: But, we can also keep that last item in another variable:
End of explanation
"""


names.remove('bob')
names

"""
Explanation: remove() will remove the 1st occurance of the argument we give it. Notice that alice and mike are now next to each other and the last bob is still in the list
End of explanation
"""


names.reverse()
names

"""
Explanation: reverse() does pretty much what you'd expect it to
End of explanation
"""


names.sort()
names

"""
Explanation: As does sort()
End of explanation
"""


print names

print names[0]

"""
Explanation: Interlude: Index notation
Before we move on to our discussion of tuples I'd like to discuss a common way to select items from objects. If we know the index of an item we can select it like this:
End of explanation
"""


'alice'[3]

"""
Explanation: But this will work for other types as well
End of explanation
"""


a = (1, 2, 3)
print a
print type(a)

"""
Explanation: Tuples
Tuples are a bit like lists but have some very important differences. First let's take a look at how they are similar:

ordered
series of things seperated by commas
can be of any length
can be a mix of any type of things

It'll be easier to show thier differences through example. First, let's look at a typical tuple:
End of explanation
"""


a = "example",
print type(a)
print a

"""
Explanation: We normally use parentheses to define a tuple but really any object followed by a comma becomes a tuple. Either way, python will add the parentheses for us anyway:
End of explanation
"""


names = ['alice', 'bob']
people = ('alice', 'bob')
print names
print people

"""
Explanation: Probably the most important difference between a list and a tuple has to do with 'immutability.' Let's take a look at an example:
End of explanation
"""


names[1] = 'eve'
names

people[1] = 'eve'

"""
Explanation: So far, not much difference. But lets say that we wanted to get rid of bob and replace him with eve.
End of explanation
"""


for i in dir(()):
    if not i.startswith('_'):
        print i

"""
Explanation: Uh-oh python has told us that the tuple does not allow us to 'mute' an item in the tuple the way we can with a list. In other words lists are mutable, tuples are immutable.
Let's see what methods we have available to us for tuples:
End of explanation
"""


eng_to_spn = {'one': 'uno', 
              'two': 'dos', 
              'three': 'tres'}
eng_to_spn

"""
Explanation: As a result of the immutability of tuples we don't have many built in methods.
Dictionaries
So the data types we've seen so far are great for collections of things but there are times where we have pieces of information that are related in some way and we'd like to keep track of those relationships.
Let's start off with an example:
End of explanation
"""


eng_to_spn['one']

"""
Explanation: Here we have a relationship between pairs of strings, each pair is seperated by a ':' The object to the left of the ':' is called the key and that thing we will use to select a relationship from the dictionary. The object to the right of the ':' is the value. 
So we have a relationship that can be described as english numbers : spanish numbers
The whole collection of these pairs is the dictionary. We represent dictionaries in python with curly brackets {}
Let's try picking out some data from the dictionary:
End of explanation
"""


eng_to_spn[0]

"""
Explanation: Good, when I use a key to select from the dictionary I get the value associated with that key as a response. Let's try another way:
End of explanation
"""


for key in eng_to_spn:
    print key

"""
Explanation: An important thing to note about dictionaries is that while they are similar to lists and tuples they are unordered.
This is where the similarity to a real-world dictionary breaks down. We call this data type a dictionary because it keeps track of the relationship between one thing (a word) and some other thing (it's definition). In the real world dictionaries are ordered alphabetically but in python the dictionary data type has no order.
And because of that we can't select the first item using the 0 index like we could with a list or tuple.
As a matter of fact reading from a dict will produce an arbitrary order:
End of explanation
"""


eng_to_spn['one'] = 1
eng_to_spn['two'] = 2
eng_to_spn['three'] = 3
eng_to_spn

"""
Explanation: Dictionaries are mutable like a list so we can change the relationship of a pair like this:
End of explanation
"""


eng_to_spn['four'] = 4
eng_to_spn

"""
Explanation: We can also add pairs:
End of explanation
"""


eng_to_spn = {'one': 'uno', 
              'two': 'dos', 
              'three': 'tres'}
eng_to_spn

"""
Explanation: But for now let's keep the dictionary as a set of english words mapped to thier spanish translations:
End of explanation
"""


for i in dir({}):
    if not i.startswith('_'):
        print i

"""
Explanation: Let's take a look at the methods available to us for dictionaries:
End of explanation
"""


eng_to_spn2 = eng_to_spn.copy()
eng_to_spn2

"""
Explanation: copy() will return a "shallow copy" of the dictionary. I won't get into detail here but if you'd like more information see: http://stackoverflow.com/a/3975388
End of explanation
"""


eng_to_spn3 = eng_to_spn.fromkeys(eng_to_spn, 'english')
eng_to_spn3

"""
Explanation: fromkeys() will take the keys from one dict and make a new dict with the same keys but with the keys that we specify
End of explanation
"""


eng_to_spn.get('one')

"""
Explanation: get() will pull the value from a dictionary:
End of explanation
"""


eng_to_spn['four']

print eng_to_spn.get('four', None)

"""
Explanation: What's useful about the get() method is that we can specify a default value in the case that what we are asking for doesn't exist yet in the dictionary. This can avoid errors:
End of explanation
"""


eng_to_spn.has_key('four')

"""
Explanation: We can also ask if a key exists using has_key():
End of explanation
"""


'four' in eng_to_spn

"""
Explanation: Although in this example we could have gotten the same result by doing the following:
End of explanation
"""


eng_to_spn.items()

"""
Explanation: Even though there are methods available to us (the ones we can see with dir()) there may be built-in tools of the language that may be a better choice. 
We can get the pairs as a list of tuples using the items() method:
End of explanation
"""


items = eng_to_spn.iteritems()
print items.next()
print items.next()

"""
Explanation: iteritems() gives an item that we can call .next() on. This is valuable in the case that don't want to load the entire dictionary into memory but still want to iterate through the items.
End of explanation
"""


keys = eng_to_spn.iterkeys()
print keys.next()
print keys.next()

"""
Explanation: We can do the same with the keys using iterkeys():
End of explanation
"""


three = eng_to_spn.pop('three')
print three
eng_to_spn

"""
Explanation: We can remove a key and return the value using pop()
End of explanation
"""


anything = eng_to_spn.popitem()
print anything
eng_to_spn

"""
Explanation: popitem() will remove a associaton and return it as a tuple but you don't get to pick which item you'd like to pop out!
End of explanation
"""


eng_to_spn.setdefault('four', 'quatro')
eng_to_spn

"""
Explanation: setdefault() works a bit like get() but will set the value for us if it doesn't exist in the dictionary:
End of explanation
"""


new_numbers = {'five': 'cinco', 'six': 'seis'}
eng_to_spn.update(new_numbers)
eng_to_spn

"""
Explanation: update() allows us to add values from another dictionary:
End of explanation
"""


eng_to_spn.values()

"""
Explanation: We can see all the values from a dictionary using values()
End of explanation
"""


eng_to_spn.viewitems()

eng_to_spn.viewkeys()

eng_to_spn.viewvalues()

"""
Explanation: These next methods, viewitems(), viewkeys() and viewvalues() each return a dictionary view object. The python docs discuss thier purpose: https://docs.python.org/2/library/stdtypes.html#dictionary-view-objects

The objects returned by dict.viewkeys(), dict.viewvalues() and dict.viewitems() are view objects. They provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the view reflects these changes.
Dictionary views can be iterated over to yield their respective data, and support membership tests:
End of explanation
"""


eng_to_spn.clear()
eng_to_spn

"""
Explanation: We skipped over clear() but here's a good time to see what it does, clear the dictionary out!
End of explanation
"""


