How to map value to a string in separate lists

Viewed 109

I am creating a function which returns the product names in order of most revenue generated. I have managed to get the function to return the costs in the correct descending order, but I am having stuck in trying to mapthe prices to the products. Is this the right way to go about solving this?

products = ["Computer", "Cell Phones", "Vacuum Cleaner"]
amounts = [3,24,8]
prices = [199,299,399]


def top3(products, amounts, prices):
    totals = []
    items = []
    for item, num1, num2 in zip(products, amounts, prices):
            totals.append(num1 * num2)
            items.append(item)
    return sorted(totals, reverse = True)
7 Answers

Using the sorted() with dictionary

def top3(products, amounts, prices):
    d = dict(zip(products, zip(amounts, prices)))
    return sorted(d.keys(), key=lambda x: d[x][1] * d[x][0], reverse=True)

I like your approach. However, your code will not sort the product names. You can do this:

items = []
for item, num1, num2 in zip(products, amounts, prices):
        totals.append((item, num1 * num2))
return sorted(totals, reverse = True)

Alternate solution using comprehensions:

items = [(item, num1 * num2) for item, num1, num2 in zip(products, amounts, prices)]
return sorted(totals, reverse = True)

but I am having stuck in trying to map the prices to the products

First, create data that includes both the product name and revenue, and then sort that. The built-in comparison for sequences (including tuples and lists) in Python compares them an element at a time (just as strings are compared a character at a time). So:

totals = sorted(
    [
        (amount * price, name)
        for name, amount, price in zip(products, amounts, prices)
    ],
    reverse=True
)

You can see that each item in the list will be a pair (2-tuple) of the revenue and name; you can do what you need with this information.

The named tuple from python's built-in collections is an overlooked tools that's handy for making code like this readable. You can create a lightweight type that gives you properties like object attributes, but with the storage requirements of a simple tuple.

For example, you can create a Sales tuple type with:

Sales = namedtuple('Sales', ('name', 'amount', 'price'))

s = Sales('A Product', 20, 30.99)
# Sales(name='A Product', amount=20, price=30.99)

s.name
# 'A Product' 

You can solve your problem in a nicely readable way since you can refer to the properties by attribute:

from collections import namedtuple

Sales = namedtuple('Sales', ('name', 'amount', 'price'))

products = ["Computer", "Cell Phones", "Vacuum Cleaner"]
amounts = [3,24,8]
prices = [199,299,399]

sales = [Sales(*item) for item in zip(products, amounts, prices)]

# [Sales(name='Computer', amount=3, price=199),
#  Sales(name='Cell Phones', amount=24, price=299),
#  Sales(name='Vacuum Cleaner', amount=8, price=399)]

#get just the names sorted by amount * price

[s.name for s in sorted(sales, key=lambda s: s.price * s.amount, reverse=True)]
# ['Cell Phones', 'Vacuum Cleaner', 'Computer']

Here is what you can do to list each product in the order of their prices, from most expensive to cheapest:

products = ["Computer", "Cell Phones", "Vacuum Cleaner"]
amounts = [3,24,8]
prices = [199,299,399]

def top3(prd, amt, prc):
    lst = sorted([(prc, prd) for prd, amt, prc in zip(products, amounts, prices)],reverse = True)
    return [t[1] for t in lst]

print(top3(products, amounts, prices))

Output:

['Vacuum Cleaner', 'Cell Phones', 'Computer']




Here is what you can do to list each product in the order of how much was spent on them, most to least:

products = ["Computer", "Cell Phones", "Vacuum Cleaner"]
amounts = [3,24,8]
prices = [199,299,399]

def top3(prd, amt, prc):
    lst = sorted([(prc*amt, prd) for prd, amt, prc in zip(products, amounts, prices)],reverse = True)
    return [t[1] for t in lst]

print(top3(products, amounts, prices))

Output:

['Cell Phones', 'Vacuum Cleaner', 'Computer']

In one form or other, you will want to put the products and the items together into some kind of collection, then use the total as the key for sorting but return the name.

In every case, the key argument to sort contains a reference to a function which takes one of the elements being sorted and returns a key to use for determining the sort position of that element.

Example with list of 2-tuples. (item, total)

def top3(products, amounts, prices):
    item_totals = []
    for item, num1, num2 in zip(products, amounts, prices):
        item_totals.append((item, num1 * num2))

    item_totals.sort(key=lambda t: t[1], reverse=True)

    return [t[0] for t in item_totals]

Here, the lambda t: t[1] is used to construct a function which takes the 2-tuple and returns the second item, i.e. the total. It is equivalent to defining a little function:

def get_sort_key(t):
    return t[1]

and then passing the function:

    item_totals.sort(key=get_sort_key, reverse=True)

Example with list of dictionaries

For better readability instead of using a list of 2-tuples, we could have a list of dictionaries with keys 'item' and 'total':

def top3(products, amounts, prices):
    item_totals = []
    for item, num1, num2 in zip(products, amounts, prices):
        item_totals.append({'item': item, 'total': num1 * num2})

    item_totals.sort(key=lambda t: t['total'], reverse=True)

    return [t['item'] for t in item_totals]

In that case, a function that you could use instead of the lambda can be generated by using itemgetter from the operator package. You would use:

from operator import itemgetter

and the call to sort can then be changed to:

    item_totals.sort(key=itemgetter('total'), reverse=True)

Example with single dictionary

We could also just put everything into one dictionary, with the product as the key and the total as the value. But in this case, it relies on the names not being duplicated.

def top3(products, amounts, prices):
    item_totals = {}
    for item, num1, num2 in zip(products, amounts, prices):
        item_totals[item] = num1 * num2

    return sorted(item_totals.keys(),
                  key=lambda item: item_totals[item],
                  reverse=True)

There are more compact ways to write all of these, which avoid the need for the explicit for loop when building the collection prior to sorting, as demonstrated by some of the other answers, but this is the general principle.

You can merge those three lists into one list containing the product name and the revenue. Then from there, you can sort the list by the product name using sorted(). With this, you can also sort the list by the revenue generated by changing lambda x : x[0] to lambda x : x[1]. This is also assuming that all lists have the same length and the prices/amounts are in the same order as products.

merged = [(products, amounts * price) for products, amounts, price in zip(products, amounts, price)]

total = sorted(merged, key = lambda x : x[0])
Related