Skip Navigation

🎄 - 2023 DAY 1 SOLUTIONS -🎄

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

65 评论
  • Part 02 in Rust 🦀 :

     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
    }
    
      
  • 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>
      
  • 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:

    Hare:

  • 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 .
    ;
    
    
      
  • 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")]))

  • 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;
    
      
  • 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.⊔)/↥)+,
    
      
  • 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.

     c
        
    #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);
        }
    }
    
      
  • Part 1 felt fairly pretty simple in Haskell:

     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.

     haskell
        
    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!

  • [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();
        }
      
  • Have a snippet of Ruby, something I hacked together as part of a solution during the WFH morning meeting;

     ruby
        
    class String
      def to_numberstring(digits_only: false)
        tokens = { 
          one: 1, two: 2, three: 3,
          four: 4, five: 5, six: 6,
          seven: 7, eight: 8, nine: 9
        }.freeze
        ret = ""
    
        i = 0
        loop do
          if self[i] =~ /\d/
            ret += self[i]
          elsif !digits_only
            tok = tokens.find { |k, _| self[i, k.size] == k.to_s }
            ret += tok.last.to_s if tok
          end
          
          i += 1
          break if i >= size
        end
    
        ret
      end
    end
    
      
  • This is my solution in Nim:

     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);
    
    
      
  • Crystal. Second one was a pain.

    part 1

     crystal
        
    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

     crystal
        
    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

  • [Language: Lean4]

    I'll only post the actual parsing and solution. I have written some helpers which are in other files, as is the main function. For the full code, please see my github repo.

    Part 2 is a bit ugly, but I'm still new to Lean4, and writing it this way (structural recursion) just worked without a proof or termination.

65 评论