tags:

views:

178

answers:

5

In some of my code I put a series of objects in a list and I build an additional list out of their attributes, which is a string. I need to determine if all the items in this second list have the exact same value, without knowing beforehand which value it is, and return a bool so that I can do different things in my code depending on the result.

I can't know the names of the properties beforehand, that is why I'm trying to make something as generic as possible.

To make the example clear, an ideal function, called "all_same" would work like this:

>>> property_list = ["one", "one", "one"]
>>> all_same(property_list)
True
>>> property_list = ["one", "one", "two"]
>>> all_same(property_list)
False

I was thinking of making a list of unique elements and then check if its length is 1, but I'm not sure if it's the most elegant solution out there.

+7  A: 

You could cheat and use set:

def all_same( items ):
    return len( set( items ) ) == 1 #== len( items )

or you could use:

def all_same( items ):
    return all( map(lambda x: x == items[0], items ) )

or if you're dealing with an iterable instead of a list:

def all_same( iterable ):
    it_copy = tee( iterable, 1 )
    return len( set( it_copy) ) == 1
wheaties
The set would have just one item, the list would have N.
FogleBird
You could use generator expression in the 2nd code. `all(x == items[0] for x in items)`.
KennyTM
len(set(items)) == 1 definitely most idiomatic.
Beni Cherniavsky-Paskin
+12  A: 
def all_same(items):
    return all(x == items[0] for x in items)

Example:

>>> def all_same(items):
...     return all(x == items[0] for x in items)
...
>>> property_list = ["one", "one", "one"]
>>> all_same(property_list)
True
>>> property_list = ["one", "one", "two"]
>>> all_same(property_list)
False
>>> all_same([])
True
FogleBird
Very nice, I'll use this one, thanks!
Einar
+5  A: 

I originally interpreted you to be testing identity ("the same item"), but you're really testing equality ("same value"). (If you were testing identity, use is instead of ==.)

def all_same(items):
  it = iter(items)
  for first in it:
    break
  else:
    return True  # empty case, note all([]) == True
  return all(x == first for x in it)

The above works on any iterable, not just lists, otherwise you could use:

def all_same(L):
  return all(x == L[0] for x in L)

(But, IMHO, you might as well use the general version—it works perfectly fine on lists.)

Roger Pate
+1 I'll have to remember that recipe.
aaronasterling
@katrielalex: Then you have to try/except for StopIteration; at that point, it's equivalent behavior and the same length.
Roger Pate
I prefer `try: first = next(it) except StopIteration: return True` -- I think the flow is clearer -- but same difference, really.
katrielalex
@Roger: sorry, noticed that and deleted too late. Indeed! =)
katrielalex
I like this solution. Even if the OP asked only for lists, generators are so pervasive now in Python that it's better not to assume that the input is a sequence. Note that you can simplify it a bit with Python >= 2.6: first = next(it, None)
tokland
@tokland: Doesn't work, what if you want to pass `[None, 42]` to the function? (You can use a sentinel object that is only ever used in this function, but way more hassle than it's worth here.)
Roger Pate
@Roger: if you pass [None, 42] you get False, as you'd expect. Note that the sentinel is never actually used if the iterable is empty.
tokland
@tokland: I know the sentinel is never used, that's why I placed None first: you can't tell if the None you get is from the list or from the second param. Could you post code? (A new answer? Since it would also answer this question.) Obviously we're imagining different things.
Roger Pate
@Roger: There is my answer, I think you'll recognize the last assert ;-) you are right that usually sentinels cannot be a potential element in the input, but it does not apply here. Or am I missing something?
tokland
(response in comment on [that answer](http://stackoverflow.com/questions/3787908/python-determine-if-all-items-of-a-list-are-the-same-item/3789000#3789000), posted before asked! :P)
Roger Pate
A: 

wrong: reduce(lambda x,y:x==y,iterable)

ralu
`import operator; operator.eq`
Roger Pate
However, your answer gives the wrong result: `reduce(operator.eq, ["a", "b", False])`. And it wouldn't short-circuit for long sequences.
Roger Pate
Actually, it gives the wrong result for *many* more inputs than I first saw, such as `["a", "a", "a"]`.
Roger Pate
+1  A: 

This works on sequences and iterables (python >= 2.6 because of next built-in):

def all_same(items):
  it = iter(items)
  first = next(it, None)
  return all(x == first for x in it)

assert all_same([])
assert all_same([1])
assert all_same([1, 1])
assert all_same([None, None])
assert not all_same([1, 2])
assert not all_same([None, 42])
tokland
Ah, I was imagining you'd check `first is None` instead of letting this fall-through. It does give the right result, but I prefer to treat this an error/"exceptional circumstance" instead of depending on later code to silently do the right thing.
Roger Pate
I know I am very unpythonic in this opinion, but I don't like that a pretty single line turns into four because I have to catch an exception (I speak in general, you used a for/break in you answer). Yeah, I known about EAFP, but still, if I can avoid it... Thanks for the +1, though :-)
tokland