this post was submitted on 01 Dec 2023
21 points (88.9% liked)

Advent Of Code

762 readers
1 users here now

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2023

Solution Threads

M T W T F S S
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 1 year ago
MODERATORS
 

Welcome everyone to the 2023 advent of code! Thank you all for stopping by and participating in it in programming.dev whether youre new to the event or doing it again.

This is an unofficial community for the event as no official spot exists on lemmy but ill be running it as best I can with Sigmatics modding as well. Ill be running a solution megathread every day where you can share solutions with other participants to compare your answers and to see the things other people come up with


Day 1: Trebuchet?!


Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • Code block support is not fully rolled out yet but likely will be in the middle of the event. Try to share solutions as both code blocks and using something such as https://topaz.github.io/paste/ or pastebin (code blocks to future proof it for when 0.19 comes out and since code blocks currently function in some apps and some instances as well if they are running a 0.19 beta)

FAQ


πŸ”’This post will be unlocked when there is a decent amount of submissions on the leaderboard to avoid cheating for top spots

πŸ”“ Edit: Post has been unlocked after 6 minutes

top 50 comments
sorted by: hot top controversial new old
[–] [email protected] 7 points 9 months ago* (last edited 9 months ago)

Rust

Part 1 Part 2

Fun with iterators! For the second part I went to regex for help.

[–] [email protected] 5 points 9 months ago* (last edited 9 months ago) (1 children)

Part 02 in Rust πŸ¦€ :

use std::{
    collections::HashMap,
    env, fs,
    io::{self, BufRead, BufReader},
};

fn main() -> io::Result<()> {
    let args: Vec = env::args().collect();
    let filename = &args[1];
    let file = fs::File::open(filename)?;
    let reader = BufReader::new(file);

    let number_map = HashMap::from([
        ("one", "1"),
        ("two", "2"),
        ("three", "3"),
        ("four", "4"),
        ("five", "5"),
        ("six", "6"),
        ("seven", "7"),
        ("eight", "8"),
        ("nine", "9"),
    ]);

    let mut total = 0;
    for _line in reader.lines() {
        let digits = get_text_numbers(_line.unwrap(), &number_map);
        if !digits.is_empty() {
            let digit_first = digits.first().unwrap();
            let digit_last = digits.last().unwrap();
            let mut cat = String::new();
            cat.push(*digit_first);
            cat.push(*digit_last);
            let cat: i32 = cat.parse().unwrap();
            total += cat;
        }
    }
    println!("{total}");
    Ok(())
}

fn get_text_numbers(text: String, number_map: &HashMap<&str, &str>) -> Vec {
    let mut digits: Vec = Vec::new();
    if text.is_empty() {
        return digits;
    }
    let mut sample = String::new();
    let chars: Vec = text.chars().collect();
    let mut ptr1: usize = 0;
    let mut ptr2: usize;
    while ptr1 < chars.len() {
        sample.clear();
        ptr2 = ptr1 + 1;
        if chars[ptr1].is_digit(10) {
            digits.push(chars[ptr1]);
            sample.clear();
            ptr1 += 1;
            continue;
        }
        sample.push(chars[ptr1]);
        while ptr2 < chars.len() {
            if chars[ptr2].is_digit(10) {
                sample.clear();
                break;
            }
            sample.push(chars[ptr2]);
            if number_map.contains_key(&sample.as_str()) {
                let str_digit: char = number_map.get(&sample.as_str()).unwrap().parse().unwrap();
                digits.push(str_digit);
                sample.clear();
                break;
            }
            ptr2 += 1;
        }
        ptr1 += 1;
    }

    digits
}
[–] [email protected] 2 points 9 months ago

Thanks, used this as input for reading the Day 2 file and looping the lines, just getting started with rust :)

[–] [email protected] 4 points 9 months ago* (last edited 9 months ago)

Java

My take on a modern Java solution (parts 1 & 2).

spoiler

package thtroyer.day1;

import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;


public class Day1 {
    record Match(int index, String name, int value) {
    }

    Map numbers = Map.of(
            "one", 1,
            "two", 2,
            "three", 3,
            "four", 4,
            "five", 5,
            "six", 6,
            "seven", 7,
            "eight", 8,
            "nine", 9);

    /**
     * Takes in all lines, returns summed answer
     */
    public int getCalibrationValue(String... lines) {
        return Arrays.stream(lines)
                .map(this::getCalibrationValue)
                .map(Integer::parseInt)
                .reduce(0, Integer::sum);
    }

    /**
     * Takes a single line and returns the value for that line,
     * which is the first and last number (numerical or text).
     */
    protected String getCalibrationValue(String line) {
        var matches = Stream.concat(
                        findAllNumberStrings(line).stream(),
                        findAllNumerics(line).stream()
                ).sorted(Comparator.comparingInt(Match::index))
                .toList();

        return "" + matches.getFirst().value() + matches.getLast().value();
    }

    /**
     * Find all the strings of written numbers (e.g. "one")
     *
     * @return List of Matches
     */
    private List findAllNumberStrings(String line) {
        return IntStream.range(0, line.length())
                .boxed()
                .map(i -> findAMatchAtIndex(line, i))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .sorted(Comparator.comparingInt(Match::index))
                .toList();
    }


    private Optional findAMatchAtIndex(String line, int index) {
        return numbers.entrySet().stream()
                .filter(n -> line.indexOf(n.getKey(), index) == index)
                .map(n -> new Match(index, n.getKey(), n.getValue()))
                .findAny();
    }

    /**
     * Find all the strings of digits (e.g. "1")
     *
     * @return List of Matches
     */
    private List findAllNumerics(String line) {
        return IntStream.range(0, line.length())
                .boxed()
                .filter(i -> Character.isDigit(line.charAt(i)))
                .map(i -> new Match(i, null, Integer.parseInt(line.substring(i, i + 1))))
                .toList();
    }

    public static void main(String[] args) {
        System.out.println(new Day1().getCalibrationValue(args));
    }
}

[–] [email protected] 4 points 9 months ago (1 children)

I wanted to see if it was possible to do part 1 in a single line of Python:

print(sum([(([int(i) for i in line if i.isdigit()][0]) * 10 + [int(i) for i in line if i.isdigit()][-1]) for line in open("input.txt")]))

[–] [email protected] 6 points 9 months ago

@calvin @Ategon
Anything is possible in one line if your terminal is wide enough.

[–] [email protected] 4 points 9 months ago* (last edited 9 months ago)

I finally got my solutions done. I used rust. I feel like 114 lines (not including empty lines or driver code) for both solutions is pretty decent. If lemmy's code blocks are hard to read, I also put my solutions on github.

use std::{
    cell::OnceCell,
    collections::{HashMap, VecDeque},
    ops::ControlFlow::{Break, Continue},
};

use crate::utils::read_lines;

#[derive(Clone, Copy, PartialEq, Eq)]
enum NumType {
    Digit,
    DigitOrWord,
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum FromDirection {
    Left,
    Right,
}

const WORD_NUM_MAP: OnceCell> = OnceCell::new();

fn init_num_map() -> HashMap<&'static str, u8> {
    HashMap::from([
        ("one", b'1'),
        ("two", b'2'),
        ("three", b'3'),
        ("four", b'4'),
        ("five", b'5'),
        ("six", b'6'),
        ("seven", b'7'),
        ("eight", b'8'),
        ("nine", b'9'),
    ])
}

const MAX_WORD_LEN: usize = 5;

fn get_digit<i>(mut bytes: I, num_type: NumType, from_direction: FromDirection) -> Option
where
    I: Iterator,
{
    let digit = bytes.try_fold(VecDeque::new(), |mut byte_queue, byte| {
        if byte.is_ascii_digit() {
            Break(byte)
        } else if num_type == NumType::DigitOrWord {
            if from_direction == FromDirection::Left {
                byte_queue.push_back(byte);
            } else {
                byte_queue.push_front(byte);
            }

            let word = byte_queue
                .iter()
                .map(|&amp;byte| byte as char)
                .collect::();

            for &amp;key in WORD_NUM_MAP
                .get_or_init(init_num_map)
                .keys()
                .filter(|k| k.len() &lt;= byte_queue.len())
            {
                if word.contains(key) {
                    return Break(*WORD_NUM_MAP.get_or_init(init_num_map).get(key).unwrap());
                }
            }

            if byte_queue.len() == MAX_WORD_LEN {
                if from_direction == FromDirection::Left {
                    byte_queue.pop_front();
                } else {
                    byte_queue.pop_back();
                }
            }

            Continue(byte_queue)
        } else {
            Continue(byte_queue)
        }
    });

    if let Break(byte) = digit {
        Some(byte)
    } else {
        None
    }
}

fn process_digits(x: u8, y: u8) -> u16 {
    ((10 * (x - b'0')) + (y - b'0')).into()
}

fn solution(num_type: NumType) {
    if let Ok(lines) = read_lines("src/day_1/input.txt") {
        let sum = lines.fold(0_u16, |acc, line| {
            let line = line.unwrap_or_else(|_| String::new());
            let bytes = line.bytes();
            let left = get_digit(bytes.clone(), num_type, FromDirection::Left).unwrap_or(b'0');
            let right = get_digit(bytes.rev(), num_type, FromDirection::Right).unwrap_or(left);

            acc + process_digits(left, right)
        });

        println!("{sum}");
    }
}

pub fn solution_1() {
    solution(NumType::Digit);
}

pub fn solution_2() {
    solution(NumType::DigitOrWord);
}
```</i>
[–] [email protected] 4 points 9 months ago* (last edited 9 months ago) (1 children)

My solution in rust. I'm sure there's a lot more clever ways to do it but this is what I came up with.

Code

use std::{io::prelude::*, fs::File, path::Path, io };

fn main() 
{
    run_solution(false); 

    println!("\nPress enter to continue");
    let mut buffer = String::new();
    io::stdin().read_line(&amp;mut buffer).unwrap();

    run_solution(true); 
}

fn run_solution(check_for_spelled: bool)
{
    let data = load_data("data/input");

    println!("\nProcessing Data...");

    let mut sum: u64 = 0;
    for line in data.lines()
    {
        // Doesn't seem like the to_ascii_lower call is needed but... just in case
        let first = get_digit(line.to_ascii_lowercase().as_bytes(), false, check_for_spelled);
        let last = get_digit(line.to_ascii_lowercase().as_bytes(), true, check_for_spelled);


        let num = (first * 10) + last;

        // println!("\nLine: {} -- First: {}, Second: {}, Num: {}", line, first, last, num);
        sum += num as u64;
    }

    println!("\nFinal Sum: {}", sum);
}

fn get_digit(line: &amp;[u8], from_back: bool, check_for_spelled: bool) -> u8
{
    let mut range: Vec = (0..line.len()).collect();
    if from_back
    {
        range.reverse();
    }

    for i in range
    {
        if is_num(line[i])
        {
            return (line[i] - 48) as u8;
        }

        if check_for_spelled
        {
            if let Some(num) = is_spelled_num(line, i)
            {
                return num;
            }
        }
    }

    return 0;
}

fn is_num(c: u8) -> bool
{
    c >= 48 &amp;&amp; c &lt;= 57
}

fn is_spelled_num(line: &amp;[u8], start: usize) -> Option
{
    let words = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];

    for word_idx in 0..words.len()
    {
        let mut i = start;
        let mut found = true;
        for c in words[word_idx].as_bytes()
        {
            if i &lt; line.len() &amp;&amp; *c != line[i]
            {
                found = false;
                break;
            }
            i += 1;
        }

        if found &amp;&amp; i &lt;= line.len()
        {
            return Some(word_idx as u8 + 1);
        }
    }

    return None;
}

fn load_data(file_name: &amp;str) -> String
{
    let mut file = match File::open(Path::new(file_name))
    {
        Ok(file) => file,
        Err(why) => panic!("Could not open file {}: {}", Path::new(file_name).display(), why),
    };

    let mut s = String::new();
    let file_contents = match file.read_to_string(&amp;mut s) 
    {
        Err(why) => panic!("couldn't read {}: {}", Path::new(file_name).display(), why),
        Ok(_) => s,
    };
    
    return file_contents;
}

[–] [email protected] 3 points 9 months ago (1 children)

One small thing that would make it easier to read would be to use (I don't know what the syntax is called) as opposed to the magic numbers for getting the ascii code for a character as a u8, e.g. b'0' instead of 48.

[–] [email protected] 3 points 9 months ago

Oh yeah that's a good point. I have seen that before but I didn't think of it at the time.

[–] [email protected] 4 points 9 months ago* (last edited 9 months ago)

I'm a bit late to the party. I forgot about this.

Anyways, my (lazy) C solutions: https://git.sr.ht/~aidenisik/aoc23/tree/master/item/day1

[–] [email protected] 4 points 9 months ago (1 children)

I think I found a decently short solution for part 2 in python:

DIGITS = {
    'one': '1',
    'two': '2',
    'three': '3',
    'four': '4',
    'five': '5',
    'six': '6',
    'seven': '7',
    'eight': '8',
    'nine': '9',
}


def find_digit(word: str) -> str:
    for digit, value in DIGITS.items():
        if word.startswith(digit):
            return value
        if word.startswith(value):
            return value
    return ''


total = 0
for line in puzzle.split('\n'):
    digits = [
        digit for i in range(len(line))
        if (digit := find_digit(line[i:]))
    ]
    total += int(digits[0] + digits[-1])


print(total)
[–] [email protected] 3 points 9 months ago (1 children)

Looks very elegant! I'm having trouble understanding how this finds digit "words" from the end of the line though, as they should be spelled backwards IIRC? I.e. eno, owt, eerht

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago)

It simply finds all possible digits and then locates the last one through the reverse indexing. It’s not efficient because there’s no shortcircuiting, but there’s no need to search the strings backwards, which is nice. Python’s startswith method is also hiding a lot of that other implementations have done explicitly.

[–] [email protected] 3 points 9 months ago* (last edited 9 months ago)

Uiua solution

I may add solutions in Uiua depending on how easy I find them, so here's today's (also available to run online):

Inp ← {"four82nine74" "hlpqrdh3" "7qt" "12" "1one"}
# if needle is longer than haystack, return zeros
SafeFind ← ((βŒ•|-.;)&lt; ∩⧻ , ,)
FindDigits ← (Γ— +1⇑9 ⊠(β–‘SafeFindβˆ©βŠ”) : Inp)
"123456789"
βŠœβ–‘ β‰ @\s . "one two three four five six seven eight nine"
∩FindDigits
BuildNum ← (/+∡(/+βŠ‚βŠƒ(Γ—10↙ 1)(↙ 1β‡Œ) β–½β‰ 0.βŠ”) /β†₯)
∩BuildNum+,

or stripping away all the fluff:

Inp ← {"four82nine74" "hlpqrdh3" "7qt" "12" "1one"}
βŠœβ–‘ β‰ @\s."one two three four five six seven eight nine" "123456789"
∩(Γ—+1⇑9⊠(β–‘(βŒ•|-.;)&lt;βŠ™:∩(⧻.βŠ”)):Inp)
∩(/+∡(/+βŠ‚βŠƒ(Γ—10↙1)(↙1β‡Œ)β–½β‰ 0.βŠ”)/β†₯)+,
[–] [email protected] 3 points 9 months ago* (last edited 9 months ago)
import re
numbers = {
    "one" : 1,
    "two" : 2,
    "three" : 3,
    "four" : 4,
    "five" : 5,
    "six" : 6,
    "seven" : 7,
    "eight" : 8,
    "nine" : 9
    }
for digit in range(10):
    numbers[str(digit)] = digit
pattern = "(%s)" % "|".join(numbers.keys())
   
re1 = re.compile(".*?" + pattern)
re2 = re.compile(".*" + pattern)
total = 0
for line in open("input.txt"):
    m1 = re1.match(line)
    m2 = re2.match(line)
    num = (numbers[m1.group(1)] * 10) + numbers[m2.group(1)]
    total += num
print(total)

There weren't any zeros in the training data I got - the text seems to suggest that "0" is allowed but "zero" isn't.

[–] [email protected] 3 points 9 months ago

My solutin in Elixir for both part 1 and part 2 is below. It does use regex and with that there are many different ways to accomplish the goal. I'm no regex master so I made it as simple as possible and relied on the language a bit more. I'm sure there are cooler solutions with no regex too, this is just what I settled on:

https://pastebin.com/u1SYJ4tY

defmodule AdventOfCode.Day01 do
  def part1(args) do
    number_regex = ~r/([0-9])/

    args
    |> String.split(~r/\n/, trim: true)
    |> Enum.map(&amp;first_and_last_number(&amp;1, number_regex))
    |> Enum.map(&amp;number_list_to_integer/1)
    |> Enum.sum()
  end

  def part2(args) do
    number_regex = ~r/(?=(one|two|three|four|five|six|seven|eight|nine|[0-9]))/

    args
    |> String.split(~r/\n/, trim: true)
    |> Enum.map(&amp;first_and_last_number(&amp;1, number_regex))
    |> Enum.map(fn number -> Enum.map(number, &amp;replace_word_with_number/1) end)
    |> Enum.map(&amp;number_list_to_integer/1)
    |> Enum.sum()
  end

  defp first_and_last_number(string, regex) do
    matches = Regex.scan(regex, string)
    [_, first] = List.first(matches)
    [_, last] = List.last(matches)

    [first, last]
  end

  defp number_list_to_integer(list) do
    list
    |> List.to_string()
    |> String.to_integer()
  end

  defp replace_word_with_number(string) do
    numbers = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]

    String.replace(string, numbers, fn x ->
      (Enum.find_index(numbers, &amp;(&amp;1 == x)) + 1)
      |> Integer.to_string()
    end)
  end
end

[–] [email protected] 3 points 9 months ago

Part 1 felt fairly pretty simple in Haskell:

import Data.Char (isDigit)

main = interact solve

solve :: String -> String
solve = show . sum . map (read . (\x -> [head x, last x]) . filter isDigit) . lines

Part 2 was more of a struggle, though I'm pretty happy with how it turned out. I ended up using concatMap inits . tails to generate all substrings, in order of appearance so one3m becomes ["","o","on","one","one3","one3m","","n","ne","ne3","ne3m","","e","e3","e3m","","3","3m","","m",""]. I then wrote a function stringToDigit :: String -> Maybe Char which simultaneously filtered out the digits and standardised them as Chars.

import Data.List (inits, tails)
import Data.Char (isDigit, digitToInt)
import Data.Maybe (mapMaybe)

main = interact solve

solve :: String -> String
solve = show . sum . map (read . (\x -> [head x, last x]) . mapMaybe stringToDigit . concatMap inits . tails) . lines
--                             |string of first&amp;last digit| |find all the digits |   |all substrings of line|

stringToDigit "one"   = Just '1'
stringToDigit "two"   = Just '2'
stringToDigit "three" = Just '3'
stringToDigit "four"  = Just '4'
stringToDigit "five"  = Just '5'
stringToDigit "six"   = Just '6'
stringToDigit "seven" = Just '7'
stringToDigit "eight" = Just '8'
stringToDigit "nine"  = Just '9'
stringToDigit [x]
  | isDigit x         = Just x
  | otherwise         = Nothing
stringToDigit _       = Nothing

I went a bit excessively Haskell with it, but I had my fun!

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago) (2 children)

Solved part one in about thirty seconds. But wow, either my brain is just tired at this hour or I'm lacking in skill, but part two is harder than any other year has been on the first day. Anyway, I managed to solve it, but I absolutely hate it, and will definitely be coming back to try to clean this one up.

https://github.com/capitalpb/advent_of_code_2023/blob/main/src/solvers/day01.rs

impl Solver for Day01 {
    fn star_one(&amp;self, input: &amp;str) -> String {
        let mut result = 0;

        for line in input.lines() {
            let line = line
                .chars()
                .filter(|ch| ch.is_ascii_digit())
                .collect::>();
            let first = line.first().unwrap();
            let last = line.last().unwrap();
            let number = format!("{first}{last}").parse::().unwrap();
            result += number;
        }

        result.to_string()
    }

    fn star_two(&amp;self, input: &amp;str) -> String {
        let mut result = 0;

        for line in input.lines() {
            let mut first = None;
            let mut last = None;

            while first == None {
                for index in 0..line.len() {
                    let line_slice = &amp;line[index..];
                    if line_slice.starts_with("one") || line_slice.starts_with("1") {
                        first = Some(1);
                    } else if line_slice.starts_with("two") || line_slice.starts_with("2") {
                        first = Some(2);
                    } else if line_slice.starts_with("three") || line_slice.starts_with("3") {
                        first = Some(3);
                    } else if line_slice.starts_with("four") || line_slice.starts_with("4") {
                        first = Some(4);
                    } else if line_slice.starts_with("five") || line_slice.starts_with("5") {
                        first = Some(5);
                    } else if line_slice.starts_with("six") || line_slice.starts_with("6") {
                        first = Some(6);
                    } else if line_slice.starts_with("seven") || line_slice.starts_with("7") {
                        first = Some(7);
                    } else if line_slice.starts_with("eight") || line_slice.starts_with("8") {
                        first = Some(8);
                    } else if line_slice.starts_with("nine") || line_slice.starts_with("9") {
                        first = Some(9);
                    }

                    if first.is_some() {
                        break;
                    }
                }
            }

            while last == None {
                for index in (0..line.len()).rev() {
                    let line_slice = &amp;line[index..];
                    if line_slice.starts_with("one") || line_slice.starts_with("1") {
                        last = Some(1);
                    } else if line_slice.starts_with("two") || line_slice.starts_with("2") {
                        last = Some(2);
                    } else if line_slice.starts_with("three") || line_slice.starts_with("3") {
                        last = Some(3);
                    } else if line_slice.starts_with("four") || line_slice.starts_with("4") {
                        last = Some(4);
                    } else if line_slice.starts_with("five") || line_slice.starts_with("5") {
                        last = Some(5);
                    } else if line_slice.starts_with("six") || line_slice.starts_with("6") {
                        last = Some(6);
                    } else if line_slice.starts_with("seven") || line_slice.starts_with("7") {
                        last = Some(7);
                    } else if line_slice.starts_with("eight") || line_slice.starts_with("8") {
                        last = Some(8);
                    } else if line_slice.starts_with("nine") || line_slice.starts_with("9") {
                        last = Some(9);
                    }

                    if last.is_some() {
                        break;
                    }
                }
            }

            result += format!("{}{}", first.unwrap(), last.unwrap())
                .parse::()
                .unwrap();
        }

        result.to_string()
    }
}
load more comments (2 replies)
[–] [email protected] 2 points 9 months ago

Python 3

I had some trouble getting Part 2 to work, until I realized that there could be overlap ( blbleightwoqsqs -> 82).

spoiler

import re

def puzzle_one():
    result_sum = 0
    with open("inputs/day_01", "r", encoding="utf_8") as input_file:
        for line in input_file:
            number_list = [char for char in line if char.isnumeric()]
            number = int(number_list[0] + number_list[-1])
            result_sum += number
    return result_sum

def puzzle_two():
    regex = r"(?=(zero|one|two|three|four|five|six|seven|eight|nine|[0-9]))"
    number_dict = {
        "zero": "0",
        "one": "1",
        "two": "2",
        "three": "3",
        "four": "4",
        "five": "5",
        "six": "6",
        "seven": "7",
        "eight": "8",
        "nine": "9",
    }
    result_sum = 0
    with open("inputs/day_01", "r", encoding="utf_8") as input_file:
        for line in input_file:
            number_list = [
                number_dict[num] if num in number_dict else num
                for num in re.findall(regex, line)
            ]
            number = int(number_list[0] + number_list[-1])
            result_sum += number
    return result_sum

I still have a hard time understanding regex, but I think it's getting there.

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago) (2 children)

I did this in C. First part was fairly trivial, iterate over the line, find first and last number, easy.

Second part had me a bit worried i would need a more string friendly library/language, until i worked out that i can just strstr to find "one", and then in place switch that to "o1e", and so on. Then run part1 code over the modified buffer. I originally did "1ne", but overlaps such as "eightwo" meant that i got the 2, but missed the 8.

#include 
#include 
#include 
#include 
#include 

size_t readfile(char* fname, char* buffer, size_t buffer_len)
{

    int f = open(fname, 'r');
    assert(f >= 0);
    size_t total = 0;
    do {
        size_t nr = read(f, buffer + total, buffer_len - total);
        if (nr == 0) {
            return total;
        }
        total += nr;
    }
    while (buffer_len - total > 0);
    return -1;
}

int part1(const char* buffer, size_t buffer_len)
{
    int first = -1;
    int last = -1;
    int total = 0;
    for (int i = 0; i &lt; buffer_len; i++)
    {
        char c = buffer[i];
        if (c == '\n')
        {
            if (first == -1) {
                continue;
            }
            total += (first*10 + last);
            first = last = -1;
            continue;
        }
        int val = c - '0';
        if (val > 9 || val &lt; 0)
        {
            continue;
        }
        if (first == -1)
        {
            first = last = val;
        }
        else
        {
            last = val;
        }
    }
    return total;
}

void part2_sanitize(char* buffer, size_t len)
{
    char* p = NULL;
    while ((p = strnstr(buffer, "one", len)) != NULL)
    {
        p[1] = '1';
    }
    while ((p = strnstr(buffer, "two", len)) != NULL)
    {
        p[1] = '2';
    }
    while ((p = strnstr(buffer, "three", len)) != NULL)
    {
        p[1] = '3';
    }
    while ((p = strnstr(buffer, "four", len)) != NULL)
    {
        p[1] = '4';
    }
    while ((p = strnstr(buffer, "five", len)) != NULL)
    {
        p[1] = '5';
    }
    while ((p = strnstr(buffer, "six", len)) != NULL)
    {
        p[1] = '6';
    }
    while ((p = strnstr(buffer, "seven", len)) != NULL)
    {
        p[1] = '7';
    }
    while ((p = strnstr(buffer, "eight", len)) != NULL)
    {
        p[1] = '8';
    }
    while ((p = strnstr(buffer, "nine", len)) != NULL)
    {
        p[1] = '9';
    }
    while ((p = strnstr(buffer, "zero", len)) != NULL)
    {
        p[1] = '0';
    }
}

int main(int argc, char** argv)
{
    assert(argc == 2);
    char buffer[1000000];
    size_t len = readfile(argv[1], buffer, sizeof(buffer));
    {
        int total = part1(buffer, len);
        printf("Part 1 total: %i\n", total);
    }

    {
        part2_sanitize(buffer, len);
        int total = part1(buffer, len);
        printf("Part 2 total: %i\n", total);
    }
}
[–] [email protected] 2 points 9 months ago

Just realised how inefficient the sanitize function is, it iterates over the buffer way too many times. Should be restarting the strnstr from the location of the last hit instead of from the start.

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago) (1 children)

Ruby

https://github.com/snowe2010/advent-of-code/blob/master/ruby_aoc/2023/day01/day01.rb

Part 1

execute(1, test_file_suffix: "p1") do |lines|
  lines.inject(0) do |acc, line|
    d = line.gsub(/\D/,'')
    acc += (d[0] + d[-1]).to_i
  end
end

Part 2

map = {
  "one": 1,
  "two": 2,
  "three": 3,
  "four": 4,
  "five": 5,
  "six": 6,
  "seven": 7,
  "eight": 8,
  "nine": 9,
}

execute(2) do |lines|
  lines.inject(0) do |acc, line|
    first_num = line.sub(/(one|two|three|four|five|six|seven|eight|nine)/) do |key|
      map[key.to_sym]
    end
    last_num = line.reverse.sub(/(enin|thgie|neves|xis|evif|ruof|eerht|owt|eno)/) do |key|
      map[key.reverse.to_sym]
    end

    d = first_num.chars.select { |num| numeric?(num) }
    e = last_num.chars.select { |num| numeric?(num) }
    acc += (d[0] + e[0]).to_i
  end
end

Then of course I also code golfed it, but didn't try very hard.

P1 Code Golf

execute(1, alternative_text: "Code Golf 60 bytes", test_file_suffix: "p1") do |lines|
  lines.inject(0){|a,l|d=l.gsub(/\D/,'');a+=(d[0]+d[-1]).to_i}
end

P2 Code Golf (ignore the formatting, I just didn't want to reformat to remove all the spaces, and it's easier to read this way.)

execute(1, alternative_text: "Code Golf 271 bytes", test_file_suffix: "p1") do |z|
  z.inject(0) { |a, l|
    w = %w(one two three four five six seven eight nine)
    x = w.join(?|)
    f = l.sub(/(#{x})/) { |k| map[k.to_sym] }
    g = l.reverse.sub(/(#{x.reverse})/) { |k| map[k.reverse.to_sym] }
    d = f.chars.select { |n| n.match?(/\d/) }
    e = g.chars.select { |n| n.match?(/\d/) }
    a += (d[0] + e[0]).to_i
  }
end
[–] [email protected] 1 points 9 months ago (1 children)

Thank you for sharing this. I also wrote a regular expression with \d|eno|owt and so on, and I was not so proud of myself :). Good to know I wasn't the only one :).

[–] [email protected] 1 points 9 months ago

I was trying so hard to avoid doing this and landed on a pretty nice solution (for me). It's funny sering everyone's approach especially when you have no problem running through that barrier that I didn't want to πŸ˜†

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago)

Dart solution

This has got to be one of the biggest jumps in trickiness in a Day 1 puzzle. In the end I rolled my part 1 answer into the part 2 logic. [Edit: I've golfed it a bit since first posting it]

import 'package:collection/collection.dart';

var ds = '0123456789'.split('');
var wds = 'one two three four five six seven eight nine'.split(' ');

int s2d(String s) => s.length == 1 ? int.parse(s) : wds.indexOf(s) + 1;

int value(String s, List digits) {
  var firsts = {for (var e in digits) s.indexOf(e): e}..remove(-1);
  var lasts = {for (var e in digits) s.lastIndexOf(e): e}..remove(-1);
  return s2d(firsts[firsts.keys.min]) * 10 + s2d(lasts[lasts.keys.max]);
}

part1(List lines) => lines.map((e) => value(e, ds)).sum;

part2(List lines) => lines.map((e) => value(e, ds + wds)).sum;
[–] [email protected] 2 points 9 months ago (1 children)

This is my solution in Nim:

import strutils, strformat

const digitStrings = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]

### Type definiton for a proc to extract a calibration function from a line
type CalibrationExtracter = proc (line:string): int

## extract a calibration value by finding the first and last numerical digit, and concatenating them
proc extractCalibration1(line:string): int =
  var first,last = -1
    
  for i, c in line:
    if c.isDigit:
      last = parseInt($c)
      if first == -1:
        first = last
        
  result = first * 10 + last

## extract a calibration value by finding the first and last numerical digit OR english lowercase word for a digit, and concatenating them
proc extractCalibration2(line:string): int =
  var first,last = -1
  
  for i, c in line:
    if c.isDigit:
      last = parseInt($c)
      if first == -1:
        first = last
    else: #not a digit parse number words
      for dsi, ds in digitStrings:
        if i == line.find(ds, i):
          last = dsi+1
          if first == -1:
            first = last
          break #break out of digit strings
        
  result = first * 10 + last

### general purpose extraction proc, accepts an extraction function for specific line handling
proc extract(fileName:string, extracter:CalibrationExtracter, verbose:bool): int =
  
  let lines = readFile(fileName).strip().splitLines();
  
  for lineIndex, line in lines:
    if line.len == 0:
      continue
    
    let value = extracter(line)
    result += value
    
    if verbose:
      echo &amp;"Extracted {value} from line {lineIndex} {line}"

### public interface for puzzle part 1
proc part1*(input:string, verbose:bool = false): int =
  result = input.extract(extractCalibration1, verbose);

### public interface for puzzle part 2
proc part2*(input:string, verbose:bool = false): int =
  result = input.extract(extractCalibration2, verbose);

[–] [email protected] 2 points 9 months ago (2 children)

Oh hey, a fellow nim person. Have you joined the community?

Here's mine. Kbin doesn't even support code blocks, so using topaz:

[–] [email protected] 1 points 9 months ago (1 children)

Have you joined the community?

I have now, thanks for the tip!

And that's some very compact code! I've only just started with nim, so seeing more nim solutions is a great way to learn 😁

[–] [email protected] 2 points 9 months ago (1 children)

I'm not doing anything too fancy here, just the first stuff that comes to mind and gets the job done. The filterIt template was pretty handy for part 1, though. I assume at some point in these puzzles I'll have to actually write some types and procedures instead of just using nested loops for everything.

I think it's a pretty cool language overall. I've only used it for one project so far, so there's a bunch that I still don't know. Haven't been able to wrap my head around how macros work, for example, though I've sort of figured out how to write really basic templates.

load more comments (1 replies)
load more comments (1 replies)
[–] [email protected] 2 points 9 months ago

Crystal. Second one was a pain.

part 1

input = File.read("./input.txt").lines

sum = 0
input.each do |line|
	digits = line.chars.select(&amp;.number?)
	next if digits.empty?
	num = "#{digits[0]}#{digits[-1]}".to_i
	sum += num
end
puts sum

part 2

numbers = {
	"one"=> "1",
	"two"=> "2",
	"three"=> "3",
	"four"=> "4",
	"five"=> "5",
	"six"=> "6",
	"seven"=> "7",
	"eight"=> "8",
	"nine"=> "9",
}
input.each do |line|
	start = ""
	last = ""
	line.size.times do |i|
		if line[i].number?
			start = line[i]
			break
		end
		if i &lt; line.size - 2 &amp;&amp; line[i..(i+2)] =~ /one|two|six/
			start = numbers[line[i..(i+2)]]
			break
		end

		if i &lt; line.size - 3 &amp;&amp; line[i..(i+3)] =~ /four|five|nine/
			start = numbers[line[i..(i+3)]]
			break
		end 
		
		if i &lt; line.size - 4 &amp;&amp; line[i..(i+4)] =~ /three|seven|eight/
			start = numbers[line[i..(i+4)]]
			break
		end 
	end
	
	(1..line.size).each do |i|
		if line[-i].number?
			last = line[-i]
			break
		end
		if i > 2 &amp;&amp; line[(-i)..(-i+2)] =~ /one|two|six/
			last = numbers[line[(-i)..(-i+2)]]
			break
		end

		if i > 3 &amp;&amp; line[(-i)..(-i+3)] =~ /four|five|nine/
			last = numbers[line[(-i)..(-i+3)]]
			break
		end 
		
		if i > 4 &amp;&amp; line[(-i)..(-i+4)] =~ /three|seven|eight/
			last = numbers[line[(-i)..(-i+4)]]
			break
		end 
	end
	sum += "#{start}#{last}".to_i
end
puts sum

Damn, lemmy's tabs are massive

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago) (1 children)

Just getting my feet wet with coding after a decade of 0 programming. CS just didn't work out for me in school, so I swapped over to math. Trying to use Python on my desktop, with Notepad++ and Windows Shell.

Part 1

with open('01A_input.txt', 'r') as file:
    data = file.readlines()
    
print(data)
NumericList=[]

for row in data:
    word=row
    while not(word[0].isnumeric()):
        word=word[1:]
    while not(word[-1].isnumeric()):
        word=word[:-1]
    #print(word)
    tempWord=word[0]+word[-1]
    NumericList.append(int(tempWord))
    #print(NumericList)
Total=sum(NumericList)
print(Total)

Part 2

with open('01A_input.txt', 'r') as file:
    data = file.readlines()
    
#print(data)
NumericList=[]
NumberWords=("one", "two", "three", "four", "five", "six", "seven", "eight", "nine")

def wordreplaceleft(wrd):
    if wrd.startswith("one"):
        return "1" + wrd[3:]
    elif wrd.startswith("two"):
        return "2" + wrd[3:]
    elif wrd.startswith("three"):
        return "3" + wrd[5:]
    elif wrd.startswith("four"):
        return "4" + wrd[4:]
    elif wrd.startswith("five"):
        return "5" + wrd[4:]
    elif wrd.startswith("six"):
        return "6" + wrd[3:]
    elif wrd.startswith("seven"):
        return "7" + wrd[5:]
    elif wrd.startswith("eight"):
        return "8" + wrd[5:]
    elif wrd.startswith("nine"):
        return "9" + wrd[4:]

def wordreplaceright(wrd):
    if wrd.endswith("one"):
        return wrd[:-3]+"1"
    elif wrd.endswith("two"):
        return wrd[:-3]+"2"
    elif wrd.endswith("three"):
        return wrd[:-5]+"3"
    elif wrd.endswith("four"):
        return wrd[:-4]+"4"
    elif wrd.endswith("five"):
        return wrd[:-4]+"5"
    elif wrd.endswith("six"):
        return wrd[:-3]+"6"
    elif wrd.endswith("seven"):
        return wrd[:-5]+"7"
    elif wrd.endswith("eight"):
        return wrd[:-5]+"8"
    elif wrd.endswith("nine"):
        return wrd[:-4]+"9"

for row in data:
    wordleft=row
    wordright=row
    
    if wordleft.startswith(NumberWords):
        wordleft=wordreplaceleft(wordleft)
    while not(wordleft[0].isnumeric()):
        if wordleft.startswith(NumberWords):
            wordleft=wordreplaceleft(wordleft)
        else:
            wordleft=wordleft[1:]
            
    if wordright.endswith(NumberWords):
        wordright=wordreplaceright(wordright)
    while not(wordright[-1].isnumeric()):
        if wordright.endswith(NumberWords):
            wordright=wordreplaceright(wordright)
        else:
            wordright=wordright[:-1]
    
    # while not(word[-1].isnumeric()):
        # word=word[:-1]
    # print(word)
    tempWord=wordleft[0]+wordright[-1]
    NumericList.append(int(tempWord))
    #print(NumericList)
Total=sum(NumericList)
print(Total)

[–] [email protected] 1 points 9 months ago

I know my entire thing is kinda super hobbled together. Any recommendations on how I can make this all easier on myself?

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago) (1 children)

A new C solution: without lookahead or backtracking! I keep a running tally of how many letters of each digit word were matched so far: https://github.com/sjmulder/aoc/blob/master/2023/c/day01.c

int main(int argc, char **argv)
{
	static const char names[][8] = {"zero", "one", "two", "three",
	    "four", "five", "six", "seven", "eight", "nine"};
	int p1=0, p2=0, i,c;
	int p1_first = -1, p1_last = -1;
	int p2_first = -1, p2_last = -1;
	int nmatched[10] = {0};
	
	while ((c = getchar()) != EOF)
		if (c == '\n') {
			p1 += p1_first*10 + p1_last;
			p2 += p2_first*10 + p2_last;
			p1_first = p1_last = p2_first = p2_last = -1;
			memset(nmatched, 0, sizeof(nmatched));
		} else if (c >= '0' &amp;&amp; c &lt;= '9') {
			if (p1_first == -1) p1_first = c-'0';
			if (p2_first == -1) p2_first = c-'0';
			p1_last = p2_last = c-'0';
			memset(nmatched, 0, sizeof(nmatched));
		} else for (i=0; i&lt;10; i++)
			/* advance or reset no. matched digit chars */
			if (c != names[i][nmatched[i]++])
				nmatched[i] = c == names[i][0];
			/* matched to end? */
			else if (!names[i][nmatched[i]]) {
				if (p2_first == -1) p2_first = i;
				p2_last = i;
				nmatched[i] = 0;
			}

	printf("%d %d\n", p1, p2);
	return 0;
}
[–] [email protected] 1 points 9 months ago

And golfed down:

char*N[]={0,"one","two","three","four","five","six","seven","eight","nine"};p,P,
i,c,a,b;A,B;m[10];main(){while((c=getchar())>0){c==10?p+=a*10+b,P+=A*10+B,a=b=A=
B=0:0;c>47&amp;&amp;c&lt;58?b=B=c-48,a||(a=b),A||(A=b):0;for(i=10;--i;)c!=N[i][m[i]++]?m[i]
=c==*N[i]:!N[i][m[i]]?A||(A=i),B=i:0;}printf("%d %d\n",p,P);
[–] [email protected] 2 points 9 months ago

Trickier than expected! I ran into an issue with Lua patterns, so I had to revert to a more verbose solution, which I then used in Hare as well.

Lua:

lua

-- SPDX-FileCopyrightText: 2023 Jummit
--
-- SPDX-License-Identifier: GPL-3.0-or-later

local sum = 0
for line in io.open("1.input"):lines() do
  local a, b = line:match("^.-(%d).*(%d).-$")
  if not a then
    a = line:match("%d+")
    b = a
  end
  if a and b then
    sum = sum + tonumber(a..b)
  end
end
print(sum)

local names = {
  ["one"] = 1,
  ["two"] = 2,
  ["three"] = 3,
  ["four"] = 4,
  ["five"] = 5,
  ["six"] = 6,
  ["seven"] = 7,
  ["eight"] = 8,
  ["nine"] = 9,
  ["1"] = 1,
  ["2"] = 2,
  ["3"] = 3,
  ["4"] = 4,
  ["5"] = 5,
  ["6"] = 6,
  ["7"] = 7,
  ["8"] = 8,
  ["9"] = 9,
}
sum = 0
for line in io.open("1.input"):lines() do
  local firstPos = math.huge
  local first
  for name, num in pairs(names) do
    local left = line:find(name)
    if left and left &lt; firstPos then
      firstPos = left
      first = num
    end
  end
  local last
  for i = #line, 1, -1 do
    for name, num in pairs(names) do
      local right = line:find(name, i)
      if right then
        last = num
        goto found
      end
    end
  end
  ::found::
  sum = sum + tonumber(first * 10 + last)
end
print(sum)

Hare:

hare

// SPDX-FileCopyrightText: 2023 Jummit
//
// SPDX-License-Identifier: GPL-3.0-or-later

use fmt;
use types;
use bufio;
use strings;
use io;
use os;

const numbers: [](str, int) = [
	("one", 1),
	("two", 2),
	("three", 3),
	("four", 4),
	("five", 5),
	("six", 6),
	("seven", 7),
	("eight", 8),
	("nine", 9),
	("1", 1),
	("2", 2),
	("3", 3),
	("4", 4),
	("5", 5),
	("6", 6),
	("7", 7),
	("8", 8),
	("9", 9),
];

fn solve(start: size) void = {
	const file = os::open("1.input")!;
	defer io::close(file)!;
	const scan = bufio::newscanner(file, types::SIZE_MAX);
	let sum = 0;
	for (let i = 1u; true; i += 1) {
		const line = match (bufio::scan_line(&amp;scan)!) {
		case io::EOF =>
			break;
		case let line: const str =>
			yield line;
		};
		let first: (void | int) = void;
		let last: (void | int) = void;
		for (let i = 0z; i &lt; len(line); i += 1) :found {
			for (let num = start; num &lt; len(numbers); num += 1) {
				const start = strings::sub(line, i, strings::end);
				if (first is void &amp;&amp; strings::hasprefix(start, numbers[num].0)) {
					first = numbers[num].1;
				};
				const end = strings::sub(line, len(line) - 1 - i, strings::end);
				if (last is void &amp;&amp; strings::hasprefix(end, numbers[num].0)) {
					last = numbers[num].1;
				};
				if (first is int &amp;&amp; last is int) {
					break :found;
				};
			};
		};
		sum += first as int * 10 + last as int;
	};
	fmt::printfln("{}", sum)!;
};

export fn main() void = {
	solve(9);
	solve(0);
};

[–] [email protected] 2 points 9 months ago* (last edited 9 months ago)

[Language: C#]

This isn't the most performant or elegant, it's the first one that worked. I have 3 kids and a full time job. If I get through any of these, it'll be first pass through and first try that gets the correct answer.

Part 1 was very easy, just iterated the string checking if the char was a digit. Ditto for the last, by reversing the string. Part 2 was also not super hard, I settled on re-using the iterative approach, checking each string lookup value first (on a substring of the current char), and if the current char isn't the start of a word, then checking if the char was a digit. Getting the last number required reversing the string and the lookup map.

Part 1:

var list = new List((await File.ReadAllLinesAsync(@".\Day 1\PuzzleInput.txt")));

int total = 0;
foreach (var item in list)
{
    //forward
    string digit1 = string.Empty;
    string digit2 = string.Empty;


    foreach (var c in item)
    {
        if ((int)c >= 48 &amp;&amp; (int)c &lt;= 57)
        {
            digit1 += c;
        
            break;
        }
    }
    //reverse
    foreach (var c in item.Reverse())
    {
        if ((int)c >= 48 &amp;&amp; (int)c &lt;= 57)
        {
            digit2 += c;

            break;
        }

    }
    total += Int32.Parse(digit1 +digit2);
}

Console.WriteLine(total);

Part 2:

var list = new List((await File.ReadAllLinesAsync(@".\Day 1\PuzzleInput.txt")));
var numbers = new Dictionary() {
    {"one" ,   1}
    ,{"two" ,  2}
    ,{"three" , 3}
    ,{"four" , 4}
    ,{"five" , 5}
    ,{"six" , 6}
    ,{"seven" , 7}
    ,{"eight" , 8}
    , {"nine" , 9 }
};
int total = 0;
string digit1 = string.Empty;
string digit2 = string.Empty;
foreach (var item in list)
{
    //forward
    digit1 = getDigit(item, numbers);
    digit2 = getDigit(new string(item.Reverse().ToArray()), numbers.ToDictionary(k => new string(k.Key.Reverse().ToArray()), k => k.Value));
    total += Int32.Parse(digit1 + digit2);
}

Console.WriteLine(total);

string getDigit(string item,                 Dictionary numbers)
{
    int index = 0;
    int digit = 0;
    foreach (var c in item)
    {
        var sub = item.AsSpan(index++);
        foreach(var n in numbers)
        {
            if (sub.StartsWith(n.Key))
            {
                digit = n.Value;
                goto end;
            }
        }

        if ((int)c >= 48 &amp;&amp; (int)c &lt;= 57)
        {
            digit = ((int)c) - 48;
            break;
        }
    }
    end:
    return digit.ToString();
}
[–] [email protected] 2 points 9 months ago

Python

Questions and feedback welcome!

import re

from .solver import Solver

class Day01(Solver):
  def __init__(self):
    super().__init__(1)
    self.lines = []

  def presolve(self, input: str):
    self.lines = input.rstrip().split('\n')

  def solve_first_star(self):
    numbers = []
    for line in self.lines:
      digits = [ch for ch in line if ch.isdigit()]
      numbers.append(int(digits[0] + digits[-1]))
    return sum(numbers)

  def solve_second_star(self):
    numbers = []
    digit_map = {
      "one": 1, "two": 2, "three": 3, "four": 4, "five": 5,
      "six": 6, "seven": 7, "eight": 8, "nine": 9, "zero": 0,
      }
    for i in range(10):
      digit_map[str(i)] = i
    for line in self.lines:
      digits = [digit_map[digit] for digit in re.findall(
          "(?=(one|two|three|four|five|six|seven|eight|nine|[0-9]))", line)]
      numbers.append(digits[0]*10 + digits[-1])
    return sum(numbers)
[–] [email protected] 2 points 9 months ago* (last edited 9 months ago)

I feel ok about part 1, and just terrible about part 2.

day01.factor on github (with comments and imports):

: part1 ( -- )
  "vocab:aoc-2023/day01/input.txt" utf8 file-lines
  [
    [ [ digit? ] find nip ]
    [ [ digit? ] find-last nip ] bi
    2array string>number
  ] map-sum .
;

MEMO: digit-words ( -- name-char-assoc )
  [ "123456789" [ dup char>name "-" split1 nip ,, ] each ] H{ } make
;

: first-digit-char ( str -- num-char/f i/f )
  [ digit? ] find swap
;

: last-digit-char ( str -- num-char/f i/f )
  [ digit? ] find-last swap
;

: first-digit-word ( str -- num-char/f )
  [
    digit-words keys [
      2dup subseq-index
      dup [
        [ digit-words at ] dip
        ,,
      ] [ 2drop ] if
    ] each drop                           !
  ] H{ } make
  [ f ] [
    sort-keys first last
  ] if-assoc-empty
;

: last-digit-word ( str -- num-char/f )
  reverse
  [
    digit-words keys [
      reverse
      2dup subseq-index
      dup [
        [ reverse digit-words at ] dip
        ,,
      ] [ 2drop ] if
    ] each drop                           !
  ] H{ } make
  [ f ] [
    sort-keys first last
  ] if-assoc-empty
;

: first-digit ( str -- num-char )
  dup first-digit-char dup [
    pick 2dup swap head nip
    first-digit-word dup [
      [ 2drop ] dip
    ] [ 2drop ] if
    nip
  ] [
    2drop first-digit-word
  ] if
;

: last-digit ( str -- num-char )
  dup last-digit-char dup [
    pick 2dup swap 1 + tail nip
    last-digit-word dup [
      [ 2drop ] dip
    ] [ 2drop ] if
    nip
  ] [
    2drop last-digit-word
  ] if
;

: part2 ( -- )
  "vocab:aoc-2023/day01/input.txt" utf8 file-lines
  [ [ first-digit ] [ last-digit ] bi 2array string>number ] map-sum .
;
[–] [email protected] 1 points 9 months ago

I decided to learn zig this year. Usually I solve both parts in the same source file, but it was annoying so here part 1 and part 2

[–] [email protected] 1 points 9 months ago

Did this in Odin (very hashed together, especially finding the last number in part 2):

spoiler

package day1

import "core:fmt"
import "core:strings"
import "core:strconv"
import "core:unicode"

p1 :: proc(input: []string) {
    total := 0

    for line in input {
        firstNum := line[strings.index_proc(line, unicode.is_digit):][:1]
        lastNum := line[strings.last_index_proc(line, unicode.is_digit):][:1]

        calibrationValue := strings.concatenate({firstNum, lastNum})
        defer delete(calibrationValue)

        num, ok := strconv.parse_int(calibrationValue)

        total += num
    }

    // daggonit thought it was the whole numbers
    /*
    for line in input {
        firstNum := line

        fFrom := strings.index_proc(firstNum, unicode.is_digit)
        firstNum = firstNum[fFrom:]

        fTo := strings.index_proc(firstNum, proc(r:rune)->bool {return !unicode.is_digit(r)})
        if fTo == -1 do fTo = len(firstNum)
        firstNum = firstNum[:fTo]


        lastNum := line
        lastNum = lastNum[:strings.last_index_proc(lastNum, unicode.is_digit)+1]
        lastNum = lastNum[strings.last_index_proc(lastNum, proc(r:rune)->bool {return !unicode.is_digit(r)})+1:]

        calibrationValue := strings.concatenate({firstNum, lastNum})
        defer delete(calibrationValue)

        num, ok := strconv.parse_int(calibrationValue, 10)
        if !ok {
            fmt.eprintf("%s could not be parsed from %s", calibrationValue, line)
            return
        }

        total += num;
    }
    */

    fmt.println(total)
}

p2 :: proc(input: []string) {
    parse_wordable :: proc(s: string) -> int {
        if len(s) == 1 {
            num, ok := strconv.parse_int(s)
            return num
        } else do switch s {
            case "one"  : return 1
            case "two"  : return 2
            case "three": return 3
            case "four" : return 4
            case "five" : return 5
            case "six"  : return 6
            case "seven": return 7
            case "eight": return 8
            case "nine" : return 9
        }

        return -1
    }

    total := 0

    for line in input {
        firstNumI, firstNumW := strings.index_multi(line, {
            "one"  , "1",
            "two"  , "2",
            "three", "3",
            "four" , "4",
            "five" , "5",
            "six"  , "6",
            "seven", "7",
            "eight", "8",
            "nine" , "9",
        })
        firstNum := line[firstNumI:][:firstNumW]


        // last_index_multi doesn't seem to exist, doing this as backup
        lastNumI, lastNumW := -1, -1
        for {
            nLastNumI, nLastNumW := strings.index_multi(line[lastNumI+1:], {
                "one"  , "1",
                "two"  , "2",
                "three", "3",
                "four" , "4",
                "five" , "5",
                "six"  , "6",
                "seven", "7",
                "eight", "8",
                "nine" , "9",
            })

            if nLastNumI == -1 do break

            lastNumI += nLastNumI+1
            lastNumW  = nLastNumW
        }
        lastNum := line[lastNumI:][:lastNumW]

        total += parse_wordable(firstNum)*10 + parse_wordable(lastNum)
    }

    fmt.println(total)
}

Had a ton of trouble with part 1 until I realized I misinterpreted it. Especially annoying because the example was working fine. So paradoxically part 2 was easier than 1.

[–] [email protected] 1 points 9 months ago

Python 3

I'm trying to practice writing clear, commented, testable functions, so I added some things that are strictly unnecessary for the challenge (docstrings, error raising, type hints, tests...), but I think it's a necessary exercise for me. If anyone has comments or criticism about my attempt at "best practices," please let me know!

Also, I thought it was odd that the correct answer to part 2 requires that you allow for overlapping letters such as "threeight", but that doesn't occur in the sample input. I imagine that many people will hit a wall wondering why their answer is rejected.

day01.py

import re
from pathlib import Path


DIGITS = [
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine",
    r"\d",
]

PATTERN_PART_1 = r"\d"
PATTERN_PART_2 = f"(?=({'|'.join(DIGITS)}))"


def get_digit(s: str) -> int:
    """Return the digit in the input

    Args:
        s (str): one string containing a single digit represented by a single arabic numeral or spelled out in lower-case English

    Returns:
        int: the digit as an integer value
    """

    try:
        return int(s)
    except ValueError:
        return DIGITS.index(s)


def calibration_value(line: str, pattern: str) -> int:
    """Return the calibration value in the input

    Args:
        line (str): one line containing a calibration value
        pattern (str): the regular expression pattern to match

    Raises:
        ValueError: if no digits are found in the line

    Returns:
        int: the calibration value
    """

    digits = re.findall(pattern, line)

    if digits:
        return get_digit(digits[0]) * 10 + get_digit(digits[-1])

    raise ValueError(f"No digits found in: '{line}'")


def calibration_sum(lines: str, pattern: str) -> int:
    """Return the sum of the calibration values in the input

    Args:
        lines (str): one or more lines containing calibration values

    Returns:
        int: the sum of the calibration values
    """

    sum = 0

    for line in lines.split("\n"):
        sum += calibration_value(line, pattern)

    return sum


if __name__ == "__main__":
    path = Path(__file__).resolve().parent / "input" / "day01.txt"

    lines = path.read_text().strip()

    print("Sum of calibration values:")
    print(f"β€’ Part 1: {calibration_sum(lines, PATTERN_PART_1)}")
    print(f"β€’ Part 2: {calibration_sum(lines, PATTERN_PART_2)}")

test_day01.py

import pytest
from advent_2023_python.day01 import (
    calibration_value,
    calibration_sum,
    PATTERN_PART_1,
    PATTERN_PART_2,
)


LINES_PART_1 = [
    ("1abc2", 12),
    ("pqr3stu8vwx", 38),
    ("a1b2c3d4e5f", 15),
    ("treb7uchet", 77),
]
BLOCK_PART_1 = (
    "\n".join([line[0] for line in LINES_PART_1]),
    sum(line[1] for line in LINES_PART_1),
)

LINES_PART_2 = [
    ("two1nine", 29),
    ("eightwothree", 83),
    ("abcone2threexyz", 13),
    ("xtwone3four", 24),
    ("4nineeightseven2", 42),
    ("zoneight234", 14),
    ("7pqrstsixteen", 76),
]
BLOCK_PART_2 = (
    "\n".join([line[0] for line in LINES_PART_2]),
    sum(line[1] for line in LINES_PART_2),
)


def test_part_1():
    for line in LINES_PART_1:
        assert calibration_value(line[0], PATTERN_PART_1) == line[1]

    assert calibration_sum(BLOCK_PART_1[0], PATTERN_PART_1) == BLOCK_PART_1[1]


def test_part_2_with_part_1_values():
    for line in LINES_PART_1:
        assert calibration_value(line[0], PATTERN_PART_2) == line[1]

    assert calibration_sum(BLOCK_PART_1[0], PATTERN_PART_2) == BLOCK_PART_1[1]


def test_part_2_with_part_2_values():
    for line in LINES_PART_2:
        assert calibration_value(line[0], PATTERN_PART_2) == line[1]

    assert calibration_sum(BLOCK_PART_2[0], PATTERN_PART_2) == BLOCK_PART_2[1]


def test_no_digits():
    with pytest.raises(ValueError):
        calibration_value("abc", PATTERN_PART_1)

    with pytest.raises(ValueError):
        calibration_value("abc", PATTERN_PART_2)

[–] [email protected] 1 points 9 months ago (1 children)

My solution is not pretty at all, but it does the job: Golang

[–] [email protected] 1 points 9 months ago (4 children)

[Rust] 11157/6740

use std::fs;

const m: [(&amp;str, u32); 10] = [
    ("zero", 0),
    ("one", 1),
    ("two", 2),
    ("three", 3),
    ("four", 4),
    ("five", 5),
    ("six", 6),
    ("seven", 7),
    ("eight", 8),
    ("nine", 9)
];

fn main() {
    let s = fs::read_to_string("data/input.txt").unwrap();

    let mut u = 0;

    for l in s.lines() {
        let mut h = l.chars();
        let mut f = 0;
        let mut a = 0;

        for n in 0..l.len() {
            let u = h.next().unwrap();

            match u.is_numeric() {
                true => {
                    let v = u.to_digit(10).unwrap();
                    if f == 0 {
                        f = v;
                    }
                    a = v;
                },
                _ => {
                    for (t, v) in m {
                        if l[n..].starts_with(t) {
                            if f == 0 {
                                f = v;
                            }
                            a = v;
                        }
                    }
                },
            }
        }

        u += f * 10 + a;
    }

    println!("Sum: {}", u);
}

Link

[–] [email protected] 1 points 9 months ago

Oh, doing this is Rust is really simple.

I tried doing the same thing in Rust, but ended up doing it in Python instead.

load more comments (3 replies)
[–] [email protected] 1 points 9 months ago

APL

spoiler

args ← {1↓⍡/⍨∨\β΅βˆŠβŠ‚'--'} βŽ•ARG
inputs ← βŽ•FIO[49]Β¨ args

words ← 'one' 'two' 'three' 'four' 'five' 'six' 'seven' 'eight' 'nine'
digits ← '123456789'

part1 ← {↑↑+/{(10×↑⍡)+Β―1↑⍡}Β¨{⍡~0}Β¨+βŠƒ(⍳9)+.Γ—digits∘.⍷⍡}
"Part 1: ", part1Β¨ inputs

part2 ← {↑↑+/{(10×↑⍡)+Β―1↑⍡}Β¨{⍡~0}Β¨+βŠƒ(⍳9)+.Γ—(words∘.⍷⍡)+digits∘.⍷⍡}
"Part 2: ", part2Β¨ inputs

)OFF

load more comments
view more: next β€Ί