.. _ch03-python-dictionaries: =============================================================== Dictionaries =============================================================== Another useful data type built into Python is the dictionary. A dictionary is like a list, but it is more general. In a list, the indices have to be integers; in a dictionary they can be any type. .. _ch03-python-key-value: Key:value pair ---------------------------------------------------------------- You can think of a dictionary as a mapping between two things: * **keys**: a set of indices, * **values**: a set of values corresponding to each key. Each key maps to a value. The association of a key and a value pair is called a **key:value pair**. You can define an empty dictionary in two ways. One way is to use a built-in function ``dict``:: >>> eng2kor = dict() or altenatively, use an empty squiggly-brackets, ``{}``:: >>> eng2kor = {} In both cases you see the following:: >>> type(eng2kor) >>> print eng2kor {} Let's add a new pair to the dictionary, ``eng2kor``. To add one, you can use square brackets:: >>> eng2kor['one'] = 'hana' This creates a new key:value pair that maps from the key ``one`` to the value ``hana``. If we print the dictionary again:: >>> print eng2kor # or simply >>> eng2kor {'one': 'hana'} One can add multiple pairs using this output format:: >>> eng2kor={'one':'hana','two':'dool','three':'set','four':'net'} Unfortunately, ``append`` method cannot be invoked on a dictionary *directly* (i.e., ``eng2kor.append('five')`` won't work). Instead, one can keep adding new pairs using:: >>> eng2kor['five'] = 'dasut' or using ``update`` (try ``help(dict)`` or ``dir(dict)`` to see more options):: >>> eng2kor.update({'five':'dasut'}) Let's now print to see what we have defined so far:: >>> print eng2kor {'four': 'net', 'three': 'set', 'five': 'dasut', 'two': 'dool', 'one': 'hana'} The order of the key:value pairs does **not** look like what you might have expected. In fact, they might look different on different computers. Surprisingly, the order of pairs in a dictionary is unpredictable. This is because the elements of a dictionary are never indexed with integer indices (they are still iterable though). Even though it might look confusing, this is not a problem as long as the one-to-one correspondance between the key:value relationships remain unchanged, which is the case all the time:: >>> eng2kor['five'] 'dasut' >>> eng2kor['two'] 'dool' or traversing through the dictionary will show:: >>> for i in eng2kor: ... print i ... four three five two one Here we see that traversing a dictionary is executed among the key lists, not the value lists. We can use the iterators that are defined as methods in dictionary (try ``help(dict)`` and find these), ``iteritems()``, ``iterkeys()``, and ``itervalues()``:: >>> for eng, kor in eng2kor.iteritems(): ... print eng, kor ... four net one hana five dasut three set two dool or, to just get keys:: >>> for eng in eng2kor.iterkeys(): ... print eng ... four one five three two Similarly, to just get values:: >>> for kor in eng2kor.itervalues(): ... print kor ... net hana dasut set dool The method ``items()`` defined in dictionary changes the dictionary to a list with ``(key,value)`` pairs as tuples:: >>> eng2kor.items() [('four', 'net'), ('one', 'hana'), ('five', 'dasut'), ('three', 'set'), ('two', 'dool')] We can apply some of the methods we learned so far to a dictionary:: >>> len(eng2kor) 5 >>> 'one' in eng2kor True >>> 'net' in eng2kor False The second example of the ``in`` operator tells us that Python checks if the search word appears as a ``key``, but not as a ``value`` in the dictionary. To see whether something appears as a value instead of a key, is to use the method ``values`` which returns the values as a list:: >>> print eng2kor.values() ['net', 'set', 'dasut', 'dool', 'hana'] With this we can now search:: >>> vals = eng2kor.values() >>> 'net' in vals True We can also compare between keys or between values:: >>> eng2kor.keys() ['four', 'three', 'five', 'two', 'one'] >>> eng2kor.keys()[0].__gt__(eng2kor.keys()[2]) # this is equivalent to eng2kor.keys()[0] > eng2kor.keys()[2] True Comparing the corresponding values (i.e., the two corresponding value elements to the 0th and 2nd key elements):: >>> eng2kor.values()[0] > eng2kor.values()[2] True .. _ch03-python-counter: Dictionary as a set of counters ---------------------------------------------------------------- Consider that you are given a string and you wish to count how many times each character appears. Recall that we did this before using a list :ref:`ch03-python-searching-counting`. This time let's use a dictionary and see how we can implement a more general algorithm: .. literalinclude:: ./codes/examples/dictionaries/histogram.py :language: python :linenos: Running this in the script mode will give:: $ python histogram.py (a): {'a': 1, 'p': 2, 'e': 1, 'l': 1} (b): {'a': 1, 'p': 2, 'e': 1, 'l': 1} (c): False (d): 4303246232 (e): 4303246512 (f): ['a', 'p', 'e', 'l'] (g): False (h): True (i): [1, 2, 1, 1] (j): False (k): False (l): 1 (m): 159 (n):-------- a 1 p 2 e 1 l 1 Dictionaries and lists ---------------------------------------------------------------- Lists can only appear as values in a dictionary, but **not** keys. For example, if you try:: >>> t=['a','e','l'] >>> type(t) >>> d = dict() >>> d[t] Traceback (most recent call last): File "", line 1, in TypeError: unhashable type: 'list' >>> d['1'] = t >>> d {'1': ['a','e','l']} The above example confirms that lists can only be used as values. Now, let's consider an application of using lists as values. Take a look at what we just obtained in the last outcome, ``{'1': ['a','e','l']}``. This looks like an inverse map of the output ``(a)`` or ``(b)``! This example tells us that we may implement an inverse map routine which inverts keys and values in a dictionary. Here is a function that inversts a dictionary: .. literalinclude:: ./codes/examples/dictionaries/invert_dictionary.py :language: python :linenos: The result look like:: $ python invert_dictionary.py {'a': 1, 'p': 2, 'e': 1, 'l': 1} {1: ['a', 'e', 'l'], 2: ['p']} {1: 1, 2: 1} {1: [1, 2]} .. note:: Why are we getting the last two outcomes? Dictionaries as memos ---------------------------------------------------------------- A dictionary can be used for storing quantities that have been already computed, and thereby one doesn't need to repeat such previously computed operations. In computing, this clearly allows a faster performance by efficiently reusing the stored data. For example, a straightforward implementation of `Fibonacci sequence `_ can look like: .. literalinclude:: ./codes/examples/dictionaries/fibonacci.py :language: python :linenos: Notice now how many times those terms that appear early in the sequence are recursively called repeatedly -- very many! A dictionary can be used in this case to keep track of the terms that have been already evaluated and store them in a dictionary: .. literalinclude:: ./codes/examples/dictionaries/fibonacci_dict.py :language: python :linenos: In the above example, ``known`` is a dictionary that stores the Fibonacci numbers we already know. It starts with the first two terms in the sequence: ``F0=0`` and ``F1=1``, or in other words, 0 maps to 0 and 1 maps to 1. To compare CPU runtimes in seconds, we can do as follow: .. literalinclude:: ./codes/examples/dictionaries/run_fibonacci.py :language: python :linenos: Global variables ---------------------------------------------------------------- In the previous example, ``known`` is initialized *outside* the function. Therefore, it belongs to the special frame called ``__main__``. Variables in ``__main__`` have their scopes *globally* because they can be accessed from any function. In order to modify any mutable global variable, especially within a local function, you need to **declare** it before using it. The following example illustrates how the global variables behaves and how they should be modified in a local function: .. literalinclude:: ./codes/examples/dictionaries/global.py :language: python :linenos: The result looks like:: (a): True (b): False (c): True (d): True (e): False (f): True An example study ---------------------------------------------------------------- Consider the following example which has been originally adopted from `Dive Into Python 3 `_ and modified for the class. This routine takes a computer file size in kilobytes as an input and converts it approximately to a human-readable form, e.g., 1TB, or 931 GiB, etc. .. literalinclude:: ./codes/examples/dictionaries/humansize.py :language: python :linenos: The output from running the routine looks like:: $ python humansize.py (a) with the multiple of 1000 bytes: 1 TB (b) with the multiple of 1024 bytes: 931 GiB