Stream: roctoberfest

Topic: Day 1


view this post on Zulip Georges Boris (Oct 01 2022 at 13:10):

Ha! Just finished the first one. :tada:

https://gist.github.com/georgesboris/3c43ed7618f5816df85d620c41eea73c

Loved the experience! Here are a few takeaways:

view this post on Zulip Georges Boris (Oct 01 2022 at 13:12):

Ah! The tag pattern matching + guard clauses are awesome :heart: I will miss them when going back to Elm for sure

view this post on Zulip Anton (Oct 01 2022 at 13:26):

Thank you so much @Georges Boris :) Excellent feedback!
PR for UX is good :+1:
Can you make issues for the listed pain points if you have the time?

view this post on Zulip jan kili (Oct 01 2022 at 14:24):

Thank you for that feedback, @Georges Boris ! And nicely done on solving the Advent of Code 2021 Day 1 puzzle. I'm hoping to do days 1-3 on Monday. I wonder who else will tackle day 1 today...

view this post on Zulip Ghislain (Oct 01 2022 at 14:32):

@Georges Boris Are you sure to have completed the entire 1st day and not the half? :innocent:

view this post on Zulip jan kili (Oct 01 2022 at 14:36):

Good point, @Ghislain , I forgot to mention - every AoC puzzle has two halves! Feel free to do one or both, but it's nice that we effectively have 62 puzzles available :present: :present:

view this post on Zulip Ghislain (Oct 01 2022 at 14:53):

My code is not as clean as yours! https://github.com/ghigt/advent-of-code

After a period of frustration on how to deal with the tags and especially retrieve the results contained inside. A lot of indentation in the when pattern, but after a lot of searching in the docs and tutorials I managed to find just about everything I wanted (I agree that it's still not very easy to find my way around at the moment).

Curiously, writing in the 3 other languages allowed me to find new ways to implement the algorithm and features in Roc (especially with Rust).

After a few iterations of implementation, I am quite satisfied with the result and I enjoyed writing in Roc for its syntax simplicity!

What I remember (for a beginner in FP) after this 1st day:

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 14:55):

I guess I should try and do this before looking at anything. Though I think I already did a few of these problems last year in BQN, so I am familiar.

view this post on Zulip Ghislain (Oct 01 2022 at 14:55):

Would love to know what is or is not idiomatic in my code to know how to improve it.

view this post on Zulip jan kili (Oct 01 2022 at 14:56):

That's one of the exciting things about Roc - I don't think there is a such thing as idiomatic Roc yet, we're inventing/discovering that right now!

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 14:56):

General question: Are people parsing or just copying the data into a list? I mean in this case the parsing is super simple, but I know it can sometimes get more annoying.

view this post on Zulip jan kili (Oct 01 2022 at 14:57):

@Brendan Hansknecht That was one of the things I was most curious to see on day 1.

view this post on Zulip Ghislain (Oct 01 2022 at 14:57):

I parsed the file, and @Georges Boris copied it inside its .roc file

view this post on Zulip Georges Boris (Oct 01 2022 at 15:38):

@JanCVanB whoops! had no idea there was a part 2 :sweat_smile:

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 15:57):

I decided to go the file parsing route. I also decided to take an arg for the file name to process.
My day 1 code is here: https://github.com/bhansconnect/roc-aoc-2021/tree/trunk/day1

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 15:58):

Overall went pretty well. Took a bit of doc diving as I tried to use proper functions. Also definitely took more time to setup the base structure than to solve the real problem

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 15:59):

I feel like my actual solutions once the data is loaded are pretty simple and nice. That being said, they are also not efficient...but eh...not important for this amount of data.

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:01):

I think may main question: Would it be worth adding a List.countIf function? Instead I just used List.keepIf and then List.len which woud be way way less efficient. I also could have used a walk, but that requires state management and I didn't feel like it.

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:02):

This is definitely a useful pipeline stage: |> List.keepIf (\x -> x) :rolling_on_the_floor_laughing:

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:14):

Ghislain said:

Would love to know what is or is not idiomatic in my code to know how to improve it.

Your code looks just fine. That being said, it definitely looks more like a port of imperative code rather than normal FP code. I feel like FP tends to break things up into stages and try to avoid all the work you had to explicitly do via state management. On the other hand, your solution should be reasonably efficient.

If you compare to my solution for part2: https://github.com/bhansconnect/roc-aoc-2021/blob/b4981b6f334c1a95fde72e5c46b23e99ff4a5e4f/day1/part2.roc#L64
Long term in Roc (once we have seamless slices), my algorithm would do the same work as yours with the extra cost of 3 lists:

view this post on Zulip Ghislain (Oct 01 2022 at 16:14):

Was thinking about taking advantage of map2/3, happy to have seen your implementation with it!

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:16):

That being said, most of those lists could go away while keeping things relatively simple:

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:17):

That being said, pipelining will likely always be slightly less performant if the implementation isn't careful due to extra allocation. Though I do reduce the amount of branches which could lead to a win in some case.

view this post on Zulip Ghislain (Oct 01 2022 at 16:18):

I was trying to do only one iteration over the list to be efficient, but is it? Your impl' looks a LOT smaller and easier to read even though you create multiple iterations/lists.

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:21):

With seamless slice such that list.dropFirst does not cause a copy and the edition of countIf, I would probably expect my implementation to run faster. It would allocate once for the movingSum and then just process into that and over it to get the final result. It would be done with no branching (assuming countIf compiles correctly)

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:21):

All the work of checking optionals and nested branches that your implementation does would probably cost more, but also, you could optimize away a number of those branches with some minor changes

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 16:24):

I think ultimately the fastest way would be just walk once (using walk3 and list.dropFirst with seamless slices) while only tracking the last sum and total count. You would init the last sum to -1 because that is guaranteed smaller than any value. The count you would increment on every loop, but using cmov to avoid the cost of an unpredictable branch (using inline if in roc would probably do this).

view this post on Zulip Ghislain (Oct 01 2022 at 16:29):

Huge thanks for this feedback!

view this post on Zulip Ayaz Hafiz (Oct 01 2022 at 17:54):

Had a lot of fun doing the first problem today. Roc is such a pleasure to use!

view this post on Zulip Ayaz Hafiz (Oct 01 2022 at 17:54):

For the second part, I used a walk holding state of the last two elements, rather than chopping out two additional lists: https://gist.github.com/ayazhafiz/0d5d40b35bb58f79fb742b1d2ec7515c

view this post on Zulip Ayaz Hafiz (Oct 01 2022 at 17:57):

one thing that actually surprised me at first is that if you define a function like

foo = \tag ->
  when tag is
    Tag A (Ok _) -> ...
    Tag A (Err _) -> ...
    Tag B _ -> ...

this actually gets inferred to [Tag [A, B] [Ok *, Err *]*], and not [Tag [A, B] [Ok *, Err *]] as you might expect (note the addition of the open tag union in the inferred type!). That's because the Tag B _ case is catch-all, so you need type annotations here.. but I wonder if we can make this better somehow.

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 18:06):

That's definitely a direct and clean solution

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 18:06):

Mine is definitely implemented closer to how someone might do it in an array language.

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 18:06):

Which is a tad weird for an FP language

view this post on Zulip Ayaz Hafiz (Oct 01 2022 at 18:07):

your solution also works well for lazy languages, like haskell

view this post on Zulip Brendan Hansknecht (Oct 01 2022 at 18:35):

Ah. That's fair

view this post on Zulip Georges Boris (Oct 02 2022 at 00:01):

ok - got 5 minutes of free time and managed to finish the 2nd part of day 1 :sweat_smile:
turns out it was just a single diff :)
https://gist.github.com/georgesboris/85dbd20b3c8c1be752fe252a59309cdb

view this post on Zulip Shritesh Bhattarai (Oct 02 2022 at 06:35):

I wanted to catch up on Roc for a while but life kept on happening. With rocktoberfest here, I had no excuses.

I did the tutorials first and I'm still trying to wrap my head around Program and Task. Program.quick seems good enough for AoC (and easy to understand). I encountered a lot of compiler crashes esp. when there was a type mismatch. But the rest of my experience was great. Love the language and the api docs.

One thing I noticed was that tags being first class functions makes up for the lack of partial function application. I like how the Day 2 parser turned out.

Anyway, here's my Day1: https://github.com/shritesh/advent/blob/main/2021/1.roc and Day2: https://github.com/shritesh/advent/blob/main/2021/2.roc

Looking forward to doing the rest as well.

view this post on Zulip jan kili (Oct 03 2022 at 02:54):

I'm finally reading day 1 solutions, and I'm already thrilled

view this post on Zulip jan kili (Oct 03 2022 at 02:54):

@Georges Boris Just l if l < num -> { next & result: state.result + 1 } is brilliant!

view this post on Zulip jan kili (Oct 03 2022 at 13:13):

@Georges Boris @Ghislain @Brendan Hansknecht @Ayaz Hafiz @Shritesh Bhattarai @Mike Kalvas Do I have your permission to show/mention your solutions for day 1 (and potentially other days going forward) in a public YouTube video?

view this post on Zulip Shritesh Bhattarai (Oct 03 2022 at 13:14):

Sure!

view this post on Zulip Mike Kalvas (Oct 03 2022 at 13:14):

Yeah

view this post on Zulip Tim (Oct 05 2022 at 02:57):

https://github.com/tritowntim/roctoberfest/blob/main/2021/01/main.roc

My first roc :guitar: ... array destructuring would be nice, List.len felt clunky... but it feels so Elm-y :drooling:

view this post on Zulip Luke Boswell (Oct 05 2022 at 07:31):

Can someone please have a look at my code and help me figure out what is wrong with my Task. I have been playing around for this a while, but the compiler error is a bit hard to decipher. It passes all the tests, and I am confident it reads the file correctly, just not sure about the Task.attempt which is where I think there is an error. aoc day 1 roc attempt

view this post on Zulip Ghislain (Oct 05 2022 at 09:23):

@Luke Boswell to use fileContents as a Task with Task.map, you need to remove the backpassing syntax and Task.await (otherwise its type is just a Str).
The other issue you will have is that your count is a Result U64 (inside a Task) but you try to concat it to a Str. You will need to transform it (in the same inspiration as your code, you could add countStr = Task.map count Num.toStr and Task.attempt it instead of count)

view this post on Zulip Luke Boswell (Oct 05 2022 at 11:27):

Ghislain said:

Luke Boswell to use fileContents as a Task with Task.map, you need to remove the backpassing syntax and Task.await (otherwise its type is just a Str).
The other issue you will have is that your count is a Result U64 (inside a Task) but you try to concat it to a Str. You will need to transform it (in the same inspiration as your code, you could add countStr = Task.map count Num.toStr and Task.attempt it instead of count)

Thank you @Ghislain I managed to get something working. I'm pretty happy with the result. I updated my gist in case anyone is interested.

view this post on Zulip Chris Duncan (Oct 09 2022 at 05:22):

I just finished doing Part B of the first day. Though my function is a little crazy.

getAnswer : List Nat -> Nat
getAnswer = \measurements ->
    result =
        acc, measurement <- List.walk measurements {first: Start,second: Start,third: Start, previousSum: Start, increases:0}
        when acc.first is
            Start -> {acc & first:Continue measurement}
            Continue firstMeasurement ->
                when acc.second is
                    Start -> {acc & second:Continue measurement}
                    Continue secondMeasurement ->
                        when acc.third is
                            Start -> {acc & third:Continue measurement, previousSum:Continue (firstMeasurement + secondMeasurement + measurement)}
                            Continue thirdMeasurement ->
                                when acc.previousSum is
                                    Start -> {acc & previousSum:Continue (firstMeasurement + secondMeasurement + thirdMeasurement)}
                                    Continue previousSum ->
                                        sum = measurement + secondMeasurement + thirdMeasurement
                                        newAcc = {acc & first: Continue secondMeasurement,second: Continue thirdMeasurement,third: Continue measurement, previousSum: Continue sum}
                                        if sum > previousSum
                                            then {newAcc & increases:acc.increases+1}
                                            else newAcc

Is there a way to pattern-match my values without nesting? In Elm, I would have done the following:

getAnswer : List number -> number
getAnswer measurements =
    case (acc.first, acc.second, acc.third, acc.previousSum) of
        (Start, _, _, _) -> ...

view this post on Zulip Luke Boswell (Oct 09 2022 at 05:33):

I did it similar, built a filter to take the numbers and then add them using a sliding window.

slidingWindow : List U64 -> List U64
slidingWindow = \depths ->
    depths
    |> List.walk
        { n1: Nothing, n2: Nothing, filtered: [] }
        \state, depth ->
            when Pair state.n1 state.n2 is
                Pair Nothing Nothing -> { n1: Just depth, n2: Nothing, filtered: [] }
                Pair (Just n1) Nothing -> { n1: Just depth, n2: Just n1, filtered: [] }
                Pair (Just n1) (Just n2) ->
                    { n1: Just depth, n2: Just n1, filtered: List.append state.filtered (n1 + n2 + depth) }

                Pair _ _ -> state
    |> .filtered

view this post on Zulip Luke Boswell (Oct 09 2022 at 05:36):

I had to use the [Nothing, Just U64] as the first and second numbers you need to handle as special cases. So n1 is like the n-1th index and n2 is like n-2nd index.

view this post on Zulip Brendan Hansknecht (Oct 09 2022 at 05:46):

You can on a tuple. We just don't have direct tuple syntax, so you have to use a tag technically:

when T acc.first acc.second acc.third acc.previousSum is
  T Start _ _ _ -> ....

view this post on Zulip Richard Feldman (Oct 09 2022 at 05:56):

tuples are planned, just not implemented yet :big_smile:

view this post on Zulip Chris Duncan (Oct 09 2022 at 06:24):

@Brendan Hansknecht Thank you so much! That is what I need.

view this post on Zulip Michał Łępicki (Oct 17 2022 at 07:13):

I'm late to the party, but I got day 1 working - my solutions. I already solved 10 days in Sesterl/Erlang back in December so I'll be mostly porting my solutions to Roc :)


Last updated: Jul 06 2025 at 12:14 UTC