Search code examples
pythondictionarykeyvaluepair

Python - using dictionary to count keys and values


I am a student in a python course where we created a list of tuples (containing 2 elements) that we're trying to manipulate in various ways. In addition, we are to convert those tuple elements into a dictionary and re-create the manipulations using the dictionary and avoiding for loops. The task I'm stuck on is that given a specific id (which could be a key OR value in the dictionary) the function returns all the other keys/values that are found in that dictionary.

It doesn't seem efficient to use a dictionary for this, but that's the section we are on in the course and is specifically asked by the assignment. Also no for loops (if that is possible?). Recall that the id can be either a key or a value in the dictionary.

example_dictionary = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}

def get_interactions(example_dictionary, id):
    output = ''
    for j,k in example_dictionary.items():
        if j == id:
            output = output + k + ' '
        if k == id:
            output = output + j + ' '
    return output

This code works just fine, however it 1) has a for loop (no good) and 2) isn't very pythonic (kind of an eyesore)! How could I use the dictionary more efficiently and condense down my lines? I am in Python 3, Thank you!


Solution

  • Expected result

    Having one dictionary and value named wanted, you want to create another dict being copy of original one with removed all items not having key or value equal to wanted value.

    It can be expressed in form of pytest test case with couple of scenarios.

    import pytest
    scenarios = [
        [
            # dct
            {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
            # wanted
            "A",
            # expected (result)
            {'A': 'C', 'D': 'A'},
        ],
        [
            # dct
            {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
            # wanted
            "E",
            # expected (result)
            {'R': 'E'},
        ],
        [
            # dct
            {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
            # wanted
            "D",
            # expected (result)
            {'D': 'A', 'C': 'D'},
        ],
        [
            # dct
            {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
            # wanted
            "nothere",
            # expected (result)
            {},
        ],
        [
            # dct
            {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
            # wanted
            "A",
            # expected (result)
            {'A': 'C', 'D': 'A'},
        ],
    ]
    
    
    # replace with real implementation
    def get_key_or_val_itms(dct, wanted):
        # something comes here
        return result
    
    @pytest.mark.parametrize("scenario", scenarios)
    def test_it(scenario):
        dct, wanted, expected = scenario
        assert get_key_or_val_itms(dct, wanted) == expected
    

    Do not bother with anything apart from scenarios. It lists couple of test scenarios with input dictionary, value for wanted and expected result.

    Building stones for the solution

    dict.items() - dict to list of tuples

    >>>  dct = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}
    >>>  dct.items()
    [('A', 'C'), ('R', 'E'), ('D', 'A'), ('L', 'R'), ('C', 'D')]
    

    testing membership of a value in a tuple/list

    >>> 'A'  in ('A', 'C')
    True
    >>> 'A'  in ('R', 'E')
    False
    

    Lambda function testing, if wanted is present in a tuple

    lambda allows "in place" function definition. It is often used in places, where some functions expects reference to a function.

    First, create named function tuple_wanted

    >>> wanted = "A"
    >>> def tuple_wanted(tpl):
    ...    return wanted in tpl
    

    and test it (note, that wanted has now value "A"):

    >>> tuple_wanted(('A', 'C'))
    True
    >>> tuple_wanted(('R', 'E'))
    False
    

    Now create the function. To play with it, we store the result of lambda in fun:

    >>> fun = lambda tpl: wanted in tpl
    

    It can be used in the same manner a tuple_wanted before:

    >>> fun(('A', 'C'))
    True
    >>> fun(('R', 'E'))
    False
    

    Later on we will use the result of lambda directly (see filter) without storing it into any variable.

    filter removing all list items not passing some test

    filter gets test function and iterable (e.g. list of items) to test by it.

    Result of calling filter is list of items from the iterable, which passed the test.

    In our case, we want to pass only the tuples, containing wanted value (e.g. "A")

    >>> filter(tuple_wanted, dct.items())
        [('A', 'C'), ('D', 'A')]
    >>> filter(fun, dct.items())
        [('A', 'C'), ('D', 'A')]
    >>> filter(lambda tpl: wanted in tpl, dct.items())
        [('A', 'C'), ('D', 'A')]
    

    Convert list of tuples with 2 items into dictionary

    >>> tpllst = [('A', 'C'), ('D', 'A')]
    >>> dict(tpllst)
        {'A': 'C', 'D': 'A'}
    

    Function doing the work

    Long version

    This version is here to explain what is going on step by step:

    def get_key_or_val_itms(dct, wanted):
        # dict as [(key, val), (key2, val2), ...]
        tpldct = dct.items()
        # find tuples, where either key or val equals `wanted` value
        # first make function, which detects, the tuple we search for
    
        def tuple_wanted(tpl):
            return wanted in tpl
        # now use it to filter only what we search for
        restpldct = filter(tuple_wanted, tpldct)
        # finally, turn the result into dict
        return dict(restpldct)
    

    Short version

    def get_key_or_val_itms(dct, wanted):
        return dict(filter(lambda tpl: wanted in tpl, dct.items()))
    

    Conclusions

    It works (with either long or short version of the function):

    >>> dct = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}
    >>> wanted = "A"
    >>> get_key_or_val_itms(dct, wanted)
    {'A': 'C', 'D': 'A'}
    

    If you put the function into file with test suite, calling $ py.test -sv the_file.py shall output:

    $ py.test -sv the_file.py
    py.test================================ test session starts =========================
    =======
    platform linux2 -- Python 2.7.9, pytest-2.8.7, py-1.4.31, pluggy-0.3.1 -- /home/javl/
    .virtualenvs/stack/bin/python2
    cachedir: .cache
    rootdir: /home/javl/sandbox/stack/dict, inifile:
    collected 5 items
    
    countdict.py::test_it[scenario0] PASSED
    countdict.py::test_it[scenario1] PASSED
    countdict.py::test_it[scenario2] PASSED
    countdict.py::test_it[scenario3] PASSED
    countdict.py::test_it[scenario4] PASSED
    
    ============================= 5 passed in 0.01 seconds ==============================
    

    As can be seen, all the scenarios are passing.

    Explanation how py.test works is out of scope of this answer, to learn more about it, see http://pytest.org/latest/