Point

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
alecSEGDEB
User
Beiträge: 1
Registriert: Montag 16. Dezember 2019, 21:16
Wohnort: Deutschland

Guten Abend,

ich habe seit kurzem mit Python angefangen und wollte nachfragen ob es ein Objekt gibt, dass zwei Int Parameter hat, die man einzeln und zusammen aufrufen kann.
Ich habe schon Erfahrung mit Java und dort gibt es das Objekt Point(x,y).
Mir würde das sehr helfen in Python reinzukommen. :lol:

Dankeschön im voraus. :mrgreen:
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Im einfachsten Fall erstellst Du einfach ein Tupel. Falls Du Namen für den Zugriff auf die beiden Elemente haben möchtest, dann kannst Du einen entsprechenden Datentyp mit `collections.namedtuple()` erstellen. Ansonsten müsstest Du Dir eine Klasse schreiben.

Die `Point`-Klasse bei Java ist aus dem `java.awt`-Package. Die GUI-Bibliothek (`tkinter`) die in der Standardbibliothek von Python enthalten ist hat keine solche Klasse. Aber sowohl in Gtk (`Gdk.Point`) als auch in Qt (`QPoint`) gibt es eine Klasse für x,y Koordinaten. Die von Gtk ist aber wirklich nur eine Datenklasse ohne weitere Funktionalität.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Ich hatte mal vor ca. 1,5 Jahren mir so eine Vektorklasse geschrieben.
Sind sicherlich einige Bugs drin und möglicherweise kann man das ein oder andere besser machen.
Verbesserungsvorschläge sind mir willkommen. Ich lerne jeden Tag.

Code: Alles auswählen

import random

class Vector:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f'({self.x},{self.y})'
    
    def __repr__(self):
        return f'Vector({self.x}, {self.y})'
    
    def __add__(self, other): # +
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        elif isinstance(other, (int, float)):
            return Vector(self.x + other, self.y + other)
        else:
            raise TypeError('Type mismatch')
        
    def __iadd__(self, other): # +=
        if isinstance(other, Vector):
            self.x += other.x
            self.y += other.y
        elif isinstance(other, (int, float)):
            self.x += other
            self.y += other
        else:
            raise TypeError(f'Type mismatch <{type(other)}>')
        return self
    
    def __sub__(self, other): # -
        if isinstance(other, Vector):
            return Vector(self.x - other.x, self.y - other.y)
        elif isinstance(other, (int, float)):
            return Vector(self.x - other, self.y - other)
        else:
            raise TypeError('Type mismatch')
    
    def __isub__(self, other): # -=
        if isinstance(other, Vector):
            self.x -= other.x
            self.y -= other.y
        elif isinstance(other, (int, float)):
            self.x -= other
            self.y -= other
        else:
            raise TypeError('Type mismatch')
        return self
    
    def __mul__(self, other): # *
        if isinstance(other, Vector):
            raise TypeError('Not yet defined')
        elif isinstance(other, (int, float)):
            return Vector(self.x * other, self.y * other)
        else:
            raise TypeError('Type mismatch')

    def __rmul__(self, other): # *
        return self * other
    
    def __imul__(self, other): # *=
        if isinstance(other, Vector):
            raise TypeError('Not yet defined')
        elif isinstance(other, (int, float)):
            self.x *= other
            self.y *= other
        else:
            raise TypeError('Type mismatch')
        return self
    
    def __truediv__(self, other): # /
        if isinstance(other, Vector):
            raise TypeError('Not yet defined')
        elif isinstance(other, (int, float)):
            return Vector(self.x / other, self.y / other)
        else:
            raise TypeError('Type mismatch')
    
    def __idiv__(self, other): # /=
        if isinstance(other, Vector):
            raise TypeError('Not yet defined')
        elif isinstance(other, (int, float)):
            self.x /= other
            self.y /= other
        else:
            raise TypeError('Type mismatch')
        return self
    
    def __neg__(self): # negation
        return Vector(-self.x, -self.y)
    
    def __pos__(self): # ?
        raise TypeError('Not yet defined')
    
    def __abs__(self): # abs()
        return Vector(abs(self.x), abs(self.y))

    def __invert__(self): # ~
        raise TypeError('Not yet defined')
    
    def __int__(self): # int()
        return Vector(int(self.x), int(self.y))
    
    def __float(self): # float()
        return Vector(float(self.x), float(self.y))
    
    def __lt__(self, other): # <
        if isinstance(other, Vector):
            return self.magnitude < other.magnitude
        elif isinstance(other, (int, float)):
            return self.magnitude < other
        else:
            raise TypeError('Type mismatch')
    
    def __le__(self, other): # <=
        if isinstance(other, Vector):
            return self.magnitude <= other.magnitude
        elif isinstance(other, (int, float)):
            return self.magnitude <= other
        else:
            raise TypeError('Type mismatch')
    
    def __eq__(self, other): # ==
        if isinstance(other, Vector):
            return self.x == other.x and self.y == other.y
        else:
            raise TypeError('Type mismatch')
    
    def __ne__(self, other): # !=
        if isinstance(other, Vector):
            return self.x != other.x or self.y != other.y
        else:
            raise TypeError('Type mismatch')
        
        return self.x != other.x or self.y != other.y
    
    def __ge__(self, other): # >=
        if isinstance(other, Vector):
            return self.magnitude >= other.magnitude
        elif isinstance(other, (int, float)):
            return self.magnitude >= other
        else:
            raise TypeError('Type mismatch')
    
    def __gt__(self, other): # >
        if isinstance(other, Vector):
            return self.magnitude > other.magnitude
        elif isinstance(other, (int, float)):
            return self.magnitude > other
        else:
            raise TypeError('Type mismatch')
    
    @property
    def magnitude(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
    @magnitude.setter
    def magnitude(self, factor):
        mag = self.norm * factor
        self.x = mag.x
        self.y = mag.y
        
    def normalize(self):
        if self.magnitude != 0:
            norm = self / self.magnitude
            self.x = norm.x
            self.y = norm.y
    
    @property
    def norm(self):
        if self.magnitude != 0:
            return self / self.magnitude
        return self

    @property
    def position(self):
        return (int(self.x), int(self.y))
    
    def scale(self, factor):
        return factor * self.norm
    
    def randomize(self, factor=1):
        self.x = factor * (2 * random.random() - 1)
        self.y = factor * (2 * random.random() - 1)

    @staticmethod
    def random_2D():
        vector = Vector(0,0)
        vector.randomize()
        return vector
    
    def limit(self, value):
        if self.magnitude > value:
            self.magnitude = value
    
    def copy(self):
        return Vector(self.x, self.y)

point = Vector(2,2)
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

ThomasL hat geschrieben: Dienstag 17. Dezember 2019, 19:02 Verbesserungsvorschläge sind mir willkommen.
Das einzige, was mir beim Querlesen jetzt auffiel, ist, dass random_2D vielleicht eine classmethod statt einer staticmethod sein sollte, da du die Klasse innerhalb der Methode verwendest; so musst du sie nicht explizit mit ihrem Namen hinschreiben.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die reinen Rechenoperationen kann man auch einfacher haben. Auszug aus IPython:

Code: Alles auswählen

In [1]: %paste
from itertools import starmap
from operator import add, floordiv, mul, sub, truediv

class Vector:
    def __init__(self, *args):
        self.args = args

    def __iter__(self):
        return iter(self.args)

    def __repr__(self):
        class_name = type(self).__name__
        args = ', '.join(map(str, self))
        return f'{class_name}({args})'

    def calculate(self, other, op):
        args = starmap(op, zip(self, other))
        return type(self)(*args)

    def __add__(self, other):
        return self.calculate(other, add)

    def __sub__(self, other):
        return self.calculate(other, sub)

    def __mul__(self, other):
        return self.calculate(other, mul)

    def __truediv__(self, other):
        return self.calculate(other, truediv)

    def __floordiv__(self, other):
        return self.calculate(other, floordiv)
## -- End pasted text --

In [2]: Vector(1, 2)
Out[2]: Vector(1, 2)

In [3]: Vector(1, 2) + Vector(3, 4)
Out[3]: Vector(4, 6)

In [4]: Vector(1,2,3) * Vector(4,5,6)
Out[4]: Vector(4, 10, 18)

In [5]: v = Vector(1,2,3)

In [6]: v += (4,5,6)

In [7]: v
Out[7]: Vector(5, 7, 9)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Übrigens, wenn man die Idee eines benannten Vektors einbezieht, dann könnte man sich eine Klassen-Hierarchie aufbauen und wäre damit auch näher an der ursprünglichen Frage hier im Thread. Wieder als kleine Demo in IPython:

Code: Alles auswählen

In [1]: %paste
from itertools import starmap
from operator import add, floordiv, mul, sub, truediv

class Vector:
    def __init__(self, *args):
        self._args = args

    def __iter__(self):
        return iter(self._args)

    def __repr__(self):
        class_name = type(self).__name__
        args = ', '.join(map(str, self))
        return f'{class_name}({args})'

    def calculate(self, other, op):
        args = starmap(op, zip(self, other))
        return type(self)(*args)

    def __add__(self, other):
        return self.calculate(other, add)

    def __sub__(self, other):
        return self.calculate(other, sub)

    def __mul__(self, other):
        return self.calculate(other, mul)

    def __truediv__(self, other):
        return self.calculate(other, truediv)

    def __floordiv__(self, other):
        return self.calculate(other, floordiv)


class NamedVector(Vector):
    def __init__(self, **params):
        self.__dict__.update(params)

    @property
    def _args(self):
        return self.__dict__.values()

    def __repr__(self):
        class_name = type(self).__name__
        items = self.__dict__.items()
        params = ', '.join(starmap('{}={}'.format, items))
        return f'{class_name}({params})'

    def calculate(self, other, op):
        other_params = getattr(other, '__dict__', other)
        params = {name: op(value, other_params[name])
                  for name, value in self.__dict__.items()}
        return type(self)(**params)


class Point(NamedVector):
    def __init__(self, x, y):
        super().__init__(x=x, y=y)


class Point3D(NamedVector):
    def __init__(self, x, y, z):
        super().__init__(x=x, y=y, z=z)

## -- End pasted text --

In [2]: Point(1, 2)
Out[2]: Point(x=1, y=2)

In [3]: Point(1, 2) + Point(3, 4)
Out[3]: Point(x=4, y=6)

In [4]: Point(1, 2) * {'x': 3, 'y': 4}
Out[4]: Point(x=3, y=8)

In [5]: list(Point3D(1, 2, 3))
Out[5]: [1, 2, 3]
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Danke, das werde ich mir mal alles genauer anschauen. (Nach Advent of Code :-) )
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei man *für* den Advent of Code recht häufig eine `Point`/`Vector`-Klasse gebrauchen kann. Ich schreibe die aber immer neu und nur mit den Methoden die für die Aufgabe gebraucht werden. Nicht selten sind das ja nur die Koordinaten und eine `__add__()`-Methode plus alles was man braucht um Punkte zu vergleichen und als Schlüssel oder in `set`\s oder als Werte in `enum.Enum` zu verwenden. Letzteres bekommt man ohne viel Schreibarbeit vom `attr`-Modul.

Für Tag 11 beispielsweise nur das hier:

Code: Alles auswählen

@attrs(frozen=True)
class Point:
    x = attrib(default=0)
    y = attrib(default=0)

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Eine Vektor-Klasse schreibe ich in der Regel auch immer neu. Zuletzt hat sich folgender Entwurf bei mir gesetzt:

Code: Alles auswählen

#!/usr/bin/env python3

from collections import namedtuple
from itertools import repeat
from math import sqrt
from operator import add, sub, mul, truediv


class Vector():

    @property
    def magnitude(self):
        return sqrt(sum(x ** 2 for x in self))

    def __add__(self, other):
        return self._apply(other, add)

    def __sub__(self, other):
        return self._apply(other, sub)

    def __mul__(self, scalar):
        return self._apply(repeat(scalar), mul)

    def __truediv__(self, scalar):
        return self._apply(repeat(scalar), truediv)

    def _apply(self, other, operation):
        return type(self)(*map(operation, self, other))

    def norm(self):
        return self / self.magnitude


class Vector2(Vector, namedtuple('Vector2T', 'x y')):
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))

class Vector3(Vector, namedtuple('Vector3T', 'x y z')):
    def __new__(cls, x, y, z):
        return tuple.__new__(cls, (x, y, z))
In der Mixin-Klasse habe ich die allgemeinen Vektoroperationen allgemeingültig für n-Vektoren definiert. Ich leite gerne von einem namedtuple ab, da ich hier die Formatierung und eben alle Tuple-spezifischen Operationen freihaus erhalte. Die arithmetischen Operationen arbeiten mit allem, was iterierbar ist und deren Elementanzahl >= der Elementzahl des Vektors ist.

Code: Alles auswählen

>>> from vector import Vector2, Vector3
>>> a = Vector2(1, 2)
>>> a + (1, 2)
Vector2(x=2, y=4)
>>> a + (1, 2, 3)
Vector2(x=2, y=4)
>>> a.magnitude
2.23606797749979
>>> a.norm().magnitude
0.9999999999999999
>>> a * -1
Vector2(x=-1, y=-2)
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

__blackjack__ hat geschrieben: Mittwoch 18. Dezember 2019, 09:03 Wobei man *für* den Advent of Code recht häufig eine `Point`/`Vector`-Klasse gebrauchen kann. Ich schreibe die aber immer neu und nur mit den Methoden die für die Aufgabe gebraucht werden.
Mathematikern rollen sich da jetzt vermutlich die Zehnnägel nach oben, aber ich habe als kleines Experiment in bestimmten Fällen auch schon komplexe Zahlen für 2D-Punkte verwendet (/missbraucht). Die sind in Python eingebaut, man kann damit rechnen (wenn man die Regeln ein bisschen kennt) und muss nichts selbst implementieren.

Zum Beispiel Advent of Code 2017.22.1:

Code: Alles auswählen

#!/usr/bin/env python3
import fileinput

ON = True
OFF = False
RIGHT = 1j
LEFT = -1j
DISPATCH = {ON: RIGHT, OFF: LEFT}


def read_grid():
    with fileinput.input() as fh:
        grid = [
            [state == '#' for state in line.strip()]
            for line in fh if line.strip()
            ]
    height = len(grid)
    half = height // 2
    new_grid = dict()
    for i in range(half, -half-1, -1):
        for j in range(-half, half+1):
            new_grid[complex(i, j)] = grid[height-(i+half)-1][j+half]
    return new_grid


def main():
    grid = read_grid()
    direction = 1 + 0j
    position = 0 + 0j
    counter = 0
    for _ in range(10_000):
        if position not in grid:
            grid[position] = OFF
        infected = grid[position]
        if not infected:
            counter += 1
            grid[position] = ON
        else:
            grid[position] = OFF
        new_direction = DISPATCH[infected]
        direction *= new_direction
        position += direction
    print(counter)


if __name__ == '__main__':
    main()
Ist jetzt vielleicht nicht unbedingt etwas für Produktivcode, aber ich fand es nett, dass das tatsächlich funktioniert.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich habe meine damalige Lösung mal auf Python 3 aktualisiert:

Code: Alles auswählen

#!/usr/bin/env python3
import sys
from collections import defaultdict
from enum import Enum

import pytest
from attr import attrib, attrs

BURST_COUNTS = [10_000, 10_000_000]


@attrs(frozen=True)
class Point:
    x = attrib()
    y = attrib()

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)


class Turn(Enum):
    NOT = 0
    LEFT = -1
    RIGHT = 1
    AROUND = 2


class Heading(Enum):
    NORTH = Point(0, -1)
    EAST = Point(1, 0)
    SOUTH = Point(0, 1)
    WEST = Point(-1, 0)

    def turned(self, direction):
        return _HEADINGS[
            (_HEADING_TO_INDEX[self] + direction.value) % len(_HEADINGS)
        ]


_HEADINGS = list(Heading)
_HEADING_TO_INDEX = {heading: i for i, heading in enumerate(Heading)}


class NodeState(Enum):
    CLEAN = "."
    WEAKENED = "W"
    INFECTED = "#"
    FLAGGED = "F"


STRATEGIES = [
    {
        NodeState.CLEAN: (NodeState.INFECTED, Turn.LEFT),
        NodeState.INFECTED: (NodeState.CLEAN, Turn.RIGHT),
    },
    {
        NodeState.CLEAN: (NodeState.WEAKENED, Turn.LEFT),
        NodeState.WEAKENED: (NodeState.INFECTED, Turn.NOT),
        NodeState.INFECTED: (NodeState.FLAGGED, Turn.RIGHT),
        NodeState.FLAGGED: (NodeState.CLEAN, Turn.AROUND),
    },
]


@attrs
class VirusCarrier:
    position = attrib()
    strategy = attrib()
    heading = attrib(default=Heading.NORTH)
    infection_count = attrib(default=0)

    def move_forward(self):
        self.position += self.heading.value

    def turn(self, direction):
        self.heading = self.heading.turned(direction)

    def burst(self, position_to_node_state):
        current_state = position_to_node_state[self.position]
        next_state, turn_direction = self.strategy[current_state]

        position_to_node_state[self.position] = next_state
        if next_state == NodeState.INFECTED:
            self.infection_count += 1

        self.turn(turn_direction)
        self.move_forward()


def read_node_map(lines):
    width = len(lines[0])
    height = len(lines)
    center = Point(width // 2, height // 2)
    position_to_node_state = defaultdict(
        lambda: NodeState.CLEAN,
        (
            (Point(x, y), NodeState(character))
            for y, line in enumerate(lines)
            for x, character in enumerate(line.strip())
        ),
    )
    return center, position_to_node_state


def main():
    lines = list(sys.stdin)
    for strategy, burst_count in zip(STRATEGIES, BURST_COUNTS):
        center, infected_postitions = read_node_map(lines)
        carrier = VirusCarrier(center, strategy)
        for _ in range(burst_count):
            carrier.burst(infected_postitions)
        print(carrier.infection_count)


TEST_NODE_MAP = ["..#", "#..", "..."]


def test_read_node_map():
    center, position_to_node_state = read_node_map(TEST_NODE_MAP)
    assert center == Point(1, 1)
    assert {
        position
        for position, state in position_to_node_state.items()
        if state is NodeState.INFECTED
    } == {Point(2, 0), Point(0, 1)}


@pytest.mark.parametrize(
    "strategy, burst_count, expected_infection_count",
    list(zip(STRATEGIES, BURST_COUNTS, [5_587, 2_511_944])),
)
def test_bursts(strategy, burst_count, expected_infection_count):
    center, position_to_node_state = read_node_map(TEST_NODE_MAP)
    carrier = VirusCarrier(center, strategy)
    for _ in range(burst_count):
        carrier.burst(position_to_node_state)
    assert carrier.infection_count == expected_infection_count


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten