tags:

views:

189

answers:

3

I have a data structure which is a collection of tuples like this:

things = ( (123, 1, "Floogle"), (154, 33, "Blurgle"), (156, 55, "Blarg") )

The first and third elements are each unique to the collection.

What I want to do is retrieve a specific tuple by referring to the third value, eg:

>>> my_thing = things.get( value(3) == "Blurgle" )
(154, 33, "Blurgle")

There must be a better way than writing a loop to check each value one by one!

+1  A: 

If things is a list, and you know that the third element is uniqe, what about a list comprehension?

>> my_thing = [x for x in things if x[2]=="Blurgle"][0]

Although under the hood, I assume that goes through all the values and checks them individually. If you don't like that, what about changing the my_things structure so that it's a dict and using either the first or the third value as the key?

Dan
Loop comprehension seems about the easiest way to solve this in the general case. Thankyou for your suggestion.
ManicDee
You can avoid looping through the whole list if you use a generator comprehension instead: "my_thing = (x for x in things if x[2]=="Blurgle").next()" - this only iterates until the item is found.
Brian
You're right Brian, except that in 2.6 and later next(x for x in things if x[2]=='zap') is even better than your 2.4-compatible idea!
Alex Martelli
+3  A: 

A loop (or something 100% equivalent like a list comprehension or genexp) is really the only approach if your outer-level structure is a tuple, as you indicate -- tuples are, by deliberate design, an extremely light-weight container, with hardly any methods in fact (just the few special methods needed to implement indexing, looping and the like;-).

Lightning-fast retrieval is a characteristic of dictionaries, not tuples. Can't you have a dictionary (as the main structure, or as a side auxiliary one) mapping "value of third element" to the subtuple you seek (or its index in the main tuple, maybe)? That could be built with a single loop and then deliver as many fast searches as you care to have!

If you choose to loop, a genexp as per Brian's comment an my reply to it is both more readable and on average maybe twice as fast than a listcomp (as it only does half the looping):

my_thing = next(item for item in things if item[2] == "Blurgle")

which reads smoothly as "the next item in things whose [2] sub-item equale Blurgle" (as you're starting from the beginning the "next" item you find will be the "first" -- and, in your case, only -- suitable one).

If you need to cover the case in which no item meets the predicate, you can pass next a second argument (which it will return if needed), otherwise (with no second argument, as in my snippet) you'll get a StopIteration exception if no item meets the predicate -- either behavior may be what you desire (as you say the case should never arise, an exception looks suitable for your particular application, since the occurrence in question would be an unexpected error).

Alex Martelli
I'm more interested in clearly expressing intent than getting the result quickly. Using Python's loop comprehension reduces the loop to one line of code, which (like the ternary '?' in C and Perl) achieves code brevity at the cost of maintenance programmer frustration :)The dictionary idea is sensible from the point of view of quickly retrieving the information based on the one parameter.Thankyou for your thoughts on this.
ManicDee
+1  A: 

if you have to do this type of search multiple times, why don't you convert things to things_dict one time , then it will be easy and faster to search later on

things = ( (123, 1, "Floogle"), (154, 33, "Blurgle"), (156, 55, "Blarg") )

things_dict = {}
for t in things:
    things_dict[t[2]] = t

print things_dict['Blarg']
Anurag Uniyal
Converting to a dict certainly solves the problem in the case where I need to retrieve a value more than once. Thankyou!
ManicDee