Kleine Erinnerung: Es gibt auch dieses Jahr wieder einen Adventskalender. 😎

Advent of Code
- __blackjack__
- Thread is Unresolved
Registriere dich jetzt, um exklusive Vorteile zu genießen! Als registriertes Mitglied kannst du Inhalte herunterladen und profitierst von einem werbefreien Forum.
Mach mit und werde Teil unserer Community!
Mach mit und werde Teil unserer Community!
-
-
Schau mal ob du hier fündig wirst!
-
Ich dachte dieses Jahr ist meine Zeit gekommen, aber das wahr wohl nichts
Quote
Also, starting this December, please don't use AI to get on the global leaderboard. -
Wie letztes Jahr versuche ich mein Glück wieder, wo möglich, mit einem VIC-20 (≈1 Mhz 6502 Prozessor, Grundausstattung von 3½ KiB Arbeitsspeicher für BASIC (Programm + Daten), furchtbar langsames Diskettenlaufwerk 🤓).
Bei Tag 1 braucht der zweite Aufgabenteil viel Zeit. Teil 1wäre in 5½ Minuten durch, aber beide Teile zusammen brauchen 38 Minuten und 22 Sekunden für 1.000 Eingabezeilen, ≈22 KiB:
BASIC
Display More10 TI$="000000":R1=0:R2=0 20 DIM W$(9):FOR I=1 TO 9:READ W$(I):NEXT 30 OPEN 1,8,2,"INPUT01,S" 40 IF ST<>0 THEN 1000 50 INPUT#1,L$ 60 D1$="":D2$="" 70 D3$="":D4$="" 80 FOR I=1 TO LEN(L$) 90 C$=MID$(L$,I,1) 100 IF C$>="1" AND C$<="9" THEN 150 110 C$="" 120 FOR J=1 TO 9:W$=W$(J) 130 IF MID$(L$,I,LEN(W$))=W$ THEN C$=CHR$(J+48):GOTO 160 140 NEXT:GOTO 170 150 D2$=C$:IF D1$="" THEN D1$=D2$ 160 D4$=C$:IF D3$="" THEN D3$=D4$ 170 NEXT I 180 R1=R1+VAL(D1$+D2$) 190 R2=R2+VAL(D3$+D4$) 200 PRINT R1,R2:PRINT"{UP}"; 210 GOTO 40 1000 CLOSE 1 1010 PRINT:PRINT TI$ 2000 DATA ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE
-
Keine Ahnung warum Du BASIC so liebst
Ein Programm ohne Einrueckungen und gotos (considered harmful - Dijsktra) werde ich nicht mehr mal lesen und noch weniger schreiben
-
VIC-20 (≈1 Mhz 6502 Prozessor, Grundausstattung von 3½ KiB Arbeitsspeicher für BASIC
Faszinierend, das BASIC! Und Dein Ehrgeiz!
Ich friemel mich wieder mit Python durch.
Teil 1 ging gut (mit regex), bei Teil 2 habe ich eine Zeit gebraucht, das "Lookahead" hinzukriegen (bzw. überhaupt erst die Notwendigkeit dafür zu erkennen).
-
__blackjack__ Irgendwo fragte ich ja schon mal, aber jetzt bin ich sicher, dass Du masochistisch veranlagt bist.
Das ist nicht negativ gemeint, sondern eher als eine tiefe Verneigung an Deine Experimentierlust!
-
framp Es ist nicht so sehr das BASIC was ich liebe, sondern diese Rechner und die Erfahrung. Das ist wie Oldtimer fahren, wo es ja sicher auch genug Leute gibt die nicht so wirklich verstehen wie man sich ein altes Auto ohne den heutigen Komfort antun kann.
Das CBM BASIC V2 war meine erste Programmiersprache. Ich hatte den C64 zu Weihnachten bekommen und gleich an dem Abend das Handbuch durchgearbeitet, was zum Hauptteil aus einer Beschreibung von dem BASIC und vielen Beispielprogrammen besteht. Als ich durch war, wurde es draussen schon langsam wieder hell.
Jetzt mit dem VIC-20, gleiche Programmiersprache, aber wegen der RAM-Einschränkung als zusätzliche Herausforderung. Und der ist ein kleines bisschen schneller als der C64.
Einrückung wäre möglich, aber da das während der Ausführung jedes mal ”überlesen” werden muss, geht das auf die Laufzeit. Und natürlich auch auf den Speicherverbrauch. Bei den ersten beiden Puzzles noch kein Problem, aber wenn man anfängt Arrays zu benötigen können die 3½ KiB RAM schnell eng werden. Im Python-Forum gab es neulich mal einen Algorithmus um viele Nachkommastellen von der Euler'schen Zahl e zu berechnen, mit eigentlich 1.000 Stellen wo ich auf 350 Stellen runtergehen musste, weil das RAM nicht ausgereicht hätte die 1.000 in einem Integer-Array zu halten. Allerdings konnte ich das Programm so anpassen, das es die 1.000 Stellen direkt auf den Drucker ausgegeben hat. 😀
Ausserdem hat man auf dem VIC-20 nur 23×22 Zeichen, da will man eigentlich keinen Platz mit Einrückung verschwenden, die man sowieso nicht wirklich sehen kann in dem kleinen Ausschnitt den man in das Programm hat. Das bringt nur ausgedruckt etwas, oder wenn man das auf Rechner exportiert, die mehr Zeichen auf den Bildschirm bringen können. Das könnte dann so aussehen (habe ich jetzt nur auf dem PC geändert und nicht laufen lassen):
BASIC
Display More10 TI$="000000":R1=0:R2=0 20 DIM W$(9):FOR I=1 TO 9:READ W$(I):NEXT 30 OPEN 1,8,2,"INPUT01,S" 40 IF ST<>0 THEN 1000 50 : INPUT#1,L$ 60 : D1$="":D2$="" 70 : D3$="":D4$="" 80 : FOR I=1 TO LEN(L$) 90 : C$=MID$(L$,I,1) 100 : IF C$>="1" AND C$<="9" THEN 150 110 : C$="" 120 : FOR J=1 TO 9:W$=W$(J) 130 : IF MID$(L$,I,LEN(W$))=W$ THEN C$=CHR$(J+48):GOTO 160 140 : NEXT:GOTO 170 150 : D2$=C$:IF D1$="" THEN D1$=D2$ 160 : D4$=C$:IF D3$="" THEN D3$=D4$ 170 : NEXT I 180 : R1=R1+VAL(D1$+D2$) 190 : R2=R2+VAL(D3$+D4$) 200 : PRINT R1,R2:PRINT"{UP}"; 210 GOTO 40 1000 CLOSE 1 1010 PRINT:PRINT TI$ 1020 : 2000 DATA ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE
GOTO kann man auch verantwortlich(er) verwenden. Es gibt halt nur die FOR-Schleife und kein ELSE in CBM BASIC V2, also muss man sich zum Beispiel eine WHILE-Schleife die alle Zeilen verarbeitet mit IF und GOTO umschreiben. Das bleibt ja trotzdem noch ein strukturiertes Programm und wird damit nicht automatisch zu Spaghetti-Code. *Das* ist ja das was Dijkstra in dem Paper anprangert — Spaghetti-Code.
Da ich neulich einen BBC Micro B in Aktion gesehen habe bin ich am überlegen ob ich mir im Laufe des nächsten Jahres so einen zulege. Die sind in 🇬🇧 recht verbreitet gewesen und dementsprechend gar nicht so teuer zu bekommen. Das BBC BASIC auf den Geräten ist auch aus den frühen 80ern, aber kein Microsoft-BASIC-Dialekt, sondern eine Eigenentwicklung. Das hat (einzeiliges) IF … THEN … ELSE, REPEAT … UNTIL, und benannte Prozeduren und Funktionen in denen man sogar lokale Variablen deklarieren kann. Und das BASIC hat einen Assembler eingebaut. Der Rechner ist so etwas wie der spirituelle Urgrossvater vom Raspi. Da haben sie auch das mit den A und B Modellen her. Der BBC MIcro A ist eine abgespecktere Variante vom B.
-
Das ist wie Oldtimer fahren,
Netter Vergleich
Ich verstehe dich jetzt
-
Die 3½ minütige Oldtimer-Fahrt von Tag 2:
BASIC
Display More10 TI$="000000":R1=0:R2=0 100 OPEN 1,8,2,"INPUT02,S" 110 IF ST<>0 THEN 900 120 FOR I=1 TO LEN("GAME "):GET#1,C$:NEXT 130 GOSUB 1000:ID=N 140 PRINT ID:PRINT"{UP}"; 150 R=0:G=0:B=0:V=-1 160 RM=0:GM=0:BM=0 170 GET#1,C$:GOSUB 1000:GET#1,C$ 180 IF C$="R" THEN R=N:GOTO 210 190 IF C$="G" THEN G=N:GOTO 210 200 IF C$="B" THEN B=N 210 GET#1,C$:IF C$>="A" AND C$<="Z" THEN 210 220 IF C$="," THEN 170 230 REM PRINT:PRINT">"R;G;B 240 IF V THEN V=R<=12 AND G<=13 AND B<=14 250 IF RM<R THEN RM=R 260 IF GM<G THEN GM=G 270 IF BM<B THEN BM=B 280 IF C$=";" THEN 170 290 IF V THEN R1=R1+ID 300 R2=R2+RM*GM*BM 310 GOTO 110 900 CLOSE 1 910 PRINT:PRINT R1,R2,TI$ 920 END 1000 N=0 1010 GET#1,C$ 1020 IF C$<"0" OR C$>"9" THEN RETURN 1030 N=N*10+VAL(C$) 1040 GOTO 1010
-
Lösung für Tag 3 in Python, weil BASIC recht aufwändig war und ich da nicht mal mit Teil 1 fertig geworden bin (obwohl das machbar sein sollte):
Python
Display More#!/usr/bin/env python3 import sys from itertools import groupby from attr import attrib, attrs EXAMPLE_LINES = """\ 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.. """.splitlines() @attrs(frozen=True) class Item: value = attrib() coordinate = attrib() def __len__(self): return len(self.value) def __int__(self): return int(self.value) @property def x(self): return self.coordinate[0] @property def y(self): return self.coordinate[1] @property def coordinates(self): return ((self.x + i, self.y) for i in range(len(self))) @property def surrounding_coordinates(self): yield from ( (self.x + d_x, self.y + d_y) for d_y in [-1, 1] for d_x in range(-1, len(self) + 1) ) yield (self.x - 1, self.y) yield (self.x + len(self), self.y) def is_empty(self): return self.value == "." def is_number(self): return self.value.isdigit() def is_symbol(self): return not (self.is_empty() or self.is_number()) def extended(self, items): return Item( self.value + "".join(item.value for item in items), self.coordinate ) @attrs(frozen=True) class Schematic: items = attrib() coordinate_to_number = attrib() coordinate_to_symbol = attrib() def has_adjacent_symbol(self, item): return any( map(self.coordinate_to_symbol.get, item.surrounding_coordinates) ) def get_adjacent_numbers(self, item): return set( map(self.coordinate_to_number.get, item.surrounding_coordinates) ) - {None} @classmethod def from_items(cls, items): items = list(items) coordinate_to_number = {} coordinate_to_symbol = {} for item in items: for coordinate in item.coordinates: if item.is_number(): coordinate_to_number[coordinate] = item elif item.is_symbol(): coordinate_to_symbol[coordinate] = item else: assert item.is_empty(), repr(Item) return cls(items, coordinate_to_number, coordinate_to_symbol) def parse_items(lines): return ( Item(character, (x, y)) for y, line in enumerate(lines) for x, character in enumerate(line.rstrip()) ) def group_and_filter_items(items): for (_, is_number), group in groupby( items, lambda item: (item.y, item.is_number()) ): if is_number: yield next(group, None).extended(group) else: yield from (item for item in group if not item.is_empty()) def parse_lines(lines): return Schematic.from_items(group_and_filter_items(parse_items(lines))) def sum_part_numbers(schematic): """ >>> sum_part_numbers(parse_lines(EXAMPLE_LINES)) 4361 """ return sum( int(item) for item in schematic.items if item.is_number() and schematic.has_adjacent_symbol(item) ) def sum_gear_ratios(schematic): """ >>> sum_gear_ratios(parse_lines(EXAMPLE_LINES)) 467835 """ candidates = ( list(schematic.get_adjacent_numbers(item)) for item in schematic.items if item.value == "*" ) return sum( int(candidate[0]) * int(candidate[1]) for candidate in candidates if len(candidate) == 2 ) def main(): schematic = parse_lines(sys.stdin) print(sum_part_numbers(schematic)) print(sum_gear_ratios(schematic)) if __name__ == "__main__": main()
Tag 4 sollte in BASIC kein Problem sein. Teil 1 habe ich heute morgen schon gemacht. Teil 2 mache ich heute abend.
-
__blackjack__ Was für ein Zufall, für den Tag 3 habe ich genau die gleiche Lösung im Kopf.
-
Ich werde noch intensiv versuchen, __blackjack__ 's Code aus #290 zumindest teilweise nachzuvollziehen.
Hier einfach mal mein Lösungsweg:
Python
Display More#!/usr/bin/env python3 """ Advent of Code. Puzzle 03 """ import sys def check_neighbors(char, nlrow, nccol, schema, mb_gear): neigh_ch = schema[nlrow][nccol] if neigh_ch in "0123456789.": return neigh_ch, False if neigh_ch == "*": mb_gear.update({(nlrow,nccol)}) return neigh_ch, True def main(): # read and expand the schema by one '.' row/col on each side with open("day03-input", "r") as f: schema = [f".{line.strip()}." for line in f.readlines()] hdr = (len(schema[0])) * "." schema.append(hdr) schema.insert(0, hdr) result = 0 current = "" gears = {} for nl, line in enumerate(schema[1:-1], start=1): valid = False mb_gear = set() for nc, char in enumerate(line[1:-1], start=1): if not char.isdigit(): continue current += char for rc_offset in ((-1, -1), (-1, +0), (-1, +1), (+0, -1), (+1, -1), (+1, +0), (+1, +1)): _, vx = check_neighbors(char, nl+rc_offset[0], nc+rc_offset[1], schema, mb_gear) if vx: valid = True break next_ch, vx = check_neighbors(char, nl, nc+1, schema, mb_gear) if vx: valid = True if next_ch.isdigit(): continue if valid: if current: result += int(current) if mb_gear: if len(mb_gear) == 1: mbg = mb_gear.pop() if mbg in gears: gears[mbg].append(current) else: gears[mbg] = [current,] mb_gear = set() valid = False current = "" valid = False current = "" print("Part one: Sum =", result) result = 0 for g in gears: gg = gears[g] if len(gg) == 2: result += int(gg[0]) * int(gg[1]) print("Part two: Sum =", result) if __name__ == "__main__": main()
Edit: Ungeschickten Variablennamen "sum" zu "result" geändert.
-
Ich werde noch intensiv versuchen, __blackjack__ 's Code aus #290 zumindest teilweise nachzuvollziehen.
Jetzt bin ich ganz wirr im Kopf...
Das mit den den Klassen und dem Drumherum werde ich wohl in diesem Leben nicht mehr lernen. Und schon gar nicht intuitiv anwenden können.
Trotzdem nehme ich fast aus jedem Deiner, __blackjack__ , Quelltexte etwas mit, zumindest als Ahaaaa-Effekt.
Und meistens komme ich mit meinem prozeduralem Code auch irgendwie zum Ziel.
-
Hallo,
speziell wenn __blackjack__ Code postet, den er nicht für jemand zur Hilfe geschrieben hat, dann muss ich auch immer viel Dokus wälzen
'attrs' wird zwar oft benutzt, muss ich aber immer wieder nachlesen und dieses mal war das 'from' in der 'yield'-Anweisung für mich neu/verwirrend. Aber ist ja dann eigentlich gar nicht so wild.
Mein Tag 4:
Python
Display More#!/usr/bin/env python3 from pathlib import Path import re INPUT = Path("/home/dennis/AoC/2023/Day4/input.txt") EXAMPLE_LINES = """\ Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11""".splitlines() def parse_cards(input_lines): cards = {} for card in input_lines: card_number, numbers = card.split(":") my_numbers, winning_numbers = numbers.strip().split("|") my_numbers = set(map(int, re.findall(r"\d+", my_numbers))) winning_numbers = set(map(int, re.findall(r"\d+", winning_numbers))) cards[int(card_number.replace("Card ", ""))] = my_numbers & winning_numbers return cards def calculate_points(winnings): return 2 ** (winnings - 1) if winnings >= 1 else 0 def update_number_of_cards(card_to_wins, wins, card_id): for extra_card in range(1, wins + 1): card_to_wins[card_id + extra_card] += card_to_wins[card_id] return card_to_wins def main(): cards = parse_cards(INPUT.read_text(encoding="UTF-8").splitlines()) print(sum(calculate_points(len(winning)) for winning in cards.values())) cards = {int(card_id): len(win) for card_id, win in cards.items()} card_to_wins = {card_id: 1 for card_id, _ in enumerate(cards, 1)} for card_id, wins in cards.items(): card_to_wins = update_number_of_cards(card_to_wins, wins, card_id) print(sum(card_to_wins.values())) if __name__ == "__main__": main()
Grüße
Dennis
-
Dennis89 Ich glaube, "unter der Haube" sind unsere "Tag 4" Lösungen gar nicht so weit voneinander entfernt.
Deine ist viel aufgeräumter. Noch mehr Lernstoff für mich, danke!!!
Python
Display More#!/usr/bin/env python3 """ Advent of Code. Puzzle 04 """ import sys import re import fileinput from pathlib import Path EXAMPLE_LINES = """\ Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 """.splitlines() def main(): if len(sys.argv) == 1: lines = EXAMPLE_LINES else: with fileinput.input(encoding="utf-8") as f: lines = list(line.rstrip() for line in f) cards = [(),] for line in lines: card_text, data = line.split(":") card = card_text.split()[1] winning_text, play_text = data.split("|") winning = winning_text.strip().split() play = play_text.strip().split() winners = [p for p in play if p in winning] cards.append((winning, play, winners, len(winners))) result = 0 card_counter = {} for n, card in enumerate(cards[1:], start=1): card_counter[n] = 0 num_winners = card[3] if num_winners: result += 2**(num_winners-1) print("Part one: Sum =", result) result = 0 for n, card in enumerate(cards[1:], start=1): card_counter[n] += 1 for c in range(card_counter[n]): num_winners = card[3] for i in range(1, num_winners+1): card_counter[n+i] += 1 result = sum(list(card_counter.values())) print("Part two: Sum =", result) if __name__ == "__main__": main()
-
Tag 4 in BASIC (Laufzeit 14 Minuten und 40 Sekunden):
BASIC
Display More10 TI$="000000":R1=0:R2=0 20 DIM C%(200),W%(99),N(200),NN(200) 100 OPEN 1,8,2,"INPUT04,S" 110 PRINT"READING & PART 1" 120 CI=0 130 IF ST<>0 THEN 500 140 CI=CI+1 150 PRINT CI:PRINT"{UP}"; 160 FOR I=1 TO 99:W%(I)=0:NEXT 170 FOR I=1 TO LEN("CARD ###:"):GET#1,C$:NEXT 180 FOR I=1 TO 10 190 GOSUB 1000:W%(N)=1 200 NEXT 210 FOR I=1 TO 2:GET#1,C$:NEXT 220 C=0 230 FOR I=1 TO 25 240 GOSUB 1000:C=C+W%(N)Gestern 250 NEXT:C%(CI)=C 260 R1=R1+INT(2↑(C-1)) 270 GET#1,C$:REM CR 280 GOTO 130 500 CLOSE 1 510 PRINT:PRINT R1 520 PRINT "PART 2" 530 FOR I=1 TO CI:N(I)=1:NEXT 540 PRINT R2:PRINT"{UP}"; 550 N=0 560 FOR I=1 TO CI:N=N+N(I):NEXT 570 IF N=0 THEN 950 580 R2=R2+N 590 FOR I=1 TO CI:NN(I)=0:NEXT 600 FOR I=1 TO CI 610 W=C%(I):IF W=0 THEN 660 620 C=N(I) 630 FOR J=1 TO W 640 K=I+J:NN(K)=NN(K)+C 650 NEXT 660 NEXT 670 FOR I=1 TO CI:N(I)=NN(I):NEXT 680 GOTO 540 950 PRINT:PRINT TI$ 960 END 1000 N=0:FOR J=1 TO 3 1010 GET#1,C$ 1020 N=N*10+VAL(C$) 1030 NEXT:RETURN
Tag 5 in Python war echt Programmierarbeit 🤓. Was man hier nicht sieht ist der ganze Code der für Teil 2 ersetzt wurde damit der für beide Teile funktioniert:
Python
Display More#!/usr/bin/env python3 import sys from attr import attrib, attrs from more_itertools import chunked, split_at from parse import parse EXAMPLE_LINES = """\ seeds: 79 14 55 13 seed-to-soil map: 50 98 2 52 50 48 soil-to-fertilizer map: 0 15 37 37 52 2 39 0 15 fertilizer-to-water map: 49 53 8 0 11 42 42 0 7 57 7 4 water-to-light map: 88 18 7 18 25 70 light-to-temperature map: 45 77 23 81 45 19 68 64 13 temperature-to-humidity map: 0 69 1 1 0 69 humidity-to-location map: 60 56 37 56 93 4 """.splitlines() @attrs(frozen=True) class Range: """ >>> Range(2) Range(start=2, end=2) >>> r = Range(23, 42) >>> r Range(start=23, end=42) >>> len(r) 20 The end points are included (unlike the built-in `range()`): >>> 3 in r False >>> 23 in r True >>> 42 in r True >>> 43 in r False """ start = attrib() end = attrib() @end.default def _end_factory(self): return self.start def __len__(self): return self.end - self.start + 1 def __contains__(self, number): return self.start <= number <= self.end @classmethod def from_start_and_length(cls, start, length): """ >>> Range.from_start_and_length(79, 14) Range(start=79, end=92) """ return cls(start, start + length - 1) @attrs(frozen=True) class RangeMap: """ >>> map_ = RangeMap.from_starts_and_length(3, 7, 11) >>> map_ RangeMap(source=Range(start=3, end=13), destination=Range(start=7, end=17)) Check if an input number can be mapped by this `RangeMap`: >>> 0 in map_, 3 in map_, 13 in map_, 14 in map_ (False, True, True, False) Numbers outside the source range are mapped to `None`: >>> map_[0], map_[3], map_[13], map_[14] (None, 7, 17, None) """ source = attrib() destination = attrib() def __attrs_post_init__(self): if len(self.source) != len(self.destination): raise ValueError( f"{self.source!r} and {self.destination!r}" f" must have the same length" f" ({len(self.source)} != {len(self.destination)})" ) def __contains__(self, number): return number in self.source def __getitem__(self, number): if number in self: return number - self.source.start + self.destination.start return None def map(self, range_): """ Map a given `Range` to a tuple of mapped ranges and remaining unmapped parts of the given `Range`. >>> map_ = RangeMap.from_starts_and_length(5, 105, 11) >>> map_ RangeMap(source=Range(start=5, end=15), destination=Range(start=105, end=115)) Mapping a range that is outside of this `RangeMap` returns no mapped ranges and the original range: >>> map_.map(Range(0, 4)) ([], [Range(start=0, end=4)]) Mapping ranges that are complete within this `RangeMap` leave no remaining ranges: >>> map_.map(Range(7, 13)) ([Range(start=107, end=113)], []) >>> map_.map(Range(5, 15)) ([Range(start=105, end=115)], []) Mapping a range that overlaps the start *or* beginning return the mapped overlapping part and the unmapped remaining part: >>> map_.map(Range(3, 7)) ([Range(start=105, end=107)], [Range(start=3, end=4)]) >>> map_.map(Range(10, 20)) ([Range(start=110, end=115)], [Range(start=16, end=20)]) Finally mapping a range that encloses the `RangeMap` returns the mapped middle part, and the remaining unmapped parts bevor and after: >>> map_.map(Range(0, 20)) ([Range(start=105, end=115)], [Range(start=0, end=4), Range(start=16, end=20)]) """ start = self[range_.start] end = self[range_.end] if start is None and end is None: if ( range_.start < self.source.start and range_.end > self.source.end ): return ( [self.destination], [ Range(range_.start, self.source.start - 1), Range(self.source.end + 1, range_.end), ], ) else: return ([], [range_]) elif start is None: return ( [Range(self.destination.start, end)], [Range(range_.start, self.source.start - 1)], ) elif end is None: return ( [Range(start, self.destination.end)], [Range(self.source.end + 1, range_.end)], ) else: return ([Range(start, end)], []) @classmethod def from_starts_and_length(cls, source_start, destination_start, length): return cls( Range.from_start_and_length(source_start, length), Range.from_start_and_length(destination_start, length), ) @classmethod def parse(cls, text): """ >>> RangeMap.parse("10 20 7") RangeMap(source=Range(start=20, end=26), destination=Range(start=10, end=16)) """ destination_start, source_start, length = map(int, text.split()) return cls.from_starts_and_length( source_start, destination_start, length ) @attrs(frozen=True) class Map: """ >>> map_ = Map("seed", ... "soil", ... [RangeMap.from_starts_and_length(98, 50, 2), ... RangeMap.from_starts_and_length(50, 52, 48)]) >>> ranges = list(map(Range, [79, 14, 55, 13])) >>> [[range_.start for range_ in map_[source_range]] ... for source_range in ranges] [[81], [14], [57], [13]] >>> map_[Range(79)] [Range(start=81, end=81)] >>> map_[Range.from_start_and_length(79, 14)] [Range(start=81, end=94)] """ # # The two names are not really needed but make debugging easier and adding a # rountrip back to source at least possible. # source_name = attrib() destination_name = attrib() range_maps = attrib() def __getitem__(self, range_): result = [] sources = [range_] for range_map in self.range_maps: next_sources = [] for source_range in sources: mapped_ranges, remaining_ranges = range_map.map(source_range) result.extend(mapped_ranges) next_sources.extend(remaining_ranges) sources = next_sources if not sources: break result.extend(sources) return result @classmethod def parse_lines(cls, lines): lines = iter(lines) source_name, destination_name = parse("{}-to-{} map:", next(lines)) return cls( source_name, destination_name, list(map(RangeMap.parse, lines)) ) @attrs(frozen=True) class Almanach: seed_ranges = attrib() maps = attrib() def get_revised(self): """ >>> almanach = Almanach.parse_lines(EXAMPLE_LINES).get_revised() >>> almanach.seed_ranges [Range(start=79, end=92), Range(start=55, end=67)] >>> almanach.get_min_location() 46 """ if not all(len(range_) == 1 for range_ in self.seed_ranges): raise ValueError("can't be revised") return Almanach( [ Range.from_start_and_length(start.start, end.start) for start, end in chunked(self.seed_ranges, 2, strict=True) ], self.maps, ) def get_location_ranges(self, seed_range): """ >>> almanach = Almanach.parse_lines(EXAMPLE_LINES) >>> almanach.get_location_ranges(Range(79)) [Range(start=82, end=82)] >>> almanach.get_location_ranges(Range(79, 92)) [Range(start=60, end=60), Range(start=46, end=55), Range(start=82, end=84)] """ ranges = [seed_range] for map_ in self.maps: next_ranges = [] for range_ in ranges: next_ranges.extend(map_[range_]) ranges = next_ranges return ranges def iter_location_ranges(self): """ >>> almanach = Almanach.parse_lines(EXAMPLE_LINES) >>> ranges = list(almanach.iter_location_ranges()) >>> all(len(range_) == 1 for range_ in ranges) True >>> [range_.start for range_ in ranges] [82, 43, 86, 35] >>> from attr import astuple >>> list(map(astuple, almanach.get_revised().iter_location_ranges())) [(60, 60), (46, 55), (82, 84), (86, 89), (94, 96), (56, 59), (97, 98)] """ for ranges in map(self.get_location_ranges, self.seed_ranges): yield from ranges def get_min_location(self): """ >>> almanach = Almanach.parse_lines(EXAMPLE_LINES) >>> almanach.get_min_location() 35 >>> almanach.get_revised().get_min_location() 46 """ return min(self.iter_location_ranges()).start @classmethod def parse_lines(cls, lines): lines = (line.rstrip() for line in lines) line_blocks = split_at(lines, lambda line: line == "") seed_ranges = [ Range(int(text)) for text in parse("seeds: {}", next(line_blocks)[0])[0].split() ] return cls(seed_ranges, list(map(Map.parse_lines, line_blocks))) def main(): almanach = Almanach.parse_lines(sys.stdin) print(almanach.get_min_location()) print(almanach.get_revised().get_min_location()) if __name__ == "__main__": main()
-
Wenn man Rechenleistung hat, braucht man heute am Tag 6 nicht viel Nachdenken — das kann man einfach brutal auszählen, auch wenn es für Teil 2 ein paar Sekunden braucht auf meinem Bürorechner.
Wenn man weniger leistungsfähige Hardware verwendet, *hust* VIC-20 *hust*, dann muss man ein bisschen Nachdenken welche Eigenschaften der Möglichen Versuche man ausnutzen kann um nicht tatsächlich alle durchrechnen zu müssen. Dabei kann eine Visualisierung der Werte aus dem Aufgabenbeispiel hilfreich sein, die man sich beispielsweise auf einem Epson-kompatiblen (9-)Nadeldrucker mit folgendem BASIC-Programm erstellen kann:
BASIC
Display More10 OPEN 1,4:E$=CHR$(27) 20 PRINT#1,E$;"W1AOC 2023, DAY 6 - WAIT FOR IT";E$;"W0" 30 FOR I=1 TO 3 40 : READ T,RD 50 : PRINT T;RD 60 : PRINT#1 70 : PRINT#1,"RACE TIME:";T;" RECORD DISTANCE:";RD 80 : FOR N=0 TO T 90 : T$=" "+STR$(N) 100 : PRINT#1,MID$(T$,LEN(T$)-3);" "; 110 : D=N*(T-N) 120 : PRINT N;"/";T;"=";D 130 : IF D=0 THEN 200 140 : B$=CHR$(255) 150 : PRINT#1,E$;"K";CHR$(D);CHR$(0); 160 : FOR J=1 TO D 170 : IF J>=RD THEN B$=CHR$(85) 180 : PRINT#1,B$; 190 : NEXT 200 : PRINT#1,D 210 : NEXT 220 NEXT 230 CLOSE 1 900 DATA 7,9 910 DATA 15,40 920 DATA 30,200
Für Leute die aus irgendwelchen nicht wirklich nachvollziehbaren Gründen so etwas nicht zur Hand haben: