6

This may be a trivial problem, but I want to learn more about other more clever and efficient ways of solving it.

I have a list of items and each item has a property a whose value is binary.

  • If every item in the list has a == 0, then I set a separate variable b = 0.
  • If every item in the list has a == 1, then I set b = 1.
  • If there is a mixture of a == 0 and a == 1 in the list, then I set b = 2.

I can use a set to keep track of the types of a value, such that if there are two items in the set after iterating through the list, then I can set b = 2, whereas if there is only one item in the set I just retrieve the item (either 0 or 1) and use it to set b.

Any better way?

ChrisG
  • 149
  • 2
  • 6
  • 3
    "Better" is subjective here and also depends a little on which of the 3 cases you expect to hit most often and it may even depend on the length of the list, etc. – mgilson Oct 07 '13 at 15:45
  • @mgilson, good points. I do not know which case(s) will come up more often than other and the property `a` is set based on input from users. I do know the length of the list will be short, i.e. <= 1000 items. – ChrisG Oct 07 '13 at 18:23

10 Answers10

26

One pass through the list, and no extra data structures constructed:

def zot(bs):
    n, s = len(bs), sum(bs)
    return 1 if n == s else 2 if s else 0
Tim Peters
  • 67,464
  • 13
  • 126
  • 132
  • nice. what does zot mean? zero-one-two? – bcollins Oct 07 '13 at 19:10
  • 1
    This draws attention to the corner case `bs == []`. It's interesting that our solutions handle it the same way (`zot` and `category` both return `1`), but I'm not certain whether that's the correct response. `all([])` returns `True`, while `any([])` returns `False`, so if I had organized my if statement differently, `category([])` would have returned `0`. – senderle Oct 08 '13 at 15:38
  • I think the problem statement is contradictory then: for an empty input, it's trivially true that all are 0, and that all are 1. So we return the sum of the two correct answers ;-) – Tim Peters Oct 08 '13 at 15:55
  • 1
    This is so absurdly clever. Is this how core developers think on a daily basis? This approach would not have occurred to me in a thousand years. – temporary_user_name Oct 22 '13 at 06:01
  • 10
    Ah, but the *next* time you see a problem like this, it will occur to you. Multiply that by some decades, and your bag of tricks becomes large ;-) – Tim Peters Oct 22 '13 at 18:15
18

I would suggest using any and all. I would say that the benefit of this is readability rather than cleverness or efficiency. For example:

>>> vals0 = [0, 0, 0, 0, 0]
>>> vals1 = [1, 1, 1, 1, 1]
>>> vals2 = [0, 1, 0, 1, 0]
>>> def category(vals):
...     if all(vals):
...         return 1
...     elif any(vals):
...         return 2
...     else:
...         return 0
... 
>>> category(vals0)
0
>>> category(vals1)
1
>>> category(vals2)
2

This can be shortened a bit if you like:

>>> def category(vals):
...     return 1 if all(vals) else 2 if any(vals) else 0
... 

This works with anything that can be interpreted by __nonzero__ (or __bool__ in Python 3) as having a true or false value.

senderle
  • 145,869
  • 36
  • 209
  • 233
15

Somebody mentioned code golf, so can't resist a variation on @senderle's:

[0,2,1][all(vals) + any(vals)]

Short explanation: This uses the boolean values as their integer equivalents to index a list of desired responses. If all is true then any must also be true, so their sum is 2. any by itself gives 1 and no matches gives 0. These indices return the corresponding values from the list.

If the original requirements could be modified to use 1 for any and 2 for all it would be even simpler to just return the integer of any + all

beroe
  • 11,784
  • 5
  • 34
  • 79
3

Using a dictionary:

zonk_values = {frozenset([0]): 0, frozenset([1]): 1, frozenset([0, 1]): 2}
def zonk(a):
    return zonk_values[frozenset(a)]

This also only needs a single pass through the list.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
2

you could also use sets.

s = set([i.a for i in your_list])
if len(s) == 1:
    b = s.pop()
else:
    b = 2
bcollins
  • 3,379
  • 4
  • 19
  • 35
  • 1
    A set object does not support indexing. Use `next(iter(s))`. Also, why the `i.a`? – Noctua Oct 07 '13 at 16:01
  • i.a was simply a pull the a values out of the list of objects as indicated in the question: "I have a list of items and each item has a property a whose value is binary." – bcollins Oct 07 '13 at 16:08
  • Yes, thanks for the comment Noctua. Sets DON'T support indexing. I was also seeing some strange behavior with the inline conditional and removed it. I think I like the any/all answer the best – bcollins Oct 07 '13 at 16:19
2
def zot(bs):
    return len(set(bs)) if sum(bs) else 0
dansalmo
  • 11,506
  • 5
  • 58
  • 53
1

You can define two boolean vars hasZero and hasOne and set them to True if corresponding value was met while iterating the list. Then b = 2 if hasZero and hasOne, b = 1 if only hasOne and b = 0 if only hasZero.

Another way: you can sum all the a values along the list. If sumA == len(list) then b = 1, if sumA == 0 then b = 0 and if 0 < sumA < len(list) then b = 2.

colriot
  • 1,978
  • 1
  • 14
  • 21
1

Short-circuiting solution. Probably the most efficient way you can do it in Python.

EDIT: Included any and all as per suggestion in comments.

EDIT2: It's now a one-liner.

b = 1 if all(A) else 2 if any(A) else 0
Shashank
  • 13,713
  • 5
  • 37
  • 63
  • 1
    The built-in `any()` and `all()` functions short-circuit and are likely faster than any explicit loop-through-the-items. – martineau Oct 07 '13 at 17:07
  • @martineau Explicit for-looping is implemented in C, just the same as `any` and `all`. I don't see why iteration would be any slower than your methods. – Shashank Oct 07 '13 at 17:09
  • The looping is not implemented in C the same way. – martineau Oct 07 '13 at 17:14
  • @martineau Okay, I'll defer. I edited my answer to do `any` and `all`. – Shashank Oct 07 '13 at 17:23
  • 1
    All off CPython is implemented in C. The difference is that executing the `for` loop involves interpretation of bytecode within the loop, while calls to `all()` and `any()` don't. On my machine, a manual implementation of `any()` applied to a big list is about three times slower than the built-in version. – Sven Marnach Oct 07 '13 at 17:24
  • Convince yourself by using the `timeit` module. – martineau Oct 07 '13 at 17:26
0

This is similar to senderle's suggestion, but written to access the objects' a properties.

from random import randint

class Item(object):
    def __init__(self, a):
        self.a = a

all_zeros = [Item(0) for _ in xrange(10)]
all_ones = [Item(1) for _ in xrange(10)]
mixture = [Item(randint(0, 1)) for _ in xrange(10)]

def check(items):
    if all(item.a for item in items):
        return 1
    if any(item.a for item in items):
        return 2
    else:
        return 0

print 'check(all_zeros):', check(all_zeros)
print 'check(all_ones):', check(all_ones)
print 'check(mixture):', check(mixture)
martineau
  • 119,623
  • 25
  • 170
  • 301
0

You can use list iterators:

>>> L = [0, 0, 0, 0, 0]
>>> L1 = [1, 1, 1, 1, 1]
>>> L2 = [0, 1, 0, 1, 0]
>>> def fn(i):
...     i = iter(i)
...     if all(i): return 1
...     return 2 if any(i) else 0
... 
>>> fn(L)
0
>>> fn(L1)
1
>>> fn(L2)
2
TerryA
  • 58,805
  • 11
  • 114
  • 143