[EN/PL]

Advent of Code 2023 Rozwiązania

Trebusz?!

Coś jest nie tak z globalną produkcją śniegu, i zostałeś wybrany, aby to sprawdzić. Elfy nawet dały ci mapę; na niej użyły gwiazd, aby oznaczyć pięćdziesiąt najpewniejszych lokalizacji, które prawdopodobnie mają problemy.

Próbujesz zapytać, dlaczego nie mogą po prostu użyć maszyny pogodowej ("nie wystarczająco silna") i dokąd cię tak w ogóle wysyłają ("na niebo") i dlaczego twoja mapa wygląda na pustą ("naprawdę zadajesz dużo pytań") i zaczekaj, czy właśnie powiedziałeś niebo ("oczywiście, skąd myślisz, że pochodzi śnieg") kiedy zdajesz sobie sprawę, że Elfy już cię ładują do trebusza ("proszę się trzymać, musimy cię przypiąć").

W trakcie ostatnich poprawek odkrywają, że ich dokument kalibracyjny (twoje dane wejściowe) został zmieniony przez bardzo młodą Elfkę, która była wyraźnie podekscytowana pokazaniem swoich umiejętności artystycznych. W związku z tym Elfy mają problemy z odczytaniem wartości na dokumencie.

Nowo ulepszony dokument kalibracyjny składa się z linii tekstu; każda linia początkowo zawierała określoną wartość kalibracji, które Elfy teraz muszą odzyskać. W każdej linii wartość kalibracji można znaleźć, łącząc pierwszą cyfrę i ostatnią cyfrę (w tej kolejności), aby utworzyć pojedynczą dwucyfrową liczbę.

Na przykład:

1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

W tym przykładzie wartości kalibracji tych czterech linii to 12, 38, 15 i 77. Ich dodanie daje 142.

Rozważ cały swój dokument kalibracyjny. Jaka jest suma wszystkich wartości kalibracji?

const solution = (data: string) => {
  return (
    data
      // Split the data into lines
      .split("\n")
      // For each line, find first and last digit
      .map((line) => {
        const digits = line.match(/d/g);

        const first = digits?.at(0);
        const last = digits?.at(-1);

        if (first === undefined || last === undefined) {
          return null;
        }

        const parsedFirst = parseInt(first);
        const parsedLast = parseInt(last);

        if (isNaN(parsedFirst) || isNaN(parsedLast)) {
          return null;
        }

        return [parsedFirst, parsedLast] as [number, number];
      })
      // Filter out nulls for invalid lines
      .filter((x) => x !== null)
      // Casting to non-null array because filter does not change the type
      .map((x) => x as NonNullable<typeof x>)
      // Map two digits into one number
      .map(([first, last]) => parseInt(`${first}${last}`))
      // Sum all numbers
      .reduce((acc, x) => acc + x, 0)
  );
};

Zagadka Kostki

Zostajesz wystrzelony wysoko w atmosferę! Apogeum twojej trajektorii ledwo dotyka powierzchni dużej wyspy unoszącej się w powietrzu. Delikatnie lądujesz w puszystym stosie liści. Jest dość zimno, ale nie widzisz zbyt dużo śniegu. Elf biegnie, aby cię przywitać.

Elf wyjaśnia, że przybyłeś na Wyspę Śnieżną i przeprasza za brak śniegu. Chętnie wyjaśni sytuację, ale to trochę spaceru, więc masz trochę czasu. Nie ma tu zbyt wielu odwiedzających; chciałbyś zagrać w grę w międzyczasie?

Podczas spaceru Elf pokazuje małą torbę i kilka kostek, które są czerwone, zielone lub niebieskie. Za każdym razem, gdy grasz w tę grę, ukryje on w torbie tajną liczbę kostek każdego koloru, a Twoim celem jest znalezienie informacji o liczbie kostek.

Aby uzyskać informacje, po załadowaniu torby kostkami Elf sięgnie do torby, weźmie garść losowych kostek, pokaże je graczowi, a następnie włoży je z powrotem do torby. Zrobi to kilka razy w ciągu gry.

Grasz w kilka gier i zapisujesz informacje z każdej z nich (dane wejściowe łamigłówki). Każda gra jest wymieniona z jej numerem ID (jak 11 w Game 11: ...), po którym następuje oddzielona średnikami lista podzbiorów kostek, które zostały ujawnione z worka (jak 3 czerwone, 5 zielonych, 4 niebieskie).

Na przykład, zapis kilku gier może wyglądać następująco:

Gra 1: 3 niebieskie, 4 czerwone; 1 czerwony, 2 zielone, 6 niebieskich; 2 zielone
Gra 2: 1 niebieski, 2 zielone; 3 zielone, 4 niebieskie, 1 czerwony; 1 zielony, 1 niebieski
Gra 3: 8 zielonych, 6 niebieskich, 20 czerwonych; 5 niebieskich, 4 czerwone, 13 zielonych; 5 zielonych, 1 czerwony
Gra 4: 1 zielony, 3 czerwone, 6 niebieskich; 3 zielone, 6 czerwonych; 3 zielone, 15 niebieskich, 14 czerwonych
Gra 5: 6 czerwonych, 1 niebieski, 3 zielone; 2 niebieskie, 1 czerwony, 2 zielone

W pierwszej grze trzy zestawy kostek są odkrywane z woreczka (a następnie odkładane z powrotem). Pierwszy zestaw to 3 niebieskie kostki i 4 czerwone kostki; drugi zestaw to 1 czerwona kostka, 2 zielone kostki i 6 niebieskich kostek; trzeci zestaw to tylko 2 zielone kostki.

Elf chciałby najpierw wiedzieć, które gry byłyby możliwe, gdyby worek zawierał tylko 12 czerwonych kostek, 13 zielonych kostek i 14 niebieskich kostek?

W powyższym przykładzie gry 1, 2 i 5 byłyby możliwe, gdyby worek był załadowany w takiej konfiguracji. Jednak gra 3 byłaby niemożliwa, ponieważ w pewnym momencie Elf pokazał ci 20 czerwonych kostek naraz; podobnie gra 4 byłaby niemożliwa, ponieważ Elf pokazał ci 15 niebieskich kostek naraz. Jeśli zsumujesz identyfikatory gier, które byłyby możliwe, otrzymasz 8.

Określ, które gry byłyby możliwe, gdyby worek zawierał tylko 12 czerwonych kostek, 13 zielonych kostek i 14 niebieskich kostek. Jaka jest suma identyfikatorów tych gier?

const solution = (data: string) => {
  const maxRedCubes = 12;
  const maxGreenCubes = 13;
  const maxBlueCubes = 14;

  return (
    data
      // Split the data into lines
      .split("\n")
      // Validate and parse the input
      .map((line) => {
        const isLineValid =
          /Game\s\d+:((\s\d+\s(blue|red|green)(,|;|$)))+/.test(line);

        if (!isLineValid) {
          return null;
        }

        const gameId = line.match(/Game\s\d+/)!.at(0)!; // Non null assertions because we did check the validation
        const parsedGameId = parseInt(gameId.replace("Game ", ""));

        const rounds = line
          .replace(/Game\s\d+:\s/, "")
          .split(";")
          .map((round) => round.trim());

        const parsedRounds = rounds.map((round) => {
          const redMatch = round.match(/\d+\sred/) ?? ["0 red"];
          const greenMatch = round.match(/\d+\sgreen/) ?? ["0 green"];
          const blueMatch = round.match(/\d+\sblue/) ?? ["0 blue"];

          const red = parseInt(redMatch.at(0)!.replace(" red", "")); // Non null assertions because we did check the validation
          const green = parseInt(greenMatch.at(0)!.replace(" green", "")); // Non null assertions because we did check the validation
          const blue = parseInt(blueMatch.at(0)!.replace(" blue", "")); // Non null assertions because we did check the validation
          return { red, green, blue };
        });

        return { game: parsedGameId, rounds: parsedRounds };
      })
      // Filter out invalid lines
      .filter((line) => line !== null)
      // Casting to the correct type because filter does not change the type
      .map((line) => line as NonNullable<typeof line>)
      // Filter out games with invalid rounds
      .filter((game) => {
        return game.rounds.every((round) => {
          return (
            round.red <= maxRedCubes &&
            round.green <= maxGreenCubes &&
            round.blue <= maxBlueCubes
          );
        });
      })
      // Sum the gameIds numbers
      .reduce((acc, game) => {
        return acc + game.game;
      }, 0)
  );
};

Silnik

Ty i Elf w końcu docieracie do stacji wyciągu gondolowego; mówi, że wyciąg gondolowy zabierze cię do źródła wody, ale to jest najdalej, jak cię może przyprowadzić. Wchodzisz do środka.

Nie trwa długo, zanim znajdujesz gondole, ale wydaje się, że jest problem: się nie poruszają.

"Aaah!"

Odwracasz się, aby zobaczyć lekko tłustego Elfa z kluczem francuskim i wyrazem zdziwienia. "Przepraszam, nie spodziewałem się nikogo! Wyciąg gondolowy nie działa; minie jeszcze trochę czasu, zanim będę mógł go naprawić." Oferujesz pomoc.

Inżynier wyjaśnia, że część silnika wydaje się brakować w silniku, ale nikt nie może zrozumieć, która to jest. Jeśli możesz dodać wszystkie numery części w schemacie silnika, powinno być łatwo ustalić, która część brakuje.

Schemat silnika (twoje dane wejściowe) składa się z wizualnej reprezentacji silnika. Jest wiele liczb i symboli, których nie rozumiesz, ale każda liczba przylegająca do symbolu, nawet na ukos, jest "numerem części" i powinna być wliczona w sumę. (Kropki (.) nie są uważane za symbol.)

Oto przykładowy schemat silnika:

467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

W tym schemacie dwie liczby nie są numerami części, ponieważ nie są przylegające do symbolu: 114 (góra po prawej) i 58 (środek po prawej). Każda inna liczba przylega do symbolu i jest numerem części; ich suma to 4361.

Oczywiście, rzeczywisty schemat silnika jest znacznie większy. Jaka jest suma wszystkich numerów części w schemacie silnika?

const solution = (data: string) => {
  type Coordinates = [number, number];

  // Possible signs that we are searching for as neighbours
  const listOfPossibleSigns = [
    "*",
    "#",
    "$",
    "@",
    "%",
    "&",
    "=",
    "+",
    "-",
    "/",
  ];

  // A matrix of all the values
  const matrix = data.split("\n").map((line) => line.split(""));

  return (
    data
      // Split the data into lines
      .split("\n")
      // Parse the input so it includes the values and its coordinates
      .map((line, row) => {
        return line.split("").reduce((acc, char, column) => {
          if (!/\d/.test(char)) {
            return [...acc, { value: "", coordinates: [] }];
          }

          if (acc.length === 0) {
            return [
              { value: char, coordinates: [[row, column] as Coordinates] },
            ];
          }

          const copy = structuredClone(acc);

          const lastElement = copy.at(-1)!; // Asserting because we know that the array is not empty

          lastElement.value += char;
          lastElement.coordinates.push([row, column] as Coordinates);

          return copy;
        }, [] as { value: string; coordinates: Coordinates[] }[]);
      })
      // Flattening the lines
      .flat(1)
      // Filtering items with empty value
      .filter((item) => item.value !== "")
      // Adding the signs neighbours to each item
      .map((item) => {
        return {
          ...item,
          neighbours: item.coordinates
            .map(([row, column]) => {
              return [
                matrix[row - 1]?.[column - 1],
                matrix[row - 1]?.[column],
                matrix[row - 1]?.[column + 1],
                matrix[row]?.[column - 1],
                matrix[row]?.[column + 1],
                matrix[row + 1]?.[column - 1],
                matrix[row + 1]?.[column],
                matrix[row + 1]?.[column + 1],
              ]
                .filter((value) => value !== undefined)
                .filter((value) => listOfPossibleSigns.includes(value));
            })
            .flat(1),
        };
      })
      // Filtering items that have no neighbours
      .filter((item) => item.neighbours.length > 0)
      // Parsing the items values to numbers
      .map((item) => parseInt(item.value))
      // Summing the values
      .reduce((acc, value) => acc + value, 0)
  );
};