Compare commits

...

17 Commits

Author SHA1 Message Date
Danila Fedorin
c9cc95c447 Clean up day 15 solution a bit more 2022-12-14 23:13:49 -08:00
Danila Fedorin
8b7da0b751 Add day 15 solution 2022-12-14 23:11:45 -08:00
Danila Fedorin
bbaedda944 Add day 14 solution in Chapel 2022-12-13 21:58:59 -08:00
Danila Fedorin
7fed659932 Add day 14 solutions 2022-12-13 21:26:38 -08:00
Danila Fedorin
d8a62d2eb4 Add Crystal solution to day 13 2022-12-12 21:47:00 -08:00
Danila Fedorin
8dda1b5ee2 Add day 11 code 2022-12-12 11:38:59 -08:00
Danila Fedorin
efa81418eb Make tweaks to day 10 code to make it clearer 2022-12-12 10:16:46 -08:00
Danila Fedorin
76705dc6fb Change day 12 from part 1 to part 2 (cleanup later) 2022-12-12 10:15:54 -08:00
Danila Fedorin
5cd1a7f157 Add day 12 part 1 solution 2022-12-12 10:15:32 -08:00
Danila Fedorin
5f2cb7c7ee Cleaned up Chapel solution 2022-12-09 22:58:39 -08:00
Danila Fedorin
e26f41cd68 Initial Chapel solution 2022-12-09 22:57:22 -08:00
Danila Fedorin
53172351df Bring day 7 up to date with the blog version 2022-12-08 22:33:30 -08:00
Danila Fedorin
268b6b24b4 "Publish" and tweak the day 1 post 2022-12-08 22:33:10 -08:00
Danila Fedorin
80921bdf95 Make day 6 use in_groups_of 2022-12-08 22:20:55 -08:00
Danila Fedorin
99ba049301 Remove debug printing from day 8's Crystal 2022-12-08 22:20:05 -08:00
Danila Fedorin
874716448e Add day 9 solutions 2022-12-08 22:19:40 -08:00
Danila Fedorin
2b88faf388 Add day 8 solutions 2022-12-07 22:34:59 -08:00
16 changed files with 1308 additions and 54 deletions

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}
}
}
}

View File

@@ -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
View File

@@ -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 arent
flexible enough to support the arbitrary levels of nesting that would be
required by a program that didnt know the directory structure _a priori_
(e.g. one that was reading it from disk). We simply dont have the
information at compile-time to describe the tuples 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
View 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
View 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
View 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
View 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