Apply a penalty if table sizes are not honored
This commit is contained in:
parent
2deaaa64a5
commit
f3b70f5a31
@ -3,16 +3,38 @@
|
||||
module Tables
|
||||
class DiscomfortCalculator
|
||||
private attr_reader :table
|
||||
def initialize(table)
|
||||
def initialize(table:)
|
||||
@table = table
|
||||
end
|
||||
|
||||
def calculate
|
||||
cohesion_penalty
|
||||
table_size_penalty + cohesion_penalty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#
|
||||
# Calculates the penalty associated with violating the table size constraints. The penalty is
|
||||
# zero when the limits are honored, and it increases linearly as the number of guests deviates
|
||||
# from the limits. Overcapacity is penalized more severely than undercapacity.
|
||||
#
|
||||
# @return [Number] The penalty associated with violating the table size constraints.
|
||||
#
|
||||
def table_size_penalty
|
||||
case table.size
|
||||
when 0...table.min_per_table then 2 * (table.min_per_table - table.size)
|
||||
when table.min_per_table..table.max_per_table then 0
|
||||
else 5 * (table.size - table.max_per_table)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Calculates the discomfort of the table based on the cohesion of the guests. The total discomfort
|
||||
# is calculated as the sum of the discomfort of each pair of guests. The discomfort of a pair of
|
||||
# guests is a rational number between 1 (unrelated groups) and 0 (same group).
|
||||
#
|
||||
# @return [Number] Total discomfort of the table.
|
||||
#
|
||||
def cohesion_penalty
|
||||
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
|
||||
distance = AffinityGroupsHierarchy.instance.distance(a, b)
|
||||
|
@ -4,7 +4,7 @@ require_relative '../../extensions/tree_node_extension'
|
||||
|
||||
module Tables
|
||||
class Distribution
|
||||
attr_accessor :tables
|
||||
attr_accessor :tables, :min_per_table, :max_per_table
|
||||
|
||||
def initialize(min_per_table:, max_per_table:)
|
||||
@min_per_table = min_per_table
|
||||
@ -17,6 +17,8 @@ module Tables
|
||||
max_tables = (people.count * 1.0 / @min_per_table).ceil
|
||||
@tables = people.in_groups(rand(min_tables..max_tables), false)
|
||||
.map { |group| Table.new(group) }
|
||||
.each { |table| table.min_per_table = @min_per_table }
|
||||
.each { |table| table.max_per_table = @max_per_table }
|
||||
end
|
||||
|
||||
def discomfort
|
||||
@ -62,7 +64,7 @@ module Tables
|
||||
private
|
||||
|
||||
def local_discomfort(table)
|
||||
table.discomfort ||= DiscomfortCalculator.new(table).calculate
|
||||
table.discomfort ||= DiscomfortCalculator.new(table:).calculate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
module Tables
|
||||
class Table < Array
|
||||
attr_accessor :discomfort
|
||||
attr_accessor :discomfort, :min_per_table, :max_per_table
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
reset
|
||||
@ -14,4 +15,4 @@ module Tables
|
||||
original_discomfort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,13 +3,61 @@
|
||||
require 'rails_helper'
|
||||
module Tables
|
||||
RSpec.describe DiscomfortCalculator do
|
||||
let(:calculator) { described_class.new(table) }
|
||||
let(:calculator) { described_class.new(table:) }
|
||||
|
||||
let(:family) { create(:group, name: 'family') }
|
||||
let(:friends) { create(:group, name: 'friends') }
|
||||
let(:work) { create(:group, name: 'work') }
|
||||
let(:school) { create(:group, name: 'school') }
|
||||
|
||||
describe '#table_size_penalty' do
|
||||
before do
|
||||
table.min_per_table = 5
|
||||
table.max_per_table = 7
|
||||
end
|
||||
context 'when the number of guests is in the lower bound' do
|
||||
let(:table) { Table.new(create_list(:guest, 5)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(0) }
|
||||
end
|
||||
|
||||
context 'when the number of guests is within the table size limits' do
|
||||
let(:table) { Table.new(create_list(:guest, 6)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(0) }
|
||||
end
|
||||
|
||||
context 'when the number of guests is in the upper bound' do
|
||||
let(:table) { Table.new(create_list(:guest, 7)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(0) }
|
||||
end
|
||||
|
||||
context 'when the number of guests is one unit below the lower bound' do
|
||||
let(:table) { Table.new(create_list(:guest, 4)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(2) }
|
||||
end
|
||||
|
||||
context 'when the number of guests is two units below the lower bound' do
|
||||
let(:table) { Table.new(create_list(:guest, 3)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(4) }
|
||||
end
|
||||
|
||||
context 'when the number of guests is one unit above the upper bound' do
|
||||
let(:table) { Table.new(create_list(:guest, 8)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(5) }
|
||||
end
|
||||
|
||||
context 'when the number of guests is two units above the upper bound' do
|
||||
let(:table) { Table.new(create_list(:guest, 9)) }
|
||||
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(10) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cohesion_penalty' do
|
||||
before do
|
||||
# Overridden in each test except trivial cases
|
||||
|
Loading…
x
Reference in New Issue
Block a user