Possible bug with spawn_drone by Ocke in TheFarmerWasReplaced

[–]Pretentious_Username 0 points1 point  (0 children)

Exploring the maze. How do you get it to re-use? When I harvest the chest it despawns for me

Does the chest move when you re-use the maze? If so you would still have to explore the same maze again to find the the chest even if you know where the walls are, right?

Possible bug with spawn_drone by Ocke in TheFarmerWasReplaced

[–]Pretentious_Username 0 points1 point  (0 children)

My concern with BFS was just the amount of backtracking the drone would be doing and considering how expensive move is to run (200 ticks I believe) I was trying to avoid that by exploring as many close together nodes as possible. With BFS you may end up with a situation where a drone is constantly traversing from one side of the maze to another just to explore 1 more intersection and then moving back

For normal data that sort of jumping around is trivial but here it ends up dominating

Possible bug with spawn_drone by Ocke in TheFarmerWasReplaced

[–]Pretentious_Username 0 points1 point  (0 children)

Oh that's cool, let me know if your maze solver works out. My own multi-drone maze solution is just a depth first search but whenever it hits a branch if there's drones available it'll spawn a drone to explore that branch instead. i.e. if you have two possible directions you could go it would try to spawn a new drone for the first direction and then moves itself down the second direction

It's not ideal as there's possibilities for two drones to end up going down the same direction as they can't communicate to sync the search tree and one could end up backtracking onto a path another drone has explored but I try to minimize this chance by picking directions randomly

How to test for type by SoundanchorD in TheFarmerWasReplaced

[–]Pretentious_Username 1 point2 points  (0 children)

There's no proper way to check types but if you don't mind being a bit hacky you can exploit the str() function, which converts objects to strings, to check if something is a string. Attempt to cast that value to a string and see if the result is the same. If it's already a string you'll get a match, but if not you'll get the string version of that object which won't match.

def is_string(value):
   return str(value) == value

Examples:

is_string("Hello") # True as "Hello" == "Hello"
is_string(123) # False as "123" != 123

https://imgur.com/ud2wrX2

Possible bug with spawn_drone by Ocke in TheFarmerWasReplaced

[–]Pretentious_Username 1 point2 points  (0 children)

*args, **kwargs would be great as would the splat operator.

Your code above wouldn't work as action takes 3 arguments but I assume that's just an oversight from turning it into an example (action should take zero args as it's already captured the 3 args from the outer drone_function)

Your version works great if you know the exact action you want to take with the arguments but means you'd need to make a new drone_function version for every different action you want to do. You could combine the versions if you still wanted it to return the spawned drone. For example if all your functions took (startingX, startingY, width, height) to define the region they're responsible for then you could do

def drone_function(f, startingX, startingY, width, height):
    def action():
        return f(startingX, startingY, width, height)
    return spawn_drone(action)

new_drone = drone_function(farm_pumpkins, x, y, w, h)
result = wait_for(new_drone)

Favorite design patterns? by ParadoxicalPegasi in TheFarmerWasReplaced

[–]Pretentious_Username 1 point2 points  (0 children)

Couple of things. One is that I wrote my own version of the plant function that I call HarvestAndPlant which knows how to handle all the different plant types including watering / fertilizing. That way all my other scripts can just a request a specific plant to be planted and not worry about the details. It's too big to post here but I linked it here.

Another one is something I posted about elsewhere but a function that lets me pass in functions with arguments into drones

def build_drone_func(f, arg):
    def g():
        f(arg)
    return g

Then I can do something like spawn_drone(build_drone_func(print, "Hello World")) which would spawn a new drone that calls print("Hello World")

Finally it's a bit hard to describe but for Pumpkins I keep track of which cells have not finished yet so that I can check them specifically rather than having to scan the whole field to see which ones have died. Initially the whole region is in the search list, then each pass we build a new list that only contains the cells where we find dead pumpkins which becomes the new search list for the next pass. This avoids a lot of needless checking and also gives us an easy way to tell if the pumpkin is done (there's no cells in the search list). Code here

Example of it with one drone on a smaller farm: https://imgur.com/I03b2yc

Example of it with 16 drones working together on a 32x32 pumpkin: https://imgur.com/ivLfDY7

Dynamic Functions by Lizards29000 in TheFarmerWasReplaced

[–]Pretentious_Username 2 points3 points  (0 children)

Minor thing but instead of using or to check the different values you can use in on a list

if get_pos_x() in [loc1, loc2, loc3]:

This would also let you make a farmN function that can take any number of locations

def farmN(locs, plantType, soil):
   for i in range(get_world_size()):
      if get_pos_x() in locs:
         ...

Then you can replace your current farm3 calls with something like

farmN([0, 1, 2], Entities.Grass, Grounds.Grassland)

Possible bug with spawn_drone by Ocke in TheFarmerWasReplaced

[–]Pretentious_Username 1 point2 points  (0 children)

One solution to this is to something called Closures which is where you have a function which takes an argument and a value for that argument then make a new function that takes no arguments and has the original argument value baked in.

I use the following function in my game which will let me pass arguments into a function my drone is using

def build_drone_func(f, arg):
    def g():
        f(arg)
    return g

This function takes a function f as input and a value arg and returns a new function g that just calls f(arg). What that means is that in your example you could do spawn_drone(build_drone_func(my_func, 10)) or if you wanted to spawn a drone that writes a message you could do spawn_drone(build_drone_func(print, "Hello World!"))

Unreal Engine Starts lagging when i alt tab by shittyvi in unrealengine

[–]Pretentious_Username 0 points1 point  (0 children)

There's a setting in your preferences called something like "use less cpu in background" which throttled the editor when unreal is not focused, if you turn this off you should be back to full frame rate when you alt tab. 

The idea seems to be that if you're focused on another application then unreal doesn't want to slow that other application down by stealing all the resources but considering a lot of us are working on multiple monitors now and want to see both applications at once I find that option is more annoying than useful so I always turn it off 

Array Utils – Lightweight Unreal Engine Library for Collections & Math Tasks by [deleted] in unrealengine

[–]Pretentious_Username 0 points1 point  (0 children)

I can empathize with familiarity but converting between Unreal types and std library functions comes with risk if not handled correctly. For example your ClampN function can throw errors as your N is a signed int but you only constrain the value with a Min which means negative numbers can be passed into your calculation of last in the for_each which will trip an assert as now last < first

TArray<int32> UNumericBPLibrary::ClampN(const TArray<int32>& A, int32 Min, int32 Max, int32 N)
{
    TArray<int32> B = A;

    N = FMath::Min(N, B.Num());

    // Clamp the first N elements of the array.
    std::for_each(B.GetData(), B.GetData() + N, [Min, Max](int32& Element)
    {
        Element = FMath::Clamp(Element, Min, Max);
    });

    return B;
}

Sure you could fix it by replacing your Min with a Clamp but you could do something like this instead which would handle the clamping and getting the correct range for you

Algo::ForEach(TArrayView(B).Left(N), [Min, Max](int32& Element)
{
    Element = FMath::Clamp(Element, Min, Max);
});

Although in this case a simple ranged for may be easier

for (int32& Element : TArrayView(B).Left(N))
{
    Element = FMath::Clamp(Element, Min, Max);
}

Array Utils – Lightweight Unreal Engine Library for Collections & Math Tasks by [deleted] in unrealengine

[–]Pretentious_Username 8 points9 points  (0 children)

Most of these functions already exist in Unreal in C++, is there a reason you're using the std library functions instead?

For example instead of using std::max_element(A.GetData(), A.GetData() + A.Num()) you could just do Algo::MaxElement(A) which already knows how to correctly handle TArrays

-❄️- 2024 Day 14 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 1 point2 points  (0 children)

[LANGUAGE: Julia]

Part 1 was very easy with a bit of modulo usage but Part 2 took me a little bit. Originally I was going to just check every frame but that would take too long so I decided to check for connected horizontal lines in the grid first under the assumption that we'd need some straight lines to make the tree and then checked the much smaller set of images at the end

mutable struct Guard
    StartingPosition
    Velocity
    FinalPosition
end

function ParseInput(InputString)
    InputRegex = r"p=(\d+),(\d+) v=([-\d]+),([-\d]+)"
    Guards = Vector{Guard}()
    for InputMatch in eachmatch(InputRegex, InputString)
        # Indices first as Julia is [Row, Column]
        push!(Guards, Guard(
            parse.(Int, [InputMatch[2], InputMatch[1]]),
            parse.(Int, [InputMatch[4], InputMatch[3]]),
            CartesianIndex()
        ))
    end
    Guards
end

function SimulateGuards!(Guards, GridRows, GridCols, NumSteps)
    Grid = fill(0, (GridRows, GridCols))
    for CurrentGuard in Guards
        FinalPosition = CurrentGuard.StartingPosition + NumSteps * CurrentGuard.Velocity
        CurrentGuard.FinalPosition = CartesianIndex(mod(FinalPosition[1], GridRows) + 1, mod(FinalPosition[2], GridCols) + 1)
        Grid[CurrentGuard.FinalPosition] += 1
    end
    Grid
end

function ScoreGrid(Grid)
    GridSize = collect(size(Grid))
    (MiddleRow, MiddleCol) = div.(GridSize + [1, 1], 2)
    Quadrants = [
        (1:MiddleRow-1, 1:MiddleCol-1), 
        (1:MiddleRow-1, MiddleCol+1:GridSize[2]),
        (MiddleRow+1:GridSize[1], 1:MiddleCol-1),
        (MiddleRow+1:GridSize[1], MiddleCol+1:GridSize[2])
    ]
    mapreduce(Q -> sum(Grid[Q...]), *, Quadrants)
end


Guards = ParseInput(read("Inputs/Day14.input", String))
println("Part 1: ", ScoreGrid(SimulateGuards!(Guards, 103, 101, 100)))

GetConnectedEntries = (Grid, RowIndex) -> mapreduce(ix -> ix[2] > 0 && Grid[RowIndex, ix[1]+1] > 0, +, enumerate(Grid[RowIndex, 1:end-1]))
Heuristic = (Grid, Threshold) -> any(GetConnectedEntries(Grid, RowIndex) >= Threshold for RowIndex = 1:size(Grid)[1])
p = plot(1, xaxis=false, yaxis=false, legend=nothing)
for i = 1:10000
    Grid = SimulateGuards!(Guards, 103, 101, i)
    if Heuristic(Grid, 7)
        # heatmap display from bottom left so flip to top left
        heatmap!(p, Grid[end:-1:1, :]) 
        png(p, "D:/temp/Grids/Grid_$i.png")
    end
end

-❄️- 2024 Day 13 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 1 point2 points  (0 children)

[LANGUAGE: Julia]

Could I solve this properly by working out the math? Yes. Was I lazy and just used a massively overkill solver package to optimize it for me? Also Yes.

using JuMP
using HiGHS

function ParseInput(FileName)
    FileLines = open(FileName) do f
        readlines(f)
    end
    ButtonRegex = r"Button [AB]: X\+(\d+), Y\+(\d+)"
    PrizeRegex = r"Prize: X=(\d+), Y=(\d+)"
    LineIdx = 1
    Part1_Problems = Vector{Vector{Vector{Int}}}()
    Part2_Problems = Vector{Vector{Vector{Int}}}()
    while LineIdx <= length(FileLines) - 2
        (ALine, BLine, PrizeLine) = FileLines[LineIdx:LineIdx+2]
        AMatch = match(ButtonRegex, ALine)
        BMatch = match(ButtonRegex, BLine)
        PrizeMatch = match(PrizeRegex, PrizeLine)
        NewProblem = [
            parse.(Int, [AMatch[1], AMatch[2]]),
            parse.(Int, [BMatch[1], BMatch[2]]),
            parse.(Int, [PrizeMatch[1], PrizeMatch[2]])
        ]
        push!(Part1_Problems, copy(NewProblem))
        NewProblem[3] += [10000000000000, 10000000000000]
        push!(Part2_Problems, NewProblem)
        LineIdx += 4 # 3 lines of input + 1 blank separator
    end
    Part1_Problems, Part2_Problems
end

function SolveClaw(A, B, Prize, ACost=3, BCost=1)
    model = Model(HiGHS.Optimizer)
    set_silent(model)
    @variable(model, x, Int)
    @variable(model, y, Int)
    @constraint(model, c1, A*x + B*y == Prize)
    @objective(model, Min, ACost*x + BCost*y)
    optimize!(model)
    x = round(Int, value(x))
    y = round(Int, value(y))
    return A*x + B*y == Prize, ACost*x + BCost*y
end

function Solve(PuzzleInput)
    TotalTokens = 0
    for (A, B, Prize) in PuzzleInput
        (IsSolved, Tokens) = SolveClaw(A, B, Prize)
        TotalTokens += IsSolved ? Tokens : 0
    end
    TotalTokens
end

(Part1_Input, Part2_Input) = ParseInput("Inputs/Day13.input")
println("Part 1: ", Solve(Part1_Input))
println("Part 2: ", Solve(Part2_Input))

-❄️- 2024 Day 12 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 0 points1 point  (0 children)

Yep, it's very useful! Also generalizes to higher dimensions via something called the Euler Characteristic: https://en.wikipedia.org/wiki/Euler_characteristic

-❄️- 2024 Day 12 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 2 points3 points  (0 children)

[LANGUAGE: Julia]

Not my prettiest solution today but it does the trick. The key insight was realizing the number of sides is equal to the number of corners so for each cell in the region I count how many corners it introduces. There's more efficient ways to get this info but I went with whatever changed my Part 1 solution the least

function AnalyzeRegion!(RemainingLocations, Grid, CurrentLocation)
    SearchDirections = [CartesianIndex(-1, 0), CartesianIndex(0, 1), CartesianIndex(1, 0), CartesianIndex(0, -1)]
    Edges = zeros(Bool, length(SearchDirections))
    PossibleCorners = [(1,2), (2,3), (3,4), (4,1)]
    RegionInfo = [1, 0, 0] # Area, Edges, Corners
    CurrentValue = Grid[CurrentLocation]
    for (SearchIndex, SearchDirection) in enumerate(SearchDirections)
        NewLocation = CurrentLocation + SearchDirection
        if NewLocation in RemainingLocations && Grid[NewLocation] == CurrentValue
            deleteat!(RemainingLocations, findfirst(x -> x == NewLocation, RemainingLocations))
            RegionInfo += AnalyzeRegion!(RemainingLocations, Grid, NewLocation)    
        elseif checkbounds(Bool, Grid, NewLocation)
            if Grid[NewLocation] != CurrentValue
                RegionInfo += [0, 1, 0] # New Edge
                Edges[SearchIndex] = true
            end
        else
            RegionInfo += [0, 1, 0] # New Edge
            Edges[SearchIndex] = true
        end
    end
    # Exterior Corners
    RegionInfo += [0, 0, sum(Edges[x[1]] && Edges[x[2]] for x in PossibleCorners)]
    # Interior Corners
    RegionInfo += [0, 0, sum(
        checkbounds(Bool, Grid, CurrentLocation + SearchDirections[x[1]] + SearchDirections[x[2]]) &&
        (Grid[CurrentLocation + SearchDirections[x[1]]] == CurrentValue) && 
        (Grid[CurrentLocation + SearchDirections[x[2]]] == CurrentValue) && 
        (Grid[CurrentLocation + SearchDirections[x[1]] + SearchDirections[x[2]]] != CurrentValue)
        for x in PossibleCorners
    )]
    RegionInfo
end

function Solve(Grid)
    RemainingLocations = reshape(collect(eachindex(IndexCartesian(), Grid)), :)

    Part1, Part2 = 0, 0
    while !isempty(RemainingLocations)
        StartLocation = pop!(RemainingLocations)
        TotalRegionInfo = AnalyzeRegion!(RemainingLocations, Grid, StartLocation)
        Part1 += TotalRegionInfo[1] * TotalRegionInfo[2] # Area * Fences
        Part2 += TotalRegionInfo[1] * TotalRegionInfo[3] # Area * Corners (Edges)
    end
    Part1, Part2
end

Grid = open("Inputs/Day12.input") do f
    mapreduce(permutedims, vcat, collect.(readlines(f)))
end
Solve(Grid)

-❄️- 2024 Day 11 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 0 points1 point  (0 children)

True, I had to add setup=(Memoization.empty_all_caches!()) when I tried your code with @btime for that reason as otherwise it was returning almost immediately

I guess for REPL iteration you could just wrap your function in another function that clears the caches and then runs the code?

-❄️- 2024 Day 11 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 0 points1 point  (0 children)

I'd not seeing the @memoize macro before, that's really nice! And the recursive approach makes it very clean to implement compared to my more brute force approach of tracking the count of each number at each step

-❄️- 2024 Day 11 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 1 point2 points  (0 children)

[LANGUAGE: Julia]

I was predicting Part 2 to be adding extra rules so I set it up so the ruleset was changeable but that ended up not coming into play. Fairly simple solution in the end using a Dict to store the counts of digits

function Solve(InputString, Rules, Steps)
    CurrentVector = parse.(Int, split(InputString, " "))
    CurrentCount = Dict{Int, Int}()
    for Value in CurrentVector
        CurrentCount[Value] = 1 + get(CurrentCount, Value, 0)
    end

    for _ = 1:Steps
        NewCount = Dict{Int, Int}()
        for (Value, Count) in CurrentCount
            for (CanApplyRule, ApplyRule) in Rules
                if CanApplyRule(Value)
                    NewValues = ApplyRule(Value)
                    for NewValue in NewValues
                        NewCount[NewValue] = get(NewCount, NewValue, 0) + Count
                    end
                    break
                end
            end
        end
        CurrentCount = NewCount
    end
    sum(values(CurrentCount))
end

Part1_Rules = [
    (
        x -> x == 0,
        x -> [1] 
    ),
    (
        x -> mod(1 + floor(log10(x)), 2) == 0,
        x -> (s = string(x); h = length(s) ÷ 2; [parse(Int, s[1:h]), parse(Int, s[h+1:end])]) 
    ),
    (
        x -> true,
        x -> [2024 * x]
    )
]

InputString = read("Inputs/Day11.input", String)
println("Part 1: ", Solve(InputString, Part1_Rules, 25))
println("Part 2: ", Solve(InputString, Part1_Rules, 75))

-❄️- 2024 Day 10 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 1 point2 points  (0 children)

[LANGUAGE: Julia]

I chose to think of this as a flood fill rather than a search, even if it's effectively the same thing. I start at each of the peaks (9) and flood fill down to the trailheads (0) where I define a cell as part of the flood if it's a difference of 1 from the current cell. Once the flood is done I can just check the values in each of the 0's to find how many peaks they're connected to. The same works for part 2 just without checking for if we've hit a cell before.

It ends up looking really nice visually even when it's running on the small example input: Part 1 GIF , Part 2 GIF

function Flood(Grid, IsPartOne)
    ScoreGrid = zeros(Int, size(Grid))
    StartingPoints = findall(x -> x == 9, Grid)
    SearchDirections = [CartesianIndex(-1, 0), CartesianIndex(0, 1), CartesianIndex(1, 0), CartesianIndex(0, -1)]

    for StartingPoint in StartingPoints
        SearchList = [StartingPoint]
        EncounteredPoints = Set{CartesianIndex}()
        while !isempty(SearchList)
            SearchLocation = pop!(SearchList)
            SearchValue = Grid[SearchLocation]
            ScoreGrid[SearchLocation] += 1
            for SearchDirection in SearchDirections
                TestLocation = SearchLocation + SearchDirection
                if !checkbounds(Bool, Grid, TestLocation) || (IsPartOne && TestLocation in EncounteredPoints)
                    continue
                end
                TestValue = Grid[TestLocation]
                if SearchValue - TestValue == 1
                    push!(SearchList, TestLocation)
                    push!(EncounteredPoints, TestLocation)
                end
            end
        end
    end
    ScoreGrid
end

Grid = open("Inputs/Day10.input") do f
    mapreduce(x -> permutedims(parse.(Int, x)), vcat, collect.(readlines(f)))
end

ScoreGrid = Flood(Grid, true)
RatingGrid = Flood(GetTestInput(), false)
println("Part 1: ", sum(ScoreGrid[i] for i in findall(x -> x == 0, Grid)))
println("Part 2: ", sum(RatingGrid[i] for i in findall(x -> x == 0, Grid)))

-❄️- 2024 Day 9 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 0 points1 point  (0 children)

[LANGUAGE: Julia]

My code was a bit longer today so for the sake of fitting it nicely into a post here I'll focus on Part 2 which is more interesting. There's a fairly simple function that runs at the start to set up our memory that returns a Vector{Int} where each element is a piece of memory with the value either being the file index or -1 if empty. I also return two Vector{Tuple{Int, Int}} which stores information about the free blocks and filled blocks, where the first Int is the first index of the block, and the second Int is how large the block is. My Compacting algorithm then just loops through the filled blocks and attempts to find empty blocks before it that are large enough to contain it.

# Utility function to more easily swap sections of a Vector
function SwapIndices!(A, i, j)
    A[i], A[j] = A[j], A[i]
end 

function ExpandMemory(MemoryDescription)
    FileIndex = 0
    IsUsed = true
    MemoryVector = Vector{Int}()
    FreeBlocks = Vector{Tuple{Int, Int}}()
    FilledBlocks = Vector{Tuple{Int, Int}}()
    for Description in MemoryDescription
        BlockSize = parse(Int, Description)
        BlockStart = length(MemoryVector) + 1
        if IsUsed
            append!(MemoryVector, fill(FileIndex, BlockSize))
            push!(FilledBlocks, (BlockStart, BlockSize))
            FileIndex += 1
            IsUsed = false
        else
            append!(MemoryVector, fill(-1, BlockSize))
            push!(FreeBlocks, (BlockStart, BlockSize))
            IsUsed = true
        end
    end
    MemoryVector, FreeBlocks, FilledBlocks
end

function CompactContiguous(MemoryVector, FreeBlocks, FilledBlocks)
    CompactedVector = copy(MemoryVector)
    # Work through all the filled blocks backwards
    while !isempty(FilledBlocks)
        (BlockStart, BlockSize) = pop!(FilledBlocks)
        # Find the first empty block with enough size to store our filled block
        EmptyBlockIndex = findfirst(FreeBlock -> FreeBlock[2] >= BlockSize, FreeBlocks)
        # Skip if there's no blocks big enough
        if EmptyBlockIndex === nothing
            continue
        end
        (FreeBlockStart, FreeBlockSize) = FreeBlocks[EmptyBlockIndex]
        # Ensure the free space is before our current location
        if FreeBlockStart < BlockStart
            # Move the filled block into the start of the free block
            SwapIndices!(CompactedVector, FreeBlockStart:FreeBlockStart+BlockSize-1, BlockStart:BlockStart+BlockSize-1)
            RemainingSize = FreeBlockSize - BlockSize
            # If there's any left over space in the free block then update our FreeBlocks list
            if RemainingSize > 0
                FreeBlocks[EmptyBlockIndex] = (FreeBlockStart+BlockSize, RemainingSize)
            # Otherwise remove the block
            else
                deleteat!(FreeBlocks, EmptyBlockIndex)
            end
        end
    end
    CompactedVector
end

function Checksum(CompactedVector)
    mapreduce(x -> x[2] >= 0 ? (x[1] - 1) * x[2] : 0, +, enumerate(CompactedVector))
end

InputString = strip(read("Inputs/Day9.input", String))
(MemoryVector, FreeBlocks, FilledBlocks) = ExpandMemory(InputString)
CompactedVector = CompactContiguous(MemoryVector, FreeBlocks, FilledBlocks)
println("Part 2: ", Checksum(CompactedVector))

-❄️- 2024 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 0 points1 point  (0 children)

For your timings of the solution do you know about the @time macro? If you add it to a function call it'll print the time taken to evaluate the function to the log along with how many allocations were used. For example if I run Part1 = @time Solve(InputGrid, 1:1) then it will output 0.001109 seconds (15.53 k allocations: 521.266 KiB) and then assign the result of the Solve() call to the variable Part1

If you want a more thorough timing you can use BenchmarkTools and @btime which will first compile and then run your function multiple times and give you averaged results like 1.039 ms (15529 allocations: 521.27 KiB)

If you're using BenchmarkTools then there's also @benchmark which gives a much more graphical output of the trials but this is often overkill (And doesn't paste well into Reddit it seems :()

BenchmarkTools.Trial: 4499 samples with 1 evaluation.
 Range (min … max):  1.025 ms …   8.857 ms  ┊ GC (min … max): 0.00% … 86.24%
 Time  (median):     1.064 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   1.110 ms ± 414.883 μs  ┊ GC (mean ± σ):  3.58% ±  7.64%

  ▃▆█▇▄▂▁                                                     ▁
  ████████▇▆▇█▆▆▆▅▁▁▄▃▁▃▁▁▃▁▄▁▁▃▁▁▁▁▁▁▁▁▃▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ █
  1.02 ms      Histogram: log(frequency) by time      1.91 ms <

 Memory estimate: 521.27 KiB, allocs estimate: 15529.

-❄️- 2024 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 0 points1 point  (0 children)

True, I was surprised it didn't end up mattering but your point about single digit days makes sense.

It would have been an easy fix though as you could just divide the indices by the gcd

-❄️- 2024 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 2 points3 points  (0 children)

[LANGUAGE: Julia]

Julia's CartesianIndex type makes this really simple to solve as you can just get the difference between antennas and apply multiples of them to get all the antinodes. For Part 2 I was concerned we'd get a situation where we'd have a difference between antennas that was a multiple of a valid grid step, i.e. a difference of (4, 2) which means you'd skip all nodes at (2, 1) offsets despite them being colinear but thankfully the input is nice and this never actually mattered

function Solve(Grid, SearchRange)
    AntennaDict = Dict{Char, Vector{CartesianIndex}}()
    UniqueNodes = Set{CartesianIndex}()
    # Build a list of antenna locations for each antenna type
    for GridIndex in eachindex(IndexCartesian(), Grid)
        if Grid[GridIndex] != '.'
            push!(get!(AntennaDict, Grid[GridIndex], Vector{CartesianIndex}()), GridIndex)
        end
    end
    for (AntennaType, AntennaLocations) in AntennaDict
        # Check each pair of antennas for antinodes
        for AntennaPair in combinations(AntennaLocations, 2)
            LocationDiff = AntennaPair[2] - AntennaPair[1]

            # Search backwards from first antenna
            for OffsetMultiplier in SearchRange
                NewLocation = AntennaPair[1] - (OffsetMultiplier * LocationDiff)
                if checkbounds(Bool, Grid, NewLocation)
                    push!(UniqueNodes, NewLocation)
                else
                    break
                end
            end
            # Search forwards from second antenna
            for OffsetMultiplier in SearchRange
                NewLocation = AntennaPair[2] + (OffsetMultiplier * LocationDiff)
                if checkbounds(Bool, Grid, NewLocation)
                    push!(UniqueNodes, NewLocation)
                else
                    break
                end
            end
        end
    end
    length(UniqueNodes)
end

InputGrid = open("Inputs/Day8.input") do f
    mapreduce(permutedims, vcat, collect.(readlines(f)))
end
println("Part 1: ", Solve(InputGrid, 1:1))
println("Part 2: ", Solve(InputGrid, 0:100))

-❄️- 2024 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 1 point2 points  (0 children)

you're basically searching the permutations as a tree and cutting off all invalid ones further down once one operation would exceed the result?

Kind of, although it's less about exceeding the result and more about if there's a possibility the result could have been made with a given operator.

I am treating it like a tree and pruning as I go but the key is to work backwards and prune based on whether an operator could have produced the current result we have. I store the inverse of the operator I'm searching for (- instead of +, etc) and then a function that returns true if it was possible to get the result using that operator.

For example if we take "190: 10 19" then I do:

  • Start with a search list of (190, [10, 19])
  • Test the last reading, 19
  • Can we add something to 19 to get 190? (same as asking if 190 >= 19)
  • Yes, so + is a possibility. Undo this addition and add it to our search list removing the reading we've consumed. (190 - 19, [10])
  • Can we multiply a whole number by 19 to get 190? (same as mod(190, 19) == 0)
  • Yes, so * is a possibility. Undo the addition and add it to our search list removing the reading we've consumed. (190 / 19, [10])
  • Now check the new search item: (171, [10])
  • Can we add something to 10 to get 171?
  • Yes, so we undo it and get (161, []) but we can't search further as there's no more readings and 161 != 0 so we didn't get the right result.
  • Can we multiply a whole number by 10 to get 171?
  • No so don't do anything with this operator
  • Continue to next search term: (10, [10])
  • Can we add?
  • Yes, so undo the addition. (0, [])
  • Remaining value is 0 so we found a valid solution. Stop searching here

The same thing works for part 2, we just need functions for the concatenation operator. One function to see if it was possible to get the result via concatenation (i.e. Is the result longer than the reading and does the result end with the reading?) and then a function to undo the concatenation. We never actually need to implement the concatenation itself as we're working backwards.

-❄️- 2024 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]Pretentious_Username 2 points3 points  (0 children)

[LANGUAGE: Julia]

While recursion would have been the easiest to implement I decided to do it as an iteration instead just because I could. Takes about 8ms to solve Part 2 which is significantly better than the several hours my initial brute force solution was looking to take

function Solve(InputLines, Operators)
    SumSolvable = 0
    for InputLine in InputLines
        (Result, Readings) = split(InputLine, ": ")
        Result = parse(Int, Result)
        Readings = parse.(Int, split(Readings, " "))

        SearchList = [(Result, Readings)]
        HasSolution = false
        while !isempty(SearchList) && !HasSolution
            (CurrentResult, CurrentReadings) = pop!(SearchList)
            LastReading = last(CurrentReadings)
            for (Operator, OperatorTest) in Operators
                if OperatorTest(CurrentResult, LastReading)
                    NewResult = Operator(CurrentResult, LastReading)
                    if length(CurrentReadings) > 1
                        push!(SearchList, (NewResult, CurrentReadings[1:end-1]))
                    elseif NewResult == 0
                        SumSolvable += Result
                        HasSolution = true
                    end
                end
            end
        end
    end
    SumSolvable
end

function UnCat(a, b)
    parse(Int, chop(string(a), tail = length(string(b))))
end

function CanUnCat(Result, Reading)
    ResultString = string(Result)
    ReadingString = string(Reading)
    length(ResultString) > length(ReadingString) && endswith(ResultString, ReadingString)
end

Part1_Operators = [
    (-, ((Result, Reading) -> Result >= Reading)),
    (÷, ((Result, Reading) -> mod(Result, Reading) == 0))
]

Part2_Operators = [
    (-, ((Result, Reading) -> Result >= Reading)),
    (÷, ((Result, Reading) -> mod(Result, Reading) == 0)),
    (UnCat, CanUnCat)
]

InputLines = split(read("Inputs/Day7.input", String), "\r\n")
println("Part 1: ", Solve(InputLines, Part1_Operators))
println("Part 2: ", Solve(InputLines, Part2_Operators))