mathspp.com feed Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com. 2021-10-12T15:00:09+02:00 Rodrigo Girão Serrão https://mathspp.com/pt/blog TIL #012 – At operator for matrix multiplication https://mathspp.com/pt/blog/til/012 2021-10-12T15:00:09+02:00 2021-10-12T00:00:00+02:00

Today I learned that Python 3.5+ supports the operator @ for matrix multiplication. # At operator @

Since Python 3.5, Python has the infix operator @. This operator was introduced with PEP 465 to be used in matrix multiplication.

You can try to use it with just vanilla Python, but no vanilla Python types define their behaviour with @:

>>> 3 @ 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for @: 'int' and 'int'

However, just looking at the error above, you see that the error is in @ not knowing what to do with integers. The error is not the fact that @ is an invalid operator! So cool!

# Matrix multiplication in numpy with @

If you have numpy at hand, you can check @ works, because numpy arrays added support to be used with @:

>>> import numpy as np
>>> np.random.rand(3, 3) @ np.random.rand(3, 3)
array([[0.89431673, 0.57949659, 0.59470797],
[0.47364302, 0.29837518, 0.33552972],
[1.12634752, 0.75218169, 0.78876082]])
>>> _ @ np.eye(3)   # The identity (eye) matrix leaves the other matrix unchanged.
array([[0.89431673, 0.57949659, 0.59470797],
[0.47364302, 0.29837518, 0.33552972],
[1.12634752, 0.75218169, 0.78876082]])

# Using @ with custom classes/types

If you want your own objects to add support for @, all you have to do is implement the dunder methods __matmul__ and __rmatmul__:

>>> class Dummy:
...     def __matmul__(self, other):
...         print("Works!")
...         return 42
...     def __rmatmul__(self, other):
...         print("Also works!")
...         return 73
...
>>> d = Dummy()
>>> d @ 1
Works!
42
>>> 1 @ d
Also works!
73

There's also the __imatmul__ method for in-place matrix multiplication:

>>> class Dummy:
...     def __imatmul__(self, other):
...         print("In-place!")
...
>>> d = Dummy()
>>> d @= 1
In-place!

Of course, this silly example above doesn't show you the proper semantics of the __matmul__, __rmatmul__, and __imatmul__ methods. It just shows you they exist and they interact with the operator @!

By the way, for reference, here is the tweet that showed me this:

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #011 – emojis in Python with pythonji https://mathspp.com/pt/blog/til/011 2021-10-12T15:00:09+02:00 2021-10-11T00:00:00+02:00

Today I learned that you can use emojis as variable names in Python if you use pythonji. # Can you use emojis in Python?

No! At the time of writing, emojis are not valid Python identifiers. This means that this code fails:

>>> 🍔 = "hamburguer"   # SyntaxError

However, if you install the package pythonji , you will be able to run code like that!

Installing pythonji is as easy as python -m pip install pythonji!

With pythonji installed, we can run programs that make use of emojis!

Here's a little program I wrote:

import enum

class 🍽(enum.Enum):
🍔 = "hamburguer"
🍕 = "pizza"
🍅 = "tomato"
🥕 = "carrot"

class 🧍:
def __init__(🤳, 😋👍):
🤳.😋👍 = 😋👍

def 🍽(🤳, 😋):
if 😋 in 🤳.😋👍:
return "Yummi!"
else:
return "Ok, I'll eat that."

👨 = 🧍([🍽.🍕, 🍽.🥕])
print(👨.🍽(🍽.🍕))
print(👨.🍽(🍽.🍅))

Save it to the file foo.🐍 (yes, the extension really is 🐍!).

Now, run it with pythonji foo.🐍 and this is the output:

 > pythonji foo.🐍
Yummi!
Ok, I'll eat that.

Amazing, right? 😆

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #010 – generator return https://mathspp.com/pt/blog/til/010 2021-10-10T01:56:57+02:00 2021-10-09T00:00:00+02:00

Today I learned that Python generators can return a value. # Generators

Generators are interesting Python objects that produce a series of values, but one by one. In a way, they can be thought of as stateful functions. (That is, functions with state.)

What I learned is that generators can also return something. Here is the tweet that prompted this discovery:

# Returning from a generator

When you use a return inside a generator, the generator will have that returned information in its StopIteration exception when it's done:

>>> def f():
...     yield 10
...     return 20
...
>>> gen = f()
>>> next(gen)
10
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 20

If you want to get access to that value, you just need to catch the exception:

>>> gen = f(); next(gen);
10
>>> try:
...     next(gen)
... except StopIteration as e:
...     val = e.value
...
>>> val
20

That's it for now! Stay tuned and I'll see you around!

]]>
TIL #009 – vars https://mathspp.com/pt/blog/til/009 2021-10-07T23:30:33+02:00 2021-10-07T00:00:00+02:00

Today I learned about the built-in function vars in Python. # vars

Python has a bunch of built-in functions. Like, a lot! It's very difficult to keep track of all of them, remember them, and use them correctly.

A built-in function I just learned about is vars. I heard about it from Reuven Lerner (a Python trainer) in his newsletter “Better Developers”. (Disclaimer: that's a referral link, but I am a subscriber and avid reader of the newsletter, so it is a very honest recommendation!)

Looking at the Python documentation, we can see what vars does:

“Return the __dict__ attribute for a module, class, instance, or any other object with a __dict__ attribute.”

The documentation also goes on to say that “without an argument, vars() acts like locals().”. So that's not useful because we can always use locals().

When vars really shines is when you give it an argument, like a module or a class instance!

>>> class Person:
...     def __init__(self, name):
...         self.name = name
...
>>> p = Person("me")
>>> vars(p)
{'name': 'me'}

So, we can see that vars is a very handy way of inspecting an instance of a class you defined. Quite cool, right?

For things like built-in classes, or modules, vars is similar to dir. Recall that dir lists the names of all the attributes of an object, but vars will give you a mapping with the names of the attributes and the corresponding values:

>>> import math
>>> dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']
>>> vars(math)
{'__name__': 'math', '__doc__': 'This module provides access to the mathematical functions\ndefined by the C standard.', ...}

That's it for now! Stay tuned and I'll see you around!

]]>
Problema #046 – grelha com triângulos https://mathspp.com/pt/blog/problems/triangle-grid 2021-10-09T16:43:22+02:00 2021-10-03T00:00:00+02:00

Consegues desenhar 4 triângulos nesta grelha 5 por 5 e cobrir todos os pontos? O teu objetivo é desenhar 4 triângulos numa grelha 5 por 5. Claro que há algumas restrições:

• todos os vértices de todos os triângulos têm de estar sobre pontos da grelha;
• os 25 pontos da grelha têm de ser atravessados por uma aresta de algum triângulo, ou estar sob algum vértice; e
• os triângulos não podem ter ângulos de 90⁰ (por este motivo, concluímos que a imagem em cima não é parte da solução, já que o triângulo da direita tem um ângulo de 90⁰).

Se precisares de clarificar alguma coisa, não hesites em perguntar na secção de comentários em baixo.

Eu encontrei este problema no site [Puzzling][source] da família de sites do Stack Exchange.

# Submissões

Parabéns a todos os que conseguiram resolver o problema e, em particular, aos que me enviaram as suas soluções:

• David H., Taiwan;
• Pedro G., Portugal;
• B. Praveen R., Índia;
• Kees L., Países Baixos;
• Mihalis G., Grécia;
• Alfredo E., México;

Junta-te à comunidade e envia-me a tua solução por email!

# Solução

Uma proposta de solução vai ser publicada aqui quando tiverem passado duas semanas desde a publicação deste problema.

Não te esqueças de subscrever a newsletter para receberes os problemas diretamente na tua caixa de correio.

]]>
TIL #008 – two pass join https://mathspp.com/pt/blog/til/008 2021-10-07T23:30:33+02:00 2021-09-30T00:00:00+02:00

Today I learned that the .join method in Python is a two-pass algorithm, and that's why joining a list comprehension is faster than a generator expression. # Context

In my talk, I walk you through successive refactors of a piece of “bad” Python code, until you reach a “better” piece of Python code. The talk follows closely the “bite-sized refactoring” Pydont'.

In the talk, I started with this code:

def myfunc(a):
empty=[]
for i in range(len(a)):
if i%2==0:
empty.append(a[i].upper())
else:
empty.append(a[i].lower())

return "".join(empty)

# ---
>>> myfunc("Hello, world!")
'HeLlO, wOrLd!'
>>> myfunc("Spongebob.")
'SpOnGeBoB.'

And refactored it up until this point:

def alternate_casing(text):
return "".join([
char.lower() if idx % 2 else char.upper()
for idx, char in enumerate(text)
])

# .join

In a feedback tweet, someone said that I could've removed the [] of the list comprehension. That would, instead, define a generator expression, that should even be faster.

Thankfully, I was prepared for that: before the talk, I checked the performance of both alternatives, and found out that the generator expression was not faster.

That surprised me, because I was under the impression that generator expressions tend to be faster that list comprehensions, but the data in front of me didn't lie... And so, I didn't change the list comprehension into a generator expression in the talk...

But why is the generator expression slower?

As it turns out, the .join method is a two-pass algorithm!

At least, according to a tweet that was quoted to me:

Sadly, I haven't been able to verify this yet.

However, it does make some sense: if .join really is a two-pass algorithm, then generator expressions hurt us, because generators can only be traversed once. Therefore, we need to do something before .join can actually start its work.

Peeking at the source code for .join will probably reveal this...

I'm a bit sleepy, but I'll interrupt typing right now to take a quick peek at the source, see if I can find this code.

[Me browsing through Python's source code.]

Ok, I think I found it 🎉🎉!

In Python's GitHub repo, there's an Objects folder that contains a stringlib that has a join.h file. Opening it, you can see that there are two for loops over the data.

In the exact commit I looked at, at the beginning of the 30th of September of 2021, those two for loops start...

]]>
TIL #007 – math.nextafter https://mathspp.com/pt/blog/til/007 2021-10-07T23:30:33+02:00 2021-09-29T00:00:00+02:00

Today I learned about the math.nextafter method. # math.nextafter

0 is a neat number, isn't it?

Perhaps one of the greatest discoveries of mankind.

But what's the number that comes after 0? That would be the smallest number in the set $$]0, +\inf[$$, if you're familiar with the mathematical notation for sets.

In short, $$[a, b]$$ is the contiguous set of numbers $$x$$ that satisfy the restriction $$a \leq x \leq b$$. Notice how $$a$$ and $$b$$ are included inside $$[a, b]$$ because the brackets are closed. If the brackets are open, then that number is not included.

For the intervals below, $$x$$ belongs to it if...

• $$[a, b]$$$$a \leq x \leq b$$;
• $$[a, b[$$$$a \leq x < b$$;
• $$]a, b]$$$$a < x \leq b$$; and
• $$]a, b[$$$$a < x < b$$.

So, in $$]0, +\infty[$$, nor 0, nor $$+\infty$$ are included. Thus, what's the minimum element of this interval? Well, there isn't any!

Mathematically speaking, there is no minimum in the interval $$]0, +\infty[$$. Why not? Whatever you pick as a potential minimum $$m$$, $$m/2$$ will be smaller than $$m$$ and still be greater than $$0$$, that is, $$0 < m/2 < m < +\infty$$, and so $$m/2$$ is in $$]0, +\infty[$$.

That's interesting, right?

But this is a whole other story if we go into the programming real! Because of how floats are represented, Python has a number that comes immediately after 0. So, what is it?

Here it is:

>>> import math
>>> math.nextafter(0, 1)
5e-324

That's $$5 \times 10^{-324}$$, it's freakishly small!

So, what's the role of the math.nextafter method?

>>> help(math.nextafter)
Help on built-in function nextafter in module math:

nextafter(x, y, /)
Return the next floating-point value after x towards y.

Hence, nextafter looks at x and then checks what's the float that's immediately next to x, if you walk in the direction of y. If I set x to zero and y to one, I get the smallest float that Python can represent on my machine.

So, what's the next float that Python can represent after 1?

Give it some thought.

Here it is:

>>> math.nextafter(1, 999)
1.0000000000000002

I'll be honest, for a second I thought it should've been 1 + 5e-324, but it makes sense it wasn't that. Floating point numbers have limited precision, right? And one thing that's limited is the size of the mantissa: the stuff that comes after the decimal point.

Above, we can see that the mantissa has 16 digits, and that's the size of the mantissa in Python.

So, what's the next number after 10?

Give it some thought.

Here it is:

>>> math.nextafter(10, 999)
10.000000000000002

If you count, now there's only 15 digits to the right of the decimal point... But...

]]>
Expressões condicionais | Pydon't 🐍 https://mathspp.com/pt/blog/pydonts/conditional-expressions 2021-10-02T18:29:26+02:00 2021-09-28T00:00:00+02:00

Este Pydon't mostra-te como usar as expressões condicionais de Python, a alternativa ao operador ternário em Python. (Se és novo aqui e não sabes o que é uma Pydon't, então talvez queiras começar por ler a Proclamação das Pydon'ts.)

Infelizmente, para poupar algum tempo, ainda não traduzi este artigo para português... Hei de o fazer eventualmente... Se quiseres, deixa um comentário em baixo a pedir que eu traduza o artigo ASAP ou submete um PR com a tua tradução.

]]>
TIL #006 – file unpacking https://mathspp.com/pt/blog/til/006 2021-10-07T23:30:33+02:00 2021-09-24T21:00:00+02:00

Today I learned that files can also be unpacked in Python. # File unpacking

After learning that strings can be unpacked in Python, I shared the short article on Twitter.

As a reply, @dabeaz suggested I tried doing it with a file:

After seeing how strings can be unpacked, unpacking a file didn't look so weird, but it was still a pleasant surprise!

But it “does make sense”, after all you can iterate directly over a file, which essentially iterates over the lines of the file.

Grab a CSV file "my_data.csv", for example with this data:

Name, Surnames
John, Doe
Mary, Smith

Then, in your Python REPL, you can get this to work:

>>> header, *data = open("my_data.csv")
'Name, Surnames\n'
>>> data
['John, Doe\n', 'Mary, Smith\n']

It is not as useful as using the [csv][csv] module to read the CSV data in and process it, but it is still a nifty trick.

Come to think of it, if there is a place when this will be useful, probably won't be with CSV files...

I'll let you know if I put this little trick to good use!

That's it for now! Stay tuned and I'll see you around!

]]>
List comprehensions 101 | Pydon't 🐍 https://mathspp.com/pt/blog/pydonts/list-comprehensions-101 2021-09-29T07:08:30+02:00 2021-09-24T19:00:00+02:00

Este Pydon't ensina-te a usar list comprehensions em Python. (Se és novo aqui e não sabes o que é uma Pydon't, então talvez queiras começar por ler a Proclamação das Pydon'ts.)

Infelizmente, para poupar algum tempo, ainda não traduzi este artigo para português... Hei de o fazer eventualmente... Se quiseres, deixa um comentário em baixo a pedir que eu traduza o artigo ASAP ou submete um PR com a tua tradução.

]]>