15

I have a list of lists like so:

[[10564, 15], [10564, 13], [10589, 18], [10637, 39], [10662, 38], [10712, 50], [10737, 15], [10762, 14], [10787, 9], [10812, 12], [10837, 45], [3, 17], [7, 21], [46, 26], [48, 12], [49, 24], [64, 14], [66,
 17], [976, 27], [981, 22], [982, 22], [983, 17], [985, 13], [517, 9], [521, 15], [525, 11], [526, 13], [528, 14], [698, 14], [788, 24], [792, 19]]

I am trying to find the lowest value for the second element in each list(so compare 15 to 13 to 18 etc not comparing 10564 and 15 ), but also to separate it into ranges, so I could say, lowest second element[1] in each list, only if element[0] is over 10000 etc. How might I do this? I tried it and can only compare elements in the same list as of yet, which is not what I want. In the case I mentions I would then be returning [10787, 9] but if there was another value over 10000 with 9 I would want to return that also.

Paul
  • 5,756
  • 6
  • 48
  • 78

5 Answers5

17

This depends on what you want for output. First, you'll need to filter your list based on the "ranges" 1

gen = (x for x in lists if x[0] > 10000)

The if condition can be as complicated as you want (within valid syntax). e.g.:

gen = (x for x in lists if 5000 < x[0] < 10000)

Is perfectly fine.


Now, If you want only the second element from the sublists:

min(x[1] for x in gen)

Of course, you could inline the whole thing:

min(x[1] for x in lists if x[0] > 10000)

If you want the entire sublist:

from operator import itemgetter
min(gen,key=itemgetter(1))

example:

>>> lists = [[10564, 15], [10564, 13], [10589, 18], [10637, 39], [10662, 38], [10712, 50], [10737, 15], [10762, 14], [10787, 9], [10812, 12], [10837, 45], [3, 17], [7, 21], [46, 26], [48, 12], [49, 24], [64, 14], [66,17], [976, 27], [981, 22], [982, 22], [983, 17], [985, 13], [517, 9], [521, 15], [525, 11], [526, 13], [528, 14], [698, 14], [788, 24], [792, 19]]
>>> gen = (x for x in lists if x[0] > 10000)
>>> min(x[1] for x in gen)
9
>>> gen = (x for x in lists if x[0] > 10000)
>>> from operator import itemgetter
>>> min(gen,key=itemgetter(1))
[10787, 9]

Unfortunately, these only give you the first sublist which matches the criteria. To get all of them:

target = min(x[1] for x in lists if x[0] > 10000)
matches = [x for x in lists if (x[1] == target) and (x[0] > 10000)]

If you know for sure that there will be less than N matches, you could do this a little more efficiently with heapq and itertools.takewhile. In the general case where you don't know an upper limit on the number of matches, I think this solution is better (It's O(N) compared to sorting which is O(NlogN)).


1Note that the "generator expression" can only be iterated over once before it is exhausted

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • Great answer. Yup I want to return [10787, 9] I will have a read of itemgetter. I don't follow what you mean by the generator expression being exhausted. That I can not iterate over it again for some reason or? – Paul Apr 16 '13 at 12:36
  • @Paul -- Exactly. Generator's can only be iterated over once. Usually this isn't a problem (you can always create another one). However, if it is a problem, you can use a list comprehension instead: `lst = [x for x in lists if x[0] > 10000]` – mgilson Apr 16 '13 at 12:38
  • Ah I see, perfect. Thanks. More familiar with list comprehensions. I can't see any difference apart from the brackets, i'll have to read into the differences with generator, thank you. – Paul Apr 16 '13 at 12:41
  • 1
    @Paul -- In terms of syntax, the only difference is the brackets. In terms of functionality, generators generate sequences of numbers on the fly, `yield`ing them only when asked (by a for loop for example). You can think of a list-comprehension as a structure which iterates over a generator and *stores* each value in a list. So, the one difference here is in storage/memory usage. – mgilson Apr 16 '13 at 12:46
  • 3
    "but if there was another value over 10000 with 9 I would want to return that also." - what about this case? – Tobias Apr 16 '13 at 12:47
5

Here's a very simply approach that just finds the minimum value and then builds the list based on that value.

>>> a = [[10564, 15], [10564, 13], [10589, 18], [10637, 39], [10662, 38], [10712, 50], [10737, 15], [10762, 14], [10787, 9], [10812, 12], [10837, 45], [3, 17], [7, 21], [46, 26], [48, 12], [49, 24], [64, 14], [66,
...  17], [976, 27], [981, 22], [982, 22], [983, 17], [985, 13], [517, 9], [521, 15], [525, 11], [526, 13], [528, 14], [698, 14], [788, 24], [792, 19]]
>>> a_min = min(i[1] for i in a)
>>> [i[0] for i in a if i[1] == a_min and i[0] > 10000] + [a_min]
[10787, 9]

The code correctly displays multiple values:

>>> a += [[10391, 9]] #add another pair with a first value > 10000
>>> [i[0] for i in a if i[1] == a_min and i[0] > 10000] + [a_min]
[10787, 10391, 9]
Nolen Royalty
  • 18,415
  • 4
  • 40
  • 50
4
>>> l=[[10564, 15], [10564, 13], [10589, 18], [10637, 39]]
>>> min(x[1] for x in l if x[0] > 10000)
13
>>>

update for your comment (you can use lambda for key in min function, itemgetter a little faster on large lists):

>>> min((x for x in l if x[0] > 10000), key=lambda k:k[1])
[10564, 13]
ndpu
  • 22,225
  • 6
  • 54
  • 69
  • Thanks nice and simple, I want to return [10564, 13] in this case. – Paul Apr 16 '13 at 12:34
  • This would be my favourite, except that if none of the `x[0]>10000` hold, you get `ValueError: min() arg is an empty sequence`. So you'd need to wrap this in a `try: - except` block, unless you can find a way to sneak in a sentinel value for `min` (I don't think that is easy) – Marc van Leeuwen Apr 16 '13 at 12:34
  • @Paul To make this code more self-documenting, consider unpacking `x[0]` and `x[1]` and give them descriptive names, like this, where I'm just guessing what `x[0]` and `x[1]` might be: `min(id for score, id in l if score > 10000)`. – Lauritz V. Thaulow Apr 16 '13 at 12:37
  • @LauritzV.Thaulow Yep, a more descriptive name for variables is always preferable! Even for myself, let alone others. – Paul Apr 16 '13 at 12:39
3

If you require multiple mins, then perhaps you're best of filtering applicable elements and sorting them...

vals = sorted((el for el in your_list if el[0] >= 10000), key=lambda L: L[1])
# [[10787, 9], [10812, 12], [10564, 13], [10762, 14], [10564, 15], [10737, 15], [10589, 18], [10662, 38], [10637, 39], [10837, 45], [10712, 50]]

Then you can take vals[0] to get the first, vals[1] to get the second, or use slicing such as vals[:5]...

Jon Clements
  • 138,671
  • 33
  • 247
  • 280
1
a=[[10564, 15], [10564, 13], [10589, 18], [10637, 39], [10662, 38], [10712, 50], [10737, 15], [10762, 14], [10787, 9], [10812, 12], [10837, 45], [3, 17], [7, 21], [46, 26], [48, 12], [49, 24], [64, 14], [66, 17], [976, 27], [981, 22], [982, 22], [983, 17], [985, 13], [517, 9], [521, 15], [525, 11], [526, 13], [528, 14], [698, 14], [788, 24], [792, 19]]

print min(map(lambda y: y[1] ,filter(lambda x: x[0]>10000,a)))
GodMan
  • 2,561
  • 2
  • 24
  • 40