Crespyl's recent activity

  1. Comment on The Beigeness, or How to Kill People with Bad Writing: The Scott Alexander Method in ~misc

    Crespyl
    Link Parent
    This is directly suggesting that multiple other users are "rape apologists"/in favor of rape apologism. I don't want to re-litigate the article and whether or not it should've been posted or...

    that rape apologist thread .... there are people in there also arguing in favor of what's presented.

    This is directly suggesting that multiple other users are "rape apologists"/in favor of rape apologism. I don't want to re-litigate the article and whether or not it should've been posted or removed, and I understand that some people were upset by the presence of the article, but I cannot imagine a good-faith reading of the contents of that comment thread that allows calling other users rape apologists as part of acceptable dialogue.

    we should probably spend a bit less time asking "but can you give me detailed specific examples so that I can tell you why you're wrong to have those feelings"

    This is neither a real quote from me nor (obviously, I would have thought) the point I was making, and it honestly feels like exactly the sort of bad-faith interpretation I mentioned.

    I'm done with this thread, and clearly I need to take a break from the site for a while.

    3 votes
  2. Comment on The Beigeness, or How to Kill People with Bad Writing: The Scott Alexander Method in ~misc

    Crespyl
    Link Parent
    One doesn't get to say "There are witches afoot!" with stirring calls to action and then go "oh, I don't mean to start a witch hunt..." If @Gaywallet thinks someone's behavior is so unacceptable...
    • Exemplary

    One doesn't get to say "There are witches afoot!" with stirring calls to action and then go "oh, I don't mean to start a witch hunt..."

    If @Gaywallet thinks someone's behavior is so unacceptable they should report the user and let Deimos ban them if he sees fit.

    Based on what I saw in the article and thread in question, and the fallout in the (unofficial) discord, I'll just say that I have more trust in Deimos's decision making than in someone casting sharp aspersions at a very vague crowd.

    If anything, the behavior that makes me want to give up on the site is the way this community seems to have a remarkably consistent cycle of verbose outbursts and trying to outdo themselves finding the worst possible interpretation of some weak article or discussion, and turning it into drama that festers on the front page for days afterward.

    Any time I see a post with more than 10-20 comments, I don't think to myself "Oh, this will be interesting and informative!", but "Oh no, here we go again..."
    I can't think of any other site I frequent where this is the case.

    16 votes
  3. Comment on Why I am obsessed with the forbidden Seuss in ~books

    Crespyl
    Link Parent
    Both of those links say that the auction was "ended by the seller because of an error in the listing", and one of the only other examples I could find was a collection of four books (including...

    Both of those links say that the auction was "ended by the seller because of an error in the listing", and one of the only other examples I could find was a collection of four books (including Zoo) that was only posted just now and doesn't include "Zoo" in the title.

    It definitely seems like they've become hard to find on the site.

    2 votes
  4. Comment on The internet’s most beloved fanfiction site is undergoing a reckoning in ~tech

    Crespyl
    Link
    I have never used AO3, so there's almost certainly context I'm missing, but is there some UX or technical reason why "[tags matching your search], X, Y, Z, ... [and 1600 other tags, click to...

    I have never used AO3, so there's almost certainly context I'm missing, but is there some UX or technical reason why "[tags matching your search], X, Y, Z, ... [and 1600 other tags, click to expand]" isn't an option here? From the way the article describes it, it's hard for me to understand how this got to be such a big community issue when it seems like it could be solved with a few fairly simple changes to the way tag lists are displayed in (some) pages. A simple version might even be possible with pure CSS.

    I feel like I must be missing something.

    9 votes
  5. Comment on Day 16: Ticket Translation in ~comp

    Crespyl
    Link
    Ruby This one was fun, it always feels nice to watch the pieces clicking into place when running these constraint satisfaction puzzles. Part 1 I don't usually use bare enumerator/iterator objects...

    Ruby

    This one was fun, it always feels nice to watch the pieces clicking into place when running these constraint satisfaction puzzles.

    Part 1 I don't usually use bare enumerator/iterator objects in ruby, but wanted to break out the parsing of each section of the input file. Not at all happy with the `rescue StopIteration` in there, but I wasn't quickly finding a good way to test if the enumerator had more values or not without handling that error. This could definitely be cleaner.
    RULE_RE = /^([\w\s]+): (\d+-\d+) or (\d+-\d+)\n?$/
    
    def parse_rule(line)
      binding.pry unless line.match?(RULE_RE)
      captures = line.match(RULE_RE).captures
      name = captures[0]
      r1 = captures[1].split('-').map(&:to_i)
      r2 = captures[2].split('-').map(&:to_i)
      return [name.to_sym, r1[0]..r1[1], r2[0]..r2[1]]
    end
    
    def compute_p1(input)
      lines = input.lines.enum_for
    
      # read rules
      rules = {}
      while (l = lines.next).strip.size > 0
        r = parse_rule(l)
        rules[r[0]] = r[1..]
      end
    
      # read my ticket
      l = lines.next until l.start_with?("your ticket:")
      my_ticket = lines.next.split(',').map(&:to_i)
    
      # read nearby tickets
      l = lines.next until l.start_with?("nearby tickets:")
      nearby_tickets = []
      begin
        while (l = lines.next.strip).size > 0
          nearby_tickets << l.split(',').map(&:to_i)
        end
      rescue StopIteration
      end
    
      # check each value in nearby_tickets for all rules
      invalid_values = []
      nearby_tickets.each do |ticket|
        ticket.each do |value|
          invalid_values << value unless rules.values.flatten.any? { |r| r.include?(value) }
        end
      end
    
      return invalid_values.sum
    end
    
    Part 2 Lost a bunch of time in part 2 due to some kind of logic bug, but I did have the right approach. My solution worked for the sample input right away, but failed on the full input for a while, and I ended up thinking that there weren't any fields that could be narrowed down to a single possibility in the first pass.

    That had me thinking about how I might have to find a subset of possible field orders, and then brute force search through those, but instead I tried re-writing my first algorithm one more time and it worked!

    The idea is to start with the set of "unknown" fields, and an array indicating which fields go in which position (initialized to all nil). We then iterate through each field (1-20) for all tickets (so we're examining the first field of ticket #1, then the first field of ticket #2, and so on, and then the second field of ticket #1, the second field of ticket #2, etc, on down the line), and compare against the fields/rules in the unknown set to find the set of rules that match that value for all tickets.

    If there is only one field in that set, we know this must be the correct position for that field, and we add it to our known list in the correct position, and remove it from the unknown set. Repeat until the unknown set is empty.

    def compute_p2(input, return_fields=nil)
      lines = input.lines.enum_for
    
      # read rules
      rules = {}
      while (l = lines.next).strip.size > 0
        r = parse_rule(l)
        rules[r[0]] = r[1..]
      end
    
      # read my ticket
      l = lines.next until l.start_with?("your ticket:")
      my_ticket = lines.next.split(',').map(&:to_i)
    
      # read nearby tickets
      l = lines.next until l.start_with?("nearby tickets:")
      nearby_tickets = []
      begin
        while (l = lines.next.strip).size > 0
          nearby_tickets << l.split(',').map(&:to_i)
        end
      rescue StopIteration
      end
    
      # check each value in each of nearby_tickets against all rules to find the valid tickets
      valid_tickets = nearby_tickets.filter do |ticket|
        ticket.all? { |value| rules.values.flatten.any? { |r| r.include?(value) } }
      end
    
      unknown_fields = Set.new(rules.keys)
      fields_order = Array.new(my_ticket.size)
    
      while unknown_fields.size > 0
        # look at the nth field in each ticket, and find the rule that matches all values
        my_ticket.size.times do |n|
          matching_rules = valid_tickets.map { _1[n] }.reduce(unknown_fields) do |set, value|
            set & unknown_fields.filter { |k| rules[k].any? { |r| r.include? value } }
          end
          if matching_rules.size == 1
            fields_order[n] = matching_rules.first
            unknown_fields -= [matching_rules.first]
          end
        end
      end
    
      binding.pry if unknown_fields.size > 0
    
      if return_fields.nil?
        return_fields = fields_order.filter { |f| f.to_s.start_with?("departure") }
      end
    
      return return_fields.reduce(1) { |product, field| product * my_ticket[fields_order.index(field)] }
    end
    
    3 votes
  6. Comment on Day 15: Rambunctious Recitation in ~comp

    Crespyl
    Link
    Ruby I was worried that this would be way harder than it turned out to be. Part 1 Reading the description had me thinking there was going to be some kind of tricky manipulation involved in part 2,...

    Ruby

    I was worried that this would be way harder than it turned out to be.

    Part 1 Reading the description had me thinking there was going to be some kind of tricky manipulation involved in part 2, so I built out a separate class to keep track of the history for each number. The biggest slow down for actually solving part 1, at least for me, was reading and re-reading the directions several times to make sure I understood the requirements. Something about the writing here kept making me feel like I was missing something.
    class SeenMap
      def initialize
        @hash = Hash.new
      end
    
      def [](n)
        if @hash[n].nil?
          @hash[n] = []
        else
          @hash[n]
        end
      end
    
      def log(n, age)
        if @hash[n].nil?
          @hash[n] = []
        end
        @hash[n].append(age)
      end
    end
    
    def compute_p1(input, max=2020)
      starting_numbers = input.strip.split(',').map(&:to_i)
      seen = SeenMap.new
    
      starting_numbers.each_with_index do |n,i|
        seen.log(n, i+1) #+1 since turn numbers start at 1 instead of 0
      end
    
      last_number = starting_numbers.last
      (max-starting_numbers.size).times do |n|
        cur_turn = starting_numbers.size + n + 1
        next_number = if seen[last_number].size < 2
                        0
                      else
                        seen[last_number][-2..].reduce(&:-).abs
                      end
        seen.log(next_number, cur_turn)
        #puts "turn #{cur_turn}: #{next_number}"
        last_number = next_number
      end
    
      return last_number
    end
    
    Part 2
    def compute_p2(input)
      compute_p1(input, max=30000000)
    end
    

    Sometimes brute force works well enough! I set this up and left it to run while I started googling and hitting up the OEIS to see if I could find some other reference for this kind of sequence (I'm sure it's out there somewhere, but I haven't found it yet, as of this comment). Before I'd gotten very far, my terminal pinged me with the correct solution. Turns out my brute force search finishes in about 34s, which is fast enough for now.

    I might go back and golf down my solution later, since it could be a bit prettier/more idiomatic.

    3 votes
  7. Comment on Chris Pine to star in ‘Dungeons & Dragons’ movie in ~movies

    Crespyl
    Link Parent
    While I agree, there are enough officially branded stories that could be adapted, the standard adventure modules are designed to produce exciting stories with minimal effort plugging in the player...

    While I agree, there are enough officially branded stories that could be adapted, the standard adventure modules are designed to produce exciting stories with minimal effort plugging in the player characters in the party. In that case it'd make more sense to call it a "Baldur's Gate movie" or "Neverwinter Nights movie", but there's enough material for the branding to make some kind of sense.

    Doesn't make me any less skeptical though.

    Edit, having actually read the article:
    Doesn't sound like they've gone that route though, so ¯\_(ツ)_/¯

    6 votes
  8. Comment on What were the best games you played this year? in ~games

    Crespyl
    Link Parent
    Thirding Outer Wilds and seconding Heaven's Vault and Obra Dinn! Obra Dinn has well earned its accolades, but I hope Heaven's Vault gets a bit more notice. Despite some shortcomings (a few...

    Thirding Outer Wilds and seconding Heaven's Vault and Obra Dinn!

    Obra Dinn has well earned its accolades, but I hope Heaven's Vault gets a bit more notice. Despite some shortcomings (a few glitches here and there, and the "sailing" felt like a missed opportunity), I think there's a decent audience for it, and the setting and gameplay are quite creative and unique. Sethian is the only other language/translation puzzle game I can think of that rivals HV in the language department, and it's even smaller and less approachable.

    In another weird tangent of "not-remotely-the-same-genre-but-still-sort-of-connected", I replayed Rain World for the nth (for some large forgotten value of n) time, in between watching my brother stream his first playthrough. It's another game that gives you all the tools and abilities right from the outset (bar one, maybe, depending on how you count), and the biggest barrier between you and the endgame is discovering what you already have available and what you actually need to do. This chiefly requires discovering how the various creatures in the world behave, their interactions with you and each other, and a little bit about the history and nature of the world at large. Unfortunately it's a harder game to recommend, given that it's a brutally "unfair" survival sim first and a pseudo-metroidvania platformer second, with a little more than its share of player hostile level design issues in some areas; but despite all that it somehow comes together into a cohesive whole that unifies the gameplay and narrative themes uniquely well.

    4 votes
  9. Comment on Day 14: Docking Data in ~comp

    Crespyl
    Link
    Got a late start due to a Dota match, but this was pretty simple. My solution is stringier than I like and a bit inelegant, but it works well enough. Part 1 def apply_mask_p1(mask, num)...

    Got a late start due to a Dota match, but this was pretty simple. My solution is stringier than I like and a bit inelegant, but it works well enough.

    Part 1
    def apply_mask_p1(mask, num)
      mask.reverse.chars.each_with_index do |c, i|
        case c
        when 'X'
          next
        when '0'
          num = num & ~(1 << i)
        when '1'
          num = num | (1 << i)
        end
      end
      return num
    end
    
    def compute_p1(input)
      prog = input.lines.map(&:strip)
      mem = Hash.new(0)
      mask = "";
    
      prog.each do |line|
        if line.start_with?("mask")
          mask = line.split(' ')[2]
        elsif line.start_with?('mem')
          c = line.match(/mem\[(\d+)\] = (\d+)/).captures
          mem[c[0].to_i] = apply_mask_p1(mask, c[1].to_i)
        end
      end
    
      return mem.values.reduce(:+)
    end
    
    Part 2 After I finished I saw a lot of other solutions splitting out the mask into two separate masks, one for the bits to zero, and one for the bits to set, which lets you directly apply the mask via standard bitwise operations. I like that a lot better than the stringy iterations I'm doing for each one, but it works well enough.
    def apply_mask_p2(mask, num)
      base = num | mask.tr("X","0").to_i(2)
      nums = []
    
      floating_bits = mask.reverse.chars.each_with_index.filter { |c, i| c == 'X' }.map { |c,i| i }
      (2 ** floating_bits.size).times do |i|
        num = base
        floating_bits.each_with_index do |b, j|
          bit = (i & (1 << j)) >> j
          case bit
          when 1
            num = num | (1 << b)
          when 0
            num = num & ~(1 << b)
          end
        end
        nums << num
      end
    
      return nums
    end
    
    def compute_p2(input)
      prog = input.lines.map(&:strip)
      mem = Hash.new(0)
      mask = "";
    
      prog.each_with_index do |line, i|
        if line.start_with?("mask")
          mask = line.split(' ')[2]
        elsif line.start_with?('mem')
          c = line.match(/mem\[(\d+)\] = (\d+)/).captures
          addrs = apply_mask_p2(mask, c[0].to_i)
          addrs.each do |a|
            mem[a] = c[1].to_i
          end
        end
      end
    
      return mem.values.reduce(:+)
    end
    
    3 votes
  10. Comment on Day 13: Shuttle Search in ~comp

    Crespyl
    Link
    Ruby Like many others, part 2 took me a lot longer than part 1. Part 1 def compute_p1(input) start = input.lines.first.to_i bus_list = input.lines[1].strip.split(',').reject { |b| b == 'x'...

    Ruby

    Like many others, part 2 took me a lot longer than part 1.

    Part 1
    def compute_p1(input)
      start = input.lines.first.to_i
      bus_list = input.lines[1].strip.split(',').reject { |b| b == 'x' }.map(&:to_i)
      soonest = bus_list.map { |bus_id|
        x = bus_id
        while x < start
          x += bus_id
        end
        [x, bus_id]
      }.sort.first
    
      return (soonest[1] * (soonest[0]-start))
    end
    
    Part 2 This puzzle reminded me straight away of 2019's [day 16](https://adventofcode.com/2019/day/16) and [day 22](https://adventofcode.com/2019/day/22), which also both involved enormous numbers.

    I started by looking at the GCD/LCM of the bus ids, and quickly wrote a dumb brute force search (which was obviously too slow), played around with using various combinations of LCM and modulus to make the search faster (this is where I realized that all the bus ids were prime) and got stuck for a good while. Thinking about primes and modulus, plus some half-remembered college material, lead me (after walking away for a bit and coming back with fresh eyes) to Residue Number Systems and the Chinese Remainder Theorem, which provided the correct solution I'd been fumbling towards.

    The key observation, as mentioned in the other posts, is that the times/solutions for each subset of buses ([bus1], [bus1, bus2], [bus1, bus2, bus3], etc) will repeat at intervals of their least common multiples (which, given that they're all prime, is just the product, though my solution leaves in the LCM call from an earlier version). This means that you can start at the first bus, and then search forward by steps of that bus's id until you find a match for the next bus, change your step size to the LCM of the solved bus ids, proceed until you find a match for the next, and so on until all the buses are solved.

    def check(num, bus_list)
      bus_list.each_with_index.reject{ |b,i| b == 1 }.all? { |b,i| ((num+i) % b) == 0}
    end
    
    def compute_p2(input)
      bus_list = input.lines[1]
                   .strip
                   .split(',')
                   .map { |x| x == 'x' ? 1 : x.to_i }
      working = []
      step = 1
      t = 0
    
      bus_list.each do |bus|
        # add the next bus to our working set
        working << bus
    
        # advance by steps until we find a time that fits all the buses in our set
        t += step until check(t, working)
    
        # change our step so that we only ever advance by the LCM of all our previous
        # buses, which ensures that every new step we take continues to satisfy the
        # constraints
        step = working[...-1].reduce(1,:lcm)
      end
    
      return t
    end
    
    4 votes
  11. Comment on Day 12: Rain Risk in ~comp

    Crespyl
    Link Parent
    Haha, yeah I've used that trick before in other situations, saves some repetition and makes it easier to extend if you have to include the corners as well (NE/NW/SE/SW). It also helps that the...

    Haha, yeah I've used that trick before in other situations, saves some repetition and makes it easier to extend if you have to include the corners as well (NE/NW/SE/SW).

    It also helps that the input only deals in rotations that are whole multiples of 90 degrees. I saw a few other solutions using complex numbers for "real" rotation math, which would make it a lot easier to handle a trickier input.

    I think that would've made my part 2 "rotate around origin" cleaner too.

    3 votes
  12. Comment on Day 12: Rain Risk in ~comp

    Crespyl
    Link
    Ruby This one is another pretty straightforward one, the only thing I got hung up on a few times was forgetting to account for the parameter to the L/R instructions. Part 1 def compute_p1(input)...

    Ruby

    This one is another pretty straightforward one, the only thing I got hung up on a few times was forgetting to account for the parameter to the L/R instructions.

    Part 1
    def compute_p1(input)
      x, y = 0, 0
      dirs = [[+1, 0], [0, +1], [-1, 0], [0, -1]]
      dir = 0 # 0 = east, 1 = south, 2 = west, 3 = north
      input
        .lines
        .map { |l| l.match(/^([NSEWLRF])(\d+)\n$/).captures }
        .each do |action, value|
          value = value.to_i
          #puts "%s: %i" % [action, value]
    
          case action
          when "N"
            y -= value
          when "S"
            y += value
          when "E"
            x += value
          when "W"
            x -= value
          when "R"
            dir = (dir + (value / 90)) % 4
          when "L"
            dir = (dir - (value / 90)) % 4
          when "F"
            x += dirs[dir][0] * value
            y += dirs[dir][1] * value
          end
    
          #puts "  (%i, %i, %i)" % [x, y, dir]
      end
    
      return (x.abs) + (y.abs)
    end
    
    Part 2
    def compute_p2(input)
      wx, wy = 10, -1
      x, y = 0, 0
    
      input
        .lines
        .map { |l| l.match(/^([NSEWLRF])(\d+)\n$/).captures }
        .each do |action, value|
          value = value.to_i
          #puts "%s: %i" % [action, value]
    
          case action
          when "N"
            wy -= value
          when "S"
            wy += value
          when "E"
            wx += value
          when "W"
            wx -= value
          when "R"
            (value/90).times do
              wx, wy = wy * -1, wx
            end
          when "L"
            (value/90).times do
              wx, wy = wy, wx * -1
            end
          when "F"
            x += (wx * value)
            y += (wy * value)
          end
    
          #puts "  (%i, %i), (%i, %i)" % [x, y, wx, wy]
      end
    
      return (x.abs) + (y.abs)
    end
    
    3 votes
  13. Comment on What games have you been playing, and what's your opinion on them? in ~games

    Crespyl
    Link Parent
    I've also been enjoying Diretide. I wasn't playing Dota 2 when the first version came around, but I've certainly been aware of how much the community loved the mode and was clamoring for its...

    I've also been enjoying Diretide. I wasn't playing Dota 2 when the first version came around, but I've certainly been aware of how much the community loved the mode and was clamoring for its return, and it really seems like the team outdid themselves with this take on it. Being able to throw your held stack of candy to a teammate right before you die is always quite exciting.

    It's a great mix of Dota's generally "serious" style and the silliness of seeing mighty Roshan stomp around with an outstretched bucket demanding candy.

    1 vote
  14. Comment on Day 10: Adapter Array in ~comp

    Crespyl
    (edited )
    Link
    Ruby This one took me a long while, first part was easy enough, but I ended up getting stuck on the second part. Part 1 def find_adapter(available, target) available .map { |x| [x - target, x] }...

    Ruby

    This one took me a long while, first part was easy enough, but I ended up getting stuck on the second part.

    Part 1
    def find_adapter(available, target)
      available
        .map { |x| [x - target, x] }
        .sort
        .first
    end
    
    def compute_p1(input)
      adapters = input.lines.map(&:to_i).sort
      adapters << adapters.max + 3
    
      target = 0
      diff_counts = Hash.new(0)
      while ! adapters.empty?
        diff, a = find_adapter(adapters, target)
        #puts "%i -> %i (%i)" % [target, a, diff]
        diff_counts[diff] += 1
        adapters.delete(a)
        target = a
      end
    
      #puts diff_counts
      return diff_counts[1] * diff_counts[3]
    end
    
    Part 2 I started off trying to find the set of removable adapters and figure the permutations directly from there, but that wasn't working and I banged my head on that wall for a while, before stepping back and looking at each *group* of removable items. Any run of 2 or more single-steps brings a number of choices that grows in a predictable sequence.

    Put generally we have: 0 -> 1 (n/a no action); 1 -> 1 (not removable); 2 -> 2 (you can remove either); 3 -> 4 (remove any or all); 4 -> 7 (remove any, all, or two sets of three). This was enough to build out the rest of the sequence, with each new number being the sum of the last three. I ended up pre-computing the first 10 numbers in the sequence, but I'm pretty sure these inputs only actually need the values for 1-4.

    def compute_p2(input)
      input = input.lines.map(&:to_i)
      adapters = [0] + input.sort + [input.max + 3]
    
      groups = adapters[...-1].zip(adapters[1...])
                 .reduce([]) { |steps, pair| steps << pair[1]-pair[0] }
                 .reduce([]) do |groups, n|
                   if groups.last && groups.last.last == n
                     groups.last << n
                   else
                     groups << [n]
                   end
                   groups
                 end
    
      group_counts = groups
                       .find_all { |g| g.first == 1 }
                       .map { |g| g.size }
                       .each_with_object(Hash.new(0)) { |count, h| h[count] += 1 }
    
      # options per group size scale with sum of previous 3, so we precompute table of options per group size
      table = [1, 1, 2]
      while table.size < 10
        table << table[-3..].sum
      end
    
      return group_counts.reduce(1) { |product, kv| product * table[kv[0]] ** kv[1] }
    end
    

    Inspired by a streamer I like to watch VODs of after I finish each puzzle, I added a little test suite that checks my implementation against the provided sample cases before running it against the full input. It also adds benchmarking, but so far everything's been fast enough for that not to matter much. You can see it in the full implementation here.

    4 votes
  15. Comment on Day 10: Adapter Array in ~comp

    Crespyl
    Link Parent
    I got the first sample for part 2 to work, but haven't finished working out the math to make my solution work correctly for the second sample and my real input. discussion By looping through the...

    I got the first sample for part 2 to work, but haven't finished working out the math to make my solution work correctly for the second sample and my real input.

    discussion By looping through the list of adapters three at a time, it's possible to find and remove all the unnecessary adapters (any time you see `a, b, c` where the difference between `a` and `c` is less/equal to 3, you can remove `b` from the set.

    This gets you the minimal valid solution, and a list/count of removable items. I believe it should be possible to use this information to directly compute the number of permutations very quickly, but I'm still trying to remember/derive the math for that.

    It's also possible that I'm chasing a dead end and am completely off base.

    2 votes
  16. Comment on Day 9: Encoding Error in ~comp

    Crespyl
    (edited )
    Link
    Ruby I suspect there's some kind of sliding-window iterator available somewhere in ruby that would make the first part cleaner, but at least I have combination to make searching the "preamble" for...

    Ruby

    I suspect there's some kind of sliding-window iterator available somewhere in ruby that would make the first part cleaner, but at least I have combination to make searching the "preamble" for valid numbers easier. Ruby is a bit like Python in that it's very "batteries included" and saves a lot of time on puzzles like this.

    Part 1
    #!/usr/bin/env ruby
    
    PREAMBLE_LENGTH = 25
    
    def valid?(preamble, number)
      preamble.combination(2).map { |a,b| a + b }.any?(number)
    end
    
    puts "Part 1"
    
    input = File.read(ARGV[0] || "test.txt").lines.map(&:to_i)
    buffer = input[0...PREAMBLE_LENGTH]
    target = nil
    
    input[PREAMBLE_LENGTH...].each do |n|
      if ! valid?(buffer, n)
        target = n
        break;
      else
        buffer.shift
        buffer << n
      end
    end
    
    puts target
    
    Part 2
    puts "Part 2"
    
    def check_contiguous(list, target)
      sum = list[0]
      list[1...].each_with_index do |n, i|
        sum += n
        return [true, i] if sum == target
        return [false, nil] if sum > target
      end
    end
    
    input.each_with_index do |n, i|
      match, len = check_contiguous(input[i...], target)
      if match
        span = input[i..i+len]
        puts span.min() + span.max()
        break
      end
    end
    
    2 votes
  17. Comment on Day 8: Handheld Halting in ~comp

    Crespyl
    Link
    Ruby Part 1 was easy, Part 2 I stumbled around a bit on a shallow vs deep copy issue. Fixed that with a goofy hack (JSON.parse(JSON.generate(...))) for the quick and dirty solution, then went back...

    Ruby

    Part 1 was easy, Part 2 I stumbled around a bit on a shallow vs deep copy issue. Fixed that with a goofy hack (JSON.parse(JSON.generate(...))) for the quick and dirty solution, then went back and fixed it with the no-copy solution presented below.

    Virtual machine puzzles are often my favorites, I love the process of debugging my own interpreter and reverse engineering the subject code simultaneously, and building all the supporting pieces for I/O, debugger, graphics/visualization etc.

    Part 1
    #!/usr/bin/env ruby
    require "ostruct"
    
    input = File.read(ARGV[0] || "test.txt")
    
    def run(code)
      acc = 0
      pc = 0
      hits = Hash.new(0)
    
      while instr = code[pc] do
        hits[pc] += 1
    
        if hits[pc] >= 2
          return [:loop, acc]
        end
    
        case instr.opcode
        when "acc"
          acc += instr.value
          pc += 1
        when "nop"
          pc += 1
        when "jmp"
          pc += instr.value
          next
        end
      end
    
      return [:ok, acc]
    end
    
    code = input.lines.map(&:strip).map { |l| o, v = l.split(' '); OpenStruct.new(opcode: o, value: v.to_i) }
    puts "Part 1"
    puts run(code)[1]
    
    Part 2
    puts "Part 2"
    
    def flip(instr)
      case instr.opcode
      when "nop"
        instr.opcode = "jmp"
      when "jmp"
        instr.opcode = "nop"
      end
      return instr
    end
    
    result = -1
    code.each do |instr|
      flip(instr)
      status, result = run(code)
    
      break if status == :ok
      flip(instr) #flip back before moving on
    end
    puts result
    
    2 votes
  18. Comment on Day 7: Handy Haversacks in ~comp

    Crespyl
    Link
    Ruby A little slow getting started on this one, but I got there in the end. Not thrilled with my rule parsing, but it works. Part 1 #!/usr/bin/env ruby require "set" input = File.read(ARGV[0] ||...

    Ruby

    A little slow getting started on this one, but I got there in the end. Not thrilled with my rule parsing, but it works.

    Part 1
    #!/usr/bin/env ruby
    require "set"
    
    input = File.read(ARGV[0] || "test2.txt")
    
    rules = {}
    
    input.lines.each do |line|
      outer_bag, inner = line.split("bags contain").map(&:strip)
      inner = inner.gsub(/\./,'')
                .split(',')
                .map(&:strip)
                .reject{ |s| s == "no other bags" }
                .map { |s| c = s.match(/(\d+)\s(\w+)\s(\w+)\s(.+)/).captures
                           [c[0].to_i, c[1] << " " << c[2]] }
    
      rules[outer_bag] = inner
    end
    
    def find_containers(rules, target)
      containers = Set.new
      rules.each do |container, contained|
        if contained.any? { |rule| rule[1] == target }
          containers.add(container)
          containers += find_containers(rules, container)
        end
      end
      return containers
    end
    
    puts "Part 1"
    puts find_containers(rules, "shiny gold").size
    
    Part 2
    def score_bag(rules, target)
      rules[target].reduce(1) do |sum, rule|
        sum + (rule[0] * score_bag(rules, rule[1]))
      end
    end
    
    puts "Part 2"
    puts score_bag(rules, "shiny gold") - 1 #sub one for the gold bag itself
    
    2 votes
  19. Comment on Day 6: Custom Customs in ~comp

    Crespyl
    Link
    Quick and dirty: Ruby Part 1 #!/usr/bin/env ruby require "set" input = File.read(ARGV[0] || "test.txt") groups = input.split("\n\n") yes_answers = groups.map { |g| g.lines.reduce(Set.new) { |set,...

    Quick and dirty:

    Ruby

    Part 1
    #!/usr/bin/env ruby
    
    require "set"
    
    input = File.read(ARGV[0] || "test.txt")
    
    groups = input.split("\n\n")
    
    yes_answers = groups.map { |g| g.lines.reduce(Set.new) { |set, line| set | line.strip.chars } }
    
    sum = yes_answers.reduce(0) { |sum, group| sum + group.size }
    
    puts "Part 1"
    puts sum
    
    Part 2
    puts "Part 2"
    yes_answers = groups.map { |g| g.lines.reduce(Set.new(g.lines.first.strip.chars)) { |set, line| set & line.strip.chars } }
    sum = yes_answers.reduce(0) { |sum, group| sum + group.size }
    puts sum
    

    Having a built in set class makes this one pretty easy. The set union/intersection operator overloads are handy too.

    4 votes
  20. Comment on Day 5: Binary Boarding in ~comp

    Crespyl
    (edited )
    Link
    Ruby Part 1 #!/usr/bin/env ruby def bstep(path, low, high) steps = path.chars while ! steps.empty? c = steps.shift case c when /F|L/ high -= (high - low)/2 when /B|R/ low += (high - low)/2 end end...

    Ruby

    Part 1
    #!/usr/bin/env ruby
    
    def bstep(path, low, high)
      steps = path.chars
      while ! steps.empty?
        c = steps.shift
        case c
        when /F|L/
          high -= (high - low)/2
        when /B|R/
          low += (high - low)/2
        end
      end
      return low
    end
    
    def find_seat_score(pass)
      row = bstep(pass[0...7], 0, 128)
      col = bstep(pass[7...], 0, 8)
      return (row * 8) + col
    end
    
    input = File.read(ARGV[0] || "test.txt")
    
    puts "Part 1"
    puts input.lines.map { |line| find_seat_score(line) }.max
    
    Part 2
    puts "Part 2"
    seats = input.lines.map { |line| find_seat_score(line) }.sort
    seats[1..].reduce(seats.first) do |prev, cur|
      if prev+1 != cur
        puts prev+1
        break
      else
        cur
      end
    end
    

    Edit
    Hah, I didn't pick up on the binary trick, I tend to think too literally when reading the specs. This serves me well in some puzzles, and not so much in others :P. It's simple enough that I went back and did it that way too:

    Alternate
    def pass_to_number(pass)
      Integer(pass.gsub(/F|L/, '0').gsub(/B|R/, '1'), 2)
    end
    puts "Part 1 (again)"
    puts input.lines.map { |l| pass_to_number(l) }.max
    
    4 votes