3

I've made a small function which, given a tuple, compares if all elements in this tuple is of the same sign.

E.g., tuple = [-1, -4, -6, -8] is good, while [-1, -4, 12, -8] is bad. I am not sure I've made the smartest implementation, so I know this is the place to ask.

def check_consistent_categories(queryset): try: first_item = queryset[0].amount if first_item < 0: for item in queryset: if item > 0: return False return True else: for item in queryset: if item < 0: return False return True except: return False 
4
  • You need to show us more code. What's queryset? As of right now, it doesn't make sense. You're first_item is of type queryset[0].amount, and you're comparing that sign to that of queryset[i] Commented Dec 29, 2010 at 22:17
  • 5
    Those don't look like tuples to me. Commented Dec 29, 2010 at 22:17
  • 4
    What should happen if one member is 0? Commented Dec 29, 2010 at 22:18
  • 4
    Why the try...except AnyExceptionIncludingCtrlCAndSystemExit? Commented Dec 29, 2010 at 22:26

9 Answers 9

14

This might help you:

def all_same_sign(ints): return all(x < 0 for x in ints) or all(x > 0 for x in ints) 

You may want to change < and > to <= and >= depending on how you want to treat 0.

Sign up to request clarification or add additional context in comments.

9 Comments

+1 I keep wondering why so many <=5-lines-ish functions posted here are beaten by perfectly readable oneliners. Is it because Python rules, or is is it because some people aren't enlightened yet? (Hint: That's not an xor).
+1: This is efficient, very clear, and also flexible, in terms of how zero is handled!
The beautiful, Pythonic thing about this solution is that all() short-circuits, stopping when it hits a False result, and you're using a generator expression rather than a list comprehension, so this function will do at most one complete traversal of the input list, and possibly less, even though it looks like it does exactly two. Nice.
This solution needs to iterate twice through the iterable. This will work for lists (or tuples, per questioners original intent), but not generators or streams. E.g. all_same_sign(iter(range(-10, 10))) evaluates to True.
I would like to thank all contributers, most were indeed viable, but this one worked for my needs. I am sorry for the typos in the code, misuse of exceptions and my missuse of the word tuples instead of lists. All these posts made me learn more than I came for :-)
|
3

After @EOL's solution, but works without list indexing or iterating multiple times.

def all_same_sign(sequence): items = iter(sequence) try: first = items.next() > 0 except StopIteration: return True return all((item > 0) == first for item in items) 

This also occurred to me, but doesn't take advantage of all/any short-circuiting:

def all_same_sign(sequence): return len(set(item > 0 for item in sequence)) <= 1 

Comments

1

Just one unrelated nit, since you are doing this:

try: [...] except: [...] 

You are ignoring all of the exceptions, be very careful my friend, this will be hiding lots of bugs, instead always be precise while doing exception handling e.g:

try: [...] except IndexError: # Handle out of index except IOError: # Handle I/O error 

etc. Keep this in mind while coding a larger python application.

Comments

1

Here is a nice pythonic way of doing this (using all()):

from math import copysign sign = lambda x: copysign(1, x) # Sign function def check_consistent_categories(sequence): main_sign = sign(sequence[0]) return all(sign(y) == main_sign for y in sequence) 

(for Python 2.6+, which introduced the math.copysign() function). This solution considers that 0 is positive.

Mark Byers' solution is more flexible, though, and it is also arguably more legible.

3 Comments

+1 to counter downvote. Yes, using lambda doesn't equal pythonic (there's a bug in EOL's code, though: should be lambda x: copysign(1, x)), but it's a good solution.
Thank you, @delnan. Using lambda does indeed not make any solution pythonic! However, using a single line for such a simple function is arguably legible enough, and therefore pythonic enough. Furthermore, using all() is really what made me say that this solution is pythonic, because all() is a useful and powerful Python function.
It works, but Mark Byers is so much more readable and understandable, so you only get a +0. :)
1

Why not take advantage of the fact that if all numbers are the same sign, then the sum of the absolute value of each individual number will be equal to the absolute value of the sum of each number?

def check_sign(queryset): return abs(sum(queryset)) == sum(map(abs, queryset)) 

Example Showing Details of the Math

Case 1: All numbers have the same sign

a = (-1, -4, -8) sum(a) = -13 abs(sum(a)) = 13 # the absolute value of the tuple's sum map(abs, a) = [1, 4, 8] sum(map(abs, a)) = 13 # the tuple's sum of each element's absolute value 

Both methods yield 13, so the signs are the same.

Case 2: Not all numbers have the same sign

b = (-1, 4, 8) sum(b) = 11 abs(sum(b)) = 11 # the absolute value of the tuple's sum map(abs, b) = [1, 4, 8] sum(map(abs, b)) = 13 # the tuple's sum of each element's absolute value 

The methods yield different numbers (11 and 13), so the signs are not all the same.

3 Comments

if <boolean expression>: return True else: return False argh! return <boolean expression>! Also, this is very wasteful compared to most other solutions (traverses the list 3 times!).
@delnan: Argh is right! Not sure what I was thinking. I've removed the unnecessary return True and return False.
@delnan: True it is wasteful compared to most other solutions, but one advantage is that it handles the iter(range(-10, 10)) case that identified by tcarobruce. (See his comment in Mark Byer's answer.)
1

Here is one that works fine with generators etc. too

def all_same_sign(ints): ints = iter(ints) first_is_positive = next(ints) > 0 return all( (x>0) == first_is_positive for x in ints) 

If ints is empty, you get a StopIteration exception.

This version gorups 0 with the negative numbers. Use >= if you wish to group with the positive numbers instead

1 Comment

+1: this is an improvement over my own solution, thanks to the bypassing of math.copysign, and the fact that it works on iterators as well.
1
def all_same_sign(iterable): # Works with any iterable producing any items that can be compared to zero. # Iterates through the input no more than once, and this fact is immediately # obvious from the code. # Exits as soon as a bad combination has been detected. pos = neg = zero = False for item in iterable: if item > 0: pos = True elif item < 0: neg = True else: zero = True # Adjust the following statement if a different # treatment of zero is required. # Redundant parentheses added for clarity. if (pos and neg) or zero: return False return True 

1 Comment

+1 This is clean. It's not the most concise... but it's safe, efficient, simple and flexible. Nice!
0

If your numbers are sorted you only need to compare the ends. If not you could sort them:

def same_sign(numbers): numbers = sorted(numbers) #if numbers[0]==0: return True Uncomment if you consider 0 positive if numbers[0]*numbers[-1]>0: return True return False 

If you changed this to >=0 zero would be considered sign neutral. I'm not sure if this is a better implementation than the current answers, but it could be faster for large sets of data.

2 Comments

It would be slower for large sets of data. The other algorithms are O(n), sorting is O(n log n).
@Malvolio I guess your right. I forgot how slow sort was. But this would be wicked fast if the numbers are sorted since it consists of one comparison.
0

Using the principal that multiplying your numbers gives a positive result if they all the same, else negative,

import operator def all_same_sign(intlist): return reduce(operator.mul, intlist) > 0 >>> all_same_sign([-1, -4, -6, -8]) True >>> all_same_sign([-1, -4, 12, -8]) False 

This doesn't handle zeros though...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.