mathspp.com feed Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com. 2021-10-20T15:46:48+02:00 Rodrigo Girão Serrão https://mathspp.com/blog Problem #047 – surgery gloves https://mathspp.com/blog/problems/surgery-gloves 2021-10-20T15:46:48+02:00 2021-10-18T00:00:00+02:00

How can two doctors operate two patients with only two pairs of latex gloves?!

# Problem statement

Alice and Bob are a bit sick, and they need to undergo surgery. Both of them need something done to their stomach and their heart.

Unfortunately, the hospital is severely underfunded and only has two pairs of latex gloves at their disposal.

How can Charles and Diana (respectively the stomach and heart surgeons) operate, knowing that

• the gloves that touch one doctor's skin cannot touch the other doctor's skin; and
• the gloves that touch one patient's blood cannot touch the other patient's blood?

Sadly, washing the gloves is out of the question, because the hospital has no means of properly sanitising the gloves!

If you need any clarification whatsoever, feel free to ask in the comment section below.

My uncle told me this puzzle a long time ago! I had a blast solving it!

# Solvers

Congratulations to the ones that solved this problem correctly and, in particular, to the ones who sent me their correct solutions:

• Pedro G., Portugal;
• Francisco M., Mexico;
• I+T, USA;
• David H., Taiwan;
• Chukwudi, Nigeria/UK;

Join the list of solvers by emailing me your solution!

# Solution

The solution to this problem will be posted here after this problem has been live for 2 weeks.

Don't forget to subscribe to the newsletter to get bi-weekly problems sent straight to your inbox.

]]>
TIL #012 – At operator for matrix multiplication https://mathspp.com/blog/til/012 2021-10-19T00:33:13+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/blog/til/011 2021-10-19T00:33:13+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/blog/til/010 2021-10-19T00:33:13+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/blog/til/009 2021-10-19T00:33:13+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!

]]>
Problem #046 – triangle grid https://mathspp.com/blog/problems/triangle-grid 2021-10-18T23:52:25+02:00 2021-10-03T00:00:00+02:00

Can you draw 4 triangles in this 5 by 5 grid, covering all dots? # Problem statement

Your challenge is to draw 4 triangles in a regular 5 by 5 grid. Of course, there are some restrictions:

• all vertices of all triangles have to lie on the dots;
• all 25 dots have to be covered by the vertices/edges of the triangles; and
• no triangle may have a right angle (thus, the image above is a bad start: the triangle to the right has a right angle).

If you need any clarification whatsoever, feel free to ask in the comment section below.

I originally found this puzzle in the Puzzling Stack Exchange website.

# Solvers

Congratulations to the ones that solved this problem correctly and, in particular, to the ones who sent me their correct solutions:

• David H., Taiwan;
• Michael W., USA;
• Pedro G., Portugal;
• B. Praveen R., India;
• Kees L., Netherlands;
• Jerry J., USA;
• Mihalis G., Greece;
• Alfredo E., Mexico;
• Martin J., Czeck Republic;

Join the list of solvers by emailing me your solution!

# Solution

There are many different solutions to this problem.

Jerry, from the USA, submitted the solution that I am most fond of: This solution is my favourite because of the vertical symmetry between the two larger triangles and because the two smaller triangles are rotations of the other.

Just for reference, here is another possible solution (among the many that exist!); this one was from Martin, picked randomly among the others. Don't forget to subscribe to the newsletter to get bi-weekly problems sent straight to your inbox.

]]>
TIL #008 – two pass join https://mathspp.com/blog/til/008 2021-10-19T00:33:13+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/blog/til/007 2021-10-19T00:33:13+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...

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

This Pydon't will teach you how to use Python's conditional expressions. (If you are new here and have no idea what a Pydon't is, you may want to read the Pydon't Manifesto.)

# Introduction

Conditional expressions are what Python has closest to what is called a “ternary operator” in other languages.

In this Pydon't, you will:

• learn about the syntax of conditional expressions;
• understand the rationale behind conditional expressions;
• learn about the precedence of conditional expressions;
• see how to nest conditional expressions;
• understand the relationship between if: ... elif: ... else: statements and conditional expressions;
• see good and bad example usages of conditional expressions;

You can now get your free copy of the ebook “Pydon'ts – Write beautiful Python code” on Gumroad to help support the series of “Pydon't” articles 💪.

# What is a conditional expression?

A conditional expression in Python is an expression (in other words, a piece of code that evaluates to a result) whose value depends on a condition.

## Expressions and statements

To make it clearer, here is an example of a Python expression:

>>> 3 + 4 * 5
23

The code 3 + 4 * 5 is an expression, and that expression evaluates to 23.

Some pieces of code are not expressions. For example, pass is not an expression because it does not evaluate to a result. pass is just a statement, it does not “have” or “evaluate to” any result.

This might be odd (or not!) but to help you figure out if something is an expression or not, try sticking it inside a print function. Expressions can be used inside other expressions, and function calls are expressions. Therefore, if it can go inside a print call, it is an expression:

>>> print(3 + 4 * 5)
23
>>> print(pass)
File "<stdin>", line 1
print(pass)
^
SyntaxError: invalid syntax

The syntactic error here is that the statement pass cannot go inside the print function, because the print function wants to print something, and pass gives nothing.

## Conditions

We are very used to using if statements to run pieces of code when certain conditions are met. Rewording that, a condition can dictate what piece(s) of code run.

In conditional expressions, we will use a condition to change the value to which the expression evaluates. Wait, isn't this the same as an if statement? No! Statements and expressions are not the same thing.

## Syntax

Instead of beating around the bush, let me just show you the anatomy of a conditional expression:

expr_if_true if condition else expr_if_false

A conditional expression is composed of three sub-expressions and the keywords if and else. None of these components are optional. All of them have to be present.

How does this work?

First, condition is evaluated. Then, depending on whether condition evaluates to Truthy or Falsy, the expression evaluates expr_if_true or expr_if_false, respectively.

As you may be guessing from the names, expr_if_true and expr_if_false can themselves be expressions. This means they can be simple literal values like...

]]>
TIL #006 – file unpacking https://mathspp.com/blog/til/006 2021-10-19T00:33:13+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")
['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.