繁体   English   中英

如何为浮点数和复数执行近似结构模式匹配

[英]How to perform approximate structural pattern matching for floats and complex

我已阅读并了解浮点舍入问题,例如:

>>> sum([0.1] * 10) == 1.0
False

>>> 1.1 + 2.2 == 3.3
False

>>> sin(radians(45)) == sqrt(2) / 2
False

我也知道如何使用math.isclose()cmath.isclose()解决这些问题。

问题是如何将这些变通方法应用于 Python 的 match/case 语句。 我希望这个工作:

match 1.1 + 2.2:
    case 3.3:
        print('hit!')  # currently, this doesn't match

解决方案的关键是构建一个覆盖__eq__方法并将其替换为近似匹配的包装器:

import cmath

class Approximately(complex):

    def __new__(cls, x, /, **kwargs):
        result = complex.__new__(cls, x)
        result.kwargs = kwargs
        return result

    def __eq__(self, other):
        try:
            return isclose(self, other, **self.kwargs)
        except TypeError:
            return NotImplemented

它为浮点值和复值创建近似相等测试:

>>> Approximately(1.1 + 2.2) == 3.3
True
>>> Approximately(1.1 + 2.2, abs_tol=0.2) == 3.4
True
>>> Approximately(1.1j + 2.2j) == 0.0 + 3.3j
True

以下是如何在 match/case 语句中使用它:

for x in [sum([0.1] * 10), 1.1 + 2.2, sin(radians(45))]:
    match Approximately(x):
        case 1.0:
            print(x, 'sums to about 1.0')
        case 3.3:
            print(x, 'sums to about 3.3')
        case 0.7071067811865475:
            print(x, 'is close to sqrt(2) / 2')
        case _:
            print('Mismatch')

这输出:

0.9999999999999999 sums to about 1.0
3.3000000000000003 sums to about 3.3
0.7071067811865475 is close to sqrt(2) / 2

雷蒙德的回答非常花哨且符合人体工程学,但对于可能更简单的东西来说似乎有很多魔力。 一个更小的版本只是捕获计算值并明确检查事物是否“接近”,例如:

import math

match 1.1 + 2.2:
    case x if math.isclose(x, 3.3):
        print(f"{x} is close to 3.3")
    case x:
        print(f"{x} wasn't close)

我还建议仅在您实际需要的地方/时使用cmath.isclose() ,使用适当的类型可以确保您的代码执行您期望的操作。

上面的示例只是用于演示匹配的最少代码,正如注释中所指出的,使用传统的if语句可以更容易地实现。 冒着偏离原始问题的风险,这是一个更完整的示例:

from dataclasses import dataclass

@dataclass
class Square:
    size: float

@dataclass
class Rectangle:
    width: float
    height: float

def classify(obj: Square | Rectangle) -> str:
    match obj:
        case Square(size=x) if math.isclose(x, 1):
            return "~unit square"

        case Square(size=x):
            return f"square, size={x}"

        case Rectangle(width=w, height=h) if math.isclose(w, h):
            return "~square rectangle"

        case Rectangle(width=w, height=h):
            return f"rectangle, width={w}, height={h}"

almost_one = 1 + 1e-10
print(classify(Square(almost_one)))
print(classify(Rectangle(1, almost_one)))
print(classify(Rectangle(1, 2)))

不确定我是否真的会在这里使用match语句,但希望更具代表性!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM