# CREATE ALIASES OF METHODS

# When working with a specific set of variables, it is
# often handy to write a couple of aliases to save a few redundant keystrokes.
# The pythonic way to do thisis quite interesting and let you feel once more
# how cool is our friend the Python.

# Let's say you got a list where you store words.
# You want to save keystrokes for appending and deleting items.
# Let's create aw() for 'a'dding 'w'ord and dw() for ... well you guessed it.
# This works for any instance of an object.

>>> words = ['eggs', 'spam']
>>> aw = words.append
>>> dw = words.remove

>>> words
['eggs', 'spam']

>>> aw('potatoes')
>>> words
['eggs', 'potatoes', 'spam']

>>> dw('spam')
>>> words
['eggs', 'potatoes']

# If you assign your alias to a class name you can still use the same syntax.
# However, be sure to tell your alias on what instance it impacts.
# Example with al() for 'a'dd to 'l'ist:

>>> al = list.append
# Wrong syntax -- It can't guess what instance you play with.
>>> al('cherry')
Traceback (most recent call last):
    File "(stdin)", line 1, in <module>
TypeError: descriptor 'append' requires a 'list' object but received a 'str'
# Proper call
>>> al(words, 'cherry')
>>> words
['cherry', 'eggs', 'potatoes']

# Last point:
# Be sure to document your aliases not to turn your code into a real cypher.


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# USING A CLASS AS CONTAINER.

# Situations happen where you need something handy to store a few variables
# into a single container. A dictionary would fit for the task, however having
# a class can ease the way to access and store those variables.
# Each class wraps a built-in dictionary, let's use it!

>>> class Container:
...     def __init__(self, **elements):
...         self.__dict__ = elements
...     def __repr__(self):
...         return repr(self.__dict__)
...     def iteritems(self):
...        for x in self.__dict__.iteritems():
...            yield x
            
>>> Entry = Container(word="house", synonyms=['apartment', 'flat'])

# Access your elements with a mix of dictionary and object syntax:
>>> Entry.word
"house"
>>> Entry
{'synonyms': ['apartement', 'flat'], 'word': 'house'}
>>> for k,v in Entry.iteritems():
...    print k,"=>",v
synonyms => ['apartment', 'flat']
word => house
      
# You can delete the container and its attributes with the 'del' statement:
>>> del Entry.word
>>> Entry.word
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: Container instance has no attribute 'word'

>>> del Entry.synonyms[0]
>>> Entry.synonyms
['flat']

>>> del Entry
>>> Entry
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'Entry' is not defined


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# REDUCING CLASSES MEMORY FOOTPRINT

# When using class as a container for storing a couple of variables (such as
# in the example above) which are predefinied, it is possible to reduce the
# memory footprint used by the class by assigning to __slots__ these
# predefined attributes. This lighten the class memory use by preventing it
# from using its inner __dict__ to store its attributes.

>>> class Container:
...     def __init__(self):
...         __slots__ = 'word', 'synonyms'


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# RETURNING A LIST OR DICTIONARY ELEMENT IF IT EXISTS

# You want to get the value of say list[4] or dict['food'] but
# you aren't sure whether it exits.
# Here is a snippet for each case that solve this.
# We wll return None when the index or key doesn't exists.

# For a dictionary first:
>>> dict = {"key": "value"}
>>> dict.get("key", None)
'value'
>>> r = dict.get('not_here', None)
>>> r is None
True

# Unfortunately get() isn't implemented for lists.
# A way to do this via exception handling. Wrap these 2 lines in a function for
# a handy usage.
>>> list = ['eggs', 'spam']
>>> try: r = list[0]
... except IndexError: r = None
>>> r
'eggs'
>>> try: r = list[42]
... except IndexError: r = None
>>> r is None
True


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# ONE TO MANY MAPPING WITH A DICTIONARY

# The usual implementation for this is to have the dict['key'] mapped to a
# list. Here we will stick to this idea and will make use of the setdefault()
# method to make it amazingly easy.
# The python doc explain it like this:
#   setdefault(key[, default])
#     If key is in the dictionary, return its value. If not, insert key with a
#     value of 'default' and return 'default'. 'default' defaults to None.
# When dict['key'] exists the element is appended to its list.
# Otherwise it is created and then the element is appended.

>>> dict = {'cake': []}
>>> dict.setdefault('cake', []).append('flour')
>>> dict.setdefault('cake', []).append('butter')
>>> dict
{'cake': ['flour', 'butter']}
>>> dict.setdefault('brownie', []).append('chocolate')
>>> dict
{'cake': ['flour', 'butter'], 'brownie': ['chocolate']}


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# LIST KEYS COMMON TO TWO DIFFERENT DICTIONARIES

>>> dict1 = {'eggs': 'yummy', 'spam': 'yummmmmy!'}
>>> dict2 = {'eggs': 'taste ok...', 'spinach': 'beurk!'}
>>> filter(dict1.has_key, dict2.keys())
['eggs']


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# SET A RECURSION LIMIT

# Python defaults to a recursion limit of 1000 levels.
# You can tell python the recursion limit via the sys module
# by invocing the setrecursinglimit method as follow:
>>> import sys
>>> sys.setrecursionlimit(1500)


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# MERGING TWO DICTIONARIES

>>> dict1 = {"foo": 3, "alpha": 42}
>>> dict2 = {"echo": 10, "alpha": 11}

# When common key found, values of dict1 are overided by dict2's.
>>> dict1.update(dict2)
{'alpha': 11, 'foo': 3, 'echo': 10}
# When common key found, values of dict2 are overided by dict1's.
>>> dict2.update(dict1)
{'alpha': 42, 'foo': 3, 'echo': 10}

# Another way of doing it:
# When common key found, values of dict1 are overided by dict2's.
>>> dict(dict1, **dict2)
{'alpha': 11, 'foo': 3, 'echo': 10}
# When common key found, values of dict2 are overided by dict1's.
>>> dict(dict2, **dict1)
{'alpha': 42, 'foo': 3, 'echo': 10}

# To keep both values in a list run the code below before the merging step.
# When merging be sure to overide the keys of the dict you looped through.
# Thanks to has_key() we keep a O(n) running time (n being the dict size).
>>> for k in dict1.keys():
...    if dict2.has_key(k): dict2[k] = [dict2[k],dict1[k]]
>>> dict(dict1, **dict2)
{'alpha': [22, 42], 'foo': 3, 'echo': 10}


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# IMPLEMENT STATIC METHOD

# Static method in python are possible.
# Before each method you want to make static insert the idiom
# @staticmethod right above the method's name.
# Note that static methods do not take the implicit first argument 'self'.
# You can either call them from the class name or a class instance.
>>> class NepsilonClass:
...    @staticmethod
...    def do_something(arg1, arg2):
...        pass


-------8<-------8<------8<-------8<------8<-------8<------8<-------8<------8<-------8<------


# INTRODUCTION TO PYTHON DECORATORS

# Python supports decorator -- not to be confused with the decorator pattern
# --, although they can be use to implement this pattern.
# Python's decorator can trigger another function/method when the function we
# 'decorated' is called.
# This allow you for instance to do some monitoring against functions/methods
# without editing them.
# Below decorators are used to time the running duration of the quicksort
# function and to count its recusion number.
import time,random

def timing(func):
    def wrapper(*arg):
        t1 = time.clock()
        if (len(*arg) < 42): print arg
        func(*arg)
        if (len(*arg) < 42): print arg
        t2 = time.clock()
        print 'The function %s took %0.3f ms and went through %s recursions.'\
             % (func.func_name, (t2-t1)*1000.0, count)
    return wrapper

def recursion_count(func):
    def wrapper(*arg):
        global count
        func(*arg)
        count += 1
    return wrapper

@recursion_count
def quicksort(array, start, end):

    if (end <= start):
        return

    q = start
    p = end - 1

    pivot = array[end-1]

    while (p > q):
        while (array[p] >= pivot and p > start):
            p -= 1
        while (array[q] < pivot and q < end-1):
            q += 1
        if (p > q):
            (array[p], array[q]) = (array[q], array[p])

    if (q != end - 1):
        (array[q], array[end-1]) = (array[end-1], array[q])

    quicksort(array, start, q)
    quicksort(array, q+1, end)

@timing
def sort(array):
    quicksort(array, 0, len(array))

if __name__ == "__main__":

    count = 0
    list = [random.random() for x in range(1,10000)]
    sort(list)