Pergunta

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?

Foi útil?

Solução 2

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.

Outras dicas

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

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

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.

you could also use sets.

s = set([i.a for i in your_list])
if len(s) == 1:
    b = s.pop()
else:
    b = 2
def zot(bs):
    return len(set(bs)) if sum(bs) else 0

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.

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

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)

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top