DEV Community

Caleb Weeks
Caleb Weeks

Posted on

1

Advent of Code #4 (in Crystal)

If you've followed any of my blog posts, you know that I am a fan of functional programming. And although I'm still a fan of the functional paradigm, I've been trying to improve my knowledge of object oriented programming and finding use cases to apply that knowledge.

Don't get me wrong, I still think that FP has advantages over OOP in many if not most situations. Even when an OOP solution might be more ergonomic or simple or elegant, it often introduces foot-guns that might not be immediately apparent. Specifically, the practice of mutable state can cause subtle issues, even if it is nicely encapsulated in an object.

Anyway, I solved part 1 with a tidy functional solution. You can see most of that process in this video. When it came to part 2, it seemed like a good use case to apply some OOP. So I did a large refactor and made sure that part 1 still gave me the right answer.

The Card type alias worked well for part 1, but I needed to store the number of cards as well for part 2. This meant expanding the type of Card from Hash(String, Array(Int32)) to Hash(String, Array(Int32) | Int32). But then, any place in the code where I am setting the value, I have to explicitly define the type. As far as I am aware, Crystal doesn't have literal hash types, meaning I could not define a hash with specific keys and corresponding value types. At this point, an object (class) seemed like the better option.

I actually really like how the refactor turned out. The parsing of the card was moved to the from_str constructor, and other derived values were split up into their respective sections. And the code for part 1 became a simple one liner.

Here's where the foot-gun comes: part 2 involves mutating the cards in the Cards hash. Fortunately, part 1 leaves the cards list alone, but if you were to reuse the cards variable after part 2, it would contain cards with counts that have been modified. Maybe not the biggest deal, but it's something to keep in mind.

Alright, enough about that. Here's the code:

input = File.read("input").strip

alias Cards = Hash(Int32, Card)

class Card

  @winning : Array(Int32)
  @have : Array(Int32)
  property count : Int32

  def initialize(@winning, @have, @count = 1)
  end

  def self.from_str(str)
    winning, have = str.split('|')
    winning = winning.scan(/\d+/).map(&.[0].to_i)
    have = have.scan(/\d+/).map(&.[0].to_i)
    self.new(winning, have)
  end

  def wins
    (@winning&(@have)).size
  end

  def worth
    self.wins > 0 ? 2 ** (self.wins - 1) : 0
  end

  def duplicate(times)
    @count += times
  end

end

cards = input.split("\n").reduce(Cards.new) do |cards, line|
  head, body = line.split(':')
  card_number = head.lchop("Card ").to_i
  cards.merge({card_number => Card.from_str(body)})
end

part1 = cards.values.map(&.worth).sum

puts part1

part2 = cards.reduce(0) do |sum, (card_number, card)|
  if card.wins > 0
    ((card_number + 1)..(card_number + card.wins)).each do |number|
      cards[number].duplicate(card.count)
    end
  end
  sum + card.count
end

puts part2
Enter fullscreen mode Exit fullscreen mode

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay