Compare commits
17 Commits
6ba1b3f326
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9cc95c447 | ||
|
|
8b7da0b751 | ||
|
|
bbaedda944 | ||
|
|
7fed659932 | ||
|
|
d8a62d2eb4 | ||
|
|
8dda1b5ee2 | ||
|
|
efa81418eb | ||
|
|
76705dc6fb | ||
|
|
5cd1a7f157 | ||
|
|
5f2cb7c7ee | ||
|
|
e26f41cd68 | ||
|
|
53172351df | ||
|
|
268b6b24b4 | ||
|
|
80921bdf95 | ||
|
|
99ba049301 | ||
|
|
874716448e | ||
|
|
2b88faf388 |
@@ -3,9 +3,9 @@
|
||||
// summary: "Daniel's take on day 1 of advent of code, featuring reduce expressions, iterators, and custom reductions"
|
||||
// authors: ["Daniel Fedorin"]
|
||||
// date: 2022-12-01
|
||||
// draft: true
|
||||
|
||||
/*
|
||||
{{< whole_file_min >}}
|
||||
|
||||
And so Advent of Code begins! Today's challenge is, as usual for the first
|
||||
day, a fairly easy one. Brad has [already written]({{< relref "aoc2022-day01-calories" >}}) a wonderful introduction for
|
||||
this challenge, and provided his own solution to the first part. In that
|
||||
|
||||
24
day10.chpl
Normal file
24
day10.chpl
Normal file
@@ -0,0 +1,24 @@
|
||||
use IO, Map;
|
||||
|
||||
iter ops() {
|
||||
yield 1; // Initial state
|
||||
for line in stdin.lines().strip() {
|
||||
select line[0..3] {
|
||||
when "noop" do yield 0;
|
||||
when "addx" {
|
||||
yield 0;
|
||||
yield line[5..] : int;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deltas = ops(),
|
||||
cycles = deltas.size,
|
||||
states: [1..cycles] int = + scan deltas,
|
||||
interesting = 20..220 by 40;
|
||||
writeln(+ reduce (states[interesting] * interesting));
|
||||
|
||||
const pixels = [(x, pc) in zip(states[1..240], 0..)]
|
||||
if abs((pc % 40) - x) <= 1 then "#" else " ";
|
||||
writeln(reshape(pixels, {1..6, 1..40}));
|
||||
96
day11.chpl
Normal file
96
day11.chpl
Normal file
@@ -0,0 +1,96 @@
|
||||
use IO, List;
|
||||
|
||||
var modulus = 1;
|
||||
|
||||
record DivByThree {
|
||||
var underlying: int;
|
||||
operator +(lhs: DivByThree, rhs: DivByThree) {
|
||||
return new DivByThree((lhs.underlying + rhs.underlying) / 3);
|
||||
}
|
||||
operator *(lhs: DivByThree, rhs: DivByThree) {
|
||||
return new DivByThree((lhs.underlying * rhs.underlying) / 3);
|
||||
}
|
||||
proc divBy(x: int) return underlying % x == 0;
|
||||
}
|
||||
|
||||
record Modulo {
|
||||
var underlying: int;
|
||||
|
||||
operator +(lhs: Modulo, rhs: Modulo) {
|
||||
return new Modulo((lhs.underlying + rhs.underlying) % modulus);
|
||||
}
|
||||
operator *(lhs: Modulo, rhs: Modulo) {
|
||||
return new Modulo((lhs.underlying * rhs.underlying) % modulus);
|
||||
}
|
||||
proc divBy(x: int) return underlying % x == 0;
|
||||
}
|
||||
|
||||
config type numtype = DivByThree;
|
||||
config const steps = 20;
|
||||
|
||||
class Op {
|
||||
proc apply(x: ?t) return x;
|
||||
}
|
||||
|
||||
class SquareOp : Op {
|
||||
override proc apply(x) return x * x;
|
||||
}
|
||||
|
||||
class AddOp : Op {
|
||||
var toAdd;
|
||||
override proc apply(x) return x + toAdd;
|
||||
}
|
||||
|
||||
class MulOp : Op {
|
||||
var toMul;
|
||||
override proc apply(x) return x * toMul;
|
||||
}
|
||||
|
||||
proc parse(op: string): owned Op {
|
||||
if op == "old * old" then return new SquareOp();
|
||||
if op.startsWith("old + ") then return new AddOp(new numtype(op[6..] : int));
|
||||
return new MulOp(new numtype(op[6..] : int));
|
||||
}
|
||||
|
||||
record Monkey {
|
||||
var op : owned Op;
|
||||
var divBy, ifTrue, ifFalse : int;
|
||||
var items: list(numtype);
|
||||
var count: int = 0;
|
||||
|
||||
iter tossItems() {
|
||||
while !items.isEmpty() {
|
||||
var item = items.pop(0);
|
||||
var changed = op.apply(item);
|
||||
var nextIdx = if changed.divBy(divBy) then ifTrue else ifFalse;
|
||||
count += 1;
|
||||
yield (changed, nextIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var monkeys = new list(Monkey);
|
||||
var line: string;
|
||||
while readLine(line) {
|
||||
proc toNum(x: string) return new numtype(x : int);
|
||||
readLine(line, stripNewline=true);
|
||||
var items = new list(toNum(line[" Starting items: ".size..].split(", ")));
|
||||
readLine(line, stripNewline=true);
|
||||
var op = parse(line[" Operation: new = ".size..]);
|
||||
var divBy, ifTrue, ifFalse: int;
|
||||
readf(" Test: divisible by %i\n", divBy);
|
||||
readf(" If true: throw to monkey %i\n", ifTrue);
|
||||
readf(" If false: throw to monkey %i\n", ifFalse);
|
||||
monkeys.append(new Monkey(op, divBy, ifTrue, ifFalse, items));
|
||||
modulus *= divBy;
|
||||
if (!readln()) then break;
|
||||
}
|
||||
|
||||
for 1..steps {
|
||||
for monkey in monkeys {
|
||||
for (item, nextIdx) in monkey.tossItems() {
|
||||
monkeys[nextIdx].items.append(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
writeln(monkeys.these().count);
|
||||
58
day11.cr
Normal file
58
day11.cr
Normal file
@@ -0,0 +1,58 @@
|
||||
require "advent"
|
||||
require "big"
|
||||
|
||||
class Monkey
|
||||
property items : Array(Int64)
|
||||
property op : String
|
||||
property divBy : Int64
|
||||
property ifTrue : Int64
|
||||
property ifFalse : Int64
|
||||
|
||||
def initialize(@items, @op, @divBy, @ifTrue, @ifFalse)
|
||||
end
|
||||
end
|
||||
|
||||
INSTS = {
|
||||
"*" => ->(x: Int64, y: Int64) { x * y },
|
||||
"+" => ->(x: Int64, y: Int64) { x + y }
|
||||
}
|
||||
|
||||
def execute(inst, old, mod)
|
||||
inst = inst.gsub("old", old.to_s);
|
||||
l, op, r = inst.split(" ")
|
||||
l = l.to_i64 % mod
|
||||
r = r.to_i64 % mod
|
||||
INSTS[op].call(l,r)
|
||||
end
|
||||
|
||||
monkeys = [] of Monkey
|
||||
input(2022, 11).split("\n\n").each do |it|
|
||||
it = it.lines
|
||||
items = it[1].split(": ")[1].split(", ").map(&.to_i64).reverse
|
||||
op = it[2].split("Operation: new = ")[1]
|
||||
divBy = it[3].split("Test: divisible by ")[1].to_i64
|
||||
ifTrue = it[4].split(" If true: throw to monkey ")[1].to_i64
|
||||
ifFalse = it[5].split(" If false: throw to monkey ")[1].to_i64
|
||||
monkeys << Monkey.new(items, op, divBy, ifTrue, ifFalse)
|
||||
end
|
||||
|
||||
counts = [0] * monkeys.size
|
||||
modulus = monkeys.map(&.divBy).product
|
||||
10000.times do
|
||||
monkeys.each_with_index do |m, i|
|
||||
while !m.items.empty?
|
||||
counts[i] += 1
|
||||
item = m.items.pop
|
||||
item = execute(m.op, item, modulus)
|
||||
item = item % modulus
|
||||
if item % m.divBy == 0
|
||||
monkeys[m.ifTrue].items.insert(0, item)
|
||||
else
|
||||
monkeys[m.ifFalse].items.insert(0, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
counts.sort!
|
||||
puts counts[-1].to_big_i * counts[-2].to_big_i
|
||||
83
day12.cr
Normal file
83
day12.cr
Normal file
@@ -0,0 +1,83 @@
|
||||
require "advent"
|
||||
INPUT = input(2022, 12).lines.map(&.chars)
|
||||
EDGES = {} of Tuple(Int32,Int32) => Array(Tuple(Int32, Int32))
|
||||
|
||||
def add_at(pos, c, x, y)
|
||||
return unless y >= 0 && y < INPUT.size
|
||||
return unless x >= 0 && x < INPUT[0].size
|
||||
|
||||
o = INPUT[y][x]
|
||||
o = 'a' if o == 'S'
|
||||
o = 'z' if o == 'E'
|
||||
|
||||
return if (c.ord-o.ord) > 1
|
||||
EDGES[pos] << ({x,y});
|
||||
end
|
||||
|
||||
def add_nearby(x, y)
|
||||
c = INPUT[y][x]
|
||||
c = 'a' if c == 'S'
|
||||
c = 'z' if c == 'E'
|
||||
|
||||
if !EDGES[{x,y}]?
|
||||
EDGES[{x,y}] = [] of Tuple(Int32,Int32)
|
||||
end
|
||||
add_at({x,y}, c, x+1, y)
|
||||
add_at({x,y}, c, x-1, y)
|
||||
add_at({x,y}, c, x, y+1)
|
||||
add_at({x,y}, c, x, y-1)
|
||||
end
|
||||
|
||||
from = {0,0}
|
||||
to = {100,100}
|
||||
INPUT.each_with_index do |row, y|
|
||||
row.each_with_index do |c, x|
|
||||
pos = {x, y}
|
||||
add_nearby(x, y)
|
||||
from = pos if c == 'E'
|
||||
end
|
||||
end
|
||||
|
||||
costs = {from => 0}
|
||||
mins = {} of Tuple(Int32, Int32) => Int32
|
||||
visited = Set(Tuple(Int32, Int32)).new
|
||||
|
||||
while !costs.empty?
|
||||
k, v = costs.min_by do |k,v|
|
||||
v
|
||||
end
|
||||
|
||||
if k == to
|
||||
puts "Found! #{v}"
|
||||
break
|
||||
end
|
||||
|
||||
costs.delete k
|
||||
mins[k] = v
|
||||
visited << k
|
||||
INPUT[k[1]][k[0]] = INPUT[k[1]][k[0]].upcase
|
||||
puts k
|
||||
|
||||
EDGES[k].each do |edge|
|
||||
next if visited.includes? edge
|
||||
if old = costs[edge]?
|
||||
costs[edge] = v+1 if old > v+1
|
||||
else
|
||||
costs[edge] = v+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
lengths = [] of Int32
|
||||
puts mins
|
||||
INPUT.each_with_index do |line, y|
|
||||
line.each_with_index do |c, x|
|
||||
next unless c == 'A'
|
||||
if amin = mins[{x,y}]?
|
||||
lengths << amin
|
||||
end
|
||||
print c
|
||||
end
|
||||
puts
|
||||
end
|
||||
puts lengths.min
|
||||
110
day13.cr
Normal file
110
day13.cr
Normal file
@@ -0,0 +1,110 @@
|
||||
require "advent"
|
||||
INPUT = input(2022, 13).lines
|
||||
|
||||
class Tree
|
||||
property value : Int32?
|
||||
property values : Array(Tree)?
|
||||
|
||||
def initialize(@value, @values)
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
if mine = value
|
||||
io << "value: " << mine
|
||||
elsif mine = values
|
||||
io << "values: "
|
||||
mine.each do |v|
|
||||
io << "("
|
||||
v.to_s io
|
||||
io << ")"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def compare(other : Tree)
|
||||
if mine = value
|
||||
if yours = other.value
|
||||
return (mine - yours).sign
|
||||
end
|
||||
end
|
||||
|
||||
if mine = values
|
||||
if yours = other.values
|
||||
mine.size.times do |i|
|
||||
return 1 unless i < yours.size # Shorter list smaller
|
||||
comp = mine[i].compare yours[i]
|
||||
return comp if comp != 0
|
||||
end
|
||||
return mine.size == yours.size ? 0 : -1 # Shorter list smaller
|
||||
end
|
||||
end
|
||||
|
||||
if mine = value
|
||||
return Tree.new(nil, [self]).compare(other)
|
||||
elsif yours = other.value
|
||||
return compare(Tree.new(nil, [other]))
|
||||
end
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
def tree(i)
|
||||
Tree.new(i, nil)
|
||||
end
|
||||
|
||||
def list(is : Array(Int32))
|
||||
Tree.new(nil, is.map { |it| tree(it) })
|
||||
end
|
||||
|
||||
def list(is : Array(Tree))
|
||||
Tree.new(nil, is)
|
||||
end
|
||||
|
||||
def parse(string)
|
||||
stack = [[] of Tree]
|
||||
current = nil
|
||||
string.as(String).each_char do |c|
|
||||
if c == '['
|
||||
stack << [] of Tree
|
||||
elsif c.alphanumeric?
|
||||
if current.nil?
|
||||
current = c.to_i32
|
||||
else
|
||||
current = current * 10 + c.to_i32
|
||||
end
|
||||
elsif c == ','
|
||||
if num = current
|
||||
stack.last << tree(num)
|
||||
current = nil
|
||||
end
|
||||
elsif c == ']'
|
||||
if num = current
|
||||
stack.last << tree(num)
|
||||
current = nil
|
||||
end
|
||||
new_tree = Tree.new(nil, stack.pop)
|
||||
stack.last << new_tree
|
||||
end
|
||||
end
|
||||
return stack[0][0]
|
||||
end
|
||||
|
||||
puts parse("[[10,1,2],[],[]]")
|
||||
total = 0
|
||||
INPUT.in_groups_of(3).each_with_index do |group, i|
|
||||
t1 = parse(group[0])
|
||||
t2 = parse(group[1])
|
||||
cmp = t1.compare t2
|
||||
total += (i+1) if cmp != 1
|
||||
end
|
||||
puts total
|
||||
|
||||
trees = INPUT.reject { |it| it.empty? }.map { |it| parse(it) }
|
||||
d1 = parse("[[2]]")
|
||||
d2 = parse("[[6]]")
|
||||
trees << d1
|
||||
trees << d2
|
||||
trees.sort! do |l, r|
|
||||
l.compare r
|
||||
end
|
||||
puts (trees.index!(d1)+1)*(trees.index!(d2)+1)
|
||||
67
day14.chpl
Normal file
67
day14.chpl
Normal file
@@ -0,0 +1,67 @@
|
||||
use IO, Set;
|
||||
|
||||
proc parseCoord(s: string) {
|
||||
const (x, _, y) = s.partition(",");
|
||||
return (x : int, y : int);
|
||||
}
|
||||
|
||||
proc (set((int,int))).draw((x1, y1), (x2, y2)) {
|
||||
for x in (min(x1,x2)..max(x1,x2)) {
|
||||
for y in (min(y1,y2)..max(y1,y2)) {
|
||||
this.add((x,y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iter ((int,int)).nextPositions() {
|
||||
yield this + (0,1);
|
||||
yield this + (-1,1);
|
||||
yield this + (1,1);
|
||||
}
|
||||
|
||||
var occupied = new set((int, int));
|
||||
for line in stdin.lines().strip() {
|
||||
const coords = parseCoord(line.split(" -> "));
|
||||
for idx in 0..#(coords.size-1) {
|
||||
occupied.draw(coords[idx], coords[idx+1]);
|
||||
}
|
||||
}
|
||||
|
||||
const maxHeight = max reduce [(x, y) in occupied] y;
|
||||
const initialPos = (500, 0);
|
||||
config const hasWall = false;
|
||||
var grainCount = 0;
|
||||
|
||||
do {
|
||||
// Start a new grain of sand, but give up if there's already one there.
|
||||
var pos = initialPos;
|
||||
if occupied.contains(pos) then break;
|
||||
|
||||
// Make the grain fall
|
||||
var abyss = false;
|
||||
do {
|
||||
var moved = false;
|
||||
for checkPos in pos.nextPositions() {
|
||||
// Check for falling past the floor
|
||||
if checkPos[1] > maxHeight + 10 {
|
||||
abyss = true;
|
||||
break;
|
||||
}
|
||||
// Try moving, but only if the position is clear and not on the floor.
|
||||
if !occupied.contains(checkPos) &&
|
||||
!(hasWall && checkPos[1] == maxHeight + 2) {
|
||||
pos = checkPos;
|
||||
moved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while moved;
|
||||
|
||||
// If we stopped because we fell off, don't count the last grain.
|
||||
if !abyss {
|
||||
grainCount += 1;
|
||||
occupied.add(pos);
|
||||
}
|
||||
} while !abyss;
|
||||
|
||||
writeln(grainCount);
|
||||
57
day14a.cr
Normal file
57
day14a.cr
Normal file
@@ -0,0 +1,57 @@
|
||||
require "advent"
|
||||
INPUT = input(2022, 14).lines
|
||||
|
||||
occupied = {} of Tuple(Int32, Int32) => Bool
|
||||
INPUT.each do |line|
|
||||
points = line.split("->").map &.split(",").map(&.to_i32)
|
||||
points.each_cons_pair do |p1,p2|
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
dx = (x2-x1).sign
|
||||
dy = (y2-y1).sign
|
||||
((x2-x1).abs+1).times do |nx|
|
||||
((y2-y1).abs+1).times do |ny|
|
||||
pos = {x1 + nx*dx, y1 + ny*dy}
|
||||
occupied[pos] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
max_height = occupied.max_of do |k,v|
|
||||
x, y = k
|
||||
y
|
||||
end
|
||||
|
||||
puts "Max height: #{max_height}"
|
||||
|
||||
def each_place(pos, &block)
|
||||
x, y = pos
|
||||
yield ({x+1, y+1})
|
||||
yield ({x-1, y+1})
|
||||
yield ({x, y+1})
|
||||
end
|
||||
|
||||
puts occupied
|
||||
|
||||
count = 0
|
||||
overflow = false
|
||||
until overflow
|
||||
pos = {500, 0}
|
||||
moved = false
|
||||
loop do
|
||||
moved = false
|
||||
each_place(pos) do |check|
|
||||
next if occupied[check]?
|
||||
pos = check
|
||||
moved = true
|
||||
end
|
||||
overflow = true if pos[1] > max_height
|
||||
break unless moved
|
||||
break if overflow
|
||||
end
|
||||
occupied[pos] = true
|
||||
count += 1
|
||||
end
|
||||
|
||||
puts count-1
|
||||
54
day14b.cr
Normal file
54
day14b.cr
Normal file
@@ -0,0 +1,54 @@
|
||||
require "advent"
|
||||
INPUT = input(2022, 14).lines
|
||||
|
||||
occupied = {} of Tuple(Int32, Int32) => Bool
|
||||
INPUT.each do |line|
|
||||
points = line.split("->").map &.split(",").map(&.to_i32)
|
||||
points.each_cons_pair do |p1,p2|
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
dx = (x2-x1).sign
|
||||
dy = (y2-y1).sign
|
||||
((x2-x1).abs+1).times do |nx|
|
||||
((y2-y1).abs+1).times do |ny|
|
||||
pos = {x1 + nx*dx, y1 + ny*dy}
|
||||
occupied[pos] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
max_height = occupied.max_of do |k,v|
|
||||
x, y = k
|
||||
y
|
||||
end
|
||||
|
||||
puts "Max height: #{max_height}"
|
||||
|
||||
def each_place(pos, &block)
|
||||
x, y = pos
|
||||
yield ({x+1, y+1})
|
||||
yield ({x-1, y+1})
|
||||
yield ({x, y+1})
|
||||
end
|
||||
|
||||
puts occupied
|
||||
|
||||
count = 0
|
||||
until occupied[{500,0}]?
|
||||
pos = {500, 0}
|
||||
moved = false
|
||||
loop do
|
||||
moved = false
|
||||
each_place(pos) do |check|
|
||||
next if occupied[check]? || check[1] == max_height+2
|
||||
pos = check
|
||||
moved = true
|
||||
end
|
||||
break unless moved
|
||||
end
|
||||
occupied[pos] = true
|
||||
count += 1
|
||||
end
|
||||
|
||||
puts count
|
||||
83
day15.chpl
Normal file
83
day15.chpl
Normal file
@@ -0,0 +1,83 @@
|
||||
use IO, Set, List;
|
||||
|
||||
iter data() {
|
||||
var x1, y1, x2, y2 = 0;
|
||||
while readf("Sensor at x=%i, y=%i: closest beacon is at x=%i, y=%i\n", x1, y1, x2, y2) {
|
||||
yield ((x1, y1), (x2, y2), abs(x1-x2) + abs(y1-y2));
|
||||
}
|
||||
}
|
||||
|
||||
record overlapping {
|
||||
var disjoint: list(range(int));
|
||||
|
||||
proc add(arg: range(int)) {
|
||||
// Don't pollute `disjoint`.
|
||||
if arg.isEmpty() then return;
|
||||
|
||||
var newRng = arg;
|
||||
do {
|
||||
var merged = false;
|
||||
for (rng, i) in zip(disjoint, 0..) {
|
||||
if newRng[rng].isEmpty() then continue;
|
||||
newRng = min(rng.lowBound, newRng.lowBound)..max(rng.highBound, newRng.highBound);
|
||||
disjoint.pop(i);
|
||||
merged = true;
|
||||
break;
|
||||
}
|
||||
} while merged;
|
||||
disjoint.append(newRng);
|
||||
}
|
||||
|
||||
iter these() { for rng in disjoint do yield rng; }
|
||||
proc size { return + [rng in this] rng.size; }
|
||||
proc boundedSize(bound) { return + reduce [rng in this] rng[bound].size; }
|
||||
proc contains(x) { return || reduce [rng in this] rng.contains(x); }
|
||||
}
|
||||
|
||||
enum axis { xAxis = 0, yAxis = 1 };
|
||||
proc ((int, int)).rangeAlong(axs: axis, reach: int, theXOrY: int) {
|
||||
const dim = axs : int;
|
||||
const dist = abs(this[dim]-theXOrY);
|
||||
|
||||
// Too far
|
||||
if dist > reach then return 0..<0;
|
||||
|
||||
// Get the range
|
||||
const remDist = max(0, reach-dist);
|
||||
return (this[1-dim]-remDist)..(this[1-dim]+remDist);
|
||||
}
|
||||
|
||||
|
||||
config const theY = 10;
|
||||
const searchSpace = 0..theY * 2;
|
||||
const input = data();
|
||||
|
||||
// Solve part 1
|
||||
var overlaps: overlapping;
|
||||
var occupied: set((int, int));
|
||||
for (sensor, beacon, reach) in input {
|
||||
occupied.add(sensor);
|
||||
occupied.add(beacon);
|
||||
overlaps.add(sensor.rangeAlong(axis.yAxis, reach, theY));
|
||||
}
|
||||
writeln(overlaps.size - (+ reduce [(x,y) in occupied] if y == theY then 1));
|
||||
|
||||
// Solve part 2
|
||||
forall checkY in searchSpace {
|
||||
var overlaps: overlapping;
|
||||
for (sensor, _, reach) in input {
|
||||
overlaps.add(sensor.rangeAlong(axis.yAxis, reach, checkY));
|
||||
}
|
||||
|
||||
if overlaps.boundedSize(searchSpace) != searchSpace.size {
|
||||
// Found the y-cordinate. Now find the x-coordinate.
|
||||
for checkX in searchSpace {
|
||||
if !overlaps.contains(checkX) {
|
||||
// x-coord isn't in the interval, so we found our answer.
|
||||
writeln("x = ", checkX, ", y = ", checkY, ", frequency = ", checkX * 4000000 + checkY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
day6.cr
9
day6.cr
@@ -2,11 +2,10 @@ require "advent"
|
||||
INPUT = input(2022, 6).lines[0].chars
|
||||
|
||||
def part1(input)
|
||||
offset = 0
|
||||
loop do
|
||||
chars = input[offset..offset+3]
|
||||
return offset + 4 if chars.uniq.size == 4
|
||||
offset += 1
|
||||
idx = 0
|
||||
input.each_cons(4) do |x|
|
||||
return idx + 4 if x.uniq.size == 4
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
505
day7.chpl
505
day7.chpl
@@ -14,7 +14,7 @@
|
||||
/*
|
||||
### The Task at Hand and My Approach
|
||||
|
||||
In today's puzzle, we are given a list of terminal-like commands (
|
||||
In [today's puzzle](https://adventofcode.com/2022/day/7), we are given a list of terminal-like commands (
|
||||
[`ls`](https://man7.org/linux/man-pages/man1/ls.1.html) and [`cd`](https://man7.org/linux/man-pages/man1/cd.1p.html)
|
||||
), as well as output corresponding to running these commands. The commands
|
||||
explore a fictional file system, which can have files (objects with size)
|
||||
@@ -23,35 +23,420 @@
|
||||
sizes of all folders that are smaller than a particular threshold.
|
||||
|
||||
The tree-like nature of the file system does not make it amenable to
|
||||
representations based on arrays, lists, and maps alone. The trouble with
|
||||
these data types is that they're flat. Our input could -- and will -- have arbitrary
|
||||
representations based on arrays, lists, or maps alone. The trouble with
|
||||
these data types is that they're flat. Our input could have arbitrary
|
||||
levels of nested directories. However, arrays, lists, and maps cannot have
|
||||
such arbitrary nesting -- we'd need something like a list of lists of lists...
|
||||
such arbitrary nesting --- we'd need something like a list of lists of lists of...
|
||||
We could, of course, use the `map` and `list` data types to represent the
|
||||
file system with some sort of [adjacency list](https://en.wikipedia.org/wiki/Adjacency_list).
|
||||
However, such an implementation would be somewhat clunky and hard to use.
|
||||
|
||||
Instead, we'll use a different tool from the repertoire of Chapel language
|
||||
features, one we haven't seen so far: classes. Much like in most languages,
|
||||
classes are a way to group together related pieces of data. up until now,
|
||||
we've used tuples for this purpose.
|
||||
Instead, in this article I use a different tool from the repertoire of Chapel language
|
||||
features, one we haven't seen so far: classes. Specifically, I use a class, `Dir`, to represent
|
||||
directories in the file system, and build up a tree of these directories
|
||||
while reading the input. I then create an iterator over this tree that
|
||||
computes and yields the sizes of the folders. From there, it's easy to
|
||||
pick out all directory sizes smaller than the threshold and sum them up.
|
||||
|
||||
**If you skip right to your favorite parts of a movie, here's a full solution for the day:**
|
||||
{{< whole_file_min >}}
|
||||
|
||||
And now, on to the explanation train. Before the train departs, let's import
|
||||
a few of the modules we'll use today. `IO` is a permanent fixture in our
|
||||
solutions (we always need to read input!), and `List` is a familiar face.
|
||||
The only newcomer here is `Map`, which helps us associate keys with values,
|
||||
much like a dictionary in Python, a hash in Ruby, or a map in C++.
|
||||
We'll use maps and lists for storing the various files and directories
|
||||
on the file system.
|
||||
*/
|
||||
|
||||
use IO, Map, List;
|
||||
|
||||
class TreeNode {
|
||||
/*
|
||||
With that, our train's first stop: classes!
|
||||
|
||||
### Classes in Chapel
|
||||
|
||||
Like in most languages, classes in Chapel are a way to group together related
|
||||
pieces of data. Up until now, we've used tuples for this purpose. Tuples,
|
||||
however, have a couple of limitations when it comes to solving today's
|
||||
Advent of Code problem:
|
||||
|
||||
* We can't name a tuple's elements. Whenever you make and use a tuple,
|
||||
it is up to _you_ to remember the order of the elements within it, and
|
||||
what each element represents.
|
||||
* Tuples can be nested, but their precise element types, including
|
||||
nesting depth, must be known at compile-time. As a result, tuples aren’t
|
||||
flexible enough to support the arbitrary levels of nesting that would be
|
||||
required by a program that didn’t know the directory structure _a priori_
|
||||
(e.g. one that was reading it from disk). We simply don’t have the
|
||||
information at compile-time to describe the tuple’s types and “shape”.
|
||||
|
||||
Classes have neither of these limitations. They do, however, need to be
|
||||
explicitly created within Chapel code. For example, one might create a
|
||||
class to store information about a person:
|
||||
|
||||
```Chapel
|
||||
class person {
|
||||
var firstName, lastName: string;
|
||||
}
|
||||
```
|
||||
|
||||
We've seen plenty of `var` statements used to create variables; when used
|
||||
within a class, `var` declares a _member variable_ (also known as a _field_)
|
||||
for the class. Our `person` contains two pieces of data in its fields: the
|
||||
person's first name (`firstName`) and last name (`lastName`).
|
||||
|
||||
With that class definition in hand, we can create instances of the `person` class
|
||||
using the `new` keyword.
|
||||
|
||||
```Chapel
|
||||
var biggestCandyFan = new person("Daniel", "Fedorin");
|
||||
```
|
||||
|
||||
As usual, we can rely on type inference to only write the type `person` once;
|
||||
Chapel figures out that `biggestCandyFan` is a `person`. Now, it's easy to get
|
||||
the various fields back out of a class:
|
||||
|
||||
```Chapel
|
||||
writeln("The biggest fan of candy is ", biggestCandyFan.firstName);
|
||||
```
|
||||
|
||||
Believe it or not, we've already seen enough of classes to see how to represent
|
||||
nested data structures. The key observation is that classes have names, which
|
||||
means that we can create fields that refer back to instances of the same class. Here's
|
||||
an example of what I mean, in the form of a modified `person` class:
|
||||
|
||||
```Chapel {hl_lines=3}
|
||||
class person {
|
||||
var firstName, lastName: string;
|
||||
var children: list(owned person);
|
||||
}
|
||||
```
|
||||
|
||||
The highlighted line is new. We've added a list of children to our person.
|
||||
These children are themselves instances of `person`, which means they too
|
||||
can have children of their own. _Et voilà_ - we've got a nested data structure!
|
||||
|
||||
#### Memory Management Strategies
|
||||
You probably noticed that `children`'s type is `list(owned person)` ---
|
||||
note the `owned`. This keyword is an indication of the way that memory is
|
||||
allocated and maintained for classes: their _memory management_. To create
|
||||
a class, a Chapel program asks for some memory from the computer (_allocates_ it).
|
||||
This memory is kept by the program until the instance of a class is no longer
|
||||
needed, at which point it's _deallocated_/_freed_. The challenge is knowing when
|
||||
a class is no longer needed! This is where _memory management strategies_,
|
||||
like `owned`, come in.
|
||||
|
||||
We don't need to get too deep into the various memory management strategies
|
||||
in today's post.
|
||||
|
||||
{{< details summary="**(If you're curious, here's a brief description of each strategy...)**" >}}
|
||||
* When using the `owned` strategy, a class instance has one "owner" variable.
|
||||
The instance is only around as long as this owner exists.
|
||||
As soon as the owner disappears, the class instance is deallocated.
|
||||
In some cases --- though we won't be covering them today --- ownership can
|
||||
be transferred from one variable to another, but no two values can
|
||||
own the same class instance at the same time.
|
||||
|
||||
Other variables can still refer to an `owned` class instance, but they must _borrow_ it,
|
||||
creating, for example, a `borrowed person`. Borrows do not affect the
|
||||
lifetime of class or when it is deallocated.
|
||||
* When using the `shared` strategy, Chapel keeps track of how many places
|
||||
still have variables that refer to a particular instance of a class. This
|
||||
is typically called a _reference count_. Each time a variable is created
|
||||
or changed to refer to a class instance, the instance's reference count
|
||||
increases. When that variable goes out of scope and disappears, the
|
||||
reference count decreases. Finally, when the reference count reaches
|
||||
zero (no more variables refer to the class instance), there's no point
|
||||
in keeping it around anymore, and its memory is deallocated.
|
||||
|
||||
As is the case with `owned`, other variables can borrow `shared` class instances.
|
||||
Such borrows do not affect the reference count at all, and therefore don't
|
||||
influence when the instance is freed.
|
||||
* When using the `unmanaged` strategy, you're promising to manually free
|
||||
the memory later, using the `delete` keyword. This is very similar to
|
||||
how `new`/`delete` work in classic C++.
|
||||
{{< /details >}}
|
||||
|
||||
So, the `owned` keyword in our `children` list means we've opted for the
|
||||
`owned` memory management strategy. The implication of this is that
|
||||
when a "parent" person is deallocated, so are all of its children
|
||||
(since the person class, through its `children` list, owns each child).
|
||||
If we aren't planning on sharing our data, `owned` is the preferred strategy. This is because
|
||||
it precludes the need for some bookkeeping, which
|
||||
often makes a difference in terms of performance. The added benefit to using
|
||||
`owned`, in my personal view, is that it's easier to figure out when something
|
||||
will be deleted --- there's no chance of some other variable, elsewhere in my program,
|
||||
preventing a class instance's deallocation.
|
||||
|
||||
#### Methods
|
||||
Remember how I said that classes can be used to group together pieces
|
||||
of related data? Well, they can do more than that. They can also group
|
||||
together operations on this data, in the form of _methods_. For instance,
|
||||
we could add the following definition **inside** the `class` declaration
|
||||
for our `person`:
|
||||
|
||||
```Chapel
|
||||
class person {
|
||||
// ... as before
|
||||
|
||||
proc getGreeting() {
|
||||
return "Hello, " + this.firstName + "!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Just like fields can be thought of as `var`s that are associated with a particular
|
||||
class instance, methods can be thought of as _procedures_ associated with
|
||||
a particular class instance. Thus, methods behave pretty much exactly
|
||||
like the `proc`s we've seen so far, with the notable difference of being able to
|
||||
access that class instance through the `this` keyword.
|
||||
For example, inside the body of a method like `getGreeting` above,
|
||||
`this.firstName` gets us the person's first name, and `this.lastName` would
|
||||
get us their last name.
|
||||
|
||||
We can call methods using the dot syntax:
|
||||
|
||||
```Chapel
|
||||
// Prints "Hello, Daniel!"
|
||||
writeln(biggestCandyFan.getGreeting());
|
||||
```
|
||||
|
||||
Methods are a powerful tool for abstraction; rather than writing external code
|
||||
that refers to the various fields of a class, we can put that logic
|
||||
inside of methods, and avoid exposing it to the rest of the world. A person
|
||||
writing `.getGreeting()` will not need to know how a name is represented
|
||||
in the `person` class.
|
||||
|
||||
Another sort of method is a _type method_ (sometimes referred to as
|
||||
a _static method_ in other languages). Rather than being called on
|
||||
an instance of a person, like `biggestCandyFan` or `daniel`, it's called
|
||||
on the class itself. For instance:
|
||||
|
||||
```Chapel
|
||||
class person {
|
||||
// ... as before
|
||||
|
||||
proc type createBiggestCandyFan() {
|
||||
return new person("Daniel", "Fedorin");
|
||||
}
|
||||
}
|
||||
|
||||
var biggestCandyFan = person.createBiggestCandyFan();
|
||||
```
|
||||
|
||||
Methods like this have the benefit of being associated with a particular class.
|
||||
This means that another class can have its own `createBiggestCandyFan()`
|
||||
method, and there won't be any confusion or problems arising from trying
|
||||
to figure out which is which. Perhaps dogs (represented by a hypothetical
|
||||
`dog` class) have a biggest candy fan, too!
|
||||
|
||||
```Chapel
|
||||
var biggestCandyFan = person.createBiggestCandyFan();
|
||||
var biggestCandyFanDog = dog.createBiggestCandyFan();
|
||||
```
|
||||
|
||||
### A `Dir` Class to Represent Directories
|
||||
Back to the solution. The class I use for tracking directories is actually not too different
|
||||
from our modified `person` class above. Each directory
|
||||
{{< sidenote right "dir-firstname-note" "will have a name" >}}
|
||||
Despite the recent media noise about ChatGPT, directories have not yet
|
||||
been granted personhood, and do not have both first and last names.
|
||||
{{< /sidenote >}}
|
||||
as well as a collection of files and directories it contains.
|
||||
*/
|
||||
|
||||
class Dir {
|
||||
var name: string;
|
||||
|
||||
var files = new map(string, int);
|
||||
var dirs = new list(owned TreeNode);
|
||||
var dirs = new list(owned Dir);
|
||||
|
||||
proc init(name: string) {
|
||||
this.name = name;
|
||||
/*
|
||||
Since files have no
|
||||
additional information to them besides their size, I decided to represent
|
||||
them as a map --- a directory's `files` field associates each file's name
|
||||
with that file's size. The subdirectories are represented just like
|
||||
the `children` field from our `person` record, as a list of owned `Dir`s.
|
||||
|
||||
There are a few more things I want to add to `Dir`;
|
||||
the first is a way to read our directory from our puzzle input.
|
||||
|
||||
#### Reading the File System with the `fromInput` Type Method
|
||||
For reasons of abstraction and avoiding conflicts, I put
|
||||
the code for creating a directory from user input into a type method on `Dir`. Within
|
||||
this method, I include the now-familiar code for reading from the
|
||||
input using `readLine`, until we run out of lines.
|
||||
*/
|
||||
|
||||
proc type fromInput(name: string): owned Dir {
|
||||
var line: string;
|
||||
var newDir = new Dir(name);
|
||||
|
||||
while readLine(line, stripNewline = true) {
|
||||
/*
|
||||
Notice that I'm accepting the name for the
|
||||
directory as a string formal and initializing a new variable `newDir` with that name.
|
||||
Notice also that I don't need to provide the `files` and `dirs`
|
||||
as arguments to `new Dir` --- they have default values in the
|
||||
class definition. By default, `new` uses the `owned` memory management
|
||||
strategy. For the time being, the `newDir` variable owns our
|
||||
directory-under-construction.
|
||||
|
||||
We're reading lines now; all that's left is to figure out what to do
|
||||
with them. The first case is that of `$ cd ..`. When we see that line,
|
||||
it means that we're done looking at the current directory; none
|
||||
of the subsequent `ls` lines will be meant for us. Thus, we break
|
||||
out of the input `while`-loop.
|
||||
*/
|
||||
if line == "$ cd .." {
|
||||
break;
|
||||
/*
|
||||
If the `cd` command is used, but its argument isn't `..`, we're being
|
||||
asked to descend into a sub-directory of our current `newDir`.
|
||||
In this case, we call the `fromInput` method again, recursively,
|
||||
to create a subdirectory of the current one. This
|
||||
call will keep consuming lines from the input until the sub-directory
|
||||
has been processed, at which point it will return it to us. We'll
|
||||
immediately append this sub-directory to the `newDir.dirs` list,
|
||||
which becomes the sub-directory's new owner.
|
||||
|
||||
Recall that we need to give `fromInput` the name of the new
|
||||
sub-directory. We can figure out the name by slicing the string
|
||||
starting after the `$ cd` prefix. Since I want to get the rest of the
|
||||
characters after the prefix, I leave the end of my range unbounded, which
|
||||
makes the slice go until the characters run out at the end of the string.
|
||||
If you're feeling shaky on lists and `append`, check out our [day 5 article]({{< relref "aoc2022-day05-cratestacks" >}}#moving-crates-within-an-array-of-lists).
|
||||
If you want a little refresher on slicing, we first covered it on [day 3]({{< relref "aoc2022-day03-rucksacks" >}}/ranges-and-slicing).
|
||||
|
||||
*/
|
||||
} else if line.startsWith("$ cd ") {
|
||||
param cdPrefix = "$ cd ";
|
||||
const dirName = line[cdPrefix.size..];
|
||||
newDir.dirs.append(Dir.fromInput(dirName));
|
||||
/*
|
||||
As it turns out, all that's left is to handle files. We already get
|
||||
directory names from `cd`, so there's no reason to worry about
|
||||
lines starting with `dir`. The `ls` command itself always precedes
|
||||
the list of files and directories; by itself, it provides us no
|
||||
additional information. Thus, our last case is a line that's neither
|
||||
`dir` nor `ls`. Such a line is a file, so its format will be a number
|
||||
followed by the file's name.
|
||||
|
||||
I use the `partition` method on the line to split it into three
|
||||
pieces: the part before the space, the space itself, and the part
|
||||
after the space. After that, I can just update the `newDir` map,
|
||||
associating the file called `name` with its size. I use an integer cast
|
||||
to convert `size` (a string) to a number.
|
||||
*/
|
||||
} else if !line.startsWith("$ ls") && !line.startsWith("dir") {
|
||||
const (size, _, name) = line.partition(" ");
|
||||
newDir.files[name] = size : int;
|
||||
/*
|
||||
That's it for the loop! Once the loop stops running, we know we're done
|
||||
processing the directory. All that remains is to return it. Returning
|
||||
an `owned` value from a function or method transfers ownership to whatever
|
||||
code calls the function or method.
|
||||
*/
|
||||
}
|
||||
}
|
||||
return newDir;
|
||||
}
|
||||
/*
|
||||
One more thing: I have explicitly annotated the
|
||||
return type of `fromInput` to be `owned Dir` to let Chapel know
|
||||
that I'm using the `owned` memory management strategy. This might just
|
||||
be the first return type annotation we've written so far. Up until now,
|
||||
Chapel has been able to deduce the return types of our procedures
|
||||
and iterators automatically. However, here, because we are using
|
||||
recursion, it needs just a little bit of help: determining the types
|
||||
in the body of `fromInput` requires knowing the type of `fromInput`!
|
||||
The manual type annotation helps break that loop.
|
||||
*/
|
||||
|
||||
/*
|
||||
#### An Iterator Method for Listing Directory Sizes
|
||||
Let's recap. What we have now is a data structure, `Dir`, which represents
|
||||
the directory tree. We also have a type method, `Dir.fromInput` that
|
||||
converts our puzzle input into this data structure. What's left?
|
||||
|
||||
The way I see it, the problem is composed of three pieces:
|
||||
|
||||
1. Go through all of the directory sizes...
|
||||
2. ... ignoring those that are above a certain threshold ...
|
||||
3. ... and sum them.
|
||||
|
||||
Over the past week, we've gotten really good at summing things! In
|
||||
Chapel, we can just use `+reduce` to compute the sum of something
|
||||
iterable, so there's point number three. For point two, it turns out that
|
||||
those [loop expressions]({{< relref "aoc2022-day06-packets" >}}#parallel-loop-expressions)
|
||||
from yesterday can be used to filter out elements like so:
|
||||
|
||||
```Chapel
|
||||
[for i in iterable] if someCondition then i
|
||||
```
|
||||
|
||||
Putting these two pieces together, we might write something like:
|
||||
```Chapel
|
||||
+ reduce [for size in directorySizes] if size < 1000000 then size
|
||||
```
|
||||
|
||||
That `directorySizes` is the only "fictional" piece of the solution.
|
||||
Perhaps we can make our `Dir` tree support an iterator of directory sizes?
|
||||
Then, we'd have our answer.
|
||||
|
||||
In my solution, I do just that. Methods on classes don't have to be procedures ---
|
||||
they can also be iterators. There's only one complication. We want our
|
||||
iterator method to yield the sizes of _all_ of the various sub-directories
|
||||
within a `Dir` including sub-directories of sub-directories. That's because
|
||||
we have to sum them all up as per the problem statement. However, when
|
||||
_computing_ the size of a directory, we don't want to include sub-sub-directories
|
||||
in our counting: the direct sub-directories already include the sizes of
|
||||
their own contents. To make this work, I added a `parentSize` formal to
|
||||
the iterator method, which represents a reference to the parent directory's
|
||||
size. When it's done yielding its own size, as well as the sizes of the
|
||||
sub-directories, the iterator method will add its own size to its parent's.
|
||||
|
||||
Here's the implementation of the iterator method; I'll talk about it in
|
||||
more detail below.
|
||||
*/
|
||||
iter dirSizes(ref parentSize = 0): int {
|
||||
// Compute sizes from files only.
|
||||
var size = + reduce files.values();
|
||||
for subDir in dirs {
|
||||
// Yield directory sizes from the dir.
|
||||
for subSize in subDir.dirSizes(size) do yield subSize;
|
||||
}
|
||||
yield size;
|
||||
parentSize += size;
|
||||
}
|
||||
/*
|
||||
The first thing this method does is create a new variable, `size`,
|
||||
representing the current directory's size. It's initialized to the sum
|
||||
of all the file sizes. However, at this point, that's not the whole size ---
|
||||
we also need to figure out how much data is stored in the subdirectories.
|
||||
|
||||
I use a `for` loop over the `dirs` list to examine each sub-directory
|
||||
of the current folder in turn. Each of these sub-directories is its
|
||||
own full-fledged `Dir`, so we can call its `dirSizes`
|
||||
method. This gives us an iterator of all directory sizes from `subDir`.
|
||||
I simply yield them from the parent iterator, making it yield
|
||||
the sizes of all directories, including nested ones. Notice that I also
|
||||
provide `size` as the argument to the recursive call to `dirSizes`:
|
||||
the inner for-loop serves the double purpose of yielding directory sizes
|
||||
and finishing computing the current folder's size.
|
||||
|
||||
Once all of the sub-directory sizes have been yielded, the `size` variable
|
||||
includes all the files in the folder, including nested ones. Thus, I use it to yield
|
||||
the size of the current folder. I also add `size` to `parentSize`.
|
||||
|
||||
That concludes our `Dir` class!
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
{{< skip >}}
|
||||
```Chapel
|
||||
iter these(param tag: iterKind): (string, int) where tag == iterKind.standalone {
|
||||
var size = + reduce files.values();
|
||||
@@ -65,46 +450,74 @@ class TreeNode {
|
||||
this.size = size;
|
||||
}
|
||||
```
|
||||
{{< /skip >}}
|
||||
|
||||
*/
|
||||
|
||||
iter dirSizes(ref parentSize = 0): (string, int) {
|
||||
var size = + reduce files.values();
|
||||
for dir in dirs {
|
||||
// Yield directory sizes from the dir.
|
||||
for subSize in dir.dirSizes(size) do yield subSize;
|
||||
}
|
||||
yield (name, size);
|
||||
parentSize += size;
|
||||
}
|
||||
|
||||
proc type fromInput(name: string, readFrom): owned TreeNode {
|
||||
var line: string;
|
||||
var newDir = new TreeNode(name);
|
||||
/*
|
||||
### Putting It All Together
|
||||
With our `Dir` class complete, we can finally make use of it in our code.
|
||||
The first thing we need to do is read our file system from the input;
|
||||
this is accomplished using the `fromInput` method.
|
||||
*/
|
||||
|
||||
while readFrom.readLine(line, stripNewline = true) {
|
||||
if line == "$ cd .." {
|
||||
break;
|
||||
} else if line.startsWith("$ cd ") {
|
||||
const dirName = line["$ cd ".size..];
|
||||
newDir.dirs.append(TreeNode.fromInput(dirName, readFrom));
|
||||
} else if !line.startsWith("$ ls") {
|
||||
const (sizeOrDir, _, name) = line.partition(" ");
|
||||
if sizeOrDir == "dir" {
|
||||
// Ignore directories, we'll `cd` into them.
|
||||
} else {
|
||||
newDir.files[name] = sizeOrDir : int;
|
||||
}
|
||||
}
|
||||
}
|
||||
return newDir;
|
||||
}
|
||||
}
|
||||
|
||||
var rootFolder = TreeNode.fromInput("", stdin);
|
||||
var rootFolder = Dir.fromInput("/");
|
||||
|
||||
/*
|
||||
Next up, we can use that `+reduce` expression I described above. I use
|
||||
a new variable, `rootSize`, to represent the size of the top-level directory.
|
||||
After the call to `dirSizes` completes, it will be set to the total size of
|
||||
the root directory, i.e., the total disk usage. */
|
||||
var rootSize = 0;
|
||||
writeln(+ reduce [(_, size) in rootFolder.dirSizes(rootSize)] if size < 100000 then size);
|
||||
writeln(+ reduce [size in rootFolder.dirSizes(rootSize)] if size < 100000 then size);
|
||||
|
||||
const toDelete = rootSize - 40000000;
|
||||
writeln(min reduce [(_, size) in rootFolder.dirSizes()] if size >= toDelete then size);
|
||||
/*
|
||||
I could've omitted the argument to `dirSizes` --- notice from the method's
|
||||
signature that I provide a default value for `parentSize`.
|
||||
|
||||
```Chapel
|
||||
iter dirSizes(ref parentSize = 0): int {
|
||||
```
|
||||
|
||||
However, knowing `rootSize` lets us easily compute the amount of space we need
|
||||
to free up (for part 2 of today's problem).
|
||||
*/
|
||||
const toDelete = rootSize - 40000000; // = 30000000 - (70000000 - rootSize)
|
||||
|
||||
/*
|
||||
We can now re-use our `dirSizes` stream to check every directory size again,
|
||||
this time looking for the smallest folder that meets a certain threshold.
|
||||
A `min` reduction takes care of this:
|
||||
*/
|
||||
writeln(min reduce [size in rootFolder.dirSizes()] if size >= toDelete then size);
|
||||
|
||||
/* And there's the solution to part 2, as well! */
|
||||
|
||||
/*
|
||||
### Summary
|
||||
This concludes today's description of my solution. This time, I introduced
|
||||
Chapel's classes --- defining them, creating fields and adding methods. We got
|
||||
a little taste of memory management strategies and ownership, though I deliberately
|
||||
kept it light to avoid introducing too many new concepts.
|
||||
|
||||
Admittedly, today's solution is (for the most part) serial. Although the
|
||||
`+reduce` expression that computes the initial `size` of a directory from
|
||||
its `files` is eligible for parallelization, the `dirSizes` iterator is not. The main
|
||||
reason for this is that the interaction between recursive parallel iterators and
|
||||
reductions is, at the time of writing, unimplemented.
|
||||
Nevertheless, I think that using even a serial iterator has _yielded_ an elegant
|
||||
solution (pun intended).
|
||||
|
||||
If you wanted to write a parallel version, I'd advise creating a new,
|
||||
non-iterator method on `Dir` that solves just part 1 of today's puzzle.
|
||||
This method could return a tuple of two elements, perhaps `sumSmallSizes`
|
||||
and `dirSize`; then, a simple `forall` loop over `dirs` (and judicious use of reduce intents,
|
||||
which are described in our [day 4 article]({{< relref "aoc2022-day04-ranges" >}}third-solution-parallel-approach))
|
||||
will let you compute the answer in parallel.
|
||||
|
||||
Thanks for reading! Please feel free
|
||||
to ask any questions or post any comments you have in the new [Blog
|
||||
Category](https://chapel.discourse.group/c/blog/21) of Chapel's
|
||||
Discourse Page. */
|
||||
|
||||
61
day8.chpl
Normal file
61
day8.chpl
Normal file
@@ -0,0 +1,61 @@
|
||||
use IO;
|
||||
|
||||
// It's easiest to just yield all numbers as a list, then reshape that list
|
||||
// into a square given `rowSize` information.
|
||||
iter allNumbers(ref rowSize: int) {
|
||||
for line in stdin.lines() {
|
||||
const trimmedLine = line.strip();
|
||||
rowSize = trimmedLine.size;
|
||||
|
||||
for num in trimmedLine {
|
||||
yield num : int;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Time to do that reshaping!
|
||||
var rowSize = 0;
|
||||
var allNums = allNumbers(rowSize);
|
||||
var shapedNums = reshape(allNums, { 1..allNums.size / rowSize, 1..rowSize });
|
||||
|
||||
// In this problem, we're considering the view from each direction at each
|
||||
// tree. Define a helper iterator to return an array slice representing trees
|
||||
// in that direction, as well as the "by" step indicating which direction
|
||||
// the path should be walked in.
|
||||
iter eachDirection((x,y)) {
|
||||
yield (shapedNums[..<x,y], -1);
|
||||
yield (shapedNums[x+1..,y], 1);
|
||||
yield (shapedNums[x,..<y], -1);
|
||||
yield (shapedNums[x,y+1..], 1);
|
||||
}
|
||||
|
||||
// All that's left is to write special-case behavior for each part.
|
||||
|
||||
// In part 1, we check if a tree is _never_ blocked along a direction; this can be
|
||||
// accomplished by checking whether or not all trees are less tall than the current tree.
|
||||
proc int.visibleAlong((trees, step)) {
|
||||
return max reduce trees < this;
|
||||
}
|
||||
|
||||
// In part 2, we count the number of trees until a taller tree is encountered.
|
||||
// Here we iterate serially, and use our `step` parameter.
|
||||
proc int.scoreAlong((trees, step)) {
|
||||
var count = 0;
|
||||
for idx in trees.domain by step {
|
||||
count += 1;
|
||||
if trees[idx] >= this then break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Finally, we iterate (in parallel) over each tree, and tally up if it's
|
||||
// visible, as well as compute and note its score.
|
||||
var visible = 0;
|
||||
var bestScore = 0;
|
||||
forall coord in shapedNums.domain with (+ reduce visible, max reduce bestScore) {
|
||||
const tree = shapedNums[coord];
|
||||
visible += || reduce tree.visibleAlong(eachDirection(coord));
|
||||
bestScore reduce= * reduce tree.scoreAlong(eachDirection(coord));
|
||||
}
|
||||
writeln(visible);
|
||||
writeln(bestScore);
|
||||
54
day8.cr
Normal file
54
day8.cr
Normal file
@@ -0,0 +1,54 @@
|
||||
require "advent"
|
||||
INPUT = input(2022, 8).lines.map(&.chars.map(&.to_i32))
|
||||
|
||||
def visible_in_row(arr, idx)
|
||||
(arr[..idx-1].max < arr[idx]) || (arr[idx+1..].max < arr[idx])
|
||||
end
|
||||
|
||||
def score(arr, x, y, dx, dy)
|
||||
tree = arr[x][y]
|
||||
x += dx
|
||||
y += dy
|
||||
count = 0
|
||||
while x >= 0 && x < arr.size && y >= 0 && y < arr[x].size && arr[x][y] < tree
|
||||
count += 1
|
||||
x += dx
|
||||
y += dy
|
||||
end
|
||||
count += 1 if (x >= 0 && x < arr.size && y >= 0 && y < arr[x].size)
|
||||
count
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
input_t = input.transpose
|
||||
count = 0
|
||||
count += input.size * 2
|
||||
count += (input[0].size - 2) * 2
|
||||
(input.size - 2).times do |x|
|
||||
x += 1
|
||||
(input[x].size - 2).times do |y|
|
||||
y += 1
|
||||
tree = input[x][y]
|
||||
if visible_in_row(input[x], y) || visible_in_row(input_t[y], x)
|
||||
count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
count
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
best = 0
|
||||
(input.size - 0).times do |x|
|
||||
(input[x].size - 0).times do |y|
|
||||
tree_score = score(input, x, y, 1, 0) * score(input, x, y, -1, 0) * score(input, x, y, 0, 1) * score(input, x, y, 0, -1)
|
||||
if tree_score > best
|
||||
best = tree_score
|
||||
end
|
||||
end
|
||||
end
|
||||
best
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
39
day9.chpl
Normal file
39
day9.chpl
Normal file
@@ -0,0 +1,39 @@
|
||||
use IO, Set;
|
||||
|
||||
const moveList = [ "L" => (-1, 0), "R" => (1, 0), "U" => (0, 1), "D" => (0, -1) ];
|
||||
|
||||
iter moves() {
|
||||
for line in stdin.lines().strip() {
|
||||
const (dir, _, n) = line.partition(" ");
|
||||
yield (moveList[dir], n : int);
|
||||
}
|
||||
}
|
||||
|
||||
config const length = 2;
|
||||
var rope: [0..<length] (int, int) = (0, 0);
|
||||
|
||||
proc move((hx, hy), (tx, ty)): (int, int) {
|
||||
const (dx, dy) = (hx - tx, hy - ty);
|
||||
if abs(dx) > 1 && dy == 0 {
|
||||
tx += sgn(dx);
|
||||
} else if abs(dy) > 1 && dx == 0 {
|
||||
ty += sgn(dy);
|
||||
} else if abs(dx) + abs(dy) > 2 {
|
||||
tx += sgn(dx);
|
||||
ty += sgn(dy);
|
||||
}
|
||||
return (tx, ty);
|
||||
}
|
||||
|
||||
var visited = new set((int, int));
|
||||
|
||||
for (delta, n) in moves() {
|
||||
for 1..n {
|
||||
rope[0] += delta;
|
||||
for idx in 1..<length {
|
||||
rope[idx] = move(rope[idx-1], rope[idx]);
|
||||
}
|
||||
visited.add(rope.last);
|
||||
}
|
||||
}
|
||||
writeln(visited.size);
|
||||
56
day9.cr
Normal file
56
day9.cr
Normal file
@@ -0,0 +1,56 @@
|
||||
require "advent"
|
||||
|
||||
INPUT = input(2022, 9).lines.map do |line|
|
||||
dir, n = line.split(" ");
|
||||
{dir, n.to_i64}
|
||||
end
|
||||
|
||||
|
||||
positions = Set(Tuple(Int32, Int32)).new
|
||||
|
||||
diff = { "L" => {-1, 0}, "R" => {1, 0}, "U" => {0, 1}, "D" => {0, -1} }
|
||||
|
||||
def move(h, d)
|
||||
{h[0] + d[0], h[1] + d[1]}
|
||||
end
|
||||
|
||||
def follow(h, t)
|
||||
hx, hy = h
|
||||
tx, ty = t
|
||||
dx = hx-tx
|
||||
dy = hy-ty
|
||||
|
||||
if dx.abs > 1 && dy.abs == 0
|
||||
tx += dx.sign
|
||||
elsif dy.abs > 1 && dx.abs == 0
|
||||
ty += dy.sign
|
||||
elsif dx.abs + dy.abs > 2
|
||||
tx += dx.sign
|
||||
ty += dy.sign
|
||||
end
|
||||
|
||||
h = {hx, hy}
|
||||
t = {tx, ty}
|
||||
return {h, t}
|
||||
end
|
||||
|
||||
def simulate(knots, d)
|
||||
knots[0] = move(knots[0], d)
|
||||
(knots.size-1).times do |i|
|
||||
knots[i], knots[i+1] = follow(knots[i], knots[i+1])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
knots = [{0, 0}] * 10
|
||||
INPUT.each do |cmd|
|
||||
dir, n = cmd
|
||||
d = diff[dir]
|
||||
|
||||
n.times do
|
||||
simulate(knots, d)
|
||||
positions << knots.last
|
||||
end
|
||||
end
|
||||
|
||||
puts positions.size
|
||||
Reference in New Issue
Block a user