Apply a penalty if table sizes are not honored #108
@ -3,16 +3,38 @@
|
|||||||
module Tables
|
module Tables
|
||||||
class DiscomfortCalculator
|
class DiscomfortCalculator
|
||||||
private attr_reader :table
|
private attr_reader :table
|
||||||
def initialize(table)
|
def initialize(table:)
|
||||||
@table = table
|
@table = table
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate
|
def calculate
|
||||||
cohesion_penalty
|
table_size_penalty + cohesion_penalty
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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
|
def cohesion_penalty
|
||||||
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
|
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
|
||||||
distance = AffinityGroupsHierarchy.instance.distance(a, b)
|
distance = AffinityGroupsHierarchy.instance.distance(a, b)
|
||||||
|
@ -4,7 +4,7 @@ require_relative '../../extensions/tree_node_extension'
|
|||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class Distribution
|
class Distribution
|
||||||
attr_accessor :tables
|
attr_accessor :tables, :min_per_table, :max_per_table
|
||||||
|
|
||||||
def initialize(min_per_table:, max_per_table:)
|
def initialize(min_per_table:, max_per_table:)
|
||||||
@min_per_table = min_per_table
|
@min_per_table = min_per_table
|
||||||
@ -17,6 +17,8 @@ module Tables
|
|||||||
max_tables = (people.count * 1.0 / @min_per_table).ceil
|
max_tables = (people.count * 1.0 / @min_per_table).ceil
|
||||||
@tables = people.in_groups(rand(min_tables..max_tables), false)
|
@tables = people.in_groups(rand(min_tables..max_tables), false)
|
||||||
.map { |group| Table.new(group) }
|
.map { |group| Table.new(group) }
|
||||||
|
.each { |table| table.min_per_table = @min_per_table }
|
||||||
|
.each { |table| table.max_per_table = @max_per_table }
|
||||||
end
|
end
|
||||||
|
|
||||||
def discomfort
|
def discomfort
|
||||||
@ -62,7 +64,7 @@ module Tables
|
|||||||
private
|
private
|
||||||
|
|
||||||
def local_discomfort(table)
|
def local_discomfort(table)
|
||||||
table.discomfort ||= DiscomfortCalculator.new(table).calculate
|
table.discomfort ||= DiscomfortCalculator.new(table:).calculate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class Table < Array
|
class Table < Array
|
||||||
attr_accessor :discomfort
|
attr_accessor :discomfort, :min_per_table, :max_per_table
|
||||||
|
|
||||||
def initialize(*args)
|
def initialize(*args)
|
||||||
super
|
super
|
||||||
reset
|
reset
|
||||||
|
@ -3,13 +3,61 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
module Tables
|
module Tables
|
||||||
RSpec.describe DiscomfortCalculator do
|
RSpec.describe DiscomfortCalculator do
|
||||||
let(:calculator) { described_class.new(table) }
|
let(:calculator) { described_class.new(table:) }
|
||||||
|
|
||||||
let(:family) { create(:group, name: 'family') }
|
let(:family) { create(:group, name: 'family') }
|
||||||
let(:friends) { create(:group, name: 'friends') }
|
let(:friends) { create(:group, name: 'friends') }
|
||||||
let(:work) { create(:group, name: 'work') }
|
let(:work) { create(:group, name: 'work') }
|
||||||
let(:school) { create(:group, name: 'school') }
|
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
|
describe '#cohesion_penalty' do
|
||||||
before do
|
before do
|
||||||
# Overridden in each test except trivial cases
|
# Overridden in each test except trivial cases
|
||||||
|
Loading…
x
Reference in New Issue
Block a user