diff --git a/2020/01/part1.pl b/2020/01/part1.pl new file mode 100644 index 0000000..dbebab1 --- /dev/null +++ b/2020/01/part1.pl @@ -0,0 +1,13 @@ +use 5.20.0; +use warnings; + +use Path::Tiny; + +my @expenses = path('input')->lines; + +while(my $this = shift @expenses) { + my $that = 2020 - $this; + next unless grep { $_ == $that } @expenses; + + print join " ", $this, $that, $this*$that; +} diff --git a/2020/01/part2.pl b/2020/01/part2.pl new file mode 100644 index 0000000..5f92118 --- /dev/null +++ b/2020/01/part2.pl @@ -0,0 +1,30 @@ +use 5.20.0; +use warnings; + +use Path::Tiny; +use List::AllUtils qw/ product /; + +use experimental qw/ signatures /; + +my @expenses = path('input')->lines; + +$, = " "; +print product find_group( \@expenses, 2020, 3, [] )->@*; + +sub find_group($list,$goal,$n,$so_far) { + + if( $n == 1 ) { + if( grep { $_ == $goal } @$list ) { + return [ @$so_far, $goal ]; + }; + return; + } + + my @list = grep { $_ < $goal } @$list; + + while ( my $next = shift @list ) { + my $result = find_group( \@list, $goal - $next, $n-1, [ @$so_far, $next ] ) or next; + + return $result; + } +} diff --git a/2020/02/part1.pl b/2020/02/part1.pl new file mode 100644 index 0000000..81765ba --- /dev/null +++ b/2020/02/part1.pl @@ -0,0 +1,22 @@ +use 5.20.0; +use warnings; + +use Path::Tiny; + +use experimental qw/ + signatures + postderef +/; + +my @lines = path(shift)->lines; + +print scalar grep { is_valid($_) } @lines; + +sub is_valid($line) { + $line =~ /(?\d+)-(?\d+) (?\w): (?\w+)/; + + my $min = $+{min}; + my $max = $+{max}; + my @matches = $+{password} =~ /$+{letter}/g; + return ( @matches >= $min and @matches <= $max ); +} diff --git a/2020/02/part2.pl b/2020/02/part2.pl new file mode 100644 index 0000000..d958561 --- /dev/null +++ b/2020/02/part2.pl @@ -0,0 +1,20 @@ +use 5.20.0; +use warnings; + +use Path::Tiny; + +use experimental qw/ + signatures + postderef +/; + +my @lines = path(shift)->lines; + +print scalar grep { is_valid($_) } @lines; + +sub is_valid($line) { + $line =~ /(?\d+)-(?\d+) (?\w): (?\w+)/; + + return 1 == grep { $_ eq $+{letter} } + (split '', $+{password})[ map { $_ -1 } @+{'min','max'} ]; +} diff --git a/2020/03/part1.pl b/2020/03/part1.pl new file mode 100644 index 0000000..a96b27a --- /dev/null +++ b/2020/03/part1.pl @@ -0,0 +1,24 @@ +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef +/; + +use Path::Tiny; + +my @forest = path(shift)->lines({chomp => 1 }); + +my $trees = 0; + +my $i = 0; +for my $line ( @forest ) { + $trees++ if '#' eq substr $line, $i, 1; + $i+=3; + $i %= length $line; +} + +print $trees; + + diff --git a/2020/03/part2.pl b/2020/03/part2.pl new file mode 100644 index 0000000..4ad18d9 --- /dev/null +++ b/2020/03/part2.pl @@ -0,0 +1,46 @@ +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef +/; + +use Path::Tiny; + +my @forest = path(shift)->lines({chomp => 1 }); + + +sub go_down($forest,$right,$down=1) { + my $trees = 0; + my @forest = @$forest; + + my $line = shift @forest; + my $i = 0; + + use DDP; + + while( $line) { + + + $trees++ if '#' eq substr $line, $i, 1; + $i+=$right; + $i %= length $line; + + $line = shift @forest for 1..$down; + } + + return $trees +} + +use List::AllUtils qw/ product /; +$, = " "; +print product map { go_down([@forest],@$_) } ( + [1,1], + [3,1], + [5,1], + [7,1], + [1,2], +) + + diff --git a/2020/03/test b/2020/03/test new file mode 100644 index 0000000..7e88cdc --- /dev/null +++ b/2020/03/test @@ -0,0 +1,11 @@ +..##....... +#...#...#.. +.#....#..#. +..#.#...#.# +.#...##..#. +..#.##..... +.#.#.#....# +.#........# +#.##...#... +#...##....# +.#..#...#.# diff --git a/2020/04/part1.pm b/2020/04/part1.pm new file mode 100644 index 0000000..271320e --- /dev/null +++ b/2020/04/part1.pm @@ -0,0 +1,25 @@ +use 5.20.0; +use warnings; + +use Path::Tiny; +use List::AllUtils qw/all/; + +use parent qw/ Exporter/; + +use experimental qw/ + signatures + postderef + /; + +our @EXPORT = qw/ valid_passports /; + +sub valid_passports($file) { + my $data = path($file)->slurp; + + my @passports = map { +{ split /:| |\n/ } } split "\n\n", $data; + + return scalar grep { + my $p = $_; + all { $p->{$_} } qw/ byr iyr eyr hgt hcl ecl pid / + } @passports; +} diff --git a/2020/04/part2.pm b/2020/04/part2.pm new file mode 100644 index 0000000..df36605 --- /dev/null +++ b/2020/04/part2.pm @@ -0,0 +1,47 @@ +use 5.20.0; +no warnings; + +use Path::Tiny; +use List::AllUtils qw/all/; + +use parent qw/ Exporter/; + +use experimental qw/ + signatures + postderef + /; + +our @EXPORT = qw/ valid_passports_2 validate_passport /; + +my %validator = ( + byr => sub($x) { $x >= 1920 and $x <= 2002 }, + iyr => sub($x) { $x >= 2010 and $x <= 2020 }, + eyr => sub($x) { $x >= 2020 and $x <= 2030 }, + hgt => sub($x) { $x =~ /(in|cm)$/ and ( + $& eq 'in' ? ( $x >= 59 and $x <= 76) :( $x >= 150 and $x <= 193) + )}, + hcl => sub($x) { + !!($x =~ /^#[0-9a-f]{6}$/) + }, + ecl => sub($x) { $x =~ /^(amb|blu|brn|gry|grn|hzl|oth)$/ }, + pid => sub($x) { $x =~ /^\d{9}$/ }, +); + +use List::AllUtils qw/ pairmap /; + +sub validate_passport(%passport) { + pairmap { $a => $validator{$a}->($b) } %passport; +} + +sub valid_passports_2($file) { + my $data = path($file)->slurp; + + my @passports = map { +{ split /:| |\n/ } } split "\n\n", $data; + + return scalar grep { + my $p = $_; + all { $validator{$_}->($p->{$_}) } qw/ byr iyr eyr hgt hcl ecl pid / + } @passports; +} + + diff --git a/2020/04/test b/2020/04/test new file mode 100644 index 0000000..0ff208e --- /dev/null +++ b/2020/04/test @@ -0,0 +1,13 @@ +ecl:gry pid:860033327 eyr:2020 hcl:#fffffd +byr:1937 iyr:2017 cid:147 hgt:183cm + +iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884 +hcl:#cfa07d byr:1929 + +hcl:#ae17e1 iyr:2013 +eyr:2024 +ecl:brn pid:760753108 byr:1931 +hgt:179cm + +hcl:#cfa07d eyr:2025 pid:166559648 +iyr:2011 ecl:brn hgt:59in diff --git a/2020/04/tests.t b/2020/04/tests.t new file mode 100644 index 0000000..4c4186d --- /dev/null +++ b/2020/04/tests.t @@ -0,0 +1,11 @@ +use Test::More; + +BEGIN { push @INC, '.'; } + +use part1; + +is valid_passports('test') => 2; + +is valid_passports('input') => 210; + +done_testing(); diff --git a/2020/04/tests2.t b/2020/04/tests2.t new file mode 100644 index 0000000..dd8c86e --- /dev/null +++ b/2020/04/tests2.t @@ -0,0 +1,19 @@ +use Test2::V0; + +BEGIN { push @INC, '.'; } + +use List::AllUtils qw/ pairmap /; +use part2; + +is { pairmap { $a => !!$b } validate_passport( + pid => '087499704', hgt => '74in', + ecl => 'grn', iyr => 2012, eyr => 2030, byr => 1980, + hcl => '#623a2f') +} => { + map { + $_ => 1 } qw/ pid hgt ecl iyr eyr byr hcl / +}; + +is valid_passports_2('input') => 131; + +done_testing(); diff --git a/2020/05/part1.pm b/2020/05/part1.pm new file mode 100644 index 0000000..df7679a --- /dev/null +++ b/2020/05/part1.pm @@ -0,0 +1,34 @@ +package part1; + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef + switch + /; + +use parent qw/ Exporter::Tiny /; + +our @EXPORT = qw/ resolve /; + +sub resolve($code) { + my @row = ( 0, 127 ); + my @column = ( 0, 7 ); + + for ( split '', $code ) { + when ('F') { $row[1] = $row[0] + int( ( $row[1] - $row[0] ) / 2 ) } + when ('B') { $row[0] = $row[0] + 1 + int( ( $row[1] - $row[0] ) / 2 ) } + when ('L') { + $column[1] = $column[0] + int( ( $column[1] - $column[0] ) / 2 ) + } + when ('R') { + $column[0] = $column[0] + 1 + + int( ( $column[1] - $column[0] ) / 2 ) + } + + } + + return 8 * $row[0] + $column[0]; +} diff --git a/2020/05/part2.pm b/2020/05/part2.pm new file mode 100644 index 0000000..8a1bee6 --- /dev/null +++ b/2020/05/part2.pm @@ -0,0 +1,26 @@ +package part2; + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef + switch + /; + +use part1; + +sub solution(@codes) { + my @positions = sort { $a <=> $b } map { resolve($_) } @codes; + + my $i = $positions[0]; + + for (@positions) { + return $_ - 1 if $_ != $i++; + } + +} + +1; + diff --git a/2020/05/tests.t b/2020/05/tests.t new file mode 100644 index 0000000..e2efe4a --- /dev/null +++ b/2020/05/tests.t @@ -0,0 +1,21 @@ +use Test2::V0; + +BEGIN { push @INC, '.' } + +use Path::Tiny; +use List::AllUtils qw/ max /; + +use part1; +use part2; + +subtest "part1" => sub { + is resolve('BFFFBBFRRR') => 567; + + is max( map { resolve($_) } path('input')->lines ) => 813; +}; + +subtest part2 => sub { + is part2::solution( path('input')->lines ) => 612; +}; + +done_testing(); diff --git a/2020/06/part1.pm b/2020/06/part1.pm new file mode 100644 index 0000000..ab081a3 --- /dev/null +++ b/2020/06/part1.pm @@ -0,0 +1,21 @@ +package part1; + +use 5.20.0; +use warnings; + +use Path::Tiny; +use List::AllUtils qw/ sum /; + +use experimental qw/ + signatures + postderef + /; + +sub solution($input) { + return sum map { + my %x = map { $_ => $_ } /(\w)/g; + scalar keys %x; + } split "\n\n", $input; +} + +1; diff --git a/2020/06/part2.pm b/2020/06/part2.pm new file mode 100644 index 0000000..f7140c9 --- /dev/null +++ b/2020/06/part2.pm @@ -0,0 +1,18 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ sum reduce /; +use Set::Object qw/ set /; + +use experimental qw/ signatures postderef /; + +sub solution($input) { + return sum map { + my $answers = reduce { $a * $b } map { set( split '' ) } split "\n"; + $answers->size; + } split "\n\n", $input; +} + +1; diff --git a/2020/06/tests.t b/2020/06/tests.t new file mode 100644 index 0000000..14d2577 --- /dev/null +++ b/2020/06/tests.t @@ -0,0 +1,11 @@ +use Test2::V0; + +BEGIN { push @INC, '.'; } +use part1; +use part2; +use Path::Tiny; + +is part1::solution( path('input')->slurp ) => 6633; +is part2::solution( path('input')->slurp ) => 3202; + +done_testing(); diff --git a/2020/07/part1.pm b/2020/07/part1.pm new file mode 100644 index 0000000..7f03eb8 --- /dev/null +++ b/2020/07/part1.pm @@ -0,0 +1,42 @@ +package part1; + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef +/; + +sub parse_line($line) { + $line =~ s/(\w+ \w+) bags//; + my $bag = $1; + + my %content = (); + + while ( $line =~ /(\d+) (\w+ \w+) bag/g ) { + $content{$2} = $1; + } + + return $bag => \%content; +} + +sub contains( $name, $target, $bags ) { + + return 1 if $bags->{$name}{$target}; + + for ( keys $bags->{$name}->%* ) { + return 1 if contains( $_, $target, $bags ); + } + + return 0; +} + +sub solution(@lines) { + my %bags = map { parse_line($_) } @lines; + + return scalar grep { contains($_, 'shiny gold', \%bags) } keys %bags; + +} + +1; diff --git a/2020/07/part2.pl b/2020/07/part2.pl new file mode 100644 index 0000000..5bb4edb --- /dev/null +++ b/2020/07/part2.pl @@ -0,0 +1,29 @@ +package part2; + + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef +/; + +require './part1.pm'; + +sub contains($name, $bags) { + my $contained = 0; + + while( my( $name, $qty ) = each $bags->{$name}->%* ) { + $contained += $qty * ( 1+ contains($name, $bags)); + } + + return $contained; +} + + +sub solution(@lines) { + my %bags = map { part1::parse_line($_) } @lines; + + return contains('shiny gold', \%bags ); +} diff --git a/2020/07/tests.t b/2020/07/tests.t new file mode 100644 index 0000000..38389ea --- /dev/null +++ b/2020/07/tests.t @@ -0,0 +1,45 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pl'; + +my $sample = <<'END'; +light red bags contain 1 bright white bag, 2 muted yellow bags. +dark orange bags contain 3 bright white bags, 4 muted yellow bags. +bright white bags contain 1 shiny gold bag. +muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. +shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. +dark olive bags contain 3 faded blue bags, 4 dotted black bags. +vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. +faded blue bags contain no other bags. +dotted black bags contain no other bags. +END + +subtest part1 => sub { + is part1::solution(split "\n", $sample) => 4; + + is part1::solution(path('input')->lines) => 278; +}; + +subtest part2 => sub { + is part2::solution( sample2() ) => 126, 'example'; + + is part2::solution(path('input')->lines) => 45157, 'solution'; +}; + + +done_testing(); + +sub sample2 { + return split "\n", <<'END'; +shiny gold bags contain 2 dark red bags. +dark red bags contain 2 dark orange bags. +dark orange bags contain 2 dark yellow bags. +dark yellow bags contain 2 dark green bags. +dark green bags contain 2 dark blue bags. +dark blue bags contain 2 dark violet bags. +dark violet bags contain no other bags. +END +} + diff --git a/2020/08/part1.pm b/2020/08/part1.pm new file mode 100644 index 0000000..5345c90 --- /dev/null +++ b/2020/08/part1.pm @@ -0,0 +1,43 @@ +package part1; + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef + switch +/; + +sub parse_code($lines) { + return map { [ split ] } + split "\n", $lines +} + +sub run_once($index,$accum,$code) { + my( $op, $val ) = $code->[$index]->@*; + + given( $op ) { + when( 'nop' ) { $index++ } + when( 'acc' ) { $index++; $accum += $val; } + when( 'jmp' ) { $index += $val; } + } + + return ( $index, $accum ); +} + +sub solution($lines) { + my @code = parse_code($lines); + + my $index = 0; + my $accum = 0; + my %seen; + + until( $seen{$index}++ ) { + ($index,$accum) = run_once($index,$accum,\@code); + } + + return $accum; +} + +1; diff --git a/2020/08/part2.pm b/2020/08/part2.pm new file mode 100644 index 0000000..8c9077f --- /dev/null +++ b/2020/08/part2.pm @@ -0,0 +1,44 @@ +package part2; + +use 5.20.0; +use warnings; + +require './part1.pm'; + +use experimental qw/ + signatures + postderef + switch +/; + +sub run_instance(@code) { + + my $index = 0; + my $accum = 0; + my %seen; + + until( $seen{$index}++ ) { + ($index,$accum) = part1::run_once($index,$accum,\@code); + + return $accum if $index >= @code; + } + + return undef; +} + +sub solution($lines) { + my @code = part1::parse_code($lines); + + for(0..$#code) { + next if $code[$_][0] eq 'acc'; + my @new_code = @code; + $new_code[$_] = [ $code[$_]->@* ]; + $new_code[$_][0] = $new_code[$_][0] eq 'nop' ? 'jmp' : 'nop'; + + my $result = run_instance(@new_code); + + return $result if $result; + } +} + +1; diff --git a/2020/08/tests.t b/2020/08/tests.t new file mode 100644 index 0000000..480f4b9 --- /dev/null +++ b/2020/08/tests.t @@ -0,0 +1,25 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +subtest part1 => sub { + is part1::solution(< 5, 'sample'; +nop +0 +acc +1 +jmp +4 +acc +3 +jmp -3 +acc -99 +acc +1 +jmp -4 +acc +6 +END + + is part1::solution( path('input')->slurp ) => 1262, 'part1'; +}; + +is part2::solution( path('input')->slurp ) => 1643, 'part2'; + +done_testing(); diff --git a/2020/09/part1.pm b/2020/09/part1.pm new file mode 100644 index 0000000..113eda6 --- /dev/null +++ b/2020/09/part1.pm @@ -0,0 +1,37 @@ +package part1; + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef +/; + +sub solution($n, $entries) { + my @numbers = split "\n", $entries; + + my %seen; + my @previous; + + # prime the pump + while( @previous < $n ) { + my $n = shift @numbers; + $seen{$n + $_}++ for @previous; + push @previous, $n; + } + + while(my $next = shift @numbers) { + return $next unless $seen{$next}; + + my $goner = shift @previous; + $seen{$goner + $_}-- for @previous; + $seen{$next + $_}++ for @previous; + + push @previous, $next; + } + + +} + +1; diff --git a/2020/09/part2.pm b/2020/09/part2.pm new file mode 100644 index 0000000..0e75fe0 --- /dev/null +++ b/2020/09/part2.pm @@ -0,0 +1,35 @@ +package part2; + +use 5.20.0; +use warnings; + +use experimental qw/ + signatures + postderef +/; + +use List::AllUtils qw/ minmax sum /; + +sub solution($target, $entries) { + my @numbers = split "\n", $entries; + + my $sum = 0; + my @seq; + + while( @numbers ) { + while( $sum < $target ) { + push @seq, shift @numbers; + $sum += $seq[-1]; + } + while( $sum > $target ) { + $sum -= shift @seq; + } + + if( $sum == $target ) { + return sum minmax @seq; + } + + } +} + +1; diff --git a/2020/09/tests.t b/2020/09/tests.t new file mode 100644 index 0000000..a1fda32 --- /dev/null +++ b/2020/09/tests.t @@ -0,0 +1,38 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +subtest part1 => sub { + is part1::solution( 5, <<'END') => 127; +35 +20 +15 +25 +47 +40 +62 +55 +65 +95 +102 +117 +150 +182 +127 +219 +299 +277 +309 +576 +END + + is part1::solution( 25, path('input')->slurp ) => 26796446; +}; + +subtest part2 => sub { + is part2::solution( 26796446, path('input')->slurp ) => 3353494; +}; + +done_testing(); diff --git a/2020/10/part1.pm b/2020/10/part1.pm new file mode 100644 index 0000000..7d21dce --- /dev/null +++ b/2020/10/part1.pm @@ -0,0 +1,20 @@ +package part1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ part /; + +use experimental qw/ signatures postderef /; + +sub solution(@lines) { + @lines = sort {$a<=>$b}@lines; + unshift @lines, 0; + push @lines, $lines[-1] + 3; + + my @diffs = part { $_ } map { $lines[$_] - $lines[$_-1] } 1..$#lines; + + return $diffs[1]->@* * $diffs[3]->@*; +} + +1; diff --git a/2020/10/part2.pm b/2020/10/part2.pm new file mode 100644 index 0000000..c744f7d --- /dev/null +++ b/2020/10/part2.pm @@ -0,0 +1,38 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; + +require './part1.pm'; + +use experimental qw/ signatures postderef /; + +use Memoize; + +memoize('ways'); + +sub ways($first, @rest) { + return 1 unless @rest; + + my $ways = 0; + + while( my $next = shift @rest ) { + last if $next - $first > 3; + $ways += ways($next, @rest); + } + + return $ways; +} + + +sub solution(@lines) { + @lines = sort {$a<=>$b}@lines; + unshift @lines, 0; + push @lines, $lines[-1] + 3; + + return ways(@lines); +} + +1; diff --git a/2020/10/sample2 b/2020/10/sample2 new file mode 100644 index 0000000..e6376dc --- /dev/null +++ b/2020/10/sample2 @@ -0,0 +1,31 @@ +28 +33 +18 +42 +31 +14 +46 +20 +48 +47 +24 +23 +49 +45 +19 +38 +39 +11 +1 +32 +25 +35 +8 +17 +7 +9 +4 +2 +34 +10 +3 diff --git a/2020/10/tests.t b/2020/10/tests.t new file mode 100644 index 0000000..ff58315 --- /dev/null +++ b/2020/10/tests.t @@ -0,0 +1,20 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +my @sample2 = path('sample2')->lines; +my @input = path('input')->lines; + +subtest part1 => sub { + is part1::solution(@sample2) => 220, 'sample2'; + + is part1::solution( @input) => 1700; +}; + +subtest part2 => sub { + is part2::solution(@input ) => 12401793332096; +}; + +done_testing(); diff --git a/2020/11/part1.pm b/2020/11/part1.pm new file mode 100644 index 0000000..c7b512a --- /dev/null +++ b/2020/11/part1.pm @@ -0,0 +1,67 @@ +package P1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; + +use Moose; + +use experimental qw/ signatures postderef /; + +sub neighbours ( $self, $i, $j, $grid ) { + + my $n = 0; + + for my $x ( -1, 0, 1 ) { + for my $y ( -1, 0, 1 ) { + next unless $x or $y; + my ( $c, $d ) = ( $i + $x, $j + $y ); + next if $c < 0 or $d < 0; + + no warnings; + $n += $grid->[$c][$d] eq '#'; + } + } + + return $n; + +} + +sub iterate ( $self, $room ) { + + my @grid = map { [ split '' ] } split "\n", $room; + my @next = map { [@$_] } @grid; + + for my $i ( 0 .. $#grid ) { + my $row = $grid[$i]; + for my $j ( 0 .. $row->$#* ) { + if ( $row->[$j] eq 'L' ) { + if ( not $self->neighbours( $i, $j, \@grid ) ) { + $next[$i][$j] = '#'; + } + } + elsif ( $row->[$j] eq '#' ) { + if ( $self->neighbours( $i, $j, \@grid ) >= 4 ) { + $next[$i][$j] = 'L'; + } + } + } + } + + return join "\n", map { join '', @$_ } @next; +} + +sub solution ( $self, $room ) { + my $previous; + + no warnings; + while ( $room ne $previous ) { + ( $previous, $room ) = ( $room, $self->iterate($room) ); + } + + my @m = $room =~ /(#)/g; + return scalar @m; +} + +1; diff --git a/2020/11/part2.pm b/2020/11/part2.pm new file mode 100644 index 0000000..0d75d57 --- /dev/null +++ b/2020/11/part2.pm @@ -0,0 +1,71 @@ +package P2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; +use Math::Vector::Real; + +require './part1.pm'; + +use Moose; +extends 'P1'; + +use experimental qw/ signatures postderef /; + +sub neighbours ( $self, $i, $j, $grid ) { + + my $n = 0; + + for my $x ( -1, 0, 1 ) { + for my $y ( -1, 0, 1 ) { + next unless $x or $y; + + my $delta = V( $x, $y ); + my $c = V( $i, $j ); + + while () { + $c += $delta; + + last if grep { $_ < 0 } @$c; + my $x = $grid->[ $c->[0] ][ $c->[1] ]; + + last if !$x or $x eq 'L'; + + next if $x eq '.'; + + $n++; + last; + } + } + } + + return $n; + +} + +sub iterate ( $self, $room ) { + + my @grid = map { [ split '' ] } split "\n", $room; + my @next = map { [@$_] } @grid; + + for my $i ( 0 .. $#grid ) { + my $row = $grid[$i]; + for my $j ( 0 .. $row->$#* ) { + if ( $row->[$j] eq 'L' ) { + if ( not $self->neighbours( $i, $j, \@grid ) ) { + $next[$i][$j] = '#'; + } + } + elsif ( $row->[$j] eq '#' ) { + if ( $self->neighbours( $i, $j, \@grid ) >= 5 ) { + $next[$i][$j] = 'L'; + } + } + } + } + + return join "\n", map { join '', @$_ } @next; +} + +1; diff --git a/2020/11/sample b/2020/11/sample new file mode 100644 index 0000000..cdea6b5 --- /dev/null +++ b/2020/11/sample @@ -0,0 +1,10 @@ +#.##.##.## +#######.## +#.#.#..#.. +####.##.## +#.##.##.## +#.#####.## +..#.#..... +########## +#.######.# +#.#####.## diff --git a/2020/11/tests.t b/2020/11/tests.t new file mode 100644 index 0000000..f30b187 --- /dev/null +++ b/2020/11/tests.t @@ -0,0 +1,20 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +my $sample = path('sample')->slurp; +my $input = path('input')->slurp; + +subtest part1 => sub { + is P1->new->solution( $sample ) => 37; + is P1->new->solution( $input ) => 2427; +}; + +subtest part2 => sub { + is P2->new->solution( $sample ) => 26; + is P2->new->solution( $input ) => 2199; +}; + +done_testing(); diff --git a/2020/12/part1.pm b/2020/12/part1.pm new file mode 100644 index 0000000..12a6eb8 --- /dev/null +++ b/2020/12/part1.pm @@ -0,0 +1,66 @@ +package P1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; +use Math::Vector::Real; + +use Moose; +use MooseX::MungeHas 'is_rw'; + +use experimental qw/ signatures postderef switch /; + +has coords => sub { + return V(0,0); +}; + +has facing => sub { + 1; +}; + +my @delta = ( + V(0,1), + V(1,0), + V(0,-1), + V(-1,0), +); + +sub delta { + return \@delta; +} + +sub turn($self,$n) { + my $d = ($self->facing + $n/90)%4; + $self->facing($d); +} + +sub move($self,$m) { + $m =~ /(.)(\d+)/; + + my ( $x, $c ) = ($1,$2); + + my $coords = $self->coords; + + given($x) { + $coords += $c * $delta[$self->facing] when 'F'; + $coords += $c * $delta[0] when 'N'; + $coords += $c * $delta[1] when 'E'; + $coords += $c * $delta[2] when 'S'; + $coords += $c * $delta[3] when 'W'; + $self->turn($c) when 'R'; + $self->turn(-$c) when 'L'; + } + + $self->coords($coords); +} + +sub solution($class,@instructions) { + my $self = $class->new; + + $self->move($_) for @instructions; + + $self->coords->manhattan_dist(V(0,0)); +} + +1; diff --git a/2020/12/part2.pm b/2020/12/part2.pm new file mode 100644 index 0000000..9a4c5f2 --- /dev/null +++ b/2020/12/part2.pm @@ -0,0 +1,57 @@ +package P2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; +use Math::Vector::Real; + +require './part1.pm'; + +use Moose; +use MooseX::MungeHas 'is_rw'; + +extends 'P1'; + +use experimental qw/ signatures postderef /; + +has +facing => sub { V(10,1) }; + +sub turn($self,$n) { + my $f = $self->facing; + my $d = ($n/90)%4; + given( $d ) { + return V($f->[1], -$f->[0]) when 1; + return V(-$f->[0], -$f->[1]) when 2; + return V(-$f->[1], $f->[0]) when 3; + } + return $f; +} + +sub move($self,$m) { + $m =~ /(.)(\d+)/; + + my ( $x, $c ) = ($1,$2); + + my $coords = $self->coords; + my $facing = $self->facing; + + + given($x) { + $coords += $c * $self->facing when 'F'; + $facing += $c * $self->delta->[0] when 'N'; + $facing += $c * $self->delta->[1] when 'E'; + $facing += $c * $self->delta->[2] when 'S'; + $facing += $c * $self->delta->[3] when 'W'; + $facing = $self->turn($c) when 'R'; + $facing = $self->turn(-$c) when 'L'; + } + + $self->facing($facing); + $self->coords($coords); + + warn $self->facing; + warn $self->coords; +} + +1; diff --git a/2020/12/tests.t b/2020/12/tests.t new file mode 100644 index 0000000..6ca8703 --- /dev/null +++ b/2020/12/tests.t @@ -0,0 +1,24 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +my @input = path('input')->lines; + +0 and subtest part1 => sub { + is P1->solution(@input) => 1221; +}; + +subtest part2 => sub { + is P2->solution(qw/ +F10 +N3 +F7 +R90 +F11 /) => 286, 'example'; + + is P2->solution(@input ) => 59435; +}; + +done_testing(); diff --git a/2020/13/part1.pm b/2020/13/part1.pm new file mode 100644 index 0000000..8ee8603 --- /dev/null +++ b/2020/13/part1.pm @@ -0,0 +1,22 @@ +package part1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ min /; + +use experimental qw/ signatures postderef /; + +sub solution($timestamp,@entries) { + @entries = grep { $_ ne 'x' } @entries; + + my %sched = map { + (int($timestamp / $_)+1 )* $_ => $_ + } @entries; + + my $k = min keys %sched; + $sched{ $k } * ( $k - $timestamp ) ; + +} + +1; diff --git a/2020/13/part2.pm b/2020/13/part2.pm new file mode 100644 index 0000000..f50d6f5 --- /dev/null +++ b/2020/13/part2.pm @@ -0,0 +1,54 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ min max /; +use POSIX qw/ ceil /; + +require './part1.pm'; + +use experimental qw/ signatures postderef /; + +sub find($t, $next=undef, @buses) { + + return 0 unless $next; + + if( $next eq 'x' ) { + return find($t+1, @buses ); + } + + my $n = $t / $next; + my $nint = int $n; + + if( $nint == $n ) { + return find( $t+1, @buses ); + } + + return (1+$nint)*$next; + +} + +sub solution(@buses) { + @buses = map { $_ eq 'x'?1:$_ + } @buses; + + my $t = 1; + T: while() { + my $i = 0; + my @min = map { + my $s = $t + $i++; + $_ * ceil($s/$_); + } @buses; + warn join " ", @min; + for(0..$#min-1) { + if( $min[$_] != $min[$_+1]-1 ) { + $t = max map { $min[$_] - $_ } 0..$#min; + next T; + } + } + return $t; + } +} + +1; diff --git a/2020/13/tests.t b/2020/13/tests.t new file mode 100644 index 0000000..2116334 --- /dev/null +++ b/2020/13/tests.t @@ -0,0 +1,18 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +my @input = path('input')->lines; + +subtest part1 => sub { + is part1::solution( $input[0], split ',', $input[1] ) => 171; +}; + +subtest part2 => sub { + is part2::solution( 17, 'x', 13, 19 ) => 3417; + # is part2::solution( split ',', $input[1] ) => 171; +}; + +done_testing(); diff --git a/2020/14/part1.pm b/2020/14/part1.pm new file mode 100644 index 0000000..def6672 --- /dev/null +++ b/2020/14/part1.pm @@ -0,0 +1,31 @@ +package part1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ zip pairmap reduce sum/; + +use experimental qw/ signatures postderef /; + +sub solution(@lines) { + my @mask; + my @mem; + + while( my $line = shift @lines ) { + if( $line =~ /mask = (.*)/ ) { + @mask = split '', $1; + next; + } + + $line =~ /mem\[(\d+)\] = (\d+)/ or die; + + my @num = split '', sprintf "%036b", $2; + + $mem[$1] = reduce { 2*$a + $b } pairmap { $a eq 'X' ? $b : $a } zip @mask, @num; + } + + no warnings; + return sum @mem; +} + +1; diff --git a/2020/14/part2.pm b/2020/14/part2.pm new file mode 100644 index 0000000..70101a2 --- /dev/null +++ b/2020/14/part2.pm @@ -0,0 +1,44 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ zip pairmap sum reduce/; + +require './part1.pm'; + +use experimental qw/ signatures postderef /; + +sub solution(@lines) { + my @mask; + my %mem; + + while( my $line = shift @lines ) { + warn $line; + if( $line =~ /mask = (.*)/ ) { + @mask = split '', $1; + next; + } + + $line =~ /mem\[(\d+)\] = (\d+)/ or die; + + my $value = $2; + my @num = split '', sprintf "%036b", $1; + + my $l = join '', pairmap { $a || $b } zip @mask, @num; + + my $num = $l =~ s/X/%d/g; + + for( 0..(2**$num)-1 ) { + my $address = reduce { + 2*$a + $b + } split '', sprintf $l, split '', sprintf "%0${num}b", $_; + $mem{$address} = $value; + } + } + + no warnings; + return sum values %mem; +} + +1; diff --git a/2020/14/sample1 b/2020/14/sample1 new file mode 100644 index 0000000..e15150a --- /dev/null +++ b/2020/14/sample1 @@ -0,0 +1,4 @@ +mask = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXX0X +mem[8] = 11 +mem[7] = 101 +mem[8] = 0 diff --git a/2020/14/sample2 b/2020/14/sample2 new file mode 100644 index 0000000..2e961e3 --- /dev/null +++ b/2020/14/sample2 @@ -0,0 +1,4 @@ +mask = 000000000000000000000000000000X1001X +mem[42] = 100 +mask = 00000000000000000000000000000000X0XX +mem[26] = 1 diff --git a/2020/14/tests.t b/2020/14/tests.t new file mode 100644 index 0000000..0d71a6f --- /dev/null +++ b/2020/14/tests.t @@ -0,0 +1,22 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +my @sample1 = path('sample1')->lines; +my @input = path('input')->lines; + +subtest part1 => sub { + skip_all; + is part1::solution(@sample1) => 165; + is part1::solution(@input) => 14954914379452; +}; + +subtest part2 => sub { + my @sample2 = path('sample2')->lines; + is part2::solution(@sample2) => 208; + is part2::solution(@input) => 3415488160714; +}; + +done_testing(); diff --git a/2020/15/part1.pm b/2020/15/part1.pm new file mode 100644 index 0000000..c7cd960 --- /dev/null +++ b/2020/15/part1.pm @@ -0,0 +1,41 @@ +package part1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; + +use experimental qw/ signatures postderef /; + +sub solution(@seeds) { + my $last = shift @seeds; + my $turn = 1; + my %said; + + for(@seeds) { + $said{$last} ||= []; + push $said{$last}->@*, $turn++; + $last = $_; + } + + until( $turn == 2020 ) { + if( !$said{$last} ) { + $said{$last} = [$turn]; + $last = 0 + } else { + push $said{$last}->@*, $turn; + if( $said{$last}->@* > 2 ) { + shift $said{$last}->@*; + } + $last = $said{$last}[-1] - $said{$last}[-2]; + } + + $turn++; + } + + return $last; + + +} + +1; diff --git a/2020/15/part2.pm b/2020/15/part2.pm new file mode 100644 index 0000000..af9f3d6 --- /dev/null +++ b/2020/15/part2.pm @@ -0,0 +1,44 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; + +require './part1.pm'; + +use experimental qw/ signatures postderef /; + +sub solution(@seeds) { + my $last = shift @seeds; + my $turn = 1; + my %said; + + for(@seeds) { + $said{$last} ||= []; + push $said{$last}->@*, $turn++; + $last = $_; + } + + until( $turn == 30000000 ) { + if( !$said{$last} ) { + $said{$last} = [$turn]; + $last = 0 + } else { + push $said{$last}->@*, $turn; + if( $said{$last}->@* > 2 ) { + shift $said{$last}->@*; + } + $last = $said{$last}[-1] - $said{$last}[-2]; + } + + $turn++; + } + + return $last; + + +} + + +1; diff --git a/2020/15/tests.t b/2020/15/tests.t new file mode 100644 index 0000000..1c29f78 --- /dev/null +++ b/2020/15/tests.t @@ -0,0 +1,16 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +subtest part1 => sub { + is part1::solution(0,3,6) => 436; + is part1::solution(0,13,1,16,6,17) => 234; +}; + +subtest part2 => sub { + is part2::solution(0,13,1,16,6,17) => 8984; +}; + +done_testing(); diff --git a/2020/16/part1.pm b/2020/16/part1.pm new file mode 100644 index 0000000..cae507a --- /dev/null +++ b/2020/16/part1.pm @@ -0,0 +1,27 @@ +package part1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ sum /; +use Range::Merge qw/ merge /; + +use experimental qw/ signatures postderef /; + +sub numbers_out_of_range($input) { + my $ranges = merge([ map { [ split '-' ] } $input =~ /(\d+-\d+)/g]); + + $input =~ /nearby tickets:(.*)/s; + my @tickets = $1 =~ /(\d+)/g; + + return grep { + not( $_ >= $ranges->[0][0] and $_ <= $ranges->[0][1]) + } @tickets; + +} + +sub solution($input) { + return sum numbers_out_of_range($input); +} + +1; diff --git a/2020/16/part2.pm b/2020/16/part2.pm new file mode 100644 index 0000000..3274996 --- /dev/null +++ b/2020/16/part2.pm @@ -0,0 +1,87 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ pairmap zip product /; +use Range::Merge qw/ merge /; +use Set::Object qw/ set /; + +require './part1.pm'; + +use experimental qw/ signatures postderef /; + +sub inRanges($n, $ranges) { + + for (@$ranges) { + return 1 if $_->[0] <= $n and $_->[1] >= $n; + } + + return 0; +} + +sub possibilities($n,$fields) { + + return set( + grep { inRanges($n, $fields->{$_})} keys %$fields + ) + + +} + +sub solution($input) { + my ( $fields, $myticket, $tickets ) = split "\n\n", $input; + + my %fields = map { + my ( $name, $ranges) = split ':'; + $name => merge([ map { [ split '-'] } $ranges =~ /(\d+-\d+)/g ]) + } split "\n", $fields; + + my @global; + + for my $ticket ( split "\n", $tickets ) { + next if $ticket =~ /nearby/; + + my @fields = split ',', $ticket; + + @fields = map { possibilities($_,\%fields) } @fields; + + next if grep { not $_->size } @fields; + + if(!@global) { + @global = @fields; + } + else { + @global = pairmap { $a*$b } zip @global, @fields; + } + } + + use DDP; say join " ", @$_ for @global; + + my %indexes; + + LOOP: while() { + for( 0..$#global ) { + if( $global[$_]->size == 1 ) { + my $e = $global[$_]; + $indexes{$global[$_][0]} = $_; + @global = map { $_ - $e } @global; + redo LOOP; + } + } + + last LOOP; + } + + p %indexes; + + my @myt = $myticket =~ /(\d+)/g; + + return product + @myt[ + map { $indexes{$_} } + grep { /departure/ } keys %indexes ]; + +} + +1; diff --git a/2020/16/sample b/2020/16/sample new file mode 100644 index 0000000..b48bf37 --- /dev/null +++ b/2020/16/sample @@ -0,0 +1,11 @@ +class: 0-1 or 4-19 +row: 0-5 or 8-19 +seat: 0-13 or 16-19 + +your ticket: +11,12,13 + +nearby tickets: +3,9,18 +15,1,5 +5,14,9 diff --git a/2020/16/tests.t b/2020/16/tests.t new file mode 100644 index 0000000..91c5c94 --- /dev/null +++ b/2020/16/tests.t @@ -0,0 +1,17 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +my $input = path('input')->slurp; + +subtest part1 => sub { + is part1::solution($input) => 20048; +}; + +subtest part2 => sub { + is part2::solution($input) => 4810284647569; +}; + +done_testing(); diff --git a/2020/template/part1.pm b/2020/template/part1.pm new file mode 100644 index 0000000..70ffb6a --- /dev/null +++ b/2020/template/part1.pm @@ -0,0 +1,13 @@ +package part1; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; + +use experimental qw/ signatures postderef /; + +sub solution() { +} + +1; diff --git a/2020/template/part2.pm b/2020/template/part2.pm new file mode 100644 index 0000000..e894608 --- /dev/null +++ b/2020/template/part2.pm @@ -0,0 +1,15 @@ +package part2; + +use 5.20.0; +use warnings; + +use List::AllUtils qw/ /; + +require './part1.pm'; + +use experimental qw/ signatures postderef /; + +sub solution() { +} + +1; diff --git a/2020/template/tests.t b/2020/template/tests.t new file mode 100644 index 0000000..8ec3074 --- /dev/null +++ b/2020/template/tests.t @@ -0,0 +1,15 @@ +use Test2::V0; +use Path::Tiny; + +require './part1.pm'; +require './part2.pm'; + +subtest part1 => sub { + is part1::solution( ) => 'TODO'; +}; + +subtest part2 => sub { + is part2::solution( ) => 'TODO'; +}; + +done_testing();