135

Let's assume I'm creating a simple class to work similar to a C-style struct, to just hold data elements. I'm trying to figure out how to search a list of objects for objects with an attribute equaling a certain value. Below is a trivial example to illustrate what I'm trying to do.

For instance:

class Data:
    pass

myList = []

for i in range(20):
    data = Data()
    data.n = i
    data.n_squared = i * i
    myList.append(data)

How would I go about searching the myList list to determine if it contains an element with n == 5?

I've been Googling and searching the Python docs, and I think I might be able to do this with a list comprehension, but I'm not sure. I might add that I'm having to use Python 2.4.3 by the way, so any new gee-whiz 2.6 or 3.x features aren't available to me.

DevPlayer
  • 5,393
  • 1
  • 25
  • 20
m0j0
  • 3,484
  • 5
  • 28
  • 33
  • Perhaps an unintentional quirk of your example: myList = [Data().n==0, Data().n=1, ...] where data.n would be assigned by range() and data.n would be the index into myList. Therefore allowing you to pull up any Data() instance just by referencing myList by an index value. Of course you could later modifiy myList[0].n = 5.2 or something. And the example was perhaps over-simplified. – DevPlayer Dec 22 '16 at 15:48

11 Answers11

204

You can get a list of all matching elements with a list comprehension:

[x for x in myList if x.n == 30]  # list of all elements with .n==30

If you simply want to determine if the list contains any element that matches and do it (relatively) efficiently, you can do

def contains(list, filter):
    for x in list:
        if filter(x):
            return True
    return False

if contains(myList, lambda x: x.n == 3)  # True if any element has .n==3
    # do stuff
Ali Afshar
  • 40,967
  • 12
  • 95
  • 109
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
96

Simple, Elegant, and Powerful:

A generator expression in conjuction with a builtin… (python 2.5+)

any(x for x in mylist if x.n == 10)

Uses the Python any() builtin, which is defined as follows:

any(iterable) -> Return True if any element of the iterable is true. Equivalent to:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False
h3xStream
  • 6,293
  • 2
  • 47
  • 57
gahooa
  • 131,293
  • 12
  • 98
  • 101
58

Just for completeness, let's not forget the Simplest Thing That Could Possibly Work:

for i in list:
  if i.n == 5:
     # do something with it
     print "YAY! Found one!"
Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
56
[x for x in myList if x.n == 30]               # list of all matches
[x.n_squared for x in myList if x.n == 30]     # property of matches
any(x.n == 30 for x in myList)                 # if there is any matches
[i for i,x in enumerate(myList) if x.n == 30]  # indices of all matches

def first(iterable, default=None):
  for item in iterable:
    return item
  return default

first(x for x in myList if x.n == 30)          # the first match, if any
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
  • 2
    This is a good answer because of the "first" method, which is probably the most common use case. – galarant Jan 28 '13 at 22:01
  • great, thanks! the match indices was what I was looking for. Is there a shortcut to use this to directly index the list to access another field? Now I get a list of list entries (there is just one entry, so it's a list with one item). To get the index, I need to perform result[0] before I can use it to index the list. From the question example, I want to access n_squared from a particular n: myList[index of myList.n==5].n_squared – Frieke Mar 22 '19 at 14:16
49
filter(lambda x: x.n == 5, myList)
vartec
  • 131,205
  • 36
  • 218
  • 244
10

You can use in to look for an item in a collection, and a list comprehension to extract the field you are interested in. This (works for lists, sets, tuples, and anything that defines __contains__ or __getitem__).

if 5 in [data.n for data in myList]:
    print "Found it"

See also:

Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
Tom Dunham
  • 5,779
  • 2
  • 30
  • 27
10

Another way you could do it is using the next() function.

matched_obj = next(x for x in list if x.n == 10)
m0j0
  • 3,484
  • 5
  • 28
  • 33
SEMICS
  • 181
  • 3
  • 5
5

You should add a __eq__ and a __hash__ method to your Data class, it could check if the __dict__ attributes are equal (same properties) and then if their values are equal, too.

If you did that, you can use

test = Data()
test.n = 5

found = test in myList

The in keyword checks if test is in myList.

If you only want to a a n property in Data you could use:

class Data(object):
    __slots__ = ['n']
    def __init__(self, n):
        self.n = n
    def __eq__(self, other):
        if not isinstance(other, Data):
            return False
        if self.n != other.n:
            return False
        return True
    def __hash__(self):
        return self.n

    myList = [ Data(1), Data(2), Data(3) ]
    Data(2) in myList  #==> True
    Data(5) in myList  #==> False
Johannes Weiss
  • 52,533
  • 16
  • 102
  • 136
3

Consider using a dictionary:

myDict = {}

for i in range(20):
    myDict[i] = i * i

print(5 in myDict)
dan-gph
  • 16,301
  • 12
  • 61
  • 79
  • Or: d = dict((i, i*i) for i in range(20)) – hughdbrown Mar 01 '09 at 22:08
  • It solves the trivial problem I used to illustrate my question, but didn't really solve my root question. The answer I was looking for (5+ years ago) was the list comprehension. :) – m0j0 Jul 18 '14 at 19:16
1

Use the following list comprehension in combination with the index method:

data_n = 30
j = [data.n for data in mylist].index(data_n)
print(mylist[j].data.n == data_n)
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Matt Koch
  • 11
  • 1
0

I'm surprised there's no answer that asserts that only one item matches.

Maybe this does that:

def one_matching(list, test):
  filtered = [item for item in list if test(item)]
  if len(filtered) != 1:
    if len(filtered) == 0:
      raise KeyError("No matching value found.")
    else:
      raise KeyError("Multiple matching values found.", *filtered)
  return filtered[0] 

# prints 2:
print(one_matching([1,2,3], lambda x: x**2 == 4))
fuzzyTew
  • 3,511
  • 29
  • 24