@kbr: Etwas spät, ich weiss, aber ich wollte noch was zu Deiner Lösung für Tag 3 schreiben.
Was mir nicht so gefällt, ist das dieses `fobj` überall übergeben wird. Das macht alle Funktionen unnötig unabhängig von einem Dateiobjekt das „seekbar“ ist und der `fabric_file()`-Funktion. Wenn die Funktionen einfach ein iterierbares Objekt mit (id, x, y, width, height)-Tupeln übergeben bekommen würden, dann wären sie wesentlich leichter testbar, weil man einfach eine feste Liste übergeben könnte.
Statt ein flaches Array mit der Anzahl der Gesamtelemente zu erstellen und das dann zu `reshape()`\en, hätte man gleich die richtige Form erzeugen können: ``np.zeros((w, h))``.
Den Test auf nicht-überlappen finde ich zu ”berechnend”. Da hätte ich einfach getestet ob alle Werte gleich 1 sind: ``(fabric[x:x+w, y:y+h] == 1).all()``. Ist auch ein kleines bisschen robuster gegen Rechtecke die nicht zum `fabric` passen, weil die Summe auch zu Breite mal Höhe passen könnte, wenn die Werte dort *nicht* alle 1 sind. Zumindest auf solche Fälle fällt der Test auf nur 1en nicht herein. Ganz sicher ist der natürlich auch nicht.
In der Funktion würde ich zudem noch etwas zurückgeben wenn kein Treffer gefunden wurde. Und wenn es nur das implizite `None` als explizites ``return None`` ist. Funktionen bei denen es Zweige gibt in denen ein ``return`` steht, und solche in denen keines steht (und auch nichts anderes was die Funktion garantiert beendet), sehen immer so halb fertig aus, und man fragt sich ob das so sein soll, oder ob der Programmierer da noch Fälle nicht bedacht hat.
Statt die Elemente die grösser als 1 sind, tatsächlich zu selektieren und dann die Länge davon zu nehmen, hätte ich nur die Wahrheitswerte gezählt: ``(fabric > 1).sum()``.
Meine Lösung ist etwas klassischer und etwas mehr selbst ausprogrammiert:
Code: Alles auswählen
#!/usr/bin/env python3
from attr import attrib, attrs
@attrs(frozen=True)
class Patch:
id = attrib()
x = attrib()
y = attrib()
width = attrib()
height = attrib()
@property
def top(self):
return self.y
@property
def bottom(self):
return self.y + self.height
@property
def left(self):
return self.x
@property
def right(self):
return self.x + self.width
def iter_coordinates(self):
for y in range(self.top, self.bottom):
for x in range(self.left, self.right):
yield x, y
@classmethod
def from_line(cls, line):
id_part, rect_part = line.split('@', 1)
if not id_part.startswith('#'):
raise ValueError('ID does not start with `#`')
coordinate_part, dimension_part = rect_part.split(':', 1)
x, y = map(int, coordinate_part.split(',', 1))
width, height = map(int, dimension_part.split('x', 1))
return cls(int(id_part[1:]), x, y, width, height)
class Sheet:
def __init__(self, size=1000):
self.squares = [[0] * size for _ in range(size)]
@property
def size(self):
return len(self.squares)
def get_max_value(self):
return max(map(max, self.squares))
def count_overlapped(self):
return sum(sum(c > 1 for c in row) for row in self.squares)
def check(self, patch):
for x, y in patch.iter_coordinates():
if self.squares[y][x] != 1:
return False
return True
def claim(self, patch):
for x, y in patch.iter_coordinates():
self.squares[y][x] += 1
def save_pgm(self, filename):
max_value = self.get_max_value()
if max_value > 255:
raise ValueError('too many gray levels')
with open(filename, 'wb') as out_file:
out_file.write(
f'P5 {self.size} {self.size} {self.get_max_value()}\n'.encode(
'ascii'
)
)
out_file.writelines(map(bytes, self.squares))
def main():
with open('input03.txt') as lines:
patches = [Patch.from_line(line) for line in lines]
print(
'max width/height:',
max(max(patch.width, patch.height) for patch in patches),
)
sheet = Sheet()
for patch in patches:
sheet.claim(patch)
sheet.save_pgm('sheet.pgm')
print(sheet.count_overlapped())
print([patch for patch in patches if sheet.check(patch)])
if __name__ == '__main__':
main()