Python

Python Map Example

In this tutorial, by using python 3.4, we’ll talk about map functions.

Strictly speaking, a map function, is a function that accepts a function and an array as arguments, and applies the given function to each element of the array, returning another array with the result of each transformation.

So, in other words, a map applies a transformation to each element of an array and returns the results.

How would we implement it? Let’s see a simple implementation to understand how a map works:
 
 

1. Map Implementation

custom_map.py

def custom_map(transformation, array):
    return [transformation(e) for e in array]

So, we defined a function that receives a transformation and an array as arguments, and using for comprehension it returns an array of the transformations of each of these elements. To use it:

custom_map.py

def custom_map(transformation, array):
    return [transformation(e) for e in array]


def add_one(x): return x + 1


def test():
    my_array = [1, 2, 3, 4, 5]
    print(str(custom_map(add_one, my_array)))

if __name__ == "__main__":
    test()

As functions are first class objects in python, we can define a function called add_one and pass it as argument to our custom_map with an array containing from 1 to 5. The output, as we expected, is:

[2, 3, 4, 5, 6]

2. Python’s Map

Now, Python provides a built-in map function, which behaves almost like our custom map. The differences:

  • It does not return a list, but a map object, but if you apply list to it, will become a list.
  • It does not accept only one array, but n instead, passing n arguments to the provided transformation, applying it to the items of the provided arrays in parallel (returning None as default if an array is shorter than others) like: transformation(array1[0], array2[0], ..., arrayN[0]).

So, let’s see an example of this:

multiply.py

def multiply(x, y):
    return x * y


def test():
    xs = [1, 2, 3, 4, 5, 6, 7, 8]
    ys = [2, 3, 4, 5, 6, 7]
    map_object = map(multiply, xs, ys)
    result_list = list(map_object)
    print(str(result_list))


if __name__ == "__main__":
    test()

Here, we defined an array xs and another one ys and ys is shorter than xs. The result will look like:

[2, 6, 12, 20, 30, 42]

As you see, the result contains as many arguments as the shorter array, as there are not enough arguments to pass to our transformation function (multiply).

Also, notice we apply the function list to the result, instead of printing it directly, as the result of a map function is not a list, but a map object as we said before.

In real life, we often don’t want to define a function to pass it to a map, as it probably will only be used there. Lambdas come to help. Let’s refactor our multiply to see this:

multiply.py

def test():
    xs = [1, 2, 3, 4, 5, 6, 7, 8]
    ys = [2, 3, 4, 5, 6, 7]
    map_object = map(lambda x, y: x * y, xs, ys)
    result_list = list(map_object)
    print(str(result_list))


if __name__ == "__main__":
    test()

The output will look the same, and our function is now defined right there, one the run, so we don’t have to worry about someone reusing it if they are not meant to.

Let’s see a real life case scenario in which map comes to save the day. Imagine a scenario in which you have a catalog of products and for each product a history of prices that looks like this:

example.py

from datetime import datetime

products = {
    1: {
        "id": 1,
        "name": "Python Book"
    },
    2: {
        "id": 2,
        "name": "JavaScript Book"
    },
    3: {
        "id": 3,
        "name": "HTML Book"
    }
}
prices = {
    1: [
        {
            "id": 1,
            "product_id": 1,
            "amount": 2.75,
            "date": datetime(2016, 2, 1, 11, 11, 17, 683987)
        },
        {
            "id": 4,
            "product_id": 1,
            "amount": 3.99,
            "date": datetime(2016, 2, 5, 11, 11, 17, 683987)
        }
    ],
    2: [
        {
            "id": 2,
            "product_id": 2,
            "amount": 1.10,
            "date": datetime(2015, 1, 5, 11, 11, 17, 683987)
        },
        {
            "id": 2,
            "product_id": 2,
            "amount": 1.99,
            "date": datetime(2015, 12, 5, 11, 11, 17, 683987)
        }
    ],
    3: [
        {
            "id": 3,
            "product_id": 3,
            "amount": 3,
            "date": datetime(2015, 12, 20, 11, 11, 17, 683987)
        }
    ]
}

When you are going to show these products to the public, you want to attach to each of them the last updated price. So, given this issue, a function which does attach the last price to a product looks like this:

example.py

...

def complete_product(product):
    if product is None:
        return None
    product_prices = prices.get(product.get("id"))
    if product_prices is None:
        return None
    else:
        p = product.copy()
        p["price"] = max(product_prices, key=lambda price: price.get("date")).copy()
        return p

This function receives a product as argument, and if it’s not None and there are prices for this product, it returns a copy of the product with a copy of its last price attached. The functions that use this one looks like this:

example.py

...
def get_product(product_id):
    return complete_product(products.get(product_id))


def many_products(ids):
    return list(map(get_product, ids))


def all_products():
    return list(map(complete_product, products.values()))

The first one, get_product, is pretty simple, it just reads one product and returns the result of applying complete_product to it. The other ones are the functions that are of interest to us.

The second function assumes, without any validation, that ids is an iterable of integers. It applies a map to it passing the function get_product as transformation, and with the resulting map object, it creates a list.

The third function receives no parameters, it just applies a map to every product, passing complete_product as transformation.

Another example of a use case of map would be when we need to discard information. Imagine a service that provides all users in an authentication system. Of course, the passwords would be hashed, therefor, not readable, but still it would be a bad idea to return those hashed with the users. Let’s see:

example2.py

users = [
    {
        "name": "svinci",
        "salt": "098u n4v04",
        "hashed_password": "q423uinf9304fh2u4nf3410uth1394hf"
    },
    {
        "name": "admin",
        "salt": "0198234nva",
        "hashed_password": "3894tumn13498ujc843jmcv92384vmqv"
    }
]


def all_users():
    def discard_passwords(user):
        u = user.copy()
        del u["salt"]
        del u["hashed_password"]
        return u
    return list(map(discard_passwords, users))


print(str(all_users()))

In all_users we are applying discard_passwords to all users an creating a list with the result. The transformation function receives a user, an returns a copy of it with the salt and hashed_passwords removed.

3. Map Object

Now, we’ve been transforming this map object to a list, which is not wrong at all, it’s the most common use case, but there is another way.

A map object is an iterator that applies the transformation function to every item of the provided iterable, yielding the results. This is a neat solution, which optimizes a lot memory consumption, as the transformation is applied only when the element is requested.

Also, as the map objects are iterators, we can use them directly. Let’s see the full example and refactor it a bit to use map objects instead of lists.

example.py

def complete_product(product):
    if product is None:
        return None
    product_prices = prices.get(product.get("id"))
    if product_prices is None:
        return None
    else:
        p = product.copy()
        p["price"] = max(product_prices, key=lambda price: price.get("date")).copy()
        return p


def get_product(product_id):
    return complete_product(products.get(product_id))


def many_products(ids):
    return map(get_product, ids)


def all_products():
    return map(complete_product, products.values())


if __name__ == "__main__":
    print("printing all...")
    ps = all_products()
    for p in ps:
        print(str(p))
    print("printing all, but by all ids...")
    ids = map(lambda p: p.get("id"), all_products())
    ps = many_products(ids)
    for p in ps:
        print(str(p))

The functions that changed are many_products and all_products, which now retrieve the map object instead of a list.

We are retrieving all products, and iterating through the map object printing each product. Notice that map objects, as they are iterators, can be iterated only once, so, we are retrieving all once again to get the ids and then retrieving by its ids and printing all again. The output below.

printing all...
{'id': 1, 'name': 'Python Book', 'price': {'id': 4, 'product_id': 1, 'amount': 3.99, 'date': datetime.datetime(2016, 2, 5, 11, 11, 17, 683987)}}
{'id': 2, 'name': 'JavaScript Book', 'price': {'id': 2, 'product_id': 2, 'amount': 1.99, 'date': datetime.datetime(2015, 12, 5, 11, 11, 17, 683987)}}
{'id': 3, 'name': 'HTML Book', 'price': {'id': 3, 'product_id': 3, 'amount': 3, 'date': datetime.datetime(2015, 12, 20, 11, 11, 17, 683987)}}
printing all, but by all ids...
{'id': 1, 'name': 'Python Book', 'price': {'id': 4, 'product_id': 1, 'amount': 3.99, 'date': datetime.datetime(2016, 2, 5, 11, 11, 17, 683987)}}
{'id': 2, 'name': 'JavaScript Book', 'price': {'id': 2, 'product_id': 2, 'amount': 1.99, 'date': datetime.datetime(2015, 12, 5, 11, 11, 17, 683987)}}
{'id': 3, 'name': 'HTML Book', 'price': {'id': 3, 'product_id': 3, 'amount': 3, 'date': datetime.datetime(2015, 12, 20, 11, 11, 17, 683987)}}

In the users example the code will look like this:

users = [
    {
        "name": "svinci",
        "salt": "098u n4v04",
        "hashed_password": "q423uinf9304fh2u4nf3410uth1394hf"
    },
    {
        "name": "admin",
        "salt": "0198234nva",
        "hashed_password": "3894tumn13498ujc843jmcv92384vmqv"
    }
]


def all_users():
    def discard_passwords(user):
        u = user.copy()
        del u["salt"]
        del u["hashed_password"]
        return u
    return map(discard_passwords, users)


if __name__ == "__main__":
    us = all_users()
    for u in us:
        print(str(u))

Again, the all_users function didn’t change too much, it just returns the map object directly. The main code now iterates over the map printing each user one by one.

4. Download the Code Project

This was an basic example on Python’s map function.

Download
You can download the full source code of this example here: python-map

Sebastian Vinci

Sebastian is a full stack programmer, who has strong experience in Java and Scala enterprise web applications. He is currently studying Computers Science in UBA (University of Buenos Aires) and working a full time job at a .com company as a Semi-Senior developer, involving architectural design, implementation and monitoring. He also worked in automating processes (such as data base backups, building, deploying and monitoring applications).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button