1
>>> lst
[('BFD', 0), ('NORTHLANDER', 3), ('HP', 23), ('VOLT', 3)]
>>> min([x for x in lst if x[1]!=0], key=lambda x: x[1])
('NORTHLANDER', 3)
>>>

Here min() only return one set. It should actually return:

[('NORTHLANDER', 3), ('VOLT', 3)]

Any in-built function to this effect?

deostroll
  • 11,661
  • 21
  • 90
  • 161
  • See the similar question [How to find positions of the list maximum?](http://stackoverflow.com/questions/3989016/how-to-find-positions-of-the-list-maximum) FWIW @unwind's answer is analogous to the accepted one there. – martineau Jan 04 '13 at 12:59

4 Answers4

8

It's a simple two-step solution to first compute the min, then collect all tuples having the min value, so write your own function to do this. It's a rather specialized operation, not what is expected of a general-purpose min() function.

Find an element with the minimum value:

>>> lstm = min([x for x in lst if x[1] > 0], key = lambda x: x[1])
>>> lstm
('NORTHLANDER', 3)

Then just form a new list taking elements from list where the value is that of lstm:

>>> [y for y in lst if y[1] == lstm[1]]
[('NORTHLANDER', 3), ('VOLT', 3)]
unwind
  • 391,730
  • 64
  • 469
  • 606
  • This can be optimized slightly by using `lstm = min((x for x in lst if x[1]), key = lambda x: x[1])[1]` and `[y for y in lst if y[1] == lstm]`. – martineau Jan 04 '13 at 12:48
1

Using collections.defaultdict:

d=collections.defaultdict(list)
for item in lst:
    d[item[1]].append(item)
d[min(key for key in d.keys() if key!=0)]

Out:

[('NORTHLANDER', 3), ('VOLT', 3)]

Test:

#unwind's solution

def f(lst):
    return [y for y in lst if y[1] == min([x for x in lst if x[1] > 0],
                                             key = lambda x: x[1])[1]]

def f2(lst):
    d=collections.defaultdict(list)
    for item in lst:
        d[item[1]].append(item)
    return d[min(key for key in d.keys() if key!=0)]

%timeit f(lst)
100000 loops, best of 3: 12.1 us per loop
%timeit f2(lst)
100000 loops, best of 3: 5.42 us per loop

So, defaultdict seems to be more than twice as fast.

edit @martineau optimization:

def f3(lst):
    lstm = min((x for x in lst if x[1]), key = lambda x: x[1])[1]
    return [y for y in lst if y[1] == lstm]

%timeit f3(lst)
100000 loops, best of 3: 4.19 us per loop

And another dict based solution using set.default is even a bit faster:

def f4(lst):
    d={}
    for item in lst:
        if item[1] != 0:
            d.setdefault(item[1],{})[item]=0
    return d[min(d.keys())].keys()

%timeit f4(lst)
100000 loops, best of 3: 3.76 us per loop
root
  • 76,608
  • 25
  • 108
  • 120
  • If you change `f()` to `lstm = min((x for x in lst if x[1]), key = lambda x: x[1])[1]` followed by `return [y for y in lst if y[1] == lstm]` it's about twice as fast as your `defaultdict` approach. – martineau Jan 04 '13 at 12:44
  • @martineau -- added your optimization and another dict based solution that is even slightly faster. – root Jan 06 '13 at 13:52
  • Ah, an excellent addition. I was able make it even faster, but it's too much code to put here in a comment, so please check out (and time) [this](http://pastebin.com/nH5fFVfx) version. – martineau Jan 06 '13 at 19:14
0

collections.Counter? It's designed for dealing with similar data:

# Instantiate excluding zero length items
>>> c = collections.Counter({k: v for (k, v) in lst if v != 0})
>>> c
Counter({'HP': 23, 'NORTHLANDER': 3, 'VOLT': 3})

# Retrieve most common then reverse it
>>> least_common = c.most_common()[::-1]
[('VOLT', 3), ('NORTHLANDER', 3), ('HP', 23)]

>>> [(k,v) for (k,v) in least_common if v == least_common[0][1]]
[('VOLT', 3), ('NORTHLANDER', 3)]

(This is just an idea, not intended to be efficient)

Alex L
  • 8,748
  • 5
  • 49
  • 75
0

You can write your own multimin function as:

def multimin(seq, key=None):
    if key is None:
        key = lambda x: x
    min_e = min(seq, key=key)
    return filter((lambda x: key(x) == key(min_e)), seq)

For example:

>>> lst = [('BFD', 0), ('NORTHLANDER', 3), ('HP', 23), ('VOLT', 3)]
>>> print multimin([x for x in lst if x[1]!=0], key=lambda x: x[1]) 
[('NORTHLANDER', 3), ('VOLT', 3)]
vz0
  • 32,345
  • 7
  • 44
  • 77