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.
Dankeschön im voraus.
Point
-
- User
- Beiträge: 1
- Registriert: Montag 16. Dezember 2019, 21:16
- Wohnort: Deutschland
- __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.
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
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.
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
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
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.
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)
Ü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]
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
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
- __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:
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
Eine Vektor-Klasse schreibe ich in der Regel auch immer neu. Zuletzt hat sich folgender Entwurf bei mir gesetzt:
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
#!/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))
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!"
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.__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.
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()
- __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