(The algorithms tag is appropriate, I think)
This post will focus on the 7th problem of AoC 2021, part 2. We first discuss the straightforward method for the original problem, and then discuss the continuous version of the problem.
This post requires some knowledge of mathematics, or more specifically, quadratic functions and absolute value.
Problem #7, AoC 2021
Basic observations
The 7th problem of 2021 Advent of Code is pretty interesting. Mathematically speaking we need to find a real number
for a certain real sequence
, such that the expression
reaches its smallest possible value.
Multiply
by 2 doesn't affect the
we're looking for. So it suffices to look for the minimum of
Expanding the expression we easily get
where and . is just a constant , which doesn't affect our .
Recall that
Let
reaches its minimum when
and
reaches its minimum when
, where
is a constant. Both value is easy and quick to obtain from the computer. Denote the interval between
and
by
. Notice that
and
are both concaving up (at least not concaving down) on
, therefore the minimum of
can only be reached on
. Since for the input provided by AoC
and
, which are close enough, and the original problem restricted
to be on
, the code can be easily written.
# The straightforward method
from math import floor, ceil
def median(list: list[int]):
leng = len(list)
list.sort()
if leng % 2 == 1:
return list[(leng + 1) // 2]
else:
return (list[leng // 2] + list[leng // 2 + 1]) / 2
def method1(data: list[int]):
med = median(data)
mean = sum(data) / len(data)
if med < mean:
med = floor(med)
mean = ceil(mean)
else:
med = ceil(med)
mean = floor(mean)
def calcVal(pos: int):
nonlocal data
sum = 0
for e in data:
dif = abs(e - pos)
sum += (dif * (dif + 1)) // 2
return sum
answer = calcVal(med)
position = med
for i in range(med, mean + 1):
newans = calcVal(i)
if (newans < answer):
answer = newans
position = i
print("Minimum is", answer, ", reached when x =", position)
method1([1, 2, 3, 5, 9, 12, 89])
Minimum is 3118 , reached when x = 17
Further Discussion
But I don't want to stop here. We assume that . Consider the graph of It is made up with segments (0 of length by chance), defined on respectively, whose slope being at the beginning, increases by 2 per segment, gradually stopping at . Therefore the segment on , denoted by , has a slope of . We also let and .
Consider
, where
. Its axis of symmetry would be
. When
,
decreases on its domain. When
,
increases. Notice that while
is increasing while
is decreasing, there must be a number
, where
, and
. Then on
is decreasing, and on
is increasing. This implies that the minimum of
could only be reached on
. It's worth noticing that if
, then
reaches minimum at
; if
, then
reaches minimum at
; because the turning point of
is exactly
, so be only need to check
,
,
and
. This method is even worse for computer (has to sort the sequence and find
), but it's better mathematically (in my opinion). Also, this can work for any real
and
.
def method2(data: list[float]):
data.sort()
length = len(data)
m2 = sum(data) / len(data)
def s(x: float):
nonlocal data
sum = 0
for e in data:
dif = abs(e - x)
sum += (dif * (dif + 1)) / 2
return sum
def d(i: int):
nonlocal m2, length
return m2 + 1 / 2 - i / length
def findBeta():
nonlocal data, m2
for i in range(1, length):
if data[i] >= d(i):
return i - 1
return length
beta = findBeta()
checklist = [beta + 1, 0, length]
reachmin = 0
minimum = s(d(beta))
for i in checklist:
newval = s(d(i))
if newval < minimum:
reachmin = d(i)
minimum = newval
print("Minimum is", minimum, ", reached when x =", reachmin)
method2([1, 2, 3, 5, 9, 12, 89])
Minimum is 3117.9821428571427 , reached when x = 16.928571428571427
Beautiful, isn't it?
Top comments (1)
Using the continuous version the best potision to align the crabs (my input data) is 462.40299999999996, where the fuel cost reaches the minimum of 96864153.79550003. A lot less than the "correct answer" 96864235!