15
\$\begingroup\$

There's already a comprehensive list of tips for python here, so what I'm asking for are tips that specifically apply to using the numpy, scipy or pylab libraries.

These can be either ways to shorten code already using numpy, or ways to shorten common python operations by using these libraries.

One tip per answer, please.

\$\endgroup\$
3
  • \$\begingroup\$ Note that pylab is just matplotlib.pyplot + numpy in a deprecated common namespace. The numpy part of pylab is trivial in the sense that their imports have the same number of bytes, so only plotting stuff could additionaly come from pylab, but I suspect that's not what you had in mind with your question. \$\endgroup\$ Feb 24, 2018 at 12:31
  • 2
    \$\begingroup\$ @AndrasDeak, I'm aware that using pylab is considered bad practice, but very little in codegolf can be considered good practice. Pylab directly includes parts of many numpy packages. For example pylab.randint is valid where numpy would require numpy.random.randint. So for golfing pylab should provide shorter code. \$\endgroup\$
    – user2699
    Feb 24, 2018 at 15:58
  • 1
    \$\begingroup\$ I'm aware that deprecation is not a problem, my point was that it also doesn't give an advantage. I simply didn't realize that subpackages were also loaded into the pylab namespace like that! So sorry, you're perfectly right :) \$\endgroup\$ Feb 24, 2018 at 16:08

4 Answers 4

8
\$\begingroup\$

Make use of Numpy's broadcasting

Broadcasting means replicating a multidimensional array along some of its singleton dimensions to match the size of another array. This happens automatically for Numpy arrays when arithmetic operators are applied to them.

For example, to generate a 10×10 multiplication table you can use

import numpy
t=numpy.arange(1,11)
print(t*t[:,None]) # Or replace t[:,None] by [*zip(t)]

Try it online!

Here t is created as the Numpy array [1, 2, ..., 10]. This has shape (10,), which is equivalent to (1,10). The other operand array, t[:,None], has size (10,1). Multiplying the two arrays implicitly replicates them, so they behave as if they both had shape (10,10). The result, which also has shape (10,10), contains the products for all pairs of entries in the original arrays.

\$\endgroup\$
5
  • \$\begingroup\$ That was a clever use of zip with the broadcasting, is that going to come up in it's own answer? \$\endgroup\$
    – user2699
    Feb 23, 2018 at 22:42
  • \$\begingroup\$ @user2699 I don't think it's worth a separate answer, because [*zip(t)] has the same byte count as the more readable t[:,None]. But you are right, it may be worth noting, so I added it back here \$\endgroup\$
    – Luis Mendo
    Feb 23, 2018 at 22:48
  • \$\begingroup\$ Good point, I guess I didn't actually count the bytes. [*zip(t)] would be two bytes shorter if there were more dimensions. \$\endgroup\$
    – user2699
    Feb 23, 2018 at 23:00
  • 1
    \$\begingroup\$ Note that extended iterable unpacking in [*zip(t)] will only work on python 3. \$\endgroup\$ Feb 23, 2018 at 23:28
  • \$\begingroup\$ I viewed this page as I am interested in finding out what numpy has that Perl 6 doesn't. Anyway that would be written as my \t = 1..10; .fmt('%3d').put for t «*» t[*,Empty] or you could use zip(t) \$\endgroup\$ Feb 24, 2018 at 2:38
6
\$\begingroup\$

Use r_[...] instead of range(...)

Numpy provides matlab like syntax for array creation using r_[...]. Any slice notation in between the brackets is interpreted as an array with the range indicated. So, for instance

r_[:30:4]

is equivalent to

arange(0,30,4)

and for most uses can replace

range(0,30 4)

It can also handle more complex expressions. For example to get indices from 0 up to 10 and back down again,

r_[:10,10:-1:-1]
\$\endgroup\$
4
\$\begingroup\$

The shorter than shortest infinite for comprehension

If numpy is *-imported then using this "feature" of numpy.r_/numpy.c_ an infinte counter is as cheap as:

for[i]in r_:print(i)

This is significantly shorter than this pure Python trick and has the added benefit of actually creating a proper counter.

This can also be used for a cheaper enumerate:

S="abcde"
for x,[i]in zip(S,r_):print(i,x)

If you can live with a "boxed" counter, i.e. [0],[1],[2],... rather than 0,1,2,... then you can save the brackets around i.

The boxes can even be useful: For example, if we have set up a loop anyway, then we can as a byproduct create a reversed "range" of the same length as the looped over sequence S:

C=[]
for e,C[:0]in zip(S,r_):f(e)

If for some reason you need double boxes [[0]],[[1]],[[2]],... use c_ instead of r_.

\$\endgroup\$
3
\$\begingroup\$

Don't forget the matrix class

It may be aesthetically challenged and semi-deprecated since all but forever but it can be useful for golfing.

Here is an example How to solve the LCM in 50 bytes of Python where on a non numpy specific challenge it allows us to outgolf @dingledooper's extremely clever pure Python approach by ~10%. And that's counting the numpy import and without requiring us to be particularly smart:

from numpy import*
print(lcm(*mat(input()).A1))

Try it online!

This shows off three useful features:

  1. Short constructor name mat although not as short as r_ or c_

  2. Hilariously graceful input parser. For example, it will accept

mat("""[1 2]\t[3,4];5  6 7        
8""")

without complaint.

  1. a handful of very short-named attributes, viz. A,A1,T,H,I which are the underlying array object,the same flattened,the transpose,the conjugate transpose and the pseudo inverse.

Finally, there is one more useful feature that we didn't use in the example:

  1. Overloaded * operator. That in itself doesn't gain us much over the @ operator for standard numpy arrays but for example taking the matrix power can be done using ** much shorter than linalg.matrix_power.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.