Python Tutorial Section 5

This is a very long section so buckle up!

String Traversal

When trying to traverse strings in Python, there are a couple different ways to do it. First, there is a while loop, but you have to declare the length of the string. Also, you have to remember that an array of letters starts at 0, which requires subtracting 1 from your index or ending the loop when the index is less than (not >=) the length of the word (eg. Len(string) -1).

There are also for loops. These are a lot cleaner but I can’t be sure about performance. (Note: is there a way to time the execution of a python function? I’d be curious to know which is faster, a for loop or a while loop…). If I needed to iterate through a string, I would probably stick with a for loop, but that’s not to say that while loops don’t have a purpose.

Slices

Now, we know from the previous section that we can retrieve a single letter by stating it’s index, but Python also allows for entering a range of indices that provides a chunk or “slice” of a word. Ranges in Python are declared as [n:m], whereas in ruby you would say n..m.

Mutating Strings

Short version is that strings are immutable. In order to have a string like another string, you’ll need to create a new one or overwrite the entire string. Altering an existing string isn’t allowed and will throw an error.

Search & Count

This section seems trivial but my limited experiences tell me that optimized searching plays a very large roll in positive UX and high performance applications. Understanding the foundations of how search is done should not be taken lightly.

Similarly counting can be just as much of a challenge in database queries. Counting isn’t as performance intensive, but is used so frequently that optimizing counting procedures would be wise.

String Functions vs. Methods

Now, this is the first time I’ve heard this but in a way, it’s just different vocabulary from what I’m used to in Ruby on Rails. When a string is used as an argument in some procedure, it is simply being used as a variable within a series of steps and can be evaluated from there. On the other hand, when using dot notation with some instance of a string, you are essentially declaring a specific function within the string namespace, that dictates how that method is executed. This is a little hard to explain, but the easiest explanation of this concept in practice is if you had a first() method. If you were to use first() on an array, it would know to return the first element of the array, but if you used it on a string, then it would know to return the first letter. Both of these are methods with the same exact name, but they’ve been namespace-d based on the object they’re invocated on. You could also write a first(arg1) function, which would check the type and evaluate the the same thing as our object specific methods, but that seems less DRY to me. It’s like a helper versus a instance level method in Rails.

The in Operator

This is quite handy and there’s nothing quiet as simple as the in operator in Ruby. It’s as simple as it seems, it simples returns a Boolean based on whether the letter is IN the string or not.

String Comparison

Python allows for string comparison, by evaluating them in alphabetical order. So “abc” < “bcd” evaluates to True; however, capitalization can cause problems. Eg. ”abc” < “Bcd” evaluates to False. It is common practice to standardize the case of the words being compared in Python.

Lists

Apparently, lists are just arrays? Now, like strings, you can access a specific element of a list, by providing it’s index in brackets.

myList = [1, 2, 3]  
myList[2] == 3  
>>> True

Unlike strings though, you can mutate lists. So if we instead wanted the 2-th element of our list to be a 7:

myList = [1, 2, 3]  
myList[2] = 7 # mutates the list  
myList  
>>> [1, 2, 7]

List Traversal

Lists are turning out to be pretty similar to strings. One major differences is the ability to mutate lists. This comes into play when trying to traverse, or loop through a variable. In strings, we would probably use a for loop because we are often only reading, not mutating the string and acting based on what happens. In the case of a list, since we are able to mutate it, we’re more likely to need the index. In that case, we’ll probably need to do some of the additional work that a while loop requires anyway, so I’m not sure I see any advantages in particular to the for loop over the while loop.

Regardless, we will want to use the len() function and the range() functions to determine the limits placed on our for loops when mutating a list.

List Operations

Just like strings, we can perform operations on lists. We can concatenate them together, or we can pull out slices by using ranges.

List Functions vs. Methods

I went more in depth to the meaning of these both with strings, and all of the principles hold the same. Python provides methods that are unique to lists, which makes manipulating them simpler. For example, you have append() and extend() which are similar to concatenating, but are “destructive” methods in that they permanently alter the object they are called on. They return nothing themselves, but if you print the object, you’ll see the changes.

a = [1, 2, 3]  
b = [2, 3, 4]  
a + b  
>>> [1, 2, 3, 2, 3, 4]
a  
>>>[1, 2, 3]
a.extend(b)  
a  
>>> [1, 2, 3, 2, 3, 4]

Accumulators

I hadn’t seen this noted anywhere else yet, so I figured I’d make mention of it. If you’re looping through a list and wish to accumulate the sum of all the elements (or any collection of numbers for that matter), you could write the total as total = total + x but Python provides the shorthand of total += x. This is called the augmented assignment statement.

Map, Filter, Reduce

If you were to do something like mentioned above, where you took a list and parsed it down to it’s total, that is commonly known as a reduce.

Now, this is beginning to shed some light on my understanding of Ruby. The map method in Ruby is a little abstract when first viewed but this Python tutorial explains it a little more simply. It “maps” a function across all the elements of a collection and produces a new, mutated collection. Example:

def capitalize_all(t):  
    res = []
    for s in t:
        res.append(s.capitalize())
return res  

A similar type of procedure for lists is to filter them. Instead of affecting all elements in a list, this evaluates that each element meets a specific criterion and returns it if it does. For example, you might filter a list to only return the capitalized elements.

Deleting Elements

There are few ways to delete elements in a list. The first is just like in JavaScript, pop(). Pop takes either the index of the element you wish to delete, or by default will delete the last element. Similarily, there is del which will delete the given element. The differenc is that pop returns the element that was deleted where del doesn't. del will also accept a range of values.

a = [1, 2, 3]  
a.pop() #In this instance, the same as a.pop(2)  
>>> 3
a  
>>> [1, 2]
del a[0]  
a  
>>> [2]
b = [4, 5, 6]  
del b[1:2]  
b  
>>> [4]

Conversely, if you know the value of the element you wish to delete, but not the index, you can use remove().

a = [1, 2, 3]  
a.remove(2)  
a  
>>> [1, 3]

Strings and Lists

We've seen all the ways lists were the same or different as strings, but now we can use them together. First, to turn a string into a list, you can use the list() method. It will break a string into a list of each character in the string. Similarily, there is the split() method, but instead of making a list of all the characers, it breaks it up based on some delimiter, which is a space by default. Lastly, there is hte join() method which will combine the elements of a list into a string with a given delimiter.

a = "blah"  
b = list(a)  
b  
>>> "["b", "l", "a", "h"]

a = "blah blah blah"  
b = a.split()  
b  
>>> ["blah", "blah", "blah"]
c = b.join("-")  
c  
>>> "blah-blah-blah"

Python Object Creation and Comparison

Now this is an interesting nuance to Python. When you wish to evaluate, not just if something is similar, but is the exact same object, you use the is operator. In Python, setting two variables to the same string, will point to the same object; however, setting two different lists to the same elements, will be different objects.

a = "blah"  
b = "blah"  
a == b  
>>> True
a = [1, 2, 3]  
b = [1, 2, 3]  
a == b  
False  

Being equivalent and an identical are not necessarily the same thing. The two strings above are identical; wheras, the two lists are equivalent.

Aliased Objects

Where this becomes really strange is mutating identical lists. In the below example, mutating b will actually affect a.

>>> a = [1, 2, 3]
>>> b = a
>>> b
[1, 2, 3]
>>> a.pop()
3  
>>> a
[1, 2]
>>> b
[1, 2]

Destructive or Not?

What a lot of these list methods demostrate is a potential for destructive methods. Whereas strings are un-mutatable, lists are not. Keeping track of which methods/functions permanently change an object, is important and can help avoid having a terrible debuggng session.

Tuples

I'm sure I'll see a use but now we've got what appears to another version of an array, called a tuple. A Tuple is just a comma seperated list of values, that is immutatable. My guess is that this has something to do with performance... Arrays provide a host of mutation related function but are potentailly slower.

#Tuples can be created as follows:
t = 'a', 'b', 'c'  
t = ('a', 'b', 'c') #Parens are not necessary  
t = 'a',  
t = tuple()  

Lists and Tuples

Most functions that work on Lists work on Tuples. Extracting data from a Tuple, very much works the same - simple state the index or range, which you want to retrieve. The major difference though is that you can mutate a tuple.

Tuple Assignment

One can assign multiple variables simultaneously through tuples. Simply have a tuple of variable names equal to a tuple of values and they'll be saved to one another.

a, b = 'blah-a', 'blah-b'  

An uneven number of values on each side will however, result in an error.

Returning Tuples

An often handy feature of tuples is the ability to return multiple values. return a, b will return both a and ```b````!

Variable Number of Inputs

When looking to input an undetermined number of values, the *gathers argument will allow for any number of inputs to be input. Similarly, one can call * scatter to break apart a tuple into it's multiple components.

Zipping and Itemizing

Zip is an interesting little function in python which when called on a string and a list, creates a list of tuples with one item from each input.

Similarly, one can call the item() method on a dictionary, which produces the same thing but is a list of key value pairs in tuples. Conversely, you can call the dict() function on a tuple, to create a dictionary with sets of keys an values.

DSU Design Pattern

DSU stands for Decorate, Sort, Undecorate. This is a design pattern frequently used for sorting elements based on some related attribute by using tuples. A good example is a follow:

def sort_by_length(words):  
    t = []
    for word in words:
       t.append((len(word), word))

    t.sort(reverse=True)

    res = []
    for length, word in t:
        res.append(word)
    return res

Lions, and Tigers, and Sequences, Oh my!

So we've gotten to the point where this python tutorial is being self aware of the numerous types of sequences it's presented. Luckily, it tries to help us understand some of the advantages and disadvantages. Primarily though, the largest distinctions are mutatability(sp?), enumeration, and readability of syntax. Lists are great for returning objects, but if you need to be able to mutate a sequence of items, a list might be better.

And don't forget that a list is basically an array in Ruby, and a Dictionary is basically a Hash. A string is still a string, and I couldn't think of a tuple equivalent? Maybe an OpenStruct?

Functional Programming

I always thought Object Oriented programming and Functional programming were sort of inverses of one another, but this note section seems to indicate otherwise. The gist of these notes is that in functional program, you treat functions like objects (strings, lists, etc.) that can be acted. Much like f(x, g(y, z)).

Lambda Functions

These are interesting one line functions where a list of variables can be passed in immediately to evaluate some procedure. Lambda's have always alluded me in Ruby and I can't say these are making it much clearer. I don't quite see the difference between a lambda statement and simply passing a block to a function.

Worksheet 5.1.1

This has less to do with the worksheet and more to do with my checker. Unforunately, I can't build a function to check if some previously defined variable is throwing an exception, so for now I'll just comment on errors in the solution.

I like using a negative step to count a range backwards, but this is kind of configuration over convention. The alternative in Ruby is simply specifying a backwards range:

#In ruby:
10..3 #same as range(10, 3, -1)  

I mean, when are you going to provide a higher first argument if you aren't specifying a backwards range!?

This threw me through a loop (no pun intended):

[x for x in range(3)]

However, it occurred to me that it's a short hand for simply returning the output of some loop. In this case, I think it's the same as doing this:

a = []  
for x in range(3):  
    a.append(x)

In short, it's saying, "Add this element x to some array where x is each value in this range." I feel like the Ruby shorthand doesn't seem as cryptic as the Python equivalents...

Worksheet 5.3.8, 5.3.9

We worked with these briefly in some of the earlier worksheets, but the term list comprehensions isn't mentioned until these work sheets. It's an interesting little tool, that I had to turn to Google for an explanaton. Essentially, this:

a = []  
for x in range(3):  
    a.append(x)

and this:

[x for x in range(3)]

are the same! I mention this example just above this, but I thought a name and definition was warranted. I will say, that the less succinct but more traditional for loop is much easier to debug your algorithms from. My suggestion would be to first implement whatever you're aiming to do in a regular for loop then refactor to use a list comprehension.

Nothing too exciting beyond that! As usual, all solutions are on Github