0

I was working on a program to find the integral of a function, where the user specifies the amount of rectangles, the start, and the stop.

NOTE: I am using left-end points of the rectangles.

I have the function working perfectly (at least, it seems to be perfect). However, I wanted to see if I could write a one-liner for it, but not sure how because I'm using eval(). Here is my original code:

def integral(function, n=1000, start=0, stop=100):
    """Returns integral of function from start to stop with 'n' rectangles"""
    increment, rectangles, x = float((stop - start)) / n, [], start
    while x <= stop:
        num = eval(function)
        rectangles.append(num)
        if x >= stop: break
        x += increment
    return increment * sum(rectangles)

This works fine:

>>> integral('x**2')
333833.4999999991

The actual answer is 1000000/3, so my function gives a pretty nice estimate (for only 1000 rectangles).

My attempt at a one-liner:

def integral2(function, n=1000, start=0, stop=100): rectangles = [(float(x) / n) for x in range(start*n, (stop*n)+1)]; return (float((stop-start))/n) * sum([eval(function) for x in rectangles])

However, this isn't a truly a one-liner since I'm using a semi-colon. Also, it's a bit slower (takes a few seconds longer, which is pretty significant) and gives the wrong answer:

>>> integral2('x**2')
33333833.334999967

So, is it possible to use a one-liner solution for this function? I wasn't sure how to implement eval() and float(x)/n in the same list comprehension. float(x)/n achieves a virtual 'step' in the range function.

Thanks!

Rushy Panchal
  • 16,979
  • 16
  • 61
  • 94
  • 6
    Remember that floating-point numbers are inherently inaccurate. Trying a [fixed-point number](http://docs.python.org/2/library/decimal.html) may help. Also, *one-liners are not something to strive for*. Right now it is *bad*. – Waleed Khan Feb 08 '13 at 03:01
  • @WaleedKhan I don't want to use the one-liner for my program, as it is slower. I was just wondering if it was possible, and if it is, how. – Rushy Panchal Feb 08 '13 at 03:05
  • 2
    Incidentally, I would pass to the function a callable object (e.g. a lambda, a function, ...) instead of a string to `eval` - it's cleaner and could speed it up by quite a bit. – Matteo Italia Feb 08 '13 at 03:09
  • @MatteoItalia Agreed - `lambda x: x**2` is much nicer than `eval('x**2')` – Alex L Feb 08 '13 at 03:24

3 Answers3

2
def integral2(function, n=1000, start=0, stop=100): return (float(1)/n) * sum([eval(function) for x in [(float(x) / n) for x in range(start*n, (stop*n)+1)]])

Note that there is a big difference between integral and integral2: integral2 makes (stop*n)+1-(start*n) rectangles, while integral only makes n rectangles.


In [64]: integral('x**2')
Out[64]: 333833.4999999991
In [68]: integral2('x**2')
Out[68]: 333338.33334999956

In [69]: %timeit integral2('x**2')
1 loops, best of 3: 704 ms per loop

In [70]: %timeit integral('x**2')
100 loops, best of 3: 7.32 ms per loop

Perhaps a more comparable translation of integral would be:

def integral3(function, n=1000, start=0, stop=100): return (float(stop-start)/n) * sum([eval(function) for x in [start+(i*float(stop-start)/n) for i in range(n)]])

In [77]: %timeit integral3('x**2')
100 loops, best of 3: 7.1 ms per loop

Of course, it should go with say that there is no purpose for making this a one-liner other than (perverse?) amusement :)

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
2

You don't need to use eval if you receive function as a Python callable itself You could also make use of numpy.arange function to generate a list of float values

Case 1: function is a Python callable

def integrate(f, n, start, end):
    return sum([f(i)*(abs(start-end)/float(n)) for i in np.arange(start, end, abs(start-end)/float(n))])

Case 2: function is not a Python callable

def integrate(f, n, start, end):
    return sum([eval(f)*(abs(start-end)/float(n)) for x in np.arange(start, end, abs(start-end)/float(n))])
Aditya Sriram
  • 443
  • 6
  • 17
0

How about some cheating using numpy's linspace?

integrate = lambda F, n0, nf: sum([F(x)*(abs(nf-n0)/(abs(nf-n0)*300))for x in np.linspace(n0, nf, abs(nf-n0)*300)])

It takes a function F, a lower bound n0 and an upper bound nf. Here's how it works for x**2 between 0 and 5:

In [31]: integrate(lambda x: x**2, 0, 5)
Out[31]: 41.68056482099179

It's pretty close.

EDIT: here's my linspace one liner.

linspace = lambda lo, hi, step:[lo + (hi-lo)/step*i + (hi-lo)/(step*step-1)*i for i in range(step)]

There's a way to tickle that into a full one liner integrator. I leave that to you my friend.

Mr Guest
  • 1
  • 1