Spyke
advent_of_code·Advent Of Codebyhades

🦆 Everybody.Codes 2025 Quest 6 Solutions 🦆

Quest 6: Mentorship Matrix

  • 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)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

Link to participate: https://everybody.codes/

View original on programming.dev

Rust

use std::collections::HashMap;
use itertools::Itertools;

pub fn solve_part_1(input: &str) -> String {
    let mut mentors = 0;
    let mut pairs = 0;
    for ch in input.chars() {
        match ch {
            'A' => mentors += 1,
            'a' => pairs += mentors,
            _ => {}
        }
    }
    pairs.to_string()
}

pub fn solve_part_2(input: &str) -> String {
    let mut mentors: HashMap<char, i64> = HashMap::new();
    let mut pairs = 0;
    for ch in input.chars() {
        match ch {
            'A'..='Z' => *mentors.entry(ch).or_default() += 1,
            'a'..='z' => pairs += *mentors.entry(ch.to_ascii_uppercase()).or_default(),
            _ => panic!("unexpected character {ch}"),
        }
    }
    pairs.to_string()
}

pub fn solve_part_3(input: &str) -> String {
    let data: Vec<_> = input.chars().collect();
    let len = data.len();
    let mentors: HashMap<char, Vec<usize>> = data
        .iter()
        .enumerate()
        .map(|(i, ch)| (*ch, i))
        .into_group_map();
    let mut pairs: i64 = 0;
    for (squire_position, ch) in data.into_iter().enumerate() {
        if ch.is_ascii_lowercase() {
            for mentor_position in mentors.get(&ch.to_ascii_uppercase()).unwrap() {
                if squire_position.abs_diff(*mentor_position) <= 1000 {
                    pairs += 1000;
                } else if (squire_position as isize)
                    .wrapping_sub_unsigned(len)
                    .abs_diff(*mentor_position as isize)
                    <= 1000
                    || (*mentor_position as isize)
                        .wrapping_sub_unsigned(len)
                        .abs_diff(squire_position as isize)
                        <= 1000
                {
                    pairs += 999;
                }
            }
        }
    }
    pairs.to_string()
}
3

Uiua

Straightforward until part3. I left the naive approach running while I put together the version below, but of course the better algorithm got there first.

Prep ← (
  ⍉˜⊟°⊏≡□                # store char with index
  ⊕(⊟⊃(⊢⊢|□≡(°□⊣)))⊛⊸≡◇⊢ # group them
  ⍉↯∞_6⊏⍏⊸≡(°□⊢⊢)        # re-arrange
  ▽=1◿2°⊏                # drop labels, as we never used them...
)
/+/+⊞>∩°□°⊟⊢Prep "ABabACacBCbca"     # Part1
/+≡(/+/+⊞>∩°□°⊟)Prep "ABabACacBCbca" # Part2

# Note that the pattern repeats, so the only distinct values
# are in the first run and last run. Everything in between
# will be identical. Only works when distance < string length...
⟜Prep "AABCBABCABCabcabcABCCBAACBCa"
Rep   ← 2
Split ← [1 -2Rep 1] # multipliers for the three results for each profession.
Dist  ← 10
≡(≡(⊟∩□)⊃(≡+⊙(¤°□⊢)|¤♭≡+⊙(¤°□⊣)))¤×⇡3⧻ # Create the start, middle, end set for each.
/+≡(/+×Split≡(/+/+⊞(≤Dist⌵-)∩°□°⊟))
3

Haskell

It took me an embarrassingly long time to figure out what was going on with this one.

You could go a bit faster by splitting the list into beginning/middle/end parts, but I like the simplicity of this approach.

import Control.Monad (forM_)  
import Data.Char (toUpper)  
import Data.IntMap.Strict qualified as IntMap  
import Data.List (elemIndices)  
import Data.Map qualified as Map  

{-  
  f is a function which, given a lookup function and an index  
  returns the number of mentors for the novice at that position.  
  The lookup function returns the number of knights up to but  
  not including a specified position.  
-}  
countMentorsWith f input = Map.fromList [(c, go c) | c <- "abc"]  
  where  
    go c =  
      let knights = elemIndices (toUpper c) input  
          counts = IntMap.fromDistinctAscList $ zip knights [1 ..]  
          preceding = maybe 0 snd . (`IntMap.lookupLT` counts)  
       in sum $ map (f preceding) $ elemIndices c input  

part1 = (Map.! 'a') . countMentorsWith id  

part2 = sum . countMentorsWith id  

part3 d r = sum . countMentorsWith nearby . concat . replicate r  
  where  
    nearby lookup i = lookup (i + d + 1) - lookup (i - d)  

main =  
  forM_  
    [ ("everybody_codes_e2025_q06_p1.txt", part1),  
      ("everybody_codes_e2025_q06_p2.txt", part2),  
      ("everybody_codes_e2025_q06_p3.txt", part3 1000 1000)  
    ]  
    $ \(input, solve) -> readFile input >>= print . solve  
2

Nim

parts 1 and 2 - easy

For part 3 - When I first looked at the example input - it seemed a bit daunting to solve. But then I had a hunch and decided to check the real input and turns out - I was right! The real input is easier to solve because it's longer than 1000 chars.

This means that there is only 3 possible configurations we care about in repeated input: leftmost section, rightmost section and 998 identical sections in the middle. We solve each individually and sum them.

Another trick I used is looking up mentors with modulo to avoid copying the input.

proc solve_part1*(input: string): Solution =
  var mentors: CountTable[char]
  for c in input:
    if c notin {'a','A'}: continue
    if c.isUpperAscii: mentors.inc c
    else:
      result.intVal += mentors.getOrDefault(c.toUpperAscii)

proc solve_part2*(input: string): Solution =
  var mentors: CountTable[char]
  for c in input:
    if c.isUpperAscii: mentors.inc c
    else:
      result.intVal += mentors.getOrDefault(c.toUpperAscii)

proc solve_part3*(input: string): Solution =
  var mentors: Table[char, seq[int]]
  for index in -1000 ..< input.len + 1000:
    let mi = index.euclMod input.len
    if input[mi].isLowerAscii: continue
    let lower = input[mi].toLowerAscii
    if mentors.hasKeyOrPut(lower, @[index]):
      mentors[lower].add index

  var most, first, last = 0
  for ci, ch in input:
    if ch.isUpperAscii: continue
    for mi in mentors[ch]:
      let dist = abs(mi - ci)
      if dist <= 1000:
        inc most
        if mi >= 0: inc first
        if mi <= input.high: inc last

  result := first + (most * 998) + last

Full solution at Codeberg: solution.nim

2

Scheme/Guile

Part 3 was a fun little challenge.

(import (rnrs io ports (6)))
#!curly-infix

(define (parse-file file-name) (string-trim-both (call-with-input-file file-name get-string-all)))

(let* ((line (parse-file "notes/everybody_codes_e2025_q06_p1.txt"))
       (line-length (string-length line)))
  (let loop ((i 0) (knight-count 0) (mentor-count 0))
    (if {i < line-length}
        (let ((letter (string-ref line i)))
          (loop (1+ i) (+ knight-count (if (eq? letter #\A) 1 0)) (+ mentor-count (if (eq? letter #\a) knight-count 0))))
        (format #t "P1 Answer: ~a\n\n" mentor-count))))

(let* ((line (parse-file "notes/everybody_codes_e2025_q06_p2.txt"))
       (line-length (string-length line)))
  (let loop ((i 0) (knight-counts '()) (mentor-count 0))
    (if {i < line-length}
        (let ((letter (string-ref line i)))
          (loop
            (1+ i)
            (if (char-upper-case? letter) (assq-set! knight-counts letter (1+ (or (assq-ref knight-counts letter) 0))) knight-counts)
            (+ mentor-count (if (char-lower-case? letter) (or (assq-ref knight-counts (char-upcase letter)) 0) 0))))
        (format #t "P2 Answer: ~a\n\n" mentor-count))))


(let* ((line (parse-file "notes/everybody_codes_e2025_q06_p3.txt"))
       (line-length (string-length line)))
  (let loop ((i 0) (mentor-count 0))
    (if {i < line-length}
        (let ((letter (string-ref line i)))
          (loop
            (1+ i)
            (+ mentor-count
               (if (char-lower-case? letter)
                   (let loop ((j (- i 1000)) (mentors-here 0))
                     (if {j <= (+ i 1000)}
                         (loop
                           (1+ j)
                           (+ mentors-here
                              (if {(string-ref line {j modulo line-length}) eq? (char-upcase letter)}
                                  (if (and {0 <= j} {j < line-length}) 1000 999)
                                  0)))
                         mentors-here))
                   0))))
        (format #t "P3 Answer: ~a\n\n" mentor-count))))
2

Python

Used sliding window for part 3. The off-by-one difference between i and j tripped me up for a while.

def part1(data: str, mentor: str = 'A'):
    mentors = 0
    pairs = 0
    for p in data:
        if p == mentor:
            mentors += 1
        elif p == mentor.lower():
            pairs += mentors
    return pairs

assert part1("ABabACacBCbca") == 5

def part2(data: str):
    all_pairs = 0
    for mentor in 'ABC':
        all_pairs += part1(data, mentor)
    return all_pairs    

assert part2("ABabACacBCbca") == 11

from collections import defaultdict

def part3(data: str, distance_limit: int = 1000, repeat: int = 1000):
    n = len(data)
    N = len(data) * repeat

    pairs = 0
    person_count = defaultdict(int)
    curr = 0

    # initialize the first window excluding the right boundary
    for j in range(distance_limit):
        person_count[data[j % n]] += 1

    for curr in range(N):
        # move left boundary (if applicable)
        if (i := curr - distance_limit - 1) >= 0:
            person_count[data[i % n]] -= 1
        # move right boundary (if applicable)
        if (j := curr + distance_limit) < N:
            person_count[data[j % n]] += 1
        # if mentee, record pairs
        if data[curr % n].islower():
            pairs += person_count[data[curr % n].upper()]

    return pairs
    

assert (t := part3("AABCBABCABCabcabcABCCBAACBCa", 10, 1)) == 34, f"Expected 34 but got {t}"
assert (t := part3("AABCBABCABCabcabcABCCBAACBCa", 10, 2)) == 72, f"Expected 72 but got {t}"
assert (t := part3("AABCBABCABCabcabcABCCBAACBCa", 1000, 1000)) == 3442321, f"Expected 3442321 but got {t}"-
2

You reached the end