Advent of Code

Gute Links und Tutorials könnt ihr hier posten.
Benutzeravatar
Dennis89
User
Beiträge: 1518
Registriert: Freitag 11. Dezember 2020, 15:13

In Rust:

Code: Alles auswählen

use std::{
    fs::File,
    io::{prelude::*, BufReader},
    path::Path,
    iter::zip
};
use itertools::Itertools;

fn read_lines(filepath: impl AsRef<Path>) -> Vec<String> {
    let file_content = File::open(filepath).expect("File not found");
    let buffer = BufReader::new(file_content);
    buffer.lines()
        .map(|l| l.expect("Can't read"))
        .collect()
}

fn format_input(lines: Vec<String>) -> (Vec<i32>, Vec<i32>) {
    let mut destinations: Vec<Vec<String>> = Vec::new();
    for line in lines {
        let line: Vec<String> = line.split_whitespace().map(str::to_string).collect();
        destinations.push(line);
    };

    let mut left_values: Vec<i32> = Vec::new();
    let mut right_values: Vec<i32> = Vec::new();
    for pair in destinations {
        let left:i32 = pair[0].parse::<i32>().unwrap();
        left_values.push(left);
        let right:i32 = pair[1].parse::<i32>().unwrap();
        right_values.push(right);
    };
    left_values.sort();
    right_values.sort();
    (left_values, right_values)
}

fn part_1(left_values: Vec<i32>, right_values: Vec<i32>) -> i32 {
    let mut calculated_values: Vec<i32> = Vec::new();
    for (left, right) in zip(left_values, right_values) {
        let result:i32 = left - right;
        calculated_values.push(result.abs());
    };
    let result: i32 = calculated_values.iter().sum();
    result
}

fn part_2(left_values: Vec<i32>, right_values: Vec<i32>) -> i32 {
    let counts = right_values.into_iter().counts();
    let mut calcualted_values: Vec<i32> = Vec::new();
    for value in left_values {
        if counts.contains_key(&value) {
            let result = value * counts[&value] as i32;
            calcualted_values.push(result);
        } 
    };
    let result: i32 = calcualted_values.iter().sum();
    result
}

fn main() {
    let lines = read_lines("/home/dennis/PycharmProjects/AdventofCode/2024/puzzel_input.txt");
    let (left_values, right_values)  = format_input(lines);
    println!("{:?}", part_1(left_values.clone(), right_values.clone()));
    println!("{:?}", part_2(left_values, right_values));
}
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Teil 1 in ALTAIR Extended BASIC läuft schon den halben Tag auf einem ALTAIR 8800 Emulator. 1.000 Zahlen mit Bubblesort dauert Stunden auf dem 2 Mhz 8080-Prozessor.

Hier das Listing mit den Beispieldaten aus der Aufgabe:

Code: Alles auswählen

10 DEFINT A-Z:READ N:DIM ID!(N,1)
20 PRINT"Reading data..."
30 FOR I=1 TO N:FOR J=0 TO 1:READ ID!(I,J):NEXT:NEXT
40 PRINT"Sorting...":FOR J=0 TO 1:PRINT"  List";J;"..."
50 F=0:FOR I=1 TO N-1
60 IF ID!(I,J)>ID!(I+1,J) THEN SWAP ID!(I,J),ID!(I+1,J):F=1
70 NEXT:IF F THEN 50
80 NEXT
90 PRINT"Sum distances...":R#=0
100 FOR I=1 TO N:R#=R#+ABS(ID!(I,0)-ID!(I,1)):NEXT:PRINT R#
9000 DATA 6
9010 DATA 3,4
9020 DATA 4,3
9030 DATA 2,5
9040 DATA 1,3
9050 DATA 3,9
9060 DATA 3,3
Ich muss mal schauen ob ich am Programmende neben dem Ergebnis von Aufgabe 1 auch die sortierten Listen noch gespeichert bekomme, damit ich die für den zweiten Teil schon sortiert in das nächste Programm füttern kann.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@Dennis89: In deiner Python-Lösung hast du zwei Mal `more_itertools.transpose` (unterschiedlich!) per Hand nachgebaut.

Die Rust-Lösung hat ein paar Stellen, an der man das besser machen kann und ein paar Stellen, an denen ich das anders machen würde (ohne dass ich da jetzt unbedingt sagen würde, dass das besser ist.

Code: Alles auswählen

use std::error::Error;
use std::fs::File;
use std::io;
use std::io::BufRead;
use std::io::BufReader;
use std::iter::zip;
use std::path::Path;

use itertools::Itertools as _;

fn read_input(filepath: impl AsRef<Path>) -> io::Result<Vec<String>> {
    let file = File::open(filepath)?;
    let file = BufReader::new(file);
    file.lines().collect()
}

fn parse_input(lines: Vec<String>) -> Result<(Vec<u64>, Vec<u64>), Box<dyn Error>> {
    let (mut left_values, mut right_values): (Vec<u64>, Vec<u64>) = lines
        .iter()
        .map(|line| -> Result<(u64, u64), Box<dyn Error>> {
            let mut parts = line.split_whitespace();
            match (parts.next(), parts.next(), parts.next()) {
                (Some(left), Some(right), None) => Ok((left.parse()?, right.parse()?)),
                _ => Err("line did not contain two numbers".into()),
            }
        })
        .process_results(|x| x.unzip())?;
    left_values.sort();
    right_values.sort();
    Ok((left_values, right_values))
}

fn part_1(left_values: &[u64], right_values: &[u64]) -> u64 {
    zip(left_values, right_values)
        .map(|(left, &right)| left.abs_diff(right))
        .sum()
}

fn part_2(left_values: &[u64], right_values: &[u64]) -> u64 {
    let counts = right_values.iter().copied().counts();
    left_values
        .iter()
        .map(|left| left * u64::try_from(*counts.get(left).unwrap_or(&0)).unwrap())
        .sum()
}

fn main() -> Result<(), Box<dyn Error>> {
    let lines = read_input("puzzel_input.txt")?;
    let (left_values, right_values) = parse_input(lines)?;
    println!("{}", part_1(&left_values, &right_values));
    println!("{}", part_2(&left_values, &right_values));
    Ok(())
}
In `read_lines` würde ich den Fragezeichenoperator benutzen, um „richtige“ Fehlerbehandlung zu machen und nicht das Programm bei einem Fehler abzuschießen. Damit spart man sich dann gleich das `map`, weil man einen `Iterator<Item = Result<T, E>>` direkt in ein `Result<Vec<T>, E>` collecten kann (das `collect` flippt das `Result` von innen nach außen).

`format_input` hat einen komischen Namen, eigentlich wird da der Input geparst, nicht formatiert.

Statt da einen `Vec<Vec<String>>` aufzubauen (das kopiert alles und allokiert mehrfach für jede Zeile; Allokationen will man in Rust normalerweise vermeiden, wenn es den Code nicht viel komplizierter macht), kann man direkt vom `Vec<String>` in das Tupel `(Vec<u64>, Vec<u64>)` gehen, indem man `Itertools::process_results` benutzt. (Wenn man da `unwrap`/`panic!()` statt richtiger Fehlerbehandlung benutzt, kann man direkt `unzip` benutzen:

Code: Alles auswählen

    let (mut left_values, mut right_values): (Vec<_>, Vec<_>) = lines
        .iter()
        .map(|line| {
            let mut parts = line.split_whitespace();
            match (parts.next(), parts.next(), parts.next()) {
                (Some(left), Some(right), None) =>
                    (left.parse::<u64>().unwrap(), right.parse::<u64>().unwrap()),
                _ => panic!("line did not contain two numbers: {line:?}"),
            }
        })
        .unzip();
Du hast `part_1` und `part_2` so geschrieben, dass sie Ownership über die beiden `Vec`s übernehmen. Das ist in diesem Fall allerdings nicht nötig, weil du nur lesend auf die `Vec`s zugreifst. Also sollte man da Slices übergeben: `&[u64]`. Damit spart man sich dann auch das unnötige `clone()` in `main`.

In beiden Funktionen erstellst du einen Iterator (in der `for`-Schleife), pushst dann einen Haufen Werte in einen `Vec` und dann erstellst du aus dem `Vec` jeweils wieder einen Iterator, nur um darauf `sum` aufzurufen. Den `Vec` als Zwischenschritt brauchst du nicht, du kannst direkt `map` auf dem Ursprungsiterator benutzen und den dann mit `sum` konsumieren. (Das Python-Äquivalent dazu wäre, wenn du `sum([abs(l - r) for l, r in zip(ls, rs)])` statt `sum(abs(l - r) for l, r in zip(ls, rs))` geschrieben hättest.)

Ich habe den Code auch so umgebaut, dass überall `u64` benutzt wird. Die Werte können nie negativ werden, also ist `u64` ein guter Default für „damit will ich rechnen“.

In `part_2` benutzt du `as` zum Konvertieren vom `usize` aus `counts` nach `i32`. Ich persönlich würde von `as` immer abraten, weil das zu viel kann. Wenn man irgendwann mal die Typen ändert, ist es möglich, dass das `as` nicht mehr macht, was man eigentlich mal wollte, aber man bekommt keinen Fehler, sondern einfach silently geändertes Verhalten.

Wenn man von einem kleineren in einen größeren Typen konvertiert, sollte man `Large::from()` oder `Small::into()` benutzen (das stellt sicher, dass die Konversion den Wert nie ändern kann). Wenn sich der Wert ändern kann, würde ich `try_from` bzw. `try_into` benutzen, damit man einen Fehler bekommt, wenn sich der Wert ändern würde. Hier auch gerne mit `unwrap`, weil alle `usize` immer auch in `u64` passen, wenn man „normale“ Hardware benutzt.
nezzcarth
User
Beiträge: 1750
Registriert: Samstag 16. April 2011, 12:47

Wie jedes Jahr habe ich neben Python wieder versucht, Tag 1 mit Shell-Einzelern zu lösen.

Code: Alles auswählen

# Teil 1
paste <(cut -f 1 -d ' ' day01.txt | sort) <(tr -s ' ' < day01.txt | cut -f 2 -d ' ' | sort) | gawk '{x+=sqrt(($1-$2)**2)}END{print x}'
# Teil 2
join <(cut -f 1 -d ' ' day01.txt | sort) <(tr -s ' ' < day01.txt | cut -f 2 -d ' ' | sort | uniq -c) -2 2 -a 1 | gawk '{x+=$1*$2}END{print x}'
@Dennis89: Generatoren kann man mit itertools.tee vervielfachen.
Benutzeravatar
Dennis89
User
Beiträge: 1518
Registriert: Freitag 11. Dezember 2020, 15:13

@narpfel
Danke für das ausführliche Feedback. `parse_input` habe ich von dir kopiert und muss ich mir in Ruhe noch einmal anschauen. Deine restlichen Anmerkungen konnte ich mit entsprechendem Aufwand aber dann doch zum großen Teil umsetzen.
Ich hoffe es kommt noch ein leichtes Rätsel, damit ich es noch einmal mit Rust versuchen kann.

Wegen `more_itertools.transpose` , da habe ich wohl doch wieder zu schnell die Seite geschlossen :D

@nezzcarth Das mit `tee` hatte ich gelesen und auch versucht, bin aber gescheitert. War aber dann doch relativ früh morgens. War ungewollt um 6 schon vorm PC -.- Und jetzt ist es mir mittlerweile zu spät. Ich teste das morgen noch einmal aus.


Grüße und Danke nochmals
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1518
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

hm `tee` liefert mir verschachtelte `tee-islice`- Objekte und wenn ich `transpose`verwende muss ich davor die listen noch "extra" sortieren.

Code: Alles auswählen

left, right = tee(destinations, 2)
So verstehe ich die Anwendung, aber `left`und `right` sind dann ìtertools._tee`- Objekte, die wiederum ìtertools.islice`-Objekte enthalten und darin sind dann meine Werte. Für mich macht es das irgendwie komplizierter.



Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Die Namensgebung ist ein bisschen komisch denn `left` und `right` liefern ja die *selben* Werte. Das ist eher so etwas wie ``destinations, destinations_copy = tee(destinations, 2)``.

`tee()` macht IMHO nur wirklich Sinn, wenn man erwartet das beide Ergebnisse ”überlappend” ausgewertet werden. Wenn man weiss, das die auf jeden Fall nacheinander ausgewertet werden, ist es IMHO einfacher und klarer die Ergebnisse selbst in einer Liste zu sammeln und die dann nacheinander mehrfach zu verwenden.

Beispiel für sinnvolles `tee()` wäre `pairwise()`:

Code: Alles auswählen

def pairwise(iterable):
    first, second = tee(iterable)
    next(second, None)
    return zip(first, second)
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1518
Registriert: Freitag 11. Dezember 2020, 15:13

Ah shit, da war ich ja irgendwie ganz wo anders im Kopf. Danke für die Erklärung.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dehnert: Fast übersehen weil das der letzte Beitrag auf der Seite ist. 😱

Das ist in der Tat falsch. Der erste Ansatz wäre sinnvolle Namen zu verwenden. Dann sollte auffallen, dass das keinen Sinn macht, beziehungsweise nicht der Aufgabenstellung entspricht.

Neben nichtssagenden Namen wird ein Name auch doppelt verwendet, also der selbe Name für unterschiedliche Werte, sowohl von der Bedeutung unterschiedlich, als auch vom Datentyp.

Wobei nichtssagend stimmt auch nicht ganz: `i` sagt dem Leser ja „eine ganze Zahl“ die als Laufindex verwendet wird. Nur stimmt das hier dann nicht. 🤔
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dehnert: Ergänzend: Das liefert ja auch schon bei den Beispieldaten aus der Aufgabe nicht das richtige Ergebnis. Die Aufgabe erklärt wie das Beispielergebnis zustande kommt, mit Zwischenergebnissen. Da kann man dann im eigenen Code schauen, ob an den entsprechenden Stellen die erwarteten Zwischenergebnisse heraus kommen, oder etwas anderes, und so die Stelle finden, ab der der Code nicht mehr das macht, was er sollte.

Es hilft den Code sinnvoll auf Funktionen aufzuteilen, die man einzeln testen kann, und das dann auch zu *tun* mit den Beispieldaten aus der Aufgabe. Zum Beispiel in Form von Doctests, also Dokumentieren wie ein Aufruf in der Shell und das Ergebnis davon aussehen, und das dann mit dem `doctest`-Modul aus der Standardbibliothek überprüfen.

Hier mal das Grundgerüst ohne die Funktionsinhalte:

Code: Alles auswählen

#!/usr/bin/env python3
import sys
...

EXAMPLE_LINES = """\
3   4
4   3
2   5
1   3
3   9
3   3
""".splitlines()


def parse_lines(lines):
    """
    >>> parse_lines(EXAMPLE_LINES)
    [(3, 4, 2, 1, 3, 3), (4, 3, 5, 3, 9, 3)]
    """
    ...


def sum_distances(ids_a, ids_b):
    """
    >>> sum_distances(*parse_lines(EXAMPLE_LINES))
    11
    """
    ...


def calculate_similarity_score(ids_a, ids_b):
    """
    >>> calculate_similarity_score(*parse_lines(EXAMPLE_LINES))
    31
    """
    ...


def main():
    ids_a, ids_b = parse_lines(sys.stdin)
    print(sum_distances(ids_a, ids_b))
    print(calculate_similarity_score(ids_a, ids_b))


if __name__ == "__main__":
    main()
Der vollständige Code mit `doctest` und der Option ``-v`` überprüft:

Code: Alles auswählen

$ python3 -m doctest -v day01.py 
Trying:
    calculate_similarity_score(*parse_lines(EXAMPLE_LINES))
Expecting:
    31
ok
Trying:
    parse_lines(EXAMPLE_LINES)
Expecting:
    [(3, 4, 2, 1, 3, 3), (4, 3, 5, 3, 9, 3)]
ok
Trying:
    sum_distances(*parse_lines(EXAMPLE_LINES))
Expecting:
    11
ok
2 items had no tests:
    day01
    day01.main
3 items passed all tests:
   1 tests in day01.calculate_similarity_score
   1 tests in day01.parse_lines
   1 tests in day01.sum_distances
3 tests in 5 items.
3 passed and 0 failed.
Test passed.
Wenn die Beispiele aus der Aufgabe funktionieren, dann ist in der Regel auch der Puzzle-Input kein Problem.

Mein Grundgerüst für Tag 2:

Code: Alles auswählen

#!/usr/bin/env python3
import sys
...

EXAMPLE_LINES = """\
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
""".splitlines()


def parse_reports(lines):
    ...


def is_safe(report):
    """
    >>> is_safe([7, 6, 4, 2, 1])
    True
    >>> is_safe([1, 2, 7, 8, 9])
    False
    >>> is_safe([9, 7, 6, 2, 1])
    False
    >>> is_safe([1, 3, 2, 4, 5])
    False
    >>> is_safe([8, 6, 4, 4, 1])
    False
    >>> is_safe([1, 3, 6, 7, 9])
    True
    """
    ...


def is_safe_with_problem_dampener(report):
    """
    >>> is_safe_with_problem_dampener([7, 6, 4, 2, 1])
    True
    >>> is_safe_with_problem_dampener([1, 2, 7, 8, 9])
    False
    >>> is_safe_with_problem_dampener([9, 7, 6, 2, 1])
    False
    >>> is_safe_with_problem_dampener([1, 3, 2, 4, 5])
    True
    >>> is_safe_with_problem_dampener([8, 6, 4, 4, 1])
    True
    >>> is_safe_with_problem_dampener([1, 3, 6, 7, 9])
    True
    """
    ...


def count_safe_reports(reports, predicate):
    """
    >>> reports = parse_reports(EXAMPLE_LINES)
    >>> count_safe_reports(reports, is_safe)
    2
    >>> count_safe_reports(reports, is_safe_with_problem_dampener)
    4
    """
    ...


def main():
    reports = parse_reports(sys.stdin)
    print(count_safe_reports(reports, is_safe))
    print(count_safe_reports(reports, is_safe_with_problem_dampener))


if __name__ == "__main__":
    main()
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Kebap
User
Beiträge: 772
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Das mit dem Grundgerüst finde ich eine großartige Idee!

Wie gehst du dazu aber konkret vor? Der Name lässt vermuten, dass das Gerüst als erstes steht. Aber zu Anfang kennst du ja überhaupt nur die erste Aufgabe.

Also kannst du noch gar keine Teile für die zweite Teilaufgabe des Tages im Gerüst berücksichtigen. Dann muss das also später umgerüstet werden?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kebap: Die Gerüste, die ich gepostet habe sind meine kompletten Lösungen abzüglich der Funktionsinhalte die nicht super-offensichtlich sind (also die `main()`). Beim Entwickeln entsteht das Stück für Stück/Funktion für Funktion.

Ich fange normalerweise mit einer `parse*()`-Funktion an, welche die Daten einliest und passend umwandelt. Wenn es so simpel ist wie bisher, reicht diese eine Funktion. Wenn die Eingabedaten komplexer sind, bieten sich weitere Funktionen an. Beispielsweise an Tag 2 könnte man eine `parse_report()`-Funktion schreiben, die *eine* Zeile verarbeitet und die testen. Dafür waren mir die Eingabedaten aber zu simpel. Es gab in der Vergangenheit aber auch deutlich aufwändigere Datenformate, mit mehreren Abschnitten, oder wo einzelne Zeilen nicht so regelmässig aufgebaut sind.

Dann eine Funktion für die erste Teilaufgabe. Daraus ergeben sich manchmal weitere Funktionen. Oft aus den Beispielen/Zwischenergebnissen aus der Aufgabe, weil man das ja separat testen kann. Also beispielsweise die Prüffunktion für einen einzelnen Bericht von Tag 2. Da lassen sich sehr leicht Testaufrufe aufgrund der Informationen aus der Aufgabe erstellen.

Wenn der erste Teil gelöst ist, schaue ich wie ähnlich der zweite Teil ist, also ob man da im Grunde die gleiche Lösung mit zusätzlichen Parametern verwenden kann, oder ob das eine unabhängige Lösung wird. An Tag 1 war es einfach eine andere Funktion. An Tag 2 hat die Zählfunktion die Prüffunktion als Argument bekommen.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Kebap
User
Beiträge: 772
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Danke __blackjack__ - Das leuchtet ein.

Aus guter Tradition löse ich die ersten 2-3 Tage grob semiautomatisch, so lange das halbwegs funktioniert.

Tag 1 in Excel:

Code: Alles auswählen

A	B	C	D	E	F	G	H	I	J	K	L	M	N
Quelldaten	(leer)	Spalte A	Spalte B	(leer)	A sortiert	B sortiert	(leer)	Distanz AB	Summe	(leer)	Häufigkeit A in B	Similarity Score	Summe
3  4		=TEXTTEILEN(A2;" ";;WAHR)			=SORTIEREN(C2:C1001)	=SORTIEREN(D2:D1002)		=ABS(F2-G2)	=SUMME(I2:I1001)		=ZÄHLENWENN(G$2#;F2)	=F2*L2	=SUMME(M2:M1001)
4  3		=TEXTTEILEN(A3;" ";;WAHR)						=ABS(F3-G3)			=ZÄHLENWENN(G$2#;F3)	=F2*L2	
2  5		=TEXTTEILEN(A4;" ";;WAHR)						=ABS(F4-G4)			=ZÄHLENWENN(G$2#;F4)	=F2*L2	
1  3		=TEXTTEILEN(A5;" ";;WAHR)						=ABS(F5-G5)			=ZÄHLENWENN(G$2#;F5)	=F2*L2	
3  9		=TEXTTEILEN(A6;" ";;WAHR)						=ABS(F6-G6)			=ZÄHLENWENN(G$2#;F6)	=F2*L2	
3  3		=TEXTTEILEN(A7;" ";;WAHR)						=ABS(F7-G7)			=ZÄHLENWENN(G$2#;F7)	=F2*L2	
Hier im Layout des Forums werden die Tabulatoren leider gesprengt durch die Länge der Texte in den einzelnen Zellen.
Mal überlegen, wie ich das schöner darstellen könnte. Also jetzt nicht als Bildschirmfoto, sondern tatsächlich lesbar.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kebap: rich.table und ein kleines bisschen Code sollte das umwandeln können. Oder man schreibst sich selbst ein bisschen Code der das besser formatiert.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1518
Registriert: Freitag 11. Dezember 2020, 15:13

Gibt's für Part2 einen alternativen, schöneren Weg, wie slicen der Liste?


Grüße
Dennis

Edit: An Part 1 gefällt mir nicht, das ich zwei mal über `report` iteriere.

Code: Alles auswählen

#!/usr/bin/env python
from itertools import pairwise
from pathlib import Path


INPUT_FILE = Path(__file__).parent / "input02.txt"
PUZZLE_INPUT = """\
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
""".splitlines()


def is_safe(report):
    return (
        all(1 <= x - y <= 3 for x, y in pairwise(report))
        or all(1 <= y - x <= 3 for x, y in pairwise(report))
    )


def get_safe_reports(reports):
    return sum(is_safe(report) for report in reports)


def main():
    puzzle_input = PUZZLE_INPUT
    puzzle_input = INPUT_FILE.read_text(encoding="UTF-8").splitlines()
    reports = list(map(lambda x: list(map(int, x.split())), puzzle_input))
    print(get_safe_reports(reports))


if __name__ == "__main__":
    main()
Edit2: Die Struktur ist tatsächlich unabhängig von @__blackjack__ Gerüst entstanden. :mrgreen:
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@Dennis89: Hm, man könnte vielleicht eine schlaue Lösung durch Nachdenken finden, aber ich habe es auch mit Slicing gelöst.

Code: Alles auswählen

def skip_index(iterable, index):
    for i, value in enumerate(iterable):
        if i != index:
            yield value
Das hier könnte man statt Slicing noch machen, aber das ist IMHO nicht wirklich schöner, und in meiner Lösung bringt das nicht so viel, weil ich auch mehrfach iteriere in Teil 1 und damit den Rückgabewert von `skip_index` in eine Liste umwandeln muss.
Benutzeravatar
__blackjack__
User
Beiträge: 14005
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mein Teil 1 `is_safe()` sieht so aus (`difference()` ist aus `more_itertools`):

Code: Alles auswählen

def is_safe(report):
    """
    >>> is_safe([7, 6, 4, 2, 1])
    True
    >>> is_safe([1, 2, 7, 8, 9])
    False
    >>> is_safe([9, 7, 6, 2, 1])
    False
    >>> is_safe([1, 3, 2, 4, 5])
    False
    >>> is_safe([8, 6, 4, 4, 1])
    False
    >>> is_safe([1, 3, 6, 7, 9])
    True
    """
    level_differences = list(difference(report, initial=False))
    is_increasing = level_differences[0] > 0
    return all(
        (
            0 < abs(level_difference) <= 3
            and (level_difference > 0) == is_increasing
        )
        for level_difference in level_differences
    )
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

Code: Alles auswählen

SAFE = frozenset(map(frozenset, chain(powerset([1, 2, 3]), powerset([-1, -2, -3]))))

def part_1(lines):
    return sum(
        frozenset(starmap(sub, pairwise(line))) in SAFE
        for line in lines
    )
Meine Single-pass-Lösung.

Edit: Argh, das funktioniert natürlich nicht, weil `substrings` nicht `(1, 3)` erzeugt. Da waren die Tests wohl nicht gut genug. :mrgreen:

Edit 2: `powerset` statt `substrings` und schon funktioniert es. :wink:
Zuletzt geändert von narpfel am Montag 2. Dezember 2024, 21:59, insgesamt 1-mal geändert.
nezzcarth
User
Beiträge: 1750
Registriert: Samstag 16. April 2011, 12:47

schöneren Weg, wie slicen der Liste
Ich habe einen hässlicheren gewählt. Ich habe Teil 2 erst falsch gelesen und eine schöne Lösung geschrieben, die die Anzahl der Verstöße auswertet. Als mir klar wurde, dass das nicht zum Ziel führt dachte ich mir, "was soll der Quatsch, manche Aufgaben haben keine schöne Lösung verdient", hab' die Liste geweils kopiert und systematisch Elemente gelöscht; Thema erledigt. 8) ;) Erstaunlicherweise und wider Erwarten nicht mal langsam.
Benutzeravatar
Kebap
User
Beiträge: 772
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Tag 3 im Texteditor

mit Suche als Regex, und "find all" markiert alle Ergebnisse bspw. zum Löschen oder Kopie in den Zwischenspeicher.

Code: Alles auswählen

- copy original data

- copy to new tab

- remove new lines
  - find: \n

- delete dont sections
  - find: don't\(\).*?do\(\)

- remove section after dont before end (it may not be followed by another do mind)
  - find: don't

- select muls
  - find: mul\((\d+),(\d+)\)

- copy to new tab

- replace muls
  - from: mul\((\d+),(\d+)\)
  - to: $1\t$2

- copy to excel, multiply, sum
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Antworten