class UniversalOrbitMap attr_reader :orbits, :bodies def initialize(orbits) @orbits = orbits body_names = orbits.map(&:orbitee) | orbits.map(&:orbiter) @bodies = body_names.to_h { |name| [name, Body.new(name)] } orbits.each do |orbit| bodies[orbit.orbitee].orbiters << orbit.orbiter bodies[orbit.orbiter].orbiting = bodies[orbit.orbitee] end end def self.for(input) new input.split("\n").map { |line| Orbit.for line } end def path_between(to, from) to_chain = bodies[to].orbit_chain.map(&:name) from_chain = bodies[from].orbit_chain.map(&:name) common_chain = to_chain & from_chain (from_chain - common_chain) + (to_chain - common_chain).reverse end class Body attr_reader :name, :orbiters attr_accessor :orbiting def initialize(name) @name = name @orbiting = nil @orbiters = [] end def orbit_chain chain = [orbiting] while chain.last chain << chain.last.orbiting end chain[..-2] end end class Orbit attr_accessor :orbitee, :orbiter, :orbitee_name, :orbiter_name def initialize(orbitee, orbiter) @orbiter = orbiter @orbiter_name = orbiter @orbitee = orbitee @orbitee_name = orbitee end def self.for(line) new *line.split(")") end end end