List methods that handle predicates in Python
12/18/2007
In continuation of a previous post of using predicates/lambdas in Python, I prepared some helpful list methods that you can drop into your current and future Python scripts. What follows in an introduction/walk-through of the five methods. Note, when I say predicate, I am referring to a literal value or a function pointer.
Exists(predicate)
* Returns true if the predicate is satisfied by at least one list member.
FirstIndex(predicate)
* Returns the first integer position in the list that satisfies the predicate.
FirstValue(predicate)
* Returns the first value in the list that satisfies the predicate.
* Note, the returned value may be a complex object.
LastIndex(predicate)
* Returns the last integer position in the list that satisfies the predicate.
LastValue(predicate)
* Returns the last value in the list that satisfies the predicate.
* Like FirstValue, the returned value may be complex.
Bored? Here is the overloaded list, which I am dubbing 'PredicateList':
from types import FunctionType
from UserList import UserList
class PredicateList(UserList):
def Exists(self, predicate):
"""Returns true if the predicate is satisfied by at least one list member."""
if type(predicate) is FunctionType:
for item in self.data:
if predicate(item):
return True
return False
else:
return predicate in self.data
def GetIndex(self, predicate, reversed):
"""Common functionality shared by FirstIndex and LastIndex."""
xr = xrange(len(self.data))
if reversed:
xr = xr.__reversed__()
for i in xr:
try:
if type(predicate) == FunctionType:
if predicate(self.data[i]):
return i
else:
if self.data[i] == predicate:
return i
# Function predicates that check object properties will fail on simple
# types like strings. This is rather unelegant, but works.
except AttributeError:
pass
return None
def FirstIndex(self, predicate):
"""Returns the first integer position in the list that satisfies the predicate."""
return self.GetIndex(predicate, False)
def FirstValue(self, predicate):
"""Returns the first value in the list that satisfies the predicate."""
firstObjectIndex = self.FirstIndex(predicate)
if not firstObjectIndex is None:
return self.data[firstObjectIndex]
else:
return None
def LastIndex(self, predicate):
"""Returns the last integer position in the list that satisfies the predicate."""
return self.GetIndex(predicate, True)
def LastValue(self, predicate):
"""Returns the last value in the list that satisfies the predicate."""
lastObjectIndex = self.LastIndex(predicate)
if not lastObjectIndex is None:
return self.data[lastObjectIndex]
else:
return None
Note, PredicateList extends UserList (this is found under your Python installation's lib/), which happens to be a Python wrapper for the list type. If you are curious, there is also a UserDict class that acts congruently for dictionaries. As you can see, everything above is straight-forward. I am however a bit embarrassed by catching the AttributeError exception; there is certainly a better way of accomplishing that - one way that comes to mind is to do a type check prior to grabbing the property value.
Alright, with that out of the way here is an example use-case. Suppose I had a Person class that looked like the following:
class Person():
def __init__(self, firstName, lastName):
self.firstName = firstName
self.lastName = lastName
def __str__(self):
return '%s %s' % (self.firstName, self.lastName)
Now let's initialize a PredicateList and append some members. Note, I would have preferred to have simply used the [] notation, but this will have to suffice:
sampleList = PredicateList()
sampleList.append('3')
sampleList.append('2')
sampleList.append('1')
sampleList.append(Person('Joe', 'Shmoe'))
sampleList.append('5')
sampleList.append('2')
print sampleList
Now, we can use the PredicateList like so:
# Existence checks
print sampleList.Exists('2')
print sampleList.Exists(lambda x: x == '2')
# First index checks
print sampleList.FirstIndex('2')
print sampleList.FirstIndex(lambda x: x == '2')
# First object checks
print sampleList.FirstValue('2')
print sampleList.FirstValue(lambda person: person.firstName == 'Joe')
# Last index checks
print sampleList.LastIndex('2')
print sampleList.LastIndex(lambda x: x == '2')
# Last object checks
print sampleList.LastValue('2')
print sampleList.LastValue(lambda person: person.lastName == 'Shmoe')
There were things that I obviously omitted from this class. For instance, map() and filter() already take lambdas as function arguments, making operations like value casting and spawning new lists seem relatively simple.
There may be an oversight in the code above as this was a really fun 10 minute exercise with the REPL. I always appreciate feedback, so feel free to drop me a line.