#Day 6

1 messages · Page 1 of 1 (latest)

sly remnant
#

Parsing part 2 was so much fun, can't wait to try and optimise it later today

unkempt berry
#

not the fastest

Ran 2025 day 6:
  Part 1: ✅ (in 18.807 ms)
  Part 2: ✅ (in 14.908 ms)
#

quickest solve so far at least

tame barn
#

I made such a mess out of this one, but got there in the end..

Ran 2025 day 6:
  Part 1: ✅ met expected value: _ (in 4.613 ms)
  Part 2: ✅ met expected value: _ (in 15.642 ms)
unkempt berry
#

helped bump me up in the gleam leaderboard

#

this is my favourite part of todays solution lol

#

p.1(p.0) is so cursed i love it

#

seems to be a few struggling with parsing pt.2 currently

fleet urchin
#

very rotation

#

much confusion

#

struggling is an understatement

#

i did it with a little under 100 lines

#

time to clean up

unkempt berry
#
Ran 2025 day 6:
  Part 1: ✅ (in 2.783 ms)
  Part 2: ✅ (in 1.425 ms)

getting better

neat walrus
#

finally managed to do it

#

again @unkempt berry thanks for the tip couldnt have figured it out on my own, i was thinking about the first way you suggested but it sounded painful in my head but i managed to do the second one with the counting the last line

unkempt berry
#

glad you got there in the end c:

unkempt berry
#

simplified my pt 2 a bit:

fn solve(nums: List(List(Int)), ops: List(fn(List(Int)) -> Int)) -> Int {
  list.zip(nums, ops)
  |> list.map(fn(p) { p.1(p.0) })
  |> int.sum
}

pub fn pt_1(input: #(List(String), List(fn(List(Int)) -> Int))) -> Int {
  let #(lines, ops) = input
  list.map(lines, utils.parsed_fields(_, utils.unsafe_int_parse))
  |> list.transpose
  |> solve(ops)
}

fn do_group_columns(
  chars: List(List(UtfCodepoint)),
  groups: List(List(Int)),
  cur_group: List(Int),
) -> List(List(Int)) {
  case chars {
    [] -> list.reverse([cur_group, ..groups])
    [[], ..chars] -> do_group_columns(chars, [cur_group, ..groups], [])
    [cs, ..chars] ->
      do_group_columns(chars, groups, [
        string.from_utf_codepoints(cs) |> utils.unsafe_int_parse,
        ..cur_group
      ])
  }
}

pub fn pt_2(input: #(List(String), List(fn(List(Int)) -> Int))) -> Int {
  let #(lines, ops) = input
  let assert Ok(space) = string.utf_codepoint(32)
  list.map(lines, string.to_utf_codepoints)
  |> list.transpose
  |> list.map(list.filter(_, fn(c) { c != space }))
  |> do_group_columns([], [])
  |> solve(ops)
}
young silo
#

let's gooooo

#

yeah this was a nice change of pace

#

not another regex problem

#

50% of AoC done, that's a new record 🥳

#

Anyone else used list.transpose ?

sly remnant
#

A real life saver

young silo
#

yeeee

sly remnant
quick trout
#

Yes, list.transpose was MVP in both parts for me

stark torrent
#

I opened this thread and I'm glad I'm not alone in having terribly unoptimised parsing

#
Ran 2025 day 6:
  Part 1: _ (in 5.088 ms)
  Part 2: _ (in 3.356 ms)

(including parsing today because that was most of the challenge)

open prairie
#

omfg today's twist is absolutely brilliant

open prairie
mortal heath
#

Indeed transpose saves the day

#

P2 is not that hard after thinking about it

#

Juste change of perspective and a lot of echo to validate transformations lol

open prairie
#

I thought I saw p2 coming and then it was entirely not what I expected lol

#

Luckily I was prepared enough that it was still fairly easy

foggy lark
#

I think that's as close as an AOC problem can get to being entirely parsing

sly remnant
#

Parse deniers in shambles rn

stark torrent
stark torrent
#

but 1. both parts needed different parsing

open prairie
#

My parse func is like 70 lines

stark torrent
#

and 2. it was 99% of the work

open prairie
#

And then each part is basically also parsing, just the last bit of it :P

foggy lark
#

yeah I ended up not using a parse function at all because so little was shared

open prairie
#

Still waiting for this year's excuse to use a parser combinator

warped wave
#

i don't like the ones that are just really stupid parsing :(

foggy lark
#

at least in a language like gleam it's just list ops forever usually

#

can't imagine parsing some of these in something like C++

warped wave
#

I wanted to do it in sql again 😂

#

Did part 1, not sure if I'll do part 2

#

maybe if literally parse it to row, col, char and then define a "parse integer" aggregate function hmm

unreal yoke
#
  Part 1: _ (in 28.912 ms)
  Part 2: _ (in 8.652 ms)```
#

wildly inefficient lmao

sly remnant
#

Nah that's plenty fast

unreal yoke
#

could improve it somewhat, im doing the parsing and solving in different passes

sly remnant
#

The most I could push part 1 to was 1.3ms including parsing

#

But it was a bit ugly

vague cape
#
Ran 2025 day 6:
  Part 1: _ (in 9.690 ms)
  Part 2: ) (in 19.634 ms)

For part 1 i parse into a list of lists, then iterate over the outer list and reduce the inner list by the operator at the end

For part 2 i built up a dict (2d grid) from the input and iterate on it column by column

mortal heath
#

Since today was a bit parsing boring oriented. I have just tried to better understand the use keyword

broken sail
#

I am really ashamed of the parsing of my Part 2 ahaha 🤣 I am surprised it works in 15ms though. (I used a "desperation-based approach": that is, hammering the data until it takes the shape I want ). lolsob Oh well, time to make this more presentable...

  let numbers =
    lines
    |> list.map(string.to_graphemes)
    |> list.transpose
    |> list.chunk(last_is_operator)
    |> list.sized_chunk(2)
    |> list.map(list.flatten)
    |> list.map(fn(c) { list.map(c, fn(d) { list.take(d, lines_number) }) })
    |> list.map(fn(c) { list.map(c, string.concat) })
    |> list.map(fn(c) { list.map(c, fn(d) { string.trim(d) }) })
    |> list.map(fn(c) { list.map(c, fn(d) { int.parse(d) }) })
    |> list.map(fn(c) { list.filter(c, result.is_ok) })
    |> list.map(fn(c) { result.all(c) })
    |> list.map(fn(c) { result.unwrap(c, []) })
unkempt berry
#

Btw using _ makes a lot of those list.map calls cleaner, i.e.
|> list.map(list.map(_, string.trim))

#

Also filtering by result.is_ok and then calling result.all is just the same as doing result.values

unreal yoke
#

I just looked at the distances between the operation chars to get the width of a column, not sure if that’s the most widely used solution or not

lofty tapir
#

Gotta love advent of parsing

#

I did it with list.group, but I end up throwing away the groups, so chunk looks like it makes more sense

unkempt berry
lofty tapir
#

Mine got messy for pt2

pub type Operation {
  Sum
  Prod
}

pub type Instruction {
  Instruction(operation: Operation, values: List(Int))
}

pub fn pt_2(input: String) {
  let #(_accumulated_group_id, tagged_columns) =
    string.split(input, "\n")
    |> list.map(string.to_graphemes)
    // Get the columns
    |> list.transpose()
    // Add a group ID, detected by columns of spaces
    |> list.fold(#(0, []), fn(acc, e) {
      let #(group_n, l) = acc

      case list.all(e, fn(v) { v == " " }) {
        True -> #(group_n + 1, l)
        False -> #(group_n, [#(group_n, e), ..l])
      }
    })

  // Group into columns with the same tag
  list.chunk(tagged_columns, fn(v: #(Int, List(String))) -> Int { v.0 })
  // Get rid of the group tags
  |> list.map(fn(v) { list.map(v, fn(v) { v.1 }) })
  // Find the group operation
  |> list.map(fn(v) {
    #(
      list.fold(v, option.None, fn(acc, v) {
        case list.find(v, fn(e) { e == "*" || e == "+" }) {
          Error(_) -> acc
          Ok("+") -> option.Some(Sum)
          Ok("*") -> option.Some(Prod)
          Ok(_) -> panic
        }
      }),
      v,
    )
  })
  // Get rid of options, since the operator has to be there somewhere in the chunk
  |> list.map(fn(v) {
    let assert option.Some(op) = v.0
    #(op, v.1)
  })
  // Get rid non-number characters
  |> list.map(fn(v) {
    let #(op, v) = v
    let v =
      list.map(
        v,
        list.filter(_, fn(e) {
          case e {
            " " -> False
            "*" -> False
            "+" -> False
            _ -> True
          }
        }),
      )
    #(op, v)
  })
  // Almost there
  // Finally, get rid of one level of nesting
  |> list.map(fn(v) {
    let #(op, v) = v
    let v = list.map(v, string.concat)
    #(op, v)
  })
  // Parse numbers and map to instructions
  |> list.map(fn(v) {
    let #(op, v) = v
    let v = list.filter_map(v, int.parse)
    Instruction(op, v)
  })
  // Now, part 1 solution
  |> list.map(calc)
  |> int.sum
}
delicate girder
lofty tapir
normal cedar
#

not a big fan of today

#

but hey we are halfway!

sly remnant
sly remnant
#

Can’t wait to get my sleep schedule back to normal lolsob

normal cedar
normal cedar
normal cedar
#

morning people are scary

warped wave
#

Eh fuck it, not doing part 2

#

I know how I'd do it I just don't wanna >:(

normal cedar
warped wave
#

yeah, but weird parsing is extremely annoying in sql

#

I already relied on sed too much for part 1 for my liking

manic meadow
#

why did I just implement this without list.transpose 😭 I swear I checked the docs for that first and couldn't find it

warped wave
#

I had to define a product aggregate function lucylaugh

-----------|
-- Setup --|
-----------|

create unlogged table input_lines (
    row serial primary key,
    value text not null
);

create unlogged table numbers (
    col int not null,
    value int not null
);

create unlogged table ops (
    col serial primary key,
    op char not null
);

create function product_sfunc(acc numeric, factor numeric)
returns numeric as $$
    select $1 * $2
$$ language sql;

create aggregate product (
    sfunc = product_sfunc,
    basetype = numeric,
    stype = numeric,
    initcond = '1'
);

-----------------|
-- Parse input --|
-----------------|

-- Copy full lines into input table, not including the operator line!
\copy input_lines (value) from program 'head -n -1 input/2025/6.txt'

insert into numbers (col, value)
select
    col.col,
    col.value::int
from input_lines as line
cross join
    lateral
    -- Trim leading whitespace, then split into array on whitespace
    -- and unnest into rows
    unnest(regexp_split_to_array(trim(line.value), '\s+'))
    with ordinality as col (value, col);

-- Copy last line into ops table. To transpose columns into rows, replace
-- white space with newlines. There's no way to change the row-delimiter
\copy ops (op) from program 'tail -n -1 input/2025/6.txt | sed -ze "s/\s\+/\n/g"'

------------|
-- Part 1 --|
------------|

with result as (
  select
      case
          when op.op = '+' then sum(number.value)
          else product(number.value)
      end as value
  from ops as op
  inner join numbers as number on op.col = number.col
  group by number.col, op.op
)
select sum(result.value)
from result;
lofty tapir
#

It has an extra comment

visual ermine
#

Guess I shouldn’t have trimmed that whitespace from the end of my file.

zenith sentinel
#

Today's problem generated ... checks notes ... 8 TODO notes for what to change in my language 😂

queen bolt
#

today was a doozy imo

queen bolt
visual ermine
#

I had a bug and frustratingly tried fixing the wrong thing for about 50 minutes until I realized my last line was one character shorter than my other lines because I stripped the trailing whitespace.

queen bolt
#

I'm gonna be honest even in the aoc subreddit everyone is talking about whitespace, but it didn't even occur to me as a problem

#

ig mostly because I'm being evil & parsing it left to right anyways since it doesn't matter as the operations given are commutative

visual ermine
#

In the end I like the way I parsed part 2:

  lines
  |> list.map(fn(line) {
    string.to_graphemes(line)
    |> list.reverse
  })
  |> list.transpose
  |> list.filter_map(fn(column) {
    let l = list.length(column)
    let #(numbers, operation) = list.split(column, l - 1)
    use value <- result.map(
      string.join(numbers, "") |> string.trim |> int.parse,
    )
    #(value, operation)
  })
unkempt berry
#

Just fyi it's a fair bit faster to go string.to_utf_codepoints <-> string.from_utf_codepoints rather than string.to_graphemes <-> string.join since codepoints are a lot less costly to calculate than graphemes

visual ermine
#

Oh... I did not know that. Thank you.

unkempt berry
#

No problem

stark torrent
#

answered my own question by looking at the code (and some research):
a unicode grapheme is made up of multiple codepoints, so finding graphemes means looking ahead for diacritics and combined codepoints and such.
even though codepoints can be 1-4 bytes, you can tell how long they will be from the first byte, so you can very quickly slice a string into codepoints

#

and when you're dealing with purely ASCII data, each codepoint effectively is just the byte value of the character in ascii

long kelp
#
Ran 2025 day 6:
  Part 1: _ (in 2.252 ms)

well, part 1 was mostly a parsing problem

queen bolt
long kelp
#
Ran 2025 day 6:
  Part 1: _(in 2.867 ms)
  Part 2: _(in 102 µs)

time feels very not real for this one lol

stark torrent
#

huh???

long kelp
#

99% of the work is in the parse

stark torrent
#

oh

#

haha yeah, i timed mine with parse today

long kelp
#

Part 2: _ (in 14.336 ms)
still actually not too bad with parsing pulled into it.
especially since i'm just chaining a ton of list ops that could be single pass

quick trout
#

Everytime I do something like

my_variable
|> list.map(f)
|> list.map(g)

I feel a little bit dirty and whisper to myself map f . map g == map (f . g), no reason to iterate over the list twice. 😢

#

But alas with no function composition operator we have to make do. psyduck

open prairie
#

Wouldn't it be really straightforward to write your func for that?

quick trout
#

See my post in #general

long kelp
#

‘import gleam/lazy’ when

proper kite
#

I'm fully prepared to read other solutions and see that I overcomplicated the parsing

#

my approach was to determine the column widths by counting the spaces between the operators

#

oh god dammit I could have just transposed the input couldn't I

#

sigh

#

well, at least I get to say I did it my own way :)

buoyant dagger
#

Very messy from me today but again no wrong submissions.

I was going to use filestreams as random access but I made a mess.

So on part 2 I just made a giant 2D list of the numbers lines and transposed it. Then joined and trimmed each row and folded that into data sets. Then I zipped it with the operations and folded that to sum/product each data set.

still parcel
#

I discovered transpose and used it twice today. Such a nice utility function lucyhearts

buoyant dagger
#

I like dabbling in array languages and transpose is a heavy lifter

still parcel
#

Yeah, each time I have to use matrices I use it. No wonder why some languages/libs simplify it to one character (t or ')

still parcel
quick trout
#

Several people are typing

#

😅

buoyant dagger
#

Uiua has ⍉, the diagonal line is the axis of reflection/rotation

still parcel
buoyant dagger
#

You can type in the string name and have the compiler convert it. You don't even need to type the full name, just enough for it to be a unique match
The online playground does this automatically when you run - https://www.uiua.org/pad

mortal heath
pseudo river
unkempt berry
#

Btw you seem to have committed your input

#

As for the solutions, to start I would extract out that file reading and parsing in parts 1 and 2 into a separate read_file and parse function, then call those once in main and pass the parsed data to each part so it doesn't need to be done twice

#

For aoc you're usually fine to assert for stuff like reading and parsing the input file, so I would probably just do like

fn read_file(path: String) -> String {
  let assert Ok(content) = simplifile.read(from: path) as "Missing input file"
  content
}

fn parse(input: String) -> List(String) {
  case file_content |> string.split("\n") {
    [] -> panic as "File is empty"
    [_] -> panic as "File contains only one line"
    lines -> list.filter(lines, fn(line) { !string.is_empty(line) })
  }
}
fleet urchin
unkempt berry
#

Yeah

fleet urchin
#
  Ok(
    list.fold(symbols, #(0, grid), fn(acc, symbol) {
      #(
        acc.0
          + list.fold(
          acc.1,
          case symbol {
            "+" -> 0
            "*" -> 1
            _ -> panic
          },
          fn(acc: Int, line: List(Int)) {
            case list.length(line) {
              0 -> panic
              _ -> Nil
            }
            let value = result.unwrap(list.first(line), 0)
            case symbol {
              "+" -> acc + value
              "*" -> acc * value
              _ -> panic
            }
          },
        ),
        list.map(acc.1, fn(line: List(Int)) { list.drop(line, 1) }),
      )
    }).0,
  )

This to me is particularly unreadable. I would try to unpack the tuples and maybe make auxiliary functions to call instead of just using anonymous functions

#

This was my similar-ish part of the solution:

  let ops =
    ops
    |> list.map(fn(op) {
      case op {
        "+" -> Add
        "*" -> Mult
        _ -> panic as "unexpected operator"
      }
    })

  let nums =
    nums
    |> list.map(
      list.map(_, fn(s) {
        let assert Ok(num) = int.parse(s)
        num
      }),
    )

  let cols = nums |> list.transpose()
  let assert Ok(zip) = list.strict_zip(cols, ops)
  zip
  |> list.map(fn(col) {
    case col {
      #(nums, Add) -> nums |> int.sum()
      #(nums, Mult) -> nums |> int.product()
    }
  })
  |> int.sum
unkempt berry
#

Also list.zip(symbols, list.transpose(grid)) would remove a lot of the stuff that fold is doing

pseudo river
#

@unkempt berry Thank you for your comments. You are offering great advice to improve on my Gleam journey.