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

Image of AssemblyAI tool

Challenge Submission: SpeechCraft - AI-Powered Speech Analysis for Better Communication

SpeechCraft is an advanced real-time speech analytics platform that transforms spoken words into actionable insights. Using cutting-edge AI technology from AssemblyAI, it provides instant transcription while analyzing multiple dimensions of speech performance.

Read full post

Top comments (0)

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay