2

I have two lists: a = [1, 2, 3] and b = [4, 5, 6].

I have used two loops in python to subtract each element of b from each element of a.

import numpy as np
a = [1, 2, 3]
b = [4, 5, 6]
p = -1
result = np.zeros(len(a)*len(a))
for i in range(0,len(a)):
    for j in range(0,len(a)):
        p = p + 1
        result[p] = a[i] - b[j]

My result is correct: result = [-3., -4., -5., -2., -3., -4., -1., -2., -3.].

However, I would like to know if there is more elegant('pythonic') way to do it.

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
DimKoim
  • 1,024
  • 6
  • 20
  • 33
  • 1
    you havnt provided ur whole code – Avinash Babu Nov 04 '14 at 13:27
  • 2
    Instead of making it more "pythonic", which is a ridiculous idea, make this code much easier to read. Start by making the variable names way more meaningful. – Tymoteusz Paul Nov 04 '14 at 13:27
  • 2
    @Puciek Cannot understand what you mean because I thing it is pretty clear. iF not, correct me. – DimKoim Nov 04 '14 at 13:33
  • @DimKoim you mean that variables named "i" and "j" are clear? I must have forgotten my decoder ring then. – Tymoteusz Paul Nov 04 '14 at 13:34
  • 2
    @Puciek Because for sure you are way more expert than me, please show me the way how i,j variables can be clear. Much appreciated from a very beginner programmer. Thank you. – DimKoim Nov 04 '14 at 13:47
  • 1
    @DimKoim well, the question is - what are they supposed to represent? Name should relate to that. So if a variable holds the letter "J", then name "j" is fine, but if it is holding a part of an list, then it should convey the name of element in question, in this case "number" would be a sorta-ok name. But trouble in naming "i" or "j" comes from the fact that lists are named "a" and "b", and thus we have no idea what are they representing. Have a read of the middle part here for an example of variable refactoring, and how much it matters: http://tinyurl.com/oasu2q2 – Tymoteusz Paul Nov 04 '14 at 13:51

3 Answers3

13

There is no need to use an index. You can iterate over the values.

a = [1, 2, 3]
b = [4, 5, 6]
result = []
for x in a:
    for y in b:
        result.append(x - y)

The pythonic way would be a list comprehension.

a = [1, 2, 3]
b = [4, 5, 6]
result = [x - y for x in a for y in b]

Please bear in mind that you should use meaningful names for a, b, xand y in real code.

Matthias
  • 12,873
  • 6
  • 42
  • 48
  • I would implement it using the list comprehension. I think it's the most Pythonic, since it uses functional programming where it's appropriate. – Kyle_S-C Nov 04 '14 at 14:06
5

Pythonic way will be to use itertools.product, as it returns Cartesian product of the iterables passed to it. As no Python loop is involved it is going to be fast compared to version that are using loop:

>>> from operator import sub
>>> from itertools import starmap, product
>>> list(starmap(sub, product(a, b)))
[-3, -4, -5, -2, -3, -4, -1, -2, -3]

In NumPy you can do this using the recipes mentioned here:

>>> arr = np.dstack(np.meshgrid(a, b)).reshape(-1, 2)
>>> np.subtract(arr[:,0], arr[:,1])
array([-3, -2, -1, -4, -3, -2, -5, -4, -3])

Timing comparisons:

>>> b = [4, 5, 6]*1000
>>> a = [1, 2, 3]*1000
>>> %timeit list(starmap(sub, product(a, b)))
1 loops, best of 3: 464 ms per loop
>>> %timeit [x - y for x in a for y in b]
1 loops, best of 3: 491 ms per loop
>>> %%timeit                                 
result = []
for x in a:
    for y in b:
        result.append(x - y) #attribute lookup is slow
... 
1 loops, best of 3: 908 ms per loop
>>> %%timeit
result = [];append = result.append 
for x in a:
    for y in b:
        append(x - y)
... 
1 loops, best of 3: 617 ms per loop
#Numpy version will be little faster if a and b were nd arrays.
>>> %timeit arr = np.dstack(np.meshgrid(a, b)).reshape(-1, 2);np.subtract(arr[:,0], arr[:,1])
1 loops, best of 3: 573 ms per loop
Community
  • 1
  • 1
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
5

Since you are using numpy already, it's as simple as this:

In [29]: numpy.repeat(a, len(b)) - numpy.tile(b, len(a))
Out[29]: array([-3, -4, -5, -2, -3, -4, -1, -2, -3])

Per request in comments, OP wants 1d output size N^2 (although 2d might be more natural), for that numpy provides convenient functions to extend N-sized array to N^M, namely repeat and tile:

numpy.repeat([1,0], 2)
array([1, 1, 0, 0])

numpy.tile([1,0], 2)
array([1, 0, 1, 0])

Once both a and b are reformatted to the shape of p, it's a matter of element-wise subtraction that's native in numpy.

Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120