The part 2 of the problem of day 19 of the AOC 2024 requires calculating the number of partitions of a string that are subsets of a given set of substrings.
This is repeated for a list of strings and to obtain the total number of admissible partitions.
I understood that, as with other problems of this type, brute force solutions do not work, but a “smart” use of recursion is better. In this case, taking advantage of the fact that even if the cases to be treated increase in the various steps, you can group those that have the same “evolution” and “carry forward” only the “representative” ones so that the number of cases to be managed does not increase explosively.
I compared two forms of one of the solutions of this specific case, finding differences in performance that I cannot explain intuitively.
I wonder why between the functions f1(…) and f1a(…) the first that uses a for loop is slower, even though it produces less allocation.
From what I can gather the form with comprehension and broadcasting
t=length.(ts[findall(endswith.(dsi[1:end-ss],ts))])
cm[t.+ss].+=cm[ss]
makes more passes through the vectors than the for loop does
for e in ts
if endswith(dsi[1:end-ss],e)
cm[length(e)+ss]+=cm[ss]
end
end
so I would have expected this to be faster as well as allocating less.
f1(...)
st,sd=open("input_aoc24_d19.txt") do f
split(read(f, String), "\n\n")
end
ts=sort(split(st,", "),by=length)
ds=split(sd,'\n')
function f1(ds,ts,i)
dsi=ds[i]
l=length(dsi)
cm=zeros(Int,l)
t=length.(ts[findall(endswith.(dsi[1:end],ts))])
cm[t].=1 # cm[t].=1 funziona anche con t vuoto (perchè?)
isempty(t) && return 0
ss=t[1]
while true
ss=findnext(!=(0),cm[1:end-1],ss)
(isnothing(ss)||ss==l) && return cm[end]
t=length.(ts[findall(endswith.(dsi[1:end-ss],ts))])
cm[t.+ss].+=cm[ss]
cm[ss]=0
end
end
using BenchmarkTools
@btime cm=f1(ds,ts,1)
@btime sum(f1(ds,ts,i) for i in 1:length($ds);init=0) # 666491493769758
f1a(...)
function f1a(ds,ts,i)
dsi=ds[i]
l=length(dsi)
cm=zeros(Int,l)
t=length.(ts[findall(endswith.(dsi[1:end],ts))])
cm[t].=1 # cm[t].=1 funziona anche con t vuoto (perchè?)
isempty(t) && return 0
ss=t[1]
while true
ss=findnext(!=(0),cm[1:end-1],ss)
(isnothing(ss)||ss==l) && return cm[end]
for e in ts
if endswith(dsi[1:end-ss],e)
cm[length(e)+ss]+=cm[ss]
end
end
cm[ss]=0
end
end
using BenchmarkTools
@btime cm=f1a(ds,ts,1)
@btime sum(f1a(ds,ts,i) for i in 1:length($ds);init=0) # 666491493769758
julia> @btime cm=f1(ds,ts,1)
161.400 μs (468 allocations: 273.02 KiB)
178604257966
julia> @btime sum(f1(ds,ts,i) for i in 1:length($ds);init=0) # 666491493769758
69.592 ms (140919 allocations: 79.66 MiB)
666491493769758
julia> @btime cm=f1a(ds,ts,1)
359.400 μs (60 allocations: 30.28 KiB)
178604257966
julia> @btime sum(f1a(ds,ts,i) for i in 1:length($ds);init=0) # 666491493769758
130.744 ms (19415 allocations: 8.94 MiB)
666491493769758
PS don’t overlook the possibility that I measured badly …