I am computing the Lp distance functions for non-negative p's. For all but p = 0 and p = ∞ a built-in pow()
function serves well.
Before I learned about a structural pattern matching, I had used a dictionary and exception handling:
from math import sqrt, inf
distance_function = { 0.0: lambda x, y: int(x != 0.0) + int(y != 0.0),
1.0: lambda x, y: abs(x) + abs(y), # Likely a tad faster than 'pow()'
inf: lambda x, y: max(abs(x), abs(y))}
def lp_distance(x, y, p):
try: return distance_function[p](x, y)
except KeyError: return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)
Some people didn't want exceptions here. So I rewrote the snippet into the following one:
def lp_distance(x, y, p):
match p:
case 0.0: return int(x != 0.0) + int(y != 0.0)
case 1.0: return abs(x) + abs(y)
# The line below triggers "SyntaxError: name capture 'inf' makes remaining patterns unreachable"
case inf: return max(abs(x), abs(y))
# But the following works:
case p if p == inf: return max(abs(x), abs(y))
case _: return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)
Why case inf:
is not correct (Python v3.10.2)?
In a case
statement, a simple name is a pattern that captures (assigns) to that name. In contrast, a dotted name is a patterns that refers to the value of that name.
In simple terms
NAME
will always succeed and it will setNAME = <subject>
.
In simple terms
NAME1.NAME2
will succeed only if<subject> == NAME1.NAME2
Using just case inf:
means that the value to match is unconditionally assigned to the name inf
– it does not matter if the name was previously bound.
What you want instead is case math.inf:
, which means to compare against this value.
import math
def lp_distance(x, y, p):
match p:
case 0.0:
return int(x != 0.0) + int(y != 0.0)
case 1.0:
return abs(x) + abs(y)
# compare against a value by using its dotted name
case math.inf:
return max(abs(x), abs(y))
case _:
return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)