'Tis the season to be coding...
As @jmhorstman did a few years back, thought I'd take a crack at a few of the Advent of Code 2021 challenges with SAS. Not sure whether to be proud or ashamed of them (maybe a little of both!), but below is how I solved today's Day 21 problem with SAS array-based (Part I) and SQL-based (Part II) solutions.
Imagine I could have tapped PROC FCMP to knock out a recursive solution for Part II, but I personally found the SQL-based one pretty easy to verify against the problem description, and this a one-off problem where performance isn't a concern. Some of the solutions in other languages I reviewed employed more CS-type constructs - regex parsing, recursive functions, lambdas/closures, state vectors, dictionaries, hashing, iterators, maps, memoization and cache packages, decorators, etc. Interesting to see all of the different approaches to get the same answer, though!
My SQL solution did take about 16 seconds to run using SAS Studio on SAS OnDemand for Academics (free, if you haven't tried it!). Not too bad for an untuned program using SQL, but it'd be interesting to see how the SQL might perform on another system where more CPUs are available (for auto-scaling) and the MEMCACHE option is available for libraries.
SOLUTION-PART I
*==> Advent of Code 2021 Day 21 Part 1: SAS Array-Based Solution
See https://adventofcode.com/2021/day/21 for problem description;
data _null_; * Advent of Code, Day 21 Part 1;
array pos(*) p1-p2; array score(*) score1-score2; * Players' positions, scores;
do i=1 to dim(pos); input @"position: " pos(i); end; * Read initial positions of players;
do while(max(of score:)<1000); * Game over when a player reaches 1000;
p=coalesce((p+1)*(p<dim(pos))+(p=dim(pos)),1); * Determine current player;
do t=1 to 3; toss+1; pos(p)=mod((pos(p)+mod(toss-1,100)+1)-1,10)+1; end; * Increment player's position (position resets to 1 after 10, increment resets to 1 after 100);
score(p)+pos(p); * Add new position to score;
end;
answer=min(of score:)*toss; * Answer is minimum score * number of dice tosses;
put answer=;
datalines;
Player 1 starting position: 1
Player 2 starting position: 5
;
run;
SOLUTION-PART II
*==> Advent of Code 2021 Day 21 Part 2: SAS SQL-Based Solution
Note: Should see better performance if #CPUs>1 (SQL autoscales) and MEMLIB option available for datasets
See https://adventofcode.com/2021/day/21#part2 for problem description;
data combos; do d=1 to 3; output; end; * Possible dice face values are 1-3;
proc sql;
create table rolls as /* Table of all possible 3-dice totals (weight N used to try to reduce size/time) */
select roll, count(*) as n from (select t1.d+t2.d+t3.d as roll from combos t1, combos t2, combos t3) t4 group by 1;
data games; /* Read player starting positions, initalize scores and # games to zero */
retain p1 p2 s1 s2 0 games 1;
input @"position: " p1; input @"position: " p2; output;
datalines;
Player 1 starting position: 1
Player 2 starting position: 5
;
data wins; retain wins1 wins2 0; output; * Initialize a table to hold # games won by each player;
%macro playgames;
proc sql;
%do %until(&GamesLeft=0); /* Repeat until all games completed */
create table games as /* Use dice totals to update player 1 positions/scores & add new games */
select mod(p1+roll-1,10)+1 as p1, s1+calculated p1 as s1, p2, s2, games*n as games from games, rolls;
create table wins as /* Count games won by player1 (score of 21+) */
select wins1+coalesce(wins,0) as wins1, wins2 from wins, (select sum(games) as wins from games where s1>=21) t2;
create table games as /* Drop games won by player 1 */
select * from games where s1<21;
create table games as /* Use dice totals to update player 2 positions/scores & add new games */
select mod(p2+roll-1,10)+1 as p2, s2+calculated p2 as s2, p1, s1, games*n as games from games, rolls;
create table wins as /* Count games won by player1 (score of 21+) */
select wins1, wins2+coalesce(wins,0) as wins2 from wins, (select sum(games) as wins from games where s2>=21) t2;
create table games as /* Drop games won by player 2 */
select * from games where s2<21;
select count(*) into :GamesLeft from games; * Games remaining?;
%end;
select max(sum(wins1),sum(wins2)) as answer format=16. from wins; * Answer is # games won by winningest player;
quit;
%mend;
%playgames; /* Let's roll the dice! */
... View more