Things just worked (language server, dbg, expect) and even though I ran into some compiler bugs this is the best experience I've had writing Roc code since I joined the project
If the roc-parser head the function getPosition like elm does, I would probably used the parser to get the positions of the numbers. @Luke Boswell Is this something you would add to the roc-parser?
I had some problems with the different number types of roc. For example, I got the following error:
The argument is an anonymous function of type:
Int Natural -> Bool
But any needs its 2nd argument to be:
Int Unsigned32 -> Bool
The only solution I found was to convert all numbers in the hole file to I32.
Another number related bug was integer addition overflowed!.
It was hard to find the place where this was happening. It was this line:
lines = List.range { start: At (number.height - 1), end: At (number.height + 1) }
number.height had type U32 and 0 - 1 is of cause is an overflow. But I would call the operation a substraction and not a addition as the error message suggested.
I pretty much wrote the whole program using roc check and then roc test occasionally. When I finally wired it all together and it compiled, there were are couple of easy to debug logic bugs ( dbg:hearts:) and then it Just Worked™ which was super fun.
# With part1, part2 at the top level:
›time./solutions/day3
real0m1.744s
user0m1.702s
sys0m0.043s
# With both definitions moved into main:
real0m0.232s
user0m0.229s
sys0m0.005s
These were debug builds, using --optimize the difference is still there, but less so (0.307s vs 0.045s real time)
Huh, things run a lot faster if you put them into main instead of at the top level.. weird.
I think you're the first person to notice this in practice! :big_smile:
this is currently the case because today the way we compile top-level values is essentially that we turn them into functions, and then call those functions every time you reference the value. There's a plan to instead evaluate them at compile time (at which point there will be no performance difference to using them this way) but we haven't started on that project yet. :big_smile:
Well that makes sense that parsing the same file thousands of times might take a second or two :big_smile:
But so far it's great! Roc does almost everything I ever wanted in a programming language, and everyone here seems super friendly and welcoming! :smiling_face:
I wrote all of the part 1 logic first and then moved onto parsing, which was an interesting change. I know it's not optimal (in terms of performance and dev speed), but it just feels so nice to parse the file to a structure and then just operate on that.
A classic off-by-one error got me stumped for a moment, because it only came up in the real input, but I managed to debug it. I've also spent some time trying to implement Eq for a custom type, but I managed to figure out how to do it by looking at the standard library.
Code: https://github.com/LoipesMas/roc-aoc2023/blob/main/3/main.roc
~30ms for part2, could probably be greatly improved with partitioning (instead of checking every * with every number...)
Not sure if anyone else does this, but for working on AOC instead of using roc check or roc test, I now just change my main function to:
main=dbg(part1p1Sample)Stout.line"done"
Enables me to just run the code and look at whatever intermediate value. Really nice repl like loop. Just see whatever value my current function generates and then add the next pipeline stage to my current function.
While doing things, I temporarily comment out the type signature for part1 so that it can return anything.
I probably should set this up with entr so it autoruns on file change.
Here's my solution for Day 3. Pain points today was Nat underflowing (it said overflow, which confused me for a second), and me using a previous state instead of updating the current one. It's a variant of the shadowing pain, kinda related to the threading issues already mentioned in #ideas > Shadowing & Redeclaration
Implementation trick
It's a bit easier to work with objects and bounding boxes than working over a 2D grid, because you don't have to deal with deltas and grid bounds checking.
I used a set of {x: Nat, y: Nat} indices to solve for adjacency. Something about this is hella slow. There's a benchmark in the gist, but the implementation using sets takes ~6 seconds to run. If I try running a build compiled with --optimize then it hangs forever while using 100% of a CPU core.
To troubleshoot I did a super naive swap, changing out the sets for lists, used concat instead of union, and searching through the two lists to find the intersection. That runs in ~2 seconds unoptimized and ~150ms optimized.
There's some inefficiency in my code but the set implementation being so much slower seems like a bug to me.
Interesting. I'll have to look at that set issue. We have something similar to anal::flat_hash_set, so it should generally be pretty fast. Though depending on the size wouldn't be surprised if skipping hashing and linearly scanning ends up being faster. But yeah, probably a bug.
I used a set of {x: Nat, y: Nat} indices to solve for adjacency. Something about this is hella slow. There's a benchmark in the gist, but the implementation using sets takes ~6 seconds to run. If I try running a build compiled with --optimize then it hangs forever while using 100% of a CPU core.
To troubleshoot I did a super naive swap, changing out the sets for lists, used concat instead of union, and searching through the two lists to find the intersection. That runs in ~2 seconds unoptimized and ~150ms optimized.
There's some inefficiency in my code but the set implementation being so much slower seems like a bug to me.
Poor performance here too at 33s
I'm running Set.fromList and Set.toList in several places. It could be the bug, or just my code isn't performant. I'm not trying to learn about performance at this point in my Roc learning journey.
And here's my code...
Happy to have finally solved this one! It took me awhile, but I learned a bunch on the way.
app"hello"packages{pf:"https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br",parser:"https://github.com/lukewilliamboswell/roc-parser/releases/download/0.3.0/-e3ebWWmlFPfe9fYrr2z1urfslzygbtQQsl69iH1qzQ.tar.br",}imports[pf.Stdout,pf.Task,"./day3-input.txt"asinput:Str,parser.Core.{Parser,const,keep,map,oneOrMore},parser.String.{anyCodeunit,digits,oneOf,parseStr,string}]provides[main]topfmain ={}<-Stdout.line"The total sum is \(part1 input)"|>Task.await{}<-Stdout.line"The gear ratios are \(part2 input)"|>Task.awaitTask.ok{}part1=\schematic->width=Str.splitschematic"\n"|>List.first|>Result.withDefault""|>Str.countGraphemes|>Num.add1#The+1isfortheNewlinecharacter|>Num.toI16identifyPartsschematic|>appendAdjacentSymbolwidth|>List.keepIf\cell->whencell.typeisCorrect->Bool.true_->Bool.false|>Set.fromList|>Set.toList|>List.map\part->whenpart.cellisPartnum->Result.withDefault(Str.toU32num)0_->0|>List.sum|>Num.toStrexpectpart1exampleInput=="4361"part2=\schematic->width=Str.splitschematic"\n"|>List.first|>Result.withDefault""|>Str.countGraphemes|>Num.add1#The+1isfortheNewlinecharacter|>Num.toI16identifyPartsschematic|>appendAdjacentSymbolwidth|>List.keepIf\cell->whencell.typeisGear_->Bool.true_->Bool.false|>Set.fromList|>Set.toList|>List.map\symbol->whensymbol.typeisGearratio->ratio_->0|>List.sum|>Num.toStrelement:Parser(ListU8)CellContentelement=oneOf[digits|>map\d->Part(Num.toStrd),string"."|>map\_->Separator,string"\n"|>map\_->NewLine,string"*"|>map\_->SymbolPossibleGear,anyCodeunit|>map\_->SymbolNotAGear,]expectparseStrelement"467"==Ok(Part"467")expectparseStrelement"."==OkSeparatorschematicParser:Parser(ListU8)(ListCellContent)schematicParser=const(\i->i)|>keep(oneOrMoreelement)expectparseStrschematicParsersmallInput==Ok[Part"11",Separator,Part"2",NewLine,SymbolNotAGear,Separator,Separator,Separator]identifyParts=\schematic->parseStrschematicParserschematic|>Result.withDefault[]|>List.walkWithIndex[]\state,cell,index->whencellisPartnum->List.concatstate(List.repeat{id:index,cell:cell,type:Unknown}(Str.countGraphemesnum))_->List.appendstate{id:index,cell:cell,type:Unknown}expectx=identifyPartssmallInputx==[{cell:Part"11",id:0,type:Unknown},{cell:Part"11",id:0,type:Unknown},{cell:Separator,id:1,type:Unknown},{cell:Part"2",id:2,type:Unknown},{cell:NewLine,id:3,type:Unknown},{cell:SymbolNotAGear,id:4,type:Unknown},{cell:Separator,id:5,type:Unknown},{cell:Separator,id:6,type:Unknown},{cell:Separator,id:7,type:Unknown}]CellType:[Unknown,Correct,Extra,GearU32,NotAPart]CellContent:[NewLine,PartStr,Separator,Symbol[PossibleGear,NotAGear],]appendAdjacentSymbol:List{cell:CellContent,type:CellType,id:Nat,},I16->List{cell:CellContent,type:CellType,id:Nat,}appendAdjacentSymbol=\identifiedParts,width->#widthshouldincludethenewlineadjacentCellIndexes=[-width-1,-width,-width+1,-1,1,width-1,width,width+1]State:List{cell:CellContent,type:CellType,id:Nat,}Cell:{cell:CellContent,type:CellType,id:Nat,}x:State,Cell,Nat->Statex=\state,cell,index->whencell.cellisPart_->adjacentSymbols=List.mapadjacentCellIndexes\relativeIndex->(Num.addrelativeIndex(Num.toI16index))|>List.keepIf\absoluteIndex->absoluteIndex>=0|>List.mapNum.toNat|>List.keepOks\absoluteIndex->List.getidentifiedPartsabsoluteIndex|>List.keepIf\adjacentCell->whenadjacentCell.cellisSymbol_->Bool.true_->Bool.falseList.appendstate{cell&type:ifList.isEmptyadjacentSymbolsthenExtraelseCorrect}SymbolsymbolType->whensymbolTypeisPossibleGear->adjacentParts=List.mapadjacentCellIndexes\relativeIndex->(Num.addrelativeIndex(Num.toI16index))|>List.keepIf\absoluteIndex->absoluteIndex>=0|>List.mapNum.toNat|>List.keepOks\absoluteIndex->List.getidentifiedPartsabsoluteIndex|>List.keepIf\adjacentCell->whenadjacentCell.cellisPart_->Bool.true_->Bool.false|>Set.fromList|>Set.toListifList.lenadjacentParts>=2then#_=debugindex"index"#_=debugcell"cell"#_=debugadjacentParts"adjacentParts"gearRatio=adjacentParts|>Set.fromList|>Set.toList|>List.map\adjacentCell->whenadjacentCell.cellisPartnum->Result.withDefault(Str.toU32num)1_->1|>List.productList.appendstate{cell&type:GeargearRatio}elseList.appendstate{cell&type:NotAPart}_->List.appendstate{cell&type:NotAPart}_->List.appendstate{cell&type:NotAPart}List.walkWithIndexidentifiedParts[]xexpectidentifyPartssmallInput|>appendAdjacentSymbol5==[{cell:Part"11",id:0,type:Correct},{cell:Part"11",id:0,type:Correct},{cell:Separator,id:1,type:NotAPart},{cell:Part"2",id:2,type:Extra},{cell:NewLine,id:3,type:NotAPart},{cell:SymbolNotAGear,id:4,type:NotAPart},{cell:Separator,id:5,type:NotAPart},{cell:Separator,id:6,type:NotAPart},{cell:Separator,id:7,type:NotAPart}]smallInput=""" 11.2 $... """|>Str.trimsmallGearInput=""" 11.2 $.*. """|>Str.trimexampleInput=""" 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.. """|>Str.trimdebug=\v,ctx->dbgctxdbgvv
Had to do a bit of a refactor for part2, but managed to reuse almost all of my code- and stuff just worked once roc dev stopped politely informing me that I was wrong about something.
Took some typing, but otherwise pretty painless day.
part2 spoilers
I spent very little on parsing today. Just made a big ol' List (List U8) and went from there.
My part-two refactor was basically just turning it into a List (List ((Nat, Nat), U8)) instead, so I could track of which specific neighbors a number has. (the (Nat, Nat) tuple is the coords of a given char, but really it's just a unique identifier).
Probably not as performant as it could be, but hey. I'm happy with this.
Really bad performance for my solutions today (takes about 10s to run both parts :grimacing:) https://github.com/keeslinp/AoC2023/blob/main/day3.roc still got both answers first try though which just shows how good the type system + expect workflow works.
I think I'm really starting to feel the pain of not having syntax highlighting setup correctly for my editor (I tried getting the treesitter stuff running but helix doesn't seem to actually display it, idk why). I kept getting a little lost, but thankfully hover from the LSP was super helpful trying to figure out how nested my lists were at any given point. Maybe a roc formatter would help me, I don't really know the best way to format things yet.
Also my solution crashes if I turn on optimize so maybe the performance wouldn't be so bad if it were on
Part 1 was pretty quick for me, but I could not reuse it for part 2, so quite some more typing for part 2. Performance is good, but I did run into a bug at one point. I will try to create a minimal example for it before opening a bug on github.
Late to the party, but here is mine (also just using Array2D, no perf problems :see_no_evil: )
The thing that bogs me down most is that dbg doesn't work during test runs (at leas on my Mac). It just terminates with [1] 85268 trace trap roc test
it used to work, but we recently changed how dbg works (to fix a bunch of bugs, among other benefits) and haven't updated tests to use that new way yet
Hi roc community! These are my solutions so far: https://github.com/ni-ko-o-kin/advent-of-code/tree/main/2023 I'm having so much fun with roc this year! A huge difference to last year where I ran into a couple of compiler bugs. And it also helped to let my brain process and digest for one year how roc works and how it differs from other languages I already know (like elm or haskell).
I look forward to seeing how outers tackle this problem. I struggled with nested lists in pipes. Sometimes I didn't realize that I was still working on the first level where I should have used List.map on the nested list. I guess this is mostly due to my brain adapting to the functional style of doing things and getting used to Roc.
A lot of things took longer than I expected, but I also had some moments of "wow, this is so cool!". Amazing work!