2

I've got a list, which may or may not contain a unique element satisfying a given predicate. I am looking for an expression which evaluates to an item satisfying that predicate if it exists and is unique, and otherwise returns None. Something like

numbers = [4, 3, 9, 7, 1, 2, 8]
print(the(item for item in numbers if item > 10))     # None
print(the(item for item in numbers if item % 2 == 0)) # None
print(the(item for item in numbers if item % 7 == 0)) # 7

Is there a built-in idiom for this, or do I have to write my own the function?

0

5 Answers 5

4

I do not know a single expression way for that, but that simple function should work:

def the(it, cond):
    l = [ i for i in it if cond(i) ]
    return l[0] if len(l) == 1 else None

Test:

>>> print(the(numbers,(lambda x: x > 10)))
None
>>> print(the(numbers,(lambda x: x % 7 == 0)))
7
>>> print(the(numbers,(lambda x: x % 2 == 0)))
None
Sign up to request clarification or add additional context in comments.

1 Comment

Could be a single expression by using (l + [None])[::2][-1] or ((l + [None]) * 3)[2], but I don't really think they're good :-)
1

You could try asking for two elements with islice, would be a bit simpler:

def the(it):
    tmp = list(islice(it, 2))
    return tmp[0] if len(tmp) == 1 else None

Or a loop:

def the(it):
    value = None
    for i, value in enumerate(it):
        if i == 1:
            return None
    return value

Or yet another way to use next:

def the(it):
    first = next(it, None)
    o = object()
    if next(it, o) is o:
        return first

Or similar to yours:

def the(it):
    first = next(it, None)
    try:
        next(it)
    except:
        return first

Comments

0

For the record, I can just write the as

def the(it):
    it = iter(it)
    try:
        value = next(it)
    except StopIteration:
        return None
    try:
        next(it)
    except StopIteration:
        return value
    return None

Comments

0

This should be a simple solution for this problem:

def the(items):
    return items[0] if (len(items) == 1) else None

numbers = [4, 3, 9, 7, 1, 2, 8]
print(the([item for item in numbers if item > 10]))     # None
print(the([item for item in numbers if item % 2 == 0])) # None
print(the([item for item in numbers if item % 7 == 0])) # 7

You could also apply the following format. It might not be any simpler in terms of code, but it certainly is faster when the item count is large.

print(the(list(filter(lambda x: x > 10, numbers))))     # None
print(the(list(filter(lambda x: x % 2 == 0, numbers)))) # None
print(the(list(filter(lambda x: x % 7 == 0, numbers)))) # 7

Comments

0

You can use the following lazy approach. Get the next two items from the generator expression and return None if the second item from the generator is not None:

def func(lst, pred):
    gen = (i for i in lst if pred(i))
    v, w = next(gen, None), next(gen, None)
    return v if w is None else None


print(func([4, 3, 9, 7, 1, 2, 8], lambda x: x>10))
# None
print(func([4, 3, 9, 7, 1, 2, 8], lambda x: x % 2 == 0))
# None
print(func([4, 3, 9, 7, 1, 2, 8], lambda x: x % 7 == 0))
# 7
print(func([4, 3, 9, 0, 1, 2, 8], lambda x: x % 7 == 0))
# 0

6 Comments

Using a set will remove duplicates. I assume print(the(item for item in (1,1,2,2) if item % 2 == 0)) should be None.
@TigerhawkT3 Didn't consider that. Updated
How about print(func([1, None], lambda x: True)) (prints 1 instead of None).
@StefanPochmann They could use a sentinel instead of None and I strongly doubt they have a None in their list of numbers
That's only in their example, though. Their question title and text say "element" and their own answer as well as yours look general. Btw, you already have an object that you could use as sentinel: gen. I think that would be cool :-)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.