Médio Python

Igualdade e Hash para Ponto

Enunciado

Implemente uma classe Ponto que representa um ponto no plano cartesiano com coordenadas x e y. Defina __eq__ para que dois pontos sejam iguais se tiverem as mesmas coordenadas, e __hash__ consistente. Depois, crie um conjunto com alguns pontos e verifique se um ponto duplicado é reconhecido como já existente.

Requisitos

  • A classe Ponto deve ter __init__, __eq__ e __hash__.
  • __eq__ deve retornar True apenas se ambos os objetos forem instâncias de Ponto e tiverem x e y iguais.
  • __hash__ deve ser baseado na tupla (x, y).
  • O programa deve criar um set com pontos e demonstrar que um ponto com mesmas coordenadas não é adicionado novamente.

Código inicial

class Ponto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # Implemente __eq__ e __hash__

# Teste
p1 = Ponto(1, 2)
p2 = Ponto(1, 2)
p3 = Ponto(3, 4)
print(p1 == p2)  # True
print(len({p1, p2, p3}))  # 2

Saída esperada

True
2
Ver dica

Use isinstance(other, Ponto) para garantir que a comparação só ocorra entre pontos. Para o hash, use hash((self.x, self.y)).

Mostrar solução
class Ponto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if not isinstance(other, Ponto):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        return hash((self.x, self.y))

p1 = Ponto(1, 2)
p2 = Ponto(1, 2)
p3 = Ponto(3, 4)
print(p1 == p2)
print(len({p1, p2, p3}))

Passo a passo

  1. Definimos a classe Ponto com __init__ que guarda x e y.
  2. __eq__ verifica se other é uma instância de Ponto; se não for, retorna NotImplemented para permitir que o Python tente a comparação reversa.
  3. Se for um Ponto, compara as coordenadas e retorna um booleano.
  4. __hash__ retorna o hash da tupla (x, y), garantindo que pontos iguais tenham o mesmo hash.
  5. Criamos três pontos: p1 e p2 iguais, p3 diferente.
  6. Imprimimos p1 == p2 (True) e o comprimento do conjunto {p1, p2, p3} (2), pois p2 é considerado duplicata.

Por que funciona

O conjunto usa __hash__ para determinar o balde (bucket) e __eq__ para verificar igualdade dentro do balde. Como p1 e p2 têm o mesmo hash e são iguais, o conjunto os trata como o mesmo elemento. Isso é essencial para usar objetos como chaves de dicionário ou membros de conjuntos.

Erros comuns

  • Esquecer de implementar __hash__ ao definir __eq__: Python 3 torna a classe não hashable, e usá-la em um set lança TypeError: unhashable type: 'Ponto'.
  • Implementar __hash__ baseado em atributos mutáveis: se x ou y mudarem, o hash muda e o objeto se perde em coleções. Use atributos imutáveis.
  • Não verificar o tipo em __eq__: comparar com um inteiro pode causar AttributeError se tentar acessar .x.

Outra forma de resolver

Podemos usar @dataclass(frozen=True) para gerar automaticamente __eq__ e __hash__. Exemplo:

from dataclasses import dataclass

@dataclass(frozen=True)
class Ponto:
    x: int
    y: int

Isso é preferível quando a classe é simples e imutável.

Saída esperada

True
2