Day 5: If You Give a Seed a Fertilizer


Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • Code block support is not fully rolled out yet but likely will be in the middle of the event. Try to share solutions as both code blocks and using something such as https://topaz.github.io/paste/ , pastebin, or github (code blocks to future proof it for when 0.19 comes out and since code blocks currently function in some apps and some instances as well if they are running a 0.19 beta)

FAQ


πŸ”’This post will be unlocked when there is a decent amount of submissions on the leaderboard to avoid cheating for top spots

πŸ”“ Unlocked after 27 mins (current record for time, hard one today)

5 points
*

[JavaScript] Well that was by far the hardest out of all of the days, part 1 was relatively fine but part 2 took me awhile of trying different things

Ended up solving it by working backwards by trying different location values and seeing if that can become a valid seed. Takes around 3 secs to compute the answer.

Link to code

Part 1 Code Block
// Part 1
// ======

function part1(input) {
  const split = input.split("\r\n\r\n");

  let pastValues = split[0].match(/\d+/g).map((x) => parseInt(x));
  let currentValues = [];

  for (const section of split.slice(1)) {
    for (const line of section.split("\r\n")) {
      const values = line.match(/\d+/g)?.map((x) => parseInt(x));

      if (!values) {
        continue;
      }

      const sourceStart = values[1];
      const destinationStart = values[0];
      const length = values[2];

      for (let i = 0; i < pastValues.length; i++) {
        if (
          pastValues[i] >= sourceStart &&
          pastValues[i] < sourceStart + length
        ) {
          currentValues.push(destinationStart + pastValues[i] - sourceStart);
          pastValues.splice(i, 1);
          i--;
        }
      }
    }

    for (let i = 0; i < pastValues.length; i++) {
      currentValues.push(pastValues[i]);
    }

    pastValues = [...currentValues];
    currentValues = [];
  }

  return Math.min(...pastValues);
}
Part 2 Code Block
// Part 2
// ======

function part2(input) {
  const split = input.split("\r\n\r\n");

  let seeds = split[0].match(/\d+/g).map((x) => parseInt(x));
  seeds = seeds
    .filter((x, i) => i % 2 == 0)
    .map((x, i) => [x, seeds[i * 2 + 1]]);

  const maps = split
    .slice(1)
    .map((x) => {
      const lines = x.split("\r\n");
      return lines
        .map((x) => x.match(/\d+/g)?.map((x) => parseInt(x)))
        .filter((x) => x);
    })
    .reverse();

  for (let i = 0; true; i++) {
    let curValue = i;

    for (const map of maps) {
      for (const line of map) {
        const sourceStart = line[1];
        const destinationStart = line[0];
        const length = line[2];

        if (
          curValue >= destinationStart &&
          curValue < destinationStart + length
        ) {
          curValue = sourceStart + curValue - destinationStart;
          break;
        }
      }
    }

    for (const [seedRangeStart, seedRangeLength] of seeds) {
      if (
        curValue >= seedRangeStart &&
        curValue < seedRangeStart + seedRangeLength
      ) {
        return i;
      }
    }
  }
}
permalink
report
reply
1 point

Ended up solving it by working backwards by trying different location values and seeing if that can become a valid seed.

Huh, that’s clever.

permalink
report
parent
reply
1 point

Turns out I got really lucky and my location value is much lower than most peoples which is why it can be solved relatively quickly

permalink
report
parent
reply
1 point

Torn between doing the problem backwards and implementing a more general case – glad to know both approaches work out in the end!

permalink
report
parent
reply
1 point

[Rust]

Part 2
use std::fs;
use std::path::PathBuf;

use clap::Parser;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
    input_file: PathBuf,
}

/// Start < end
#[derive(Debug, Copy, Clone)]
struct Range {
    start: Idx,
    end: Idx,
}

impl From> for Range {
    fn from(item: std::ops::Range) -> Self {
        Range {
            start: item.start,
            end: item.end,
        }
    }
}

impl std::ops::Add for Range {
    type Output = Self;

    fn add(self, offset: i64) -> Self {
        Range {
            start: self.start + offset,
            end: self.end + offset,
        }
    }
}

#[derive(Debug, Copy, Clone)]
enum Value {
    Unmapped(Range),
    Mapped(Range),
}

impl Value {
    fn value(self) -> Range {
        match self {
            Self::Mapped(n) => n,
            Self::Unmapped(n) => n,
        }
    }

    fn min(self) -> i64 {
        self.value().start
    }
}

fn main() {
    // Parse CLI arguments
    let cli = Cli::parse();

    // Read file
    let input_text = fs::read_to_string(&cli.input_file)
        .expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());

    println!("{}", input_text);

    // Split into seeds and individual maps
    let mut maps_text = input_text.split("\n\n");
    let seeds_text = maps_text.next().expect("Seeds");

    // Seed numbers; will be mapped to the final locations
    let seed_numbers: Vec = parse_seeds(seeds_text);
    let seed_numbers = parse_seed_ranges(seed_numbers);
    let mut seed_values: Vec = seed_numbers.iter().map(|n| Value::Unmapped(*n)).collect();

    eprintln!("Seeds: {:?}", seed_values);

    // Apply maps to seeds
    for map_text in maps_text {
        let mut map_ranges_text = map_text.lines();
        let map_description = map_ranges_text
            .next()
            .expect("There should be a description of the map here");
        eprintln!("Map: {}", map_description);

        // Apply ranges to seeds
        // Map structure:
        // dest start, source start, range length
        for range_text in map_ranges_text {
            let range_numbers: Vec = range_text
                .split_ascii_whitespace()
                .map(|n| n.parse().expect("Should be a number here"))
                .collect();

            eprintln!("\trange: {:?}", range_numbers);

            let source_range = range_numbers[1]..range_numbers[1] + range_numbers[2];
            let offset = range_numbers[0] - range_numbers[1];

            eprintln!("\t\tSource range: {:?}\tOffset: {}", source_range, offset);

            // Apply the range map to the seeds
            seed_values = seed_values
                .iter()
                .map(|n| match n {
                    Value::Unmapped(n) => map_seed_range(*n, source_range.clone().into(), offset),
                    Value::Mapped(_) => vec![*n],
                })
                .flatten()
                .collect();

            eprintln!("\t\tSeed values: {:?}", seed_values);
        }

        // Reset seed values to unmapped
        seed_values = seed_values
            .iter()
            .map(|v| Value::Unmapped(v.value()))
            .collect();
    }

    // Find minimum
    let min_location = seed_values.iter().map(|v| v.min()).min().unwrap();

    println!();
    println!("Part 2: {}", min_location);
}

/// Parse string to vec of string numbers
fn parse_seeds(seeds_text: &str) -> Vec {
    seeds_text
        .split_ascii_whitespace()
        .skip(1)
        .map(|n| n.parse().expect("Should be a number here"))
        .collect()
}

/// Fill out ranges of seed numbers
fn parse_seed_ranges(seed_numbers: Vec) -> Vec> {
    seed_numbers
        .chunks(2)
        .map(|v| (v[0]..v[0] + v[1]).into())
        .collect()
}

/// Maps a seed range to possibly multiple based on a source range and offset
fn map_seed_range(seed_range: Range, source_range: Range, offset: i64) -> Vec {
    let start_cmp = seed_range.start < source_range.start;
    let end_cmp = seed_range.end <= source_range.end;

    match (start_cmp, end_cmp) {
        (false, false) => {
            if source_range.end <= seed_range.start {
                vec![Value::Unmapped(seed_range)]
            } else {
                vec![
                    Value::Mapped((seed_range.start + offset..source_range.end + offset).into()),
                    Value::Unmapped((source_range.end..seed_range.end).into()),
                ]
            }
        }
        (false, true) => vec![Value::Mapped(seed_range + offset)],
        (true, false) => vec![
            Value::Unmapped((seed_range.start..source_range.start).into()),
            Value::Mapped((source_range.start + offset..source_range.end + offset).into()),
            Value::Unmapped((source_range.end..seed_range.end).into()),
        ],
        (true, true) => {
            if seed_range.end <= source_range.start {
                vec![Value::Unmapped(seed_range)]
            } else {
                vec![
                    Value::Unmapped((seed_range.start..source_range.start).into()),
                    Value::Mapped((source_range.start + offset..seed_range.end + offset).into()),
                ]
            }
        }
    }
}

permalink
report
reply
3 points

Nim

Woof. Part 1 was simple enough. I thought I could adapt my solution to part 2 pretty easily, just add all the values in the ranges to the starting set. Worked fine for the example, but the ranges for the actual input are too large. Ended up taking 16gb of RAM and crunching forever.

I finally abandoned my quick and dirty approach when rewriting part 2, and made some proper types and functions. Treated each range as an object, and used set operations on them. The difference operation tends to fragment the range that it’s used on, so I meant to write some code to defragment the ranges after each round of mappings. Forgot to do so, but the code ran quick enough this time anyway.

permalink
report
reply
1 point

Hi there! Looks like you linked to a Lemmy community using a URL instead of its name, which doesn’t work well for people on different instances. Try fixing it like this: !nim@programming.dev

permalink
report
parent
reply
1 point

Treated each range as an object, and used set operations on them

That’s smart. Honestly, I don’t understand how it works. πŸ˜…

The difference operation tends to fragment the range that it’s used on, so I meant to write some code to defragment the ranges after each round of mappings. Forgot to do so, but the code ran quick enough this time anyway.

I’ve got different solution from yours, but this part is the same, lol. My code slices the ranges into 1-3 parts on each step, so I also planned to β€˜defragment’ them. But performance is plenty without this step, ~450 microseconds for both parts on my PC.

permalink
report
parent
reply
2 points

Treated each range as an object, and used set operations on them

That’s smart. Honestly, I don’t understand how it works. πŸ˜…

β€œSet operations” should probably be in quotes. I just mean that I implemented the * (intersection) and - (difference) operators for my ValueRange type. The intersection operator works like it does for sets, just returning the overlap. The difference operator has to work a little differently, because ranges have to be contiguous, whereas sets don’t, so it returns a sequence of ValueRange objects.

My ValueMapping type uses a ValueRange for it’s source, so applying it to a range just involves using the intersection operator to determine what part of the range needs to move, and the difference operator to determine which parts are left.

permalink
report
parent
reply
1 point
*

Well, then we have the same solution but coded very differently. Here’s mine.

ruleApplied is one function with almost all logic. I take a range and compare it to a rule’s source range (50 98 2 is a rule). Overlaps get transformed and collected into the first sequence and everything that left goes in the second. I need two seqs there, for transformed values to skip next rules in the same map.

Repeat for each rule and each map (seq[Rule]). And presto, it’s working!

permalink
report
parent
reply
2 points
*

[C]

My first approach to part 2 was to take the list of ranges, map it to a new list of (possibly split up) ranges, etc, but I realized that would take more memory and bookkeeping than I’d like. Scrapped it and rewrote it with recursion. Much cleaner and the benchmarks are still looking good! (But for how much longer?)

$ bmake bench
day01  0:00.00  1380 Kb  0+78 faults
day02  0:00.00  1660 Kb  0+82 faults
day03  0:00.00  1540 Kb  0+107 faults
day04  0:00.00  1536 Kb  0+80 faults
day05  0:00.00  1668 Kb  0+83 faults

https://github.com/sjmulder/aoc/blob/master/2023/c/day05.c

Edit: I see some people went for looping from 0 to try possible answers. Clever and short but I wanted to go for a more efficient approach.

permalink
report
reply
1 point

Ah, nice! Dealing with each range individually makes things much simpler.

permalink
report
parent
reply
2 points
*
Deleted by creator
permalink
report
reply

Advent Of Code

!advent_of_code@programming.dev

Create post

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2023

Solution Threads

M T W T F S S
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25

Rules/Guidelines

  • Follow the programming.dev instance rules
  • Keep all content related to advent of code in some way
  • If what youre posting relates to a day, put in brackets the year and then day number in front of the post title (e.g. [2023 Day 10])
  • When an event is running, keep solutions in the solution megathread to avoid the community getting spammed with posts

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

Community stats

  • 2

    Monthly active users

  • 76

    Posts

  • 779

    Comments