8 votes

Day 15: Lens Library

Today's problem description: https://adventofcode.com/2023/day/15

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

10 comments

  1. [4]
    whs
    (edited )
    Link
    Rank 674! Wow, that was easier than expected. I saved Python for last as I thought the problem would get progressively harder. If I know it was this easy I might have used C. Python was my third...

    Rank 674! Wow, that was easier than expected. I saved Python for last as I thought the problem would get progressively harder. If I know it was this easy I might have used C.

    Python was my third language, after Visual Basic (that I didn't know how to use if or loops) and PHP. My code quality was getting very bad that I wrote my blog in a single PHP file, and with a lot of one line ifs. So, I thought I should learn a language that forcibly make you write readable code. The choice was either Python, or Ruby. And I picked Python as I thought Ruby's end make it inferior to Python. A decade and a half later, I think I made the right choice as Python is a lot more popular. Although I only see employers here asking for Ruby backend engineers and not Python where it get more used in data-related fields.

    And with that I've wrote & reminiscence all 15 languages I've written in all my life in AoC. Thanks for the trip down memory lane. After this, I don't know. Maybe I'll quit, maybe I'll write more Rust, C or C++ to get some practice in.

    Part 1
    def hash(v: str):
    	out = 0
    	for ch in v:
    		out += ord(ch)
    		out = out * 17
    		out = out % 256
    	return out
    
    assert hash("HASH") == 52
    
    if __name__ == "__main__":
    	problems = input().split(",")
    	out = [hash(p) for p in problems]
    	print(sum(out))
    
    Part 2
    import re
    from aoc import hash
    
    boxes = [list() for x in range(256)]
    
    problems = input().split(",")
    
    for problem in problems:
    	parts = re.split('([=-])', problem)
    	box_index = hash(parts[0])
    	mutating_box = boxes[box_index]
    	if parts[1] == '-':
    		for item in mutating_box:
    			if item[0] == parts[0]:
    				mutating_box.remove(item)
    				break
    	elif parts[1] == '=':
    		existing = False
    		for item in mutating_box:
    			if item[0] == parts[0]:
    				item[1] = parts[2]
    				existing = True
    				break
    		if not existing:
    			mutating_box.append([parts[0], parts[2]])
    
    print(boxes)
    
    out = 0
    for index, content in enumerate(boxes):
    	for lensIndex, lens in enumerate(content):
    		out += (1 + index) * (lensIndex + 1) * int(lens[1])
    
    print(out)
    
    3 votes
    1. [3]
      first-must-burn
      Link Parent
      Noting that in your "lens add" case, you could have used the for..else Python idiom. No particular reason to, just pointing it out in case it was new to you.

      Noting that in your "lens add" case, you could have used the for..else Python idiom. No particular reason to, just pointing it out in case it was new to you.

      3 votes
      1. RheingoldRiver
        Link Parent
        not op but I had no idea about that, thanks for pointing it out! this seems super nice.

        not op but I had no idea about that, thanks for pointing it out! this seems super nice.

        2 votes
      2. whs
        Link Parent
        Thanks for the suggestion. I'm only familiar with for..empty in Django template so initially I thought that is what for..else is for.

        Thanks for the suggestion. I'm only familiar with for..empty in Django template so initially I thought that is what for..else is for.

        2 votes
  2. jzimbel
    Link
    Elixir Very easy! List.keystore/4 and List.keydelete/3 were perfect for the map-building operations. Parts 1 and 2 Squeezed down to 24 mostly-illegible lines, because I felt like it defmodule...

    Elixir

    Very easy! List.keystore/4 and List.keydelete/3 were perfect for the map-building operations.

    Parts 1 and 2

    Squeezed down to 24 mostly-illegible lines, because I felt like it

    defmodule AdventOfCode.Solution.Year2023.Day15 do
      def part1(input), do: input |> parse() |> Stream.map(&hash/1) |> Enum.sum()
      def part2(input), do: input |> parse() |> to_map() |> stream_focusing_powers() |> Enum.sum()
    
      defp to_map(ops) do
        ops
        |> Stream.map(&Regex.run(~r/([^=]+)(?:-|=(.+))/, &1, capture: :all_but_first))
        |> Enum.reduce(Map.new(0..255, &{&1, []}), fn
          [i, f], m -> Map.update!(m, hash(i), &List.keystore(&1, i, 0, {i, String.to_integer(f)}))
          [i], m -> Map.update!(m, hash(i), &List.keydelete(&1, i, 0))
        end)
      end
    
      defp stream_focusing_powers(map) do
        Stream.flat_map(map, fn {box, lenses} ->
          lenses
          |> Stream.with_index(1)
          |> Stream.map(fn {{_label, f}, slot} -> (1 + box) * slot * f end)
        end)
      end
    
      defp hash(str), do: for(<<char <- str>>, reduce: 0, do: (acc -> rem((acc + char) * 17, 256)))
      defp parse(input), do: input |> String.split(",", trim: true) |> Stream.map(&String.trim/1)
    end
    
    2 votes
  3. RheingoldRiver
    Link
    Not my finest day. I did part 1 in 6 minutes, but part 2 took me another 30 minutes, mostly because I kept not understanding the statement of the problem. "it's just a hashmap" "yeah but uhh how...

    Not my finest day. I did part 1 in 6 minutes, but part 2 took me another 30 minutes, mostly because I kept not understanding the statement of the problem. "it's just a hashmap" "yeah but uhh how does that work again...."

    Part 1
    class Solver:
    
        def __init__(self):
            with open('test.txt', 'r', encoding='utf-8') as f:
                self.raw_input = [line.strip() for line in f.readlines()]
            self.data = self.raw_input[0].split(',')
            self.boxes = {}
            for i in range(225):
                self.boxes[i] = {
                    'lenses': [],
                    'labels': [],
                }
    
        def run(self):
    
            total = 0
            for step in self.data:
                total += self.get_hash(step)
    
            return total
    
        def get_hash(self, step):
            cur_value = 0
            for char in step:
                cur_value += ord(char)
                cur_value *= 17
                cur_value = cur_value % 256
            return cur_value
    
    
    if __name__ == '__main__':
        print(Solver().run())
    
    Part 2
    class Solver:
    
        def __init__(self):
            with open('input.txt', 'r', encoding='utf-8') as f:
                self.raw_input = [line.strip() for line in f.readlines()]
            self.data = self.raw_input[0].split(',')
            self.boxes = {}
            for i in range(256):
                self.boxes[i] = {
                    'lenses': [],
                    'labels': [],
                }
    
        def run(self):
            total = 0
            for step in self.data:
                print(f"{step}")
                operation = 'equals' if '=' in step else 'minus'
                if operation == 'minus':
                    self.do_minus(step)
                else:
                    self.do_equals(step)
                self.print_boxes(operation)
    
            for b, box in self.boxes.items():
                for i, lens in enumerate(box['lenses']):
                    total += (lens * (len(box['lenses'])-i) * (b + 1))
            return total
    
        def print_boxes(self, operation):
            for i, box in self.boxes.items():
                if len(box['labels']) > 0:
                    print(f"{i} {','.join(box['labels'])} {','.join([str(x) for x in box['lenses']])} - {operation}")
    
        def do_minus(self, step):
            label = step.split('-')[0]
            hash = self.get_hash(label)
            box = self.boxes[hash]
            if label in box['labels']:
                index = box['labels'].index(label)
                box['labels'].pop(index)
                box['lenses'].pop(index)
    
        def do_equals(self, step):
            label, lens = step.split('=')
            lens = int(lens)
            hash = self.get_hash(label)
            box = self.boxes[hash]
            if label in box['labels']:
                i = box['labels'].index(label)
                box['lenses'][i] = lens
            else:
                box['labels'].insert(0, label)
                box['lenses'].insert(0, lens)
    
        def get_hash(self, step):
            cur_value = 0
            for char in step:
                cur_value += ord(char)
                cur_value *= 17
                cur_value = cur_value % 256
            return cur_value
    
    
    if __name__ == '__main__':
        print(Solver().run())
    

    github

    1 vote
  4. lily
    Link
    Surprisingly easy for day 15, though I wasn't quite as fast as I would've liked (I'm posting quite a bit later, but I finished in around 22 minutes or so). I expected the part 2 twist to be more...

    Surprisingly easy for day 15, though I wasn't quite as fast as I would've liked (I'm posting quite a bit later, but I finished in around 22 minutes or so). I expected the part 2 twist to be more difficult than it ended up being.

    Solution
    # Advent of Code 2023
    # Day 15: Lens Library
    
    def hash_(s):
        current = 0
    
        for char in s:
            current += ord(char)
            current *= 17
            current %= 256
    
        return current
    
    hash_sum = 0
    boxes = [[] for _ in range(256)]
    
    with open("inputs/day_15.txt") as file:
        for step in file.read().rstrip("\n").split(","):
            hash_sum += hash_(step)
    
            label = "".join(char for char in step if char.isalpha())
            label_len = len(label)
    
            box = boxes[hash_(label)]
    
            if step[label_len] == "=":
                existing = False
    
                for lens in box:
                    if lens[0] == label:
                        lens[1] = int(step[label_len + 1])
                        existing = True
                        break
    
                if not existing:
                    box.append([label, int(step[label_len + 1])])
            else:
                for lens in box:
                    if lens[0] == label:
                        box.remove(lens)
                        break
    
    print(f"Part 1: {hash_sum}")
    
    focusing_power = 0
    
    for i, box in enumerate(boxes):
        for j, lens in enumerate(box):
            old = focusing_power
            focusing_power += (i + 1) * (j + 1) * lens[1]
    
    print(f"Part 2: {focusing_power}")
    
    1 vote
  5. scarecrw
    Link
    This was the first day I actually felt hampered by Haskell. I'm sure someone more comfortable with the language could accomplish this naturally, but I was just wishing for my familiar data...

    This was the first day I actually felt hampered by Haskell. I'm sure someone more comfortable with the language could accomplish this naturally, but I was just wishing for my familiar data structures.

    I initially implemented everything with lists, but went back and updated the outer list to an Array as the original seemed wastefully slow. I could probably substitute the inner lists as well for something like OMap, but it's running reasonably quickly as is.

    Haskell Solution
    module Main (main) where
    
    import AOCTools (splitBy)
    import Data.Char (ord, isNumber, isAlpha)
    import Data.Array (listArray, Array, (//), (!), elems)
    
    main :: IO ()
    main = do
        input <- readFile "./input.txt"
        putStrLn $ "Part 1: " ++ show (solve1 $ splitBy "," input)
        putStrLn $ "Part 2: " ++ show (solve2 $ splitBy "," input)
    
    hashFunc :: String -> Int
    hashFunc = foldl (\n x -> (n + ord x) * 17 `mod` 256) 0
    
    solve1 :: [String] -> Int
    solve1 = sum . map hashFunc
    
    data Lens = Lens { lensLabel :: String, focalLength :: Int }
    
    calculateFocusingPower :: Array Int [Lens] -> Int
    calculateFocusingPower boxes = prod (prod focalLength) (elems boxes) where
        prod f x = sum $ zipWith (*) [1..] (map f x)
    
    applyStep :: Array Int [Lens] -> String -> Array Int [Lens]
    applyStep boxes step
        | '=' `elem` step = addLens boxes (Lens label focalLength)
        | otherwise = removeLens boxes label
        where
            label = takeWhile isAlpha step
            focalLength = read $ dropWhile (not . isNumber) step
    
    addLens :: Array Int [Lens] -> Lens -> Array Int [Lens]
    addLens boxes lens = boxes // [(boxNum, add lens (boxes ! boxNum))] where
        boxNum = hashFunc $ lensLabel lens
        add lens [] = [lens]
        add lens (l:ls)
            | lensLabel l == lensLabel lens = lens : ls
            | otherwise = l : add lens ls
    
    removeLens :: Array Int [Lens] -> String -> Array Int [Lens]
    removeLens boxes label = boxes // [(boxNum, remove label (boxes ! boxNum))] where
        boxNum = hashFunc label
        remove label [] = []
        remove label (l:ls)
            | lensLabel l == label = ls
            | otherwise = l : remove label ls
    
    solve2 :: [String] -> Int
    solve2 = calculateFocusingPower . foldl applyStep initialBoxes where
        initialBoxes = listArray (0, 255) $ replicate 256 []
    
    1 vote
  6. [2]
    first-must-burn
    Link
    Golang solution This seemed very (too?) straightforward. Not much more than implementing the algorithm as described. But go does have all the important primitives. I keep expecting to hit these...

    Golang solution

    This seemed very (too?) straightforward. Not much more than implementing the algorithm as described. But go does have all the important primitives. I keep expecting to hit these big algorithmic scalability or complex search challenges that I remember from previous years.

    Maybe I shouldn't be complaining, I remember some solutions from previous years taking me several days to crack. Maybe they are trying to make it more fun and less work.

    1 vote
    1. jzimbel
      Link Parent
      It seemed a little weird to me as well for day 15, especially with how much they spelled out the instructions compared to past days from this year. Kind of a basic data structures problem.

      It seemed a little weird to me as well for day 15, especially with how much they spelled out the instructions compared to past days from this year. Kind of a basic data structures problem.

      2 votes