# Plus Minus in Python

You're given an array containing integer values. You need to print the fraction of count of positive numbers, negative numbers and zeroes to the total numbers. Print the value of the fractions correct to 3 decimal places.

Input Format

First line contains $N$, which is the size of the array. Next line contains $N$ integers $A_1, A_2, A_3, ⋯, A_N$, separated by spaces.

Constraints

• $1 \le N \le 100$
• $−100 \le A_{i} \le 100$

Solution

from __future__ import division

N = int(raw_input())
ary = map(int, raw_input().split())

count_negatives = len(filter(lambda x: x < 0, ary))
count_positives = len(filter(lambda x: x >0, ary))
count_zeros = len(filter(lambda x: x == 0, ary))

print count_positives / N
print count_negatives / N
print count_zeros / N


It seems that this problem has a very straightforward solution but I am more interested if there is a better functional approach.

Here is another approach using reduce (n stands for negative, z for zero, p for positive):

def counter((n, z, p), raw_value):
x = int(raw_value)
return n + (x<0), z + (x==0), p + (x>0)

n, z, p = reduce(counter, raw_input().split(), (0,0,0))


We consider the tuple (n, z, p) (negative, zero, positive) which will count the number of positive, "zero" and negative numbers in the string. It starts with the value 0,0,0 (3rd argument of reduce).

The work is mainly done by the reduce function: it calls the function counter with the n, z, p tuple and every value of the input string (thanks to raw_input().split()).

It uses a boolean->integer implicit conversion (True -> 1, False -> 0) to increment the correct element of the tuple, depending on the value of the current item.

One of the main advantage, is that every element of the array is read only once.

Regarding memory consumption, it could be further improved by using a generator instead of raw_input.split()

• Good to see the use of reduce here. – CodeYogi Oct 6 '15 at 7:52
• I think n, z, p = reduce(counter, (int(x) for x in raw_input().split()), (0,0,0)) no need for int in function. – CodeYogi Oct 20 '15 at 4:26

Print the value of the fractions correct to 3 decimal places.

You didn't really do this part. You're just printing the full float. You can use str.format() to print to three places. With this syntax:

print "{:.3f}".format(count_positives / N)


The .3f syntax tells Python to print the first three digits only. Note that str.format will actually round the value, not just truncate it:

"{:.3f}".format(1.3449)
>>> '1.345'


Though, since you're just printing the value by itself, you can use the format builtin where you pass the string and the format as a string.

format(count_positives / N, '.3f')


You don't need a filter to count the zeros. Lists have a built in count function which will return the number of times a particular object occurs in it. Like this:

count_zeros = ary.count(0)


Your algorithm is needless constructing the entire filtered list and then finding the length of it. Instead of len(filter(lambda x: x < 0, ary)) and similar commands, you could try sum([1 for el in my_arr if el > 0]).

On my chat machine, this gives a speedup of about 2×. Here's some code to back that up, along with testing of a NumPy version.

from random import randint
import timeit
import numpy as np

# construct array
my_arr = [randint(-100, 100) for _ in xrange(100000)]

# check consistency of approaches
assert np.count_nonzero(np.array(my_arr) > 0) == sum([1 for el in my_arr if el > 0])
assert len(filter(lambda x: x > 0, my_arr)) == sum([1 for el in my_arr if el > 0])

# time native python list comprehension + sum()
%timeit  sum([1 for el in my_arr if el > 0])

# time filter() + len()
%timeit len(filter(lambda x: x > 0, my_arr))

# time numpy solution w/ np.count_nonzero
%timeit np.count_nonzero(np.array(my_arr) > 0)

# time numpy solution w/ np.sum
%timeit (np.array(my_arr) > 0).sum()


100 loops, best of 3: 4.97 ms per loop
100 loops, best of 3: 10.2 ms per loop
100 loops, best of 3: 5.02 ms per loop
100 loops, best of 3: 4.91 ms per loop


The bottom line conclusion is that whether in native Python or NumPy, sum()-based approaches are about twice as fast.

Of course:

1. The constraints of your particular problem, in particular 1 ≤ N ≤ 100, mean that timing differences of this magnitude will be of no practical concern.

2. You didn't do the requested formatting, as SuperBiasedMan has noted.

• sum([1 for el in my_arr if el > 0]) - doesn't this still create the whole list in memory? sum(1 for el in my_arr if el > 0) wouldn't, and might be faster – Izkata Sep 22 '15 at 16:51
• Good point. My version creates a list in memory, but it is a list of ones, not of elements from my_arr. I expected the generator you proposed to be faster but when I tried it on my machine, it was about the same speed as the list comprehension. – Curt F. Sep 22 '15 at 17:44
• @CurtF. could you benchmark my reduce solution below? Since it generates only one array for the whole processing, even if it's a little bit slower, you need to call it only once :-) – oliverpool Sep 23 '15 at 8:56

Instead of filtering through your array 3 times, you could use a single loop and increment counters. This makes for less beautiful code but it is much more practical.

Otherwise, you could simply filter twice and calculate the last count by substracting the two filter results from your total count :

from __future__ import division

N = int(raw_input())
ary = map(int, raw_input().split())

count_negatives = len(filter(lambda x: x < 0, ary))
count_positives = len(filter(lambda x: x >0, ary))
count_zeros = N - count_negatives - count_positives

print count_positives / N
print count_negatives / N
print count_zeros / N