scarecrw's recent activity
-
Comment on Day 18: RAM Run in ~comp.advent_of_code
-
Comment on Day 17: Chronospatial Computer in ~comp.advent_of_code
scarecrw (edited )LinkPretty fun day! Very reminiscent of a previous year (I can't remember which one) which also relied on interpreting the given instructions. I think that day was about optimizing them, whereas today...Pretty fun day! Very reminiscent of a previous year (I can't remember which one) which also relied on interpreting the given instructions. I think that day was about optimizing them, whereas today the only necessary information was how the A register was iterated over 3 bits of at a time (I'm assuming this was the same in everyone's input, or at least similar).
I'm still getting burned by indexing from 1... Today it was that
findNext:
returns a 1-based index, or 0 on failure.Computer Simulator
Class { #name : 'Day17Computer', #superclass : 'Object', #instVars : [ 'a', 'b', 'c', 'program', 'ip', 'halted', 'output' ], #category : 'AoCDay17', #package : 'AoCDay17' } Day17Computer >> initialize [ ip := 1. halted := false. output := OrderedCollection new. ] Day17Computer >> step [ | opcode operations combo literal | (ip between: 1 and: program size) ifFalse: [ halted := true. ^ self ]. opcode := program at: ip. literal := program at: (ip + 1). combo := { 0 . 1 . 2 . 3 . a . b . c } at: ((program at: (ip + 1)) + 1). operations := Dictionary newFromPairs: { 0 . [ a := a >> combo ] . 1 . [ b := b bitXor: literal ] . 2 . [ b := combo % 8 ] . 3 . [ a ~= 0 ifTrue: [ ip := literal - 1 ] ] . 4 . [ b := b bitXor: c ] . 5 . [ output add: combo % 8 ] . 6 . [ b := a >> combo ] . 7 . [ c := a >> combo ] }. (operations at: opcode) value. ip := ip + 2 ]
Solver
Class { #name : 'Day17Solver', #superclass : 'AoCSolver', #instVars : [ 'aInitial', 'bInitial', 'cInitial', 'program' ], #category : 'AoCDay17', #package : 'AoCDay17' } Day17Solver >> parseRawData [ | nums | nums := ('\d+' asRegex matchesIn: rawData) collect: #asInteger. aInitial := nums first. bInitial := nums second. cInitial := nums third. program := nums allButFirst: 3 ] Day17Solver >> solvePart1 [ ^ $, join: ((self runComputer: aInitial) collect: #asString) ] Day17Solver >> solvePart2 [ ^ self solve2Helper: 0 finished: 0 ] Day17Solver >> runComputer: aStart [ | computer | computer := Day17Computer new a: aStart; b: bInitial; c: cInitial; program: program. [ computer halted ] whileFalse: [ computer step ]. ^ computer output ] Day17Solver >> solve2Helper: a finished: n [ | output target recResult | n = program size ifTrue: [ ^ a ]. target := program at: program size - n. 0 to: 7 do: [ :x | output := self runComputer: a << 3 + x. output first = target ifTrue: [ recResult := self solve2Helper: a << 3 + x finished: n + 1. recResult > 0 ifTrue: [ ^ recResult ] ] ]. ^ 0 ]
-
Comment on Day 16: Reindeer Maze in ~comp.advent_of_code
scarecrw While exploring Pharo before AoC started, I did notice that there are some pathfinding algorithms programmed in. I actually found them because they seem to be where the generalized graph data...While exploring Pharo before AoC started, I did notice that there are some pathfinding algorithms programmed in. I actually found them because they seem to be where the generalized graph data structure is also contained. I didn't end up using them, but I might go back and try to re-solve with that approach to learn how those work.
I ended up storing the nodes as 3D points, with the regular position in x and y and the z storing the direction. I mostly did this to avoid having to write a new class with its own hash function, but it worked quite well. I've also gotten more comfortable adding methods to base classes: today I extended the
x@y
syntax for creating aPoint
tox@y@z
to create aG3DCoordinates
as well as addedatPoint:put:
forArray2D
which I had been wanting in previous days. -
Comment on Day 15: Warehouse Woes in ~comp.advent_of_code
scarecrw Definitely my longest day as far as LOC, but that's mostly because there's lots of repeated code for the slightly different cases. Paired with the way Pharo outputs to text files it's nearing 200...Definitely my longest day as far as LOC, but that's mostly because there's lots of repeated code for the slightly different cases. Paired with the way Pharo outputs to text files it's nearing 200 lines...
Other than getting tripped up again with input parsing (I've updated my tools to avoid it going forward), this was a pretty smooth day.
-
Comment on Day 14: Restroom Redoubt in ~comp.advent_of_code
scarecrw Continuing to find more new tools that Pharo has built-in! Today was quadrantOf: which was helpful for part 1 and fourNeighbors which I could have avoided writing myself in some previous days and...Continuing to find more new tools that Pharo has built-in! Today was
quadrantOf:
which was helpful for part 1 andfourNeighbors
which I could have avoided writing myself in some previous days and helped for my approach to part 2.If I have time this weekend I might look a bit more into Morphs and how to set up some useful displays for future days. In the past I always just relied on ASCII displays, but Morphs seem to make generating simple visualizations very straightforward, so worth checking out.
-
Comment on Day 13: Claw Contraption in ~comp.advent_of_code
scarecrw You're absolutely right that the tools to edit the environment and built-ins is probably the coolest part. Numerous times now I've found myself thinking "oh man, I wish that X had a Y feature..."...You're absolutely right that the tools to edit the environment and built-ins is probably the coolest part. Numerous times now I've found myself thinking "oh man, I wish that X had a Y feature..." before realizing, I can just add that! That and the tests/debugging definitely set it apart from other languages in how you work.
I've definitely been learning a lot as I go! Most days I'm kicking myself as I find a tool that would have been very helpful on a previous problem. I looked at those MOOC courses myself, and started through the first couple weeks of one, but ended up finding this book more useful: Pharo By Example.
The only pain point so far is that finding/guessing method names and what they do has not been very intuitive. The aim of having the names flow more like natural language is neat, but ends up making it tough to get used to, coming from other languages.
-
Comment on Day 13: Claw Contraption in ~comp.advent_of_code
scarecrw (edited )LinkProbably one of the easier days so far for most of the logic. I guess if you haven't dealt with math in a bit this could be tricky, but compared to some previous AoC math-y problems this was...Probably one of the easier days so far for most of the logic. I guess if you haven't dealt with math in a bit this could be tricky, but compared to some previous AoC math-y problems this was fairly tame.
I spent most of my time trying to get regex working properly.
Smalltalk Solution
Class { #name : 'Day13Solver', #superclass : 'AoCSolver', #instVars : [ 'machines' ], #category : 'AoCDay13', #package : 'AoCDay13' } Day13Solver >> parseRawData [ machines := ('<n><n>' expandMacros split: rawData) collect: [ :machineString | Day13Machine newFromString: machineString ] ] Day13Solver >> solvePart1 [ ^ (machines collect: [ :machine | machine tokensToWin]) sum ] Day13Solver >> solvePart2 [ ^ (machines collect: [ :machine | machine tokensToWinAlt]) sum ] Class { #name : 'Day13Machine', #superclass : 'Object', #instVars : [ 'aButton', 'bButton', 'prizeLocation', 'prizeLocationAlt' ], #category : 'AoCDay13', #package : 'AoCDay13' } Day13Machine class >> newFromString: aString [ | machine re nums | machine := self new. re := '\d+' asRegex. nums := (re matchesIn: aString) collect: #asInteger. machine aButton: nums first @ nums second. machine bButton: nums third @ nums fourth. machine prizeLocation: nums fifth @ nums sixth. machine prizeLocationAlt: machine prizeLocation + 10000000000000. ^ machine ] Day13Machine >> tokensToWin [ 0 to: 100 do: [ :aPresses | 0 to: 100 do: [ :bPresses | aPresses * aButton + (bPresses * bButton) = prizeLocation ifTrue: [ ^ 3 * aPresses + bPresses ] ] ]. ^ 0 ] Day13Machine >> tokensToWinAlt [ | l1 l2 result | l1 := GLine a: aButton x b: bButton x c: prizeLocationAlt x negated. l2 := GLine a: aButton y b: bButton y c: prizeLocationAlt y negated. result := (l1 intersectionsWith: l2) first asPoint. ^ result isIntegerPoint ifTrue: [ 3 * result x + result y ] ifFalse: [ 0 ] ]
-
Comment on Day 12: Garden Groups in ~comp.advent_of_code
scarecrw Ugh, first day truly annoyed by Pharo... I had the logic quickly, but ran into so many issues just trying to store a grid. There's a built in Array2D, but it doesn't have a neat way to create it...Ugh, first day truly annoyed by Pharo... I had the logic quickly, but ran into so many issues just trying to store a grid. There's a built in
Array2D
, but it doesn't have a neat way to create it from the original data and is also deprecated. I found Containers-Array2D, which hasCTArray2D
withfromRows
, but I spent far too long before realizing that it uses points as x-y instead of row-column... but it does includeCTAlternateArray2D
which uses row-column, but doesn't havefromRows
!I like a lot of what Pharo has as far as tools, but to have something as basic as a 2D array have 3+ inconsistent versions is just ridiculous. I haven't even looked at
CTNewArray2D
,CTNewArray2DRowsAndColumns
, orCTNewArray2DXAndY
... -
Comment on Day 11: Plutonian Pebbles in ~comp.advent_of_code
scarecrw Probably would have been my fastest day yet, but I could not get the caching working on the recursive function. Pharo Dictionaries have a at:ifAbsentPut: which seemed perfect for implementing a...Probably would have been my fastest day yet, but I could not get the caching working on the recursive function. Pharo Dictionaries have a
at:ifAbsentPut:
which seemed perfect for implementing a simple cache, but I haven't been able to figure out why I couldn't get it to work. I ended up using mutual recursion and it worked alright, but I'll have to dig deeper to figure out what's up.I miss python's
@cache
...Smalltalk Solution
Class { #name : 'Day11Solver', #superclass : 'AoCSolver', #instVars : [ 'stones', 'cache' ], #category : 'AoCDay11', #package : 'AoCDay11' } Day11Solver >> blink: blinkCount timesStone: stone [ | s firstHalf secondHalf | blinkCount = 0 ifTrue: [ ^ 1 ]. stone = 0 ifTrue: [ ^ self cachedBlink: blinkCount - 1 timesStone: 1 ]. s := stone asString. s size even ifTrue: [ firstHalf := (s first: s size // 2) asInteger. secondHalf := (s allButFirst: s size // 2) asInteger. ^ (self cachedBlink: blinkCount - 1 timesStone: firstHalf) + (self cachedBlink: blinkCount - 1 timesStone: secondHalf) ]. ^ self cachedBlink: blinkCount - 1 timesStone: stone * 2024 ] Day11Solver >> cachedBlink: blinkCount timesStone: stone [ | key | key := { blinkCount . stone }. (cache includesKey: key) ifTrue: [ ^ cache at: key ] ifFalse: [ ^ cache at: key put: (self blink: blinkCount timesStone: stone). ] ] Day11Solver >> parseRawData [ cache := Dictionary new. stones := (rawData splitOn: ' ') collect: #asInteger. ] Day11Solver >> solvePart1 [ ^ (stones collect: [ :stone | self cachedBlink: 25 timesStone: stone ]) sum ] Day11Solver >> solvePart2 [ ^ (stones collect: [ :stone | self cachedBlink: 75 timesStone: stone ]) sum ]
-
Comment on Day 10: Hoof It in ~comp.advent_of_code
scarecrw (edited )LinkManaged to keep things simple enough for today, including reusing my code from Part 1 for Part 2. I'm finding a bunch of tools that I wished I'd found previous days like flatCollect: and...Managed to keep things simple enough for today, including reusing my code from Part 1 for Part 2. I'm finding a bunch of tools that I wished I'd found previous days like
flatCollect:
anddigitValue
.Smalltalk Solution
Class { #name : 'Day10Solver', #superclass : 'AoCSolver', #instVars : [ 'trailMap', 'trailHeads' ], #category : 'AoCDay10', #package : 'AoCDay10' } Day10Solver >> parseRawData [ | temp | temp := rawData lines collect: [ :row | row collect: #digitValue as: Array ]. trailMap := Array2D rows: temp size columns: (temp at: 1) size tabulate: [ :r :c | (temp at: r) at: c ]. trailHeads := OrderedCollection new. trailMap withIndicesDo: [ :x :r : c | x = 0 ifTrue: [ trailHeads add: r @ c ] ] ] Day10Solver >> solvePart1 [ ^ (trailHeads collect: [ :trailHead | self numPathsStartingAt: trailHead unique: false ]) sum ] Day10Solver >> solvePart2 [ ^ (trailHeads collect: [ :trailHead | self numPathsStartingAt: trailHead unique: true ]) sum ] Day10Solver >> isPointInRange: aPoint [ ^ aPoint > (0@0) and: aPoint <= (trailMap numberOfRows @ trailMap numberOfColumns) ] Day10Solver >> neighborsOf: aPoint [ | neighbors height | height := trailMap atPoint: aPoint. neighbors := { (aPoint + (1 @ 0)). (aPoint + (0 @ 1)). (aPoint - (1 @ 0)). (aPoint - (0 @ 1)) }. ^ neighbors select: [ :p | (self isPointInRange: p) and: [ (trailMap atPoint: p) = (height + 1) ] ] ] Day10Solver >> numPathsStartingAt: aPoint unique: aBoolean [ | curr pathCount | curr := aBoolean ifTrue: [ OrderedCollection new ] ifFalse: [ Set new ]. curr add: aPoint. pathCount := 0. [ curr isEmpty ] whileFalse: [ curr := curr flatCollect: [ :p | (trailMap atPoint: p) = 9 ifTrue: [ pathCount := pathCount + 1. #( ) ] ifFalse: [ self neighborsOf: p ] ] ]. ^ pathCount ]
-
Comment on Day 9: Disk Fragmenter in ~comp.advent_of_code
scarecrw Didn't run into too many problems with the solution itself, but lots of bugs along the way. Before coming up with a better solution I was trying to just get a slow version working inserting with...Didn't run into too many problems with the solution itself, but lots of bugs along the way. Before coming up with a better solution I was trying to just get a slow version working inserting with
insert:before:
, not realizing that it's a private method and doesn't actually allow for insertions...Both reasonably efficient (~0.01 and ~1 seconds), but I'm still thinking of faster approaches for part 2.
Part 2 Spoilers
Since all the gap sizes are 1-9, I suspect you could speed things up by storing separate lists of each size, sorted by position in memory. Then just take the head of each list from the needed size and above and pick the one with the minimum position. I haven't implemented this, but I think it should work and reduce the time substantially.
-
Comment on Day 8: Resonant Collinearity in ~comp.advent_of_code
scarecrw Nothing too special today. I'm enjoying having built-in points and rectangles, though I'm still getting tripped up indexing from 1. I made use of combinations:atATimeDo: which, along with other...Nothing too special today. I'm enjoying having built-in points and rectangles, though I'm still getting tripped up indexing from 1. I made use of
combinations:atATimeDo:
which, along with other combinatorics tools, will likely come back in future days. -
Comment on Day 7: Bridge Repair in ~comp.advent_of_code
scarecrw (edited )LinkFirst day actually feeling good about my solution! I'm sure I'm still doing plenty of unidiomatic things, but most of the solution came together nicely. Two things I'm happy with: Managing to pass...First day actually feeling good about my solution! I'm sure I'm still doing plenty of unidiomatic things, but most of the solution came together nicely. Two things I'm happy with:
- Managing to pass operators to reuse the same method for both parts
- Being able to just add a new operator to the Integer class
The whole setup of Pharo with images is weird, but seems to really encourage doing things like messing with built-in classes. It's very fun to just decide "I guess Integers do that now!"
Smalltalk Solution
Class { #name : 'Day7Solver', #superclass : 'AoCSolver', #instVars : [ 'equations' ], #category : 'AoCDay7', #package : 'AoCDay7' } Day7Solver >> parseRawData [ equations := rawData lines collect: [ :eqString | Day7Equation newFromString: eqString ] ] Day7Solver >> solvePart1 [ ^ ((equations select: [ :eq | eq canBeSolved: { #+. #* } ]) collect: #testValue) sum ] Day7Solver >> solvePart2 [ ^ ((equations select: [ :eq | eq canBeSolved: { #+. #*. #|| } ]) collect: #testValue) sum ] Class { #name : 'Day7Equation', #superclass : 'Object', #instVars : [ 'testValue', 'terms' ], #category : 'AoCDay7', #package : 'AoCDay7' } Day7Equation class >> newFromString: aString [ | parts terms | parts := aString splitOn: ': '. terms := (parts second splitOn: ' ') collect: #asInteger. ^ self new testValue: parts first asInteger; terms: terms ] Day7Equation >> canBeSolved: operators [ | totals | totals := { terms first }. terms allButFirstDo: [ :term | totals := (operators collect: [ :op | totals collect: [ :prev | prev perform: op with: term ] ]) flattened select: [ :x | x <= testValue ] ]. ^ totals includes: testValue ] Integer >> || anInteger ^ (self asString , anInteger asString) asInteger
-
Comment on Day 6: Guard Gallivant in ~comp.advent_of_code
scarecrw Did the brute-force the same as others; about 35 seconds to run. Not super proud to be getting slow execution times already at day 6, but I wasn't aiming for time this year so we'll push onward. I...Did the brute-force the same as others; about 35 seconds to run. Not super proud to be getting slow execution times already at day 6, but I wasn't aiming for time this year so we'll push onward.
I figured out how to make a class hashable, which I suspect will be a useful tool, and it seems like Pharo's
clone
is sufficient for copying at least non-nested objects. I'd like to explore more about the debugger tools. Being able to live edit methods is super cool, but I suspect there's a lot more I'm missing. -
Comment on Day 5: Print Queue in ~comp.advent_of_code
scarecrw Sigh... I spent probably a third of my time on parsing because when I copied the sample data it just used carriage returns but my actual data used carriage returns and line feeds. I was glad I...Sigh... I spent probably a third of my time on parsing because when I copied the sample data it just used carriage returns but my actual data used carriage returns and line feeds.
I was glad I caught that the ruleset was complete. I almost started working out how to order based on a chain of rules (I think that was part of a previous year's puzzle) but correctly guessed that was overkill for day 5.
-
Comment on Day 4: Ceres Search in ~comp.advent_of_code
scarecrw (edited )LinkI'm very much enjoying having a Point class with simple syntax, but I can't say it's making up for the rest of Pharo's syntax being kind of a pain. It's tough to go from: arr[r-1][c-1] + arr[r][c]...I'm very much enjoying having a Point class with simple syntax, but I can't say it's making up for the rest of Pharo's syntax being kind of a pain. It's tough to go from:
arr[r-1][c-1] + arr[r][c] + arr[r+1][c+1]
for finding a diagonal string, to:
String withAll: { (self at: aPoint - (1 @ 1)) . (self at: aPoint) . (self at: aPoint + (1 @ 1)) }
Maybe I'm just not finding the cleanest approaches, but it's frustrating.
I have, however, been enjoying the pressure to heavily decompose tasks and easily add tests. I feel like I'm leaving each day in a state that would be easily refactored or improved.
-
Comment on Day 3: Mull It Over in ~comp.advent_of_code
scarecrw Intcode! that was the first year I participated, and definitely got me hooked. I feel like it's been teased a few times since then but we haven't gotten anything quite the same.Intcode! that was the first year I participated, and definitely got me hooked. I feel like it's been teased a few times since then but we haven't gotten anything quite the same.
-
Comment on Day 3: Mull It Over in ~comp.advent_of_code
scarecrw Well this was frustrating. No problems solving it, but every step felt like I was going against the grain. I couldn't get regex groups to work so just fell back on string manipulation and my part...Well this was frustrating. No problems solving it, but every step felt like I was going against the grain. I couldn't get regex groups to work so just fell back on string manipulation and my part 2 didn't feel great.
I'm going to have to dive deeper into Pharo's regex tools, as I'm sure many future days are going to be a pain if I can't get that working well.
Smalltalk Solution
Class { #name : 'Day3Solver', #superclass : 'AoCSolver', #instVars : [ 'operations' ], #category : 'AoCDay3', #package : 'AoCDay3' } Day3Solver >> evalMul: aString [ | idx val1 val2 | idx := aString indexOf: $,. val1 := (aString copyFrom: 5 to: idx) asNumber. val2 := (aString copyFrom: (idx + 1) to: (aString size - 1)) asNumber. ^ val1 * val2 ] Day3Solver >> parseRawData [ operations := rawData regex: 'mul\(\d{1,3},\d{1,3}\)|do\(\)|don''t\(\)' matchesCollect: [ :x | x ] ] Day3Solver >> solvePart1 [ | mulOps | mulOps := operations select: [ :op | op beginsWith: 'mul' ]. ^ (mulOps collect: [ :mulOp | self evalMul: mulOp ]) sum ] Day3Solver >> solvePart2 [ | total shouldMul | total := 0. shouldMul := true. operations do: [ :op | ((op beginsWith: 'mul') and: shouldMul) ifTrue: [ total := total + (self evalMul: op) ] ifFalse: [ shouldMul := op = 'do()' ] ]. ^ total ]
-
Comment on Day 2: Red-Nosed Reports in ~comp.advent_of_code
scarecrw I don't know if there's that much of an easier way, but the main idiomatic difference would be using list comprehension instead of mapping. Here's a one-liner if you want which would leave you...I don't know if there's that much of an easier way, but the main idiomatic difference would be using list comprehension instead of mapping.
Here's a one-liner if you want which would leave you with a list of lists of ints:
reports = [[int(x) for x in row.split()] for row in input.split('\n')]
You can also use
map
, as you've done, though I'll note you can actually just passint
as a function, rather than writing a lambda:levels = list(map(int, report.split(' '))
Last little idiomatic tip, you probably want to avoid using
input
as a variable name, as it overshadows the built-in function of the same name. -
Comment on Day 2: Red-Nosed Reports in ~comp.advent_of_code
scarecrw (edited )LinkSo far keeping up OOP and TDD! This took... far longer than it should have, but I'm definitely learning more about Smalltalk. Discovering overlappingPairsCollect and copyWithoutIndex handled a lot...So far keeping up OOP and TDD! This took... far longer than it should have, but I'm definitely learning more about Smalltalk. Discovering
overlappingPairsCollect
andcopyWithoutIndex
handled a lot of the heavy lifting here. Also, Pharo shows little green dots when the tests pass, and getting them all to light up is a wonderful way to tie problem completion to dopamine release.I think I'll have to swap to just posting a github link and snippets going forward. Getting to a somewhat reasonable length means cutting out the comments, protocols, accessors, tests...
Smalltalk Solution
Class { #name : 'Day2Solver', #superclass : 'AoCSolver', #instVars : [ 'reports' ], #category : 'AoCDay2', #package : 'AoCDay2' } Day2Solver >> parseRawData [ reports := rawData collect: [ :s | Day2Report newFromString: s]. ] Day2Solver >> solvePart1 [ ^ reports count: [ :report | report isSafe ] ] Day2Solver >> solvePart2 [ ^ reports count: [ :report | report isNearlySafe ] ] Class { #name : 'Day2Report', #superclass : 'OrderedCollection', #category : 'AoCDay2', #package : 'AoCDay2' } Day2Report class >> newFromString: aString [ ^ self newFrom: ((aString findTokens: ' ') collect: [ :x | x asNumber ]) ] Day2Report >> isDecreasing [ ^ (self overlappingPairsCollect: [ :a :b | a > b ]) allSatisfy: [ :x | x ]. ] Day2Report >> isIncreasing [ ^ (self overlappingPairsCollect: [ :a :b | a < b ]) allSatisfy: [ :x | x ]. ] Day2Report >> isNearlySafe [ ^ (1 to: self size) anySatisfy: [ :idx | (self copyWithoutIndex: idx) isSafe] ] Day2Report >> isSafe [ ^ (self isIncreasing or: self isDecreasing) and: self levelsAreSimilar ] Day2Report >> levelsAreSimilar [ ^ (self overlappingPairsCollect: [ :a :b | (a - b) abs between: 1 and: 3]) allSatisfy: [ :x | x ]. ]
Fastest day so far! That's not saying too much, as I wasn't particularly going for speed this year, but I'm happy that I'm getting more fluent with Pharo and not having to spend time searching around too much.
I just used BFS, which was fast enough when combined with a binary search for part 2. Thankfully, I've learned from previous years not to use an actual 2D grid when possible, and instead just use borders and a set of obstacles, which made running the search with a different collection of obstacles trivial.
Smalltalk Solution