1

I've been trying to process data stored in a list of dictionaries, and store it in another list of tuples. For example say i have the following data:

triangles= [{"name": "triangle1", "base":3, "height":4}, 
            {"name": "triangle2", "base":5, "height":12}, 
            {"name": "triangle3", "base":8, "height":15}
           ]

And I want to run all the data through the following function which i cannot change:

def hypotenuse(base, height):
    hyp_sq=base**2+height**2

    return hyp_sq**(1.0/2.0)

Ideally, after computing all the data, I want to sort the triangles based on their hypotenuse length and I want to return a list of tuples in the following format:

hypotenuse_results=[("triangle1", 5), ("triangle2", 13), ("triangle3", 17)]

I know I have to use the map() function in conjunction with sorted() but I have no idea how to pass only the values corresponding to "base" and "height" keys.

If someone could point me in the right direction, it would be greatly appreciated.

Thanks

canecse
  • 1,772
  • 3
  • 16
  • 20

3 Answers3

7

Around 1993, Python got lambda, reduce(), filter() and map(), courtesy of a Lisp hacker who missed them and submitted working patches. These Lisp-flavored constructs are considered a little alien in Python specially after the introduction of list comprehensions in 2000. So no, you don't need map, you can use list comprehensions or generator expressions.

You can let your hypotenuse function take extra arguments and ignore them:

def hypotenuse(base, height, **kwargs):
    hyp_sq=base**2+height**2
    return hyp_sq**(1.0/2.0)

Then you can use a list comprehension:

hypotenuse_results = [(t['name'], hypotenuse(**t)) for t in triangles]
hypotenuse_results.sort(key=lambda pair: pair[1])

This should perform well enough even for large len(triangles). The generator expression version is:

unsorted_results = ((t['name'], hypotenuse(**t)) for t in triangles)
hypotenuse_results = sorted(unsorted_results, key=lambda pair: pair[1])

Profiling both solutions and posting here would be a great exercise.

thanks. is there a way to this without modifying the hypotenuse function? – canecse

Sure! Just call it with both arguments:

hypotenuse_results = [(t['name'], hypotenuse(t['base'], t['height'])) for t in triangles]
hypotenuse_results.sort(key=lambda pair: pair[1])

Note that the accepted solution is allocating an actual list and trowing it away so you may want to use a generator expression instead of a list comprehension if you are concerned about memory footprint (specially useful if len(triangles) is big but always a good habit):

hypotenuse_results = sorted(
    ((t['name'], hypotenuse(t['base'], t['height'])) for t in triangles), 
    key=lambda x: x[1]
)
Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
3

You need do modify you hypotenuse function, for some like this:

def hypotenuse(triangle):
    hyp_sq=triangle["base"]**2 + triangle["height"]**2
    return (triangle["name"], hyp_sq**(1.0/2.0))

map will returns a generator of tuples, when, each tuple is (name, hyp), so, just sort using the second element of tuple:

sorted(map(hypotenuse, triangles), key=lambda x: x[1])

UPDATE:

Cause you can't change hypotenuse function, you can just use list comprehension:

sorted([(t['name'], hypotenuse(t['base'], t['height'])) for t in triangles], key=lambda x: x[1])
Anderson Lima
  • 246
  • 4
  • 8
  • This solves the problem. But what if i couldn't modify the function hypotenuse as the function in the actual problem given to me is imported through a library. – canecse Jan 03 '18 at 15:40
  • @canecse if you can't change the function, you can't use map in this case, cause original hypotenuse function just return a value without triangle name, so, map generator returns a list of hypo values, but, you need a list of tuples. – Anderson Lima Jan 03 '18 at 15:55
  • You could create another function, can be a lambda, and call hypotenuse and create a tuple like you want, like this: `sorted(map(lambda t: (t['name'], hypotenuse(t['base'], t['height'])), triangles), key=lambda x: x[1])` – Anderson Lima Jan 03 '18 at 16:10
3

Since your hypotenuse function works fine here, you can construct a comprehension to create your list of tuples:

from operator import itemgetter

result = sorted(((x['name'], hypotenuse(x['base'], x['height'])) for x in triangles), key = itemgetter(1))

print(result)

Which gives:

[('triangle1', 5), ('triangle2', 13), ('triangle3', 17)]

or if you really wanted to use map(), you could try this:

result = sorted(map(lambda x: (x['name'], hypotenuse(x['base'], x['height'])), triangles), key = itemgetter(1))

Note: You can use lambda x: x[1] instead of operator.itemgetter(1). It's just a matter of preference here. If your interested, you can read this to see the performance between the two, and their respective pros and cons.

UPDATE:

@Paulo Scardine pointed out in the comments that if triangle gets bigger in the future, using a generator expression within sorted() is more efficient. This is because the list comprehension creates a list on the spot, but sorted() removes this list anyways in the process, so its a waste to pass in a list when its not needed. This isn't a problem for the second example, since map() already returns a generator. I updated the above code to account for these recommendations.

RoadRunner
  • 25,803
  • 6
  • 42
  • 75
  • 2
    @NickA Look [here](https://stackoverflow.com/questions/17243620/operator-itemgetter-or-lambda/17243726), it's performance is slightly better. Shouldn't make a difference here, I'm just used to using it. – RoadRunner Jan 03 '18 at 15:46
  • 1
    Also, `itemgetter(1)` is imho slightly easier to understand then `lambda x: x[1]`, same as its cousin `attrgetter(name)` – mata Jan 03 '18 at 15:51
  • @mata I couldn't agree more. It's also more flexible in my opinion. One instance where I've found a big difference is getting a number of elements at once: `itemgetter(2,4,6)` vs `lambda x: (x[2], x[4], x[6])`. I prefer the first way by far. – RoadRunner Jan 03 '18 at 15:54
  • @NickA I don't understand your problem here, `hypotenuse` returns a `float`, but OP wants an `int` in the output. Certainly their is nothing wrong with converting the types to match the output. – RoadRunner Jan 03 '18 at 15:58
  • 1
    Good answer. For small `len(triangles)` it does not matter but for larger inputs a generator expression is better (since the list comprehension is allocating an actual list that `sorted` is throwing away). – Paulo Scardine Jan 03 '18 at 16:00
  • @PauloScardine Good catch, Cheers for the feedback :-). I'll update my answer. – RoadRunner Jan 03 '18 at 16:02
  • @RoadRunner The point was simply that the OP never said they wanted an `int`, the examples they gave just happened to be [Pythagorean triples](https://en.wikipedia.org/wiki/Pythagorean_triple) – Nick is tired Jan 03 '18 at 16:49