# Provides a ruby implementation of several common matching algorithms
#
# Author:: Abhishek Chandrasekhar (mailto:me@abhchand.me)
# License:: MIT
require_relative "stable_matching"
require_relative "logging_helper"
require_relative "marriage/validator"
require_relative "marriage/preference_table"
require_relative "marriage/phase_i_runner"
class StableMatching
# Provides a solution to the Stable Marriage problem by implementing
# the Gale-Shapley algorithm
#
# Takes as input the preferences of two groups - alpha and beta - and
# produces a mathematically optimal matching/pairing between the two groups.
#
# Example Input:
#
# alpha_preferences = {
# "A" => ["M", "N", "L"],
# "B" => ["N", "M", "L"],
# "C" => ["M", "L", "N"],
# }
#
# beta_preferences = {
# "L" => ["B", "C", "A"],
# "M" => ["B", "A", "C"],
# "N" => ["A", "C", "B"],
# }
#
# Example Output:
#
# {"A"=>"M", "B"=>"N", "C"=>"L", "L"=>"C", "M"=>"A", "N"=>"B"}
class Marriage
include StableMatching::LoggingHelper
# Runs the algorithm with the specified inputs.
#
# This is a class level shortcut to initialize a new
# +StableMatching::Marriage+ instance and calls +solve!+ on it.
#
# Inputs:
#
# alpha_preferences::
# A +Hash+ of +Array+ values specifying the preferences of the alpha
# group
# beta_preferences::
# A +Hash+ of +Array+ values specifying the preferences of the beta
# group
#
# Output:
# A +Hash+ mapping alpha members to beta and beta members to alpha members.
def self.solve!(alpha_preferences, beta_preferences)
new(alpha_preferences, beta_preferences).solve!
end
# Initializes a `StableMatching::Marriage` object.
#
#
# Inputs:
#
# alpha_preferences::
# A +Hash+ of +Array+ values specifying the preferences of the alpha
# group. +Array+ can contain +String+ or +Integer+ entries.
# beta_preferences::
# A +Hash+ of +Array+ values specifying the preferences of the beta
# group. +Array+ can contain +String+ or +Integer+ entries.
# opts[:logger]::
# +Logger+ instance to use for logging
#
# Output:
#
# +StableMatching::Marriage+ instance
def initialize(alpha_preferences, beta_preferences, opts = {})
@orig_alpha_preferences = alpha_preferences
@orig_beta_preferences = beta_preferences
@alpha_preferences, @beta_preferences =
PreferenceTable.initialize_pair(alpha_preferences, beta_preferences)
set_logger(opts)
end
# Runs the algorithm on the alpha and beta preference sets.
# Also validates the preference sets and raises an error if invalid.
#
# Output:
#
# A +Hash+ mapping alpha members to beta and beta members to alpha members.
#
# Raises:
#
# StableMatching::InvalidPreferences::
# When alpha or beta preference groups are of invalid format
def solve!
validate!
@logger.info("Running Phase I")
PhaseIRunner.new(alpha_preferences, beta_preferences, logger: @logger).run
build_solution
end
private
def validate!
Validator.validate_pair!(@orig_alpha_preferences, @orig_beta_preferences)
end
def alpha_preferences
@alpha_preferences ||= PreferenceTable.new(@orig_alpha_preferences)
end
def beta_preferences
@beta_preferences ||= PreferenceTable.new(@orig_beta_preferences)
end
def build_solution
solution = {}
@alpha_preferences.members.each do |partner|
solution[partner.name] = partner.current_acceptor.name
end
@beta_preferences.members.each do |partner|
solution[partner.name] = partner.current_proposer.name
end
solution
end
end
end