Aesthetic way of appending to a list in Python? - python

When appending longer statements to a list, I feel append becomes awkward to read. I would like a method that would work for dynamic list creation (i.e. don't need to initialize with zeros first, etc.), but I cannot seem to come up with another way of doing what I want.
Example:
import math
mylist = list()
phi = [1,2,3,4] # lets pretend this is of unknown/varying lengths
i, num, radius = 0, 4, 6
while i < num:
mylist.append(2*math.pi*radius*math.cos(phi[i]))
i = i + 1
Though append works just fine, I feel it is less clear than:
mylist[i] = 2*math.pi*radius*math.cos(phi[i])
But this does not work, as that element does not exist in the list yet, yielding:
IndexError: list assignment index out of range
I could just assign the resulting value to temporary variable, and append that, but that seems ugly and inefficient.

You don;t need an existing list and append to it later. Just use list comprehension
List comprehension,
is fast,
easy to comprehend,
and can easily be ported as a generator expression
>>> import math
>>> phi = [1,2,3,4]
>>> i, num, radius = 0, 4, 6
>>> circum = 2*math.pi*radius
>>> mylist = [circum * math.cos(p) for p in phi]
Reviewing your code, here are some generic suggestions
Do not compute a known constant in an iteration
while i < num:
mylist.append(2*math.pi*radius*math.cos(phi[i]))
i = i + 1
should be written as
circum = 2*math.pi
while i < num:
mylist.append(circum*math.cos(phi[i]))
i = i + 1
Instead of while use for-each construct
for p in phi:
mylist.append(circum*math.cos(p))
If an expression is not readable, break it into multiple statements, after all readability counts in Python.

In this particular case you could use a list comprehension:
mylist = [2*math.pi*radius*math.cos(phi[i]) for i in range(num)]
Or, if you're doing this sort of computations a lot, you could move away from using lists and use NumPy instead:
In [78]: import numpy as np
In [79]: phi = np.array([1, 2, 3, 4])
In [80]: radius = 6
In [81]: 2 * np.pi * radius * np.cos(phi)
Out[81]: array([ 20.36891706, -15.68836613, -37.32183785, -24.64178397])
I find this last version to be the most aesthetically pleasing of all. For longer phi it will also be more performant than using lists.

mylist += [2*math.pi*radius*math.cos(phi[i])]

you can use list concatenation, but append is twice as fast according to this:
import math
mylist = list()
phi = [1,2,3,4] # lets pretend this is of unknown/varying lengths
i, num, radius = 0, 4, 6
while i < num:
mylist += [(2*math.pi*radius*math.cos(phi[i]))]
i = i + 1

Related

Alternate between add and subtract on a list

Does anybody here know how to sum elements from a list alternating the sign? I tried this:
i = 40
suma = 0
sign = 1
while i <= 100:
suma = suma + sign * i
sign = sign * -1
i = i + 1
print(suma)
but I don't know how to improve it into a function and add a list. Please help.
def add_alternate_sign(a_list):
total = 0
for i, number in enumerate(a_list):
total += number * (-1) ** i
print(total)
numbers = [1,2,3,4,5]
add_alternate_sign(numbers)
You can use numpy for faster vectorized operation. Using your example:
import numpy as np
x = np.arange(1, 101)
np.sum(x*(-1)**x)
For any arbitrary list:
x = [5,10,20,100,2, 1]
y = np.array(x)
np.sum(y*(-1)**np.arange(len(x)))
You can use itertools.cycle to loop continuously through 1, -1 for the multiplier. For example:
import itertools
def alternate_sum(a):
"""
sums elements of a list with alternating multiplier +1, -1, +1, -1, ...
"""
return sum(x * s for x, s in zip(a, itertools.cycle([1, -1])))
print(alternate_sum([10,11,12,14,15]))
This approach is not limited to special case of +1 and -1; it could be used for any sequence of multipliers (of any length).
Another solution:
def alternate_sum(a):
"""
return sum of the elements of a with alternating multiplier 1, -1, ...
"""
return sum(x * (-1)**i for i, x in enumerate(a))
Here i is the index number 0, 1, 2, ...., so (-1)**i is the sequence 1, -1, 1, ...
You can use slices to sum alternate entries in a list:
def altsum(v):
return sum(v[0::2]) - sum(v[1::2])
The only downside is that the slice creates two new lists, one with the even indexes and the other with the odd indexes. If your original list is big, that's a lot of wasted storage (and some time).
So you could use itertools.islice to avoid creating the lists, although it's unlikely to turn out to be faster since islice is not optimised for lists, as far as I know.
from itertools import islice
def altsum(v):
return sum(islice(v, 0, None, 2)) - sum(islice(v, 1, None, 2))
But that won't work for arbitrary iterables because it relies on being able to iterate the argument twice. To construct the alternating sum of an arbitrary iterable, you can use itertools.zip_longest (or itertools.izip_longest in Python 2) along with functools.reduce (or reduce in Python 2):
from functools import reduce
from itertools import zip_longest
def altsum(v):
it = iter(v)
return reduce(lambda accum, pn: accum + pn[0] - pn[1],
zip_longest(it, it, fillvalue=0), 0)

Pythonic way to generate sequence from linear difference

I have a sequence definition
xn+1 = f(xn , xn-1)
Where xn is x evaluated at time tn. Any value in the sequence is defined by some function of the previous two values (and the time step, but for now that's constant). I would like to generate the first N values in this sequence, given x0 & x1.
What's the most pythonic way to do this?
My current approach is just to loop. I create a numpy.ones array of the correct size, then loop through it by index. If index = 0 or 1 then I change the value from 1 to x0 / x1 respectively. For greater indecies I lookup the previous values in the array and apply the function.
But I feel like this doesn't seem to be making use of the numpy array methods, so I wonder if there's a better approach?
Code
In my code I have a createSequence function, which takes in a definition of xn+1 as well as boundary conditions and timestep, and outputs a sequence following those rules. NB, I'm very new to Python so any general advice would also be appreciated!
import numpy as np
def x_next(x_current,x_previous,dt):
"""Function to return the next value in the sequence
x_current and x_previous are the values at tn and tn-1 respectively
dt is the time step
"""
return (x_current - x_previous)/dt #as an example
def createSequence(x_next,x0,x1,start,stop,dt):
""" Function to create sequence based on x_next function, and boundary conditions"""
num = (stop-start)/dt
x_array = np.ones(int(num))
x_array[0] = x0
x_array[1] = x1
for index in range(len(x_array)):
if index == 0:
x_array[index] = x0
elif index == 1:
x_array[index] = x1
else:
x_current = x_array[index - 1]
x_previous = x_array[index - 2]
x_array[index] = x_next(x_current,x_previous,dt)
return x_array
print(createSequence(x_next=x_next,x0=0.1,x1=0.2,start=0,stop=20,dt=0.1))
I would recommend using a generator because
it allows you to generate sequences of arbitrary length without wasting memory
one might argue it is "pythonic".
In the following, I will use the Fibonacci sequence as an example because it takes a similar form to your problem.
def fibonacci(a=0, b=1, length=None):
# Generate a finite or infinite sequence
num = 0
while length is None or num < length:
# Evaluate the next Fibonacci number
c = a + b
yield c
# Advance to the next item in the sequence
a, b = c, a
num += 1
Note that a corresponds to your x_n, b corresponds to x_{n-1}, and c corresponds to x_{n+1}. And a simple example:
>>> list(fibonacci(length=10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
If you want to get the sequence into a numpy array
>>> np.fromiter(fibonacci(length=10), int)
array([ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55])
I think you want the initial collection of terms. However, if it should happen that you, or anyone reading this question, might want individual terms then the sympy library comes in handy. Everything here up to the horizontal line is from Solve a recurrence relation.
>>> from sympy import *
>>> var('y')
y
>>> var('n', integer=True)
n
>>> f = Function('f')
>>> f = y(n)-2*y(n-1)-5*y(n-2)
>>> r = rsolve(f, y(n), [1, 4])
Once you have r you can either evaluate it for various values of n within the sympy facilities ...
>>> N(r.subs(n,1))
4.00000000000000
>>> N(r.subs(n,2))
13.0000000000000
>>> N(r.subs(n,10))
265333.000000000
Or you could 'lift' the code in r and re-use it for your own routines.
>>> r
(1/2 + sqrt(6)/4)*(1 + sqrt(6))**n + (-sqrt(6) + 1)**n*(-sqrt(6)/4 + 1/2)

NumPy slicing: All except one array entry

What is the best way to exclude exact one NumPy array entry from an operation?
I have an array x containing n values and want to exclude the i-th entry when I call numpy.prod(x). I know about MaskedArray, but is there another/better way?
I think the simplest would be
np.prod(x[:i]) * np.prod(x[i+1:])
This should be fast and also works when you don't want to or can't modify x.
And in case x is multidimensional and i is a tuple:
x_f = x.ravel()
i_f = np.ravel_multi_index(i, x.shape)
np.prod(x_f[:i_f]) * np.prod(x_f[i_f+1:])
You could use np.delete whch removes an element from a one-dimensional array:
import numpy as np
x = np.arange(1, 5)
i = 2
y = np.prod(np.delete(x, i)) # gives 8
I don't think there is any better way, honestly. Even without knowing the NumPy functions, I would do it like:
#I assume x is array of len n
temp = x[i] #where i is the index of the value you don't want to change
x = x * 5
#...do whatever with the array...
x[i] = temp
If I understand correctly, your problem is one dimensional? Even if not, you can do this the same way.
EDIT:
I checked the prod function and in this case I think you can just replace the value u don't want to use with 1 (using temp approach I've given you above) and later just put in the right value. It is just a in-place change, so it's kinda efficient. The second way you can do this is just to divide the result by the x[i] value (assuming it's not 0, as commenters said).
As np.prod is taking the product of all the elements in an array, if we want to exclude one element from the solution, we can set that element to 1 first in order to ignore it (as p * 1 = p).
So:
>>> n = 10
>>> x = np.arange(10)
>>> i = 0
>>> x[i] = 1
>>> np.prod(x)
362880
which, we can see, works:
>>> 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
362880
You could use a list comprehension to index all the points but 1:
i = 2
np.prod(x[[val for val in range(len(x)) if val != i]])
or use a set difference:
np.prod(x[list(set(range(len(x)) - {i})])

How do I perform math on every other number in a list?

E.g.: How do I change
a = [1,2,3,4]
to this:
a = [2,2,6,4]
so every other element is doubled?
If you want to do it in place, you can use slice assignment:
>>> a[::2] = [x*2 for x in a[::2]]
>>> a
[2, 2, 6, 4]
You can loop through every other index:
for index in range(0, len(your_list), 2):
your_list[index] *= 2
You can also do it using slice assignment, as #mgilson notes:
your_list[::2] = [x*2 for x in your_list[::2]]
While this is certainly more concise, it may also be more confusing for the average person reading through the code - assigning to a slice with a non-default skip factor isn't very intuitive.
There is another way to take two steps at a time a little more intuitive, like this
for i in range(len(yourList)/2):
yourList[2*i] = 2*yourList[2*i]
Though I do like the neat tricks used in the other answers, perhaps a more verbose and less-language specific explanation of what's going on is as follows:
for i in range(0, len(a)): # Iterate through the list
if i%2 == 0: # If the remainder of i รท 2 is equal to 0...
a[i] = a[i] * 2 # Change the current element to twice what it was

How to create an integer array in Python?

It should not be so hard. I mean in C,
int a[10];
is all you need. How to create an array of all zeros for a random size. I know the zeros() function in NumPy but there must be an easy way built-in, not another module.
If you are not satisfied with lists (because they can contain anything and take up too much memory) you can use efficient array of integers:
import array
array.array('i')
See here
If you need to initialize it,
a = array.array('i',(0 for i in range(0,10)))
two ways:
x = [0] * 10
x = [0 for i in xrange(10)]
Edit: replaced range by xrange to avoid creating another list.
Also: as many others have noted including Pi and Ben James, this creates a list, not a Python array. While a list is in many cases sufficient and easy enough, for performance critical uses (e.g. when duplicated in thousands of objects) you could look into python arrays. Look up the array module, as explained in the other answers in this thread.
>>> a = [0] * 10
>>> a
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Use the array module. With it you can store collections of the same type efficiently.
>>> import array
>>> import itertools
>>> a = array_of_signed_ints = array.array("i", itertools.repeat(0, 10))
For more information - e.g. different types, look at the documentation of the array module. For up to 1 million entries this should feel pretty snappy. For 10 million entries my local machine thinks for 1.5 seconds.
The second parameter to array.array is a generator, which constructs the defined sequence as it is read. This way, the array module can consume the zeros one-by-one, but the generator only uses constant memory. This generator does not get bigger (memory-wise) if the sequence gets longer. The array will grow of course, but that should be obvious.
You use it just like a list:
>>> a.append(1)
>>> a.extend([1, 2, 3])
>>> a[-4:]
array('i', [1, 1, 2, 3])
>>> len(a)
14
...or simply convert it to a list:
>>> l = list(a)
>>> len(l)
14
Surprisingly
>>> a = [0] * 10000000
is faster at construction than the array method. Go figure! :)
import numpy as np
new_array=np.linspace(0,10,11).astype('int')
An alternative for casting the type when the array is made.
a = 10 * [0]
gives you an array of length 10, filled with zeroes.
import random
def random_zeroes(max_size):
"Create a list of zeros for a random size (up to max_size)."
a = []
for i in xrange(random.randrange(max_size)):
a += [0]
Use range instead if you are using Python 3.x.
If you need to initialize an array fast, you might do it by blocks instead of with a generator initializer, and it's going to be much faster. Creating a list by [0]*count is just as fast, still.
import array
def zerofill(arr, count):
count *= arr.itemsize
blocksize = 1024
blocks, rest = divmod(count, blocksize)
for _ in xrange(blocks):
arr.fromstring("\x00"*blocksize)
arr.fromstring("\x00"*rest)
def test_zerofill(count):
iarr = array.array('i')
zerofill(iarr, count)
assert len(iarr) == count
def test_generator(count):
iarr = array.array('i', (0 for _ in xrange(count)))
assert len(iarr) == count
def test_list(count):
L = [0]*count
assert len(L) == count
if __name__ == '__main__':
import timeit
c = 100000
n = 10
print timeit.Timer("test(c)", "from __main__ import c, test_zerofill as test").repeat(number=n)
print timeit.Timer("test(c)", "from __main__ import c, test_generator as test").repeat(number=n)
print timeit.Timer("test(c)", "from __main__ import c, test_list as test").repeat(number=n)
Results:
(array in blocks) [0.022809982299804688, 0.014942169189453125, 0.014089107513427734]
(array with generator) [1.1884641647338867, 1.1728270053863525, 1.1622772216796875]
(list) [0.023866891860961914, 0.035660028457641602, 0.023386955261230469]

Categories

Resources