beoliver
beoliver

Reputation: 5759

testing for equality using itemgetter

from operator import itemgetter
from itertools import takewhile

xs = [ ('foo',1), ('bar',1), ('baz',2) ]

xs is sorted on the second item - there are no more 1s after 'bar'.

def item_map(xs):
    getcount = itemgetter(1)
    return list(map(getcount,xs))

print(item_map(xs))
>>> [1, 1, 2]

returns a list of the second element of every tuple.

def item_take(xs):   
    return list(takewhile(lambda x: x[1] == 1, xs))

print(item_take(xs))
[('foo', 1), ('bar', 1)]

returns the tuples that have a second element that == 1.

def could_this_work(xs):
    match = itemgetter(1) == 1 
    return list(takewhile(match, xs))

print(could_this_work(xs))
TypeError: 'bool' object is not callable

does not return the tuples that have a second element that == 1

Is there a way to use itemgetter in place of the lambda? Or can itemgetter not be used in this way?

EDIT. takewhile is being used for a reason. I understand what it does. This function is going to be used on a sorted list. I appreciate that the tuples are backwards for this, but the code that I have used it correct for what I want and expect.

Upvotes: 2

Views: 252

Answers (4)

Sven Marnach
Sven Marnach

Reputation: 602115

Your lambda function is actually the composition of two functions: operator.itemgetter(1) and operator.eq. Doing this in a purely functional style would require a compose() function, like this one:

def compose(f, g):
    def composed(x):
        return f(g(x))
    return composed

Using this function, you could do

from operator import itemgetter, eq
from functools import partial

def take_items(a):
    return takewhile(compose(partial(eq, 1), itemgetter(1)), a)

I don't think this is a godd idea, though. I would probably go with the straight-forward

def take_items(a):
    for x in a:
        if x[1] != 1:
            break
        yield x

I think this requires less thinking on part of the reader of the code.

Upvotes: 2

Karl Knechtel
Karl Knechtel

Reputation: 61607

Please don't do this in real code. Just use the lambda, honestly.

from operator import itemgetter, eq
from functools import partial
from itertools import takewhile

def compose_unary(func1, func2):
    return lambda x: func1(func2(x))

def item_take(xs):
    return list(takewhile(compose_unary(partial(eq, 1), itemgetter(1)), xs))

Note that takewhile doesn't actually do quite what you seem to think; it will stop at the first element that doesn't match the predicate, and ignore anything beyond that point that does.

Upvotes: 0

Ned Batchelder
Ned Batchelder

Reputation: 375754

itemgetter doesn't do comparisons, it simply gives you a function that retrieves items. You'll need to make your own function if you want comparisons.

Also, note that you can use list comprehensions:

def could_this_work(xs):
    return [x for x in xs if x[1] == 1]

or even generator expressions, which can work lazily on even infinite streams:

def could_this_work(xs):
    return (x for x in xs if x[1] == 1)

(These do what your English said: get the items with 1 in the second element. If you want to stop when you find a non-1 element, use Sven's answer.)

Upvotes: 1

jadkik94
jadkik94

Reputation: 7078

Try:

getcount = itemgetter(1)
match = lambda x: getcount(x) == 1

What you did commpared the itemgetter(1) to 1. This comaprison returns False. Then you call that. False(x) won't work, so you have this error.

itemgetter(n) is basically a function similar to:

def itemgetter(n):
    return lambda x: x[n]

You notice that returns another function, comparing it to an int makes no sense.

Upvotes: 2

Related Questions