Compare commits
6 Commits
fix-upsert
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
f550b8d92c | ||
7c968f555f | |||
1f0c6c2aac | |||
390899524b | |||
|
59e7653064 | ||
d6fd72a45c |
12
Gemfile.lock
12
Gemfile.lock
@ -220,7 +220,7 @@ GEM
|
||||
racc (~> 1.4)
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.6.0)
|
||||
parser (3.3.7.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.9)
|
||||
@ -343,12 +343,12 @@ GEM
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-factory_bot (2.26.1)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rails (2.28.0)
|
||||
rubocop-rails (2.29.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.52.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (3.3.0)
|
||||
rubocop-rspec (3.4.0)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec_rails (2.30.0)
|
||||
rubocop (~> 1.61)
|
||||
@ -541,7 +541,7 @@ CHECKSUMS
|
||||
nokogiri (1.18.1-x86_64-linux-gnu) sha256=e516cf16ccde67ed4cc595a2621ca5ddd42562ecb24928914b0045a20a41620e
|
||||
orm_adapter (0.5.0) sha256=aa5d0be5d540cbb46d3a93e88061f4ece6a25f6e97d6a47122beb84fe595e9b9
|
||||
parallel (1.26.3) sha256=d86babb7a2b814be9f4b81587bf0b6ce2da7d45969fab24d8ae4bf2bb4d4c7ef
|
||||
parser (3.3.6.0) sha256=25d4e67cc4f0f7cab9a2ae1f38e2005b6904d2ea13c34734511d0faad038bc3b
|
||||
parser (3.3.7.0) sha256=7449011771e3e7881297859b849de26a6f4fccd515bece9520a87e7d2116119b
|
||||
pg (1.5.9) sha256=761efbdf73b66516f0c26fcbe6515dc7500c3f0aa1a1b853feae245433c64fdc
|
||||
pluck_to_hash (1.0.2) sha256=1599906239716f98262a41493dd7d4cb72e8d83ad3d76d666deacfc5de50a47e
|
||||
pry (0.15.2) sha256=12d54b8640d3fa29c9211dd4ffb08f3fd8bf7a4fd9b5a73ce5b59c8709385b6b
|
||||
@ -581,8 +581,8 @@ CHECKSUMS
|
||||
rubocop (1.70.0) sha256=96751f8440b36a0ac6e9a8ab596900803118d83d6b83f2037bf8b3d7a5bc440e
|
||||
rubocop-ast (1.37.0) sha256=9513ac88aaf113d04b52912533ffe46475de1362d4aa41141b51b2455827c080
|
||||
rubocop-factory_bot (2.26.1) sha256=8de13cd4edcee5ca800f255188167ecef8dbfc3d1fae9f15734e9d2e755392aa
|
||||
rubocop-rails (2.28.0) sha256=4967bed9ea13e6dcab566fea4265a6dd0381db739b305e48930aba1282da2715
|
||||
rubocop-rspec (3.3.0) sha256=79e1b281a689044d1516fefbc52e2e6c06cd367c25ebeaf06a7a198e9071cd7d
|
||||
rubocop-rails (2.29.0) sha256=35bffd140c80671453aafac0e2d5ab5b3dd65736a3fc8f3936ccca226b89c234
|
||||
rubocop-rspec (3.4.0) sha256=8721c13b6a8c9530a7ac481cea9423022f946fcf72428bda8289f8b57e4d4885
|
||||
rubocop-rspec_rails (2.30.0) sha256=888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46
|
||||
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
||||
rubytree (2.1.1) sha256=4925016356a81730e982f1f8c3b5f8da461f18906c77d238bad4c4ba896abd41
|
||||
|
@ -36,17 +36,21 @@ class AffinitiesController < ApplicationController
|
||||
end
|
||||
|
||||
def default
|
||||
hierarchy = AffinityGroupsHierarchy.new
|
||||
|
||||
for_each_group do |group_id|
|
||||
Tables::DiscomfortCalculator.cohesion_discomfort(id_a: @group.id, id_b: group_id).to_f
|
||||
hierarchy.default_discomfort(@group.id, group_id).to_f
|
||||
end
|
||||
end
|
||||
|
||||
def reset
|
||||
hierarchy = AffinityGroupsHierarchy.new
|
||||
|
||||
affinities = Group.pluck(:id).combination(2).map do |(group_a_id, group_b_id)|
|
||||
{
|
||||
group_a_id:,
|
||||
group_b_id:,
|
||||
discomfort: Tables::DiscomfortCalculator.cohesion_discomfort(id_a: group_a_id, id_b: group_b_id).to_f
|
||||
discomfort: hierarchy.default_discomfort(group_a_id, group_b_id).to_f
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -31,6 +31,6 @@ class ExpensesController < ApplicationController
|
||||
private
|
||||
|
||||
def expense_params
|
||||
params.require(:expense).permit(:name, :amount, :pricing_type)
|
||||
params.expect(expense: %i[name amount pricing_type])
|
||||
end
|
||||
end
|
||||
|
@ -32,6 +32,6 @@ class GuestsController < ApplicationController
|
||||
private
|
||||
|
||||
def guest_params
|
||||
params.require(:guest).permit(:name, :group_id, :status)
|
||||
params.expect(guest: %i[name group_id status])
|
||||
end
|
||||
end
|
||||
|
@ -5,7 +5,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AffinityGroupsHierarchy < Array
|
||||
include Singleton
|
||||
DEFAULT_DISCOMFORT = 1
|
||||
|
||||
def initialize
|
||||
super
|
||||
@ -16,6 +16,9 @@ class AffinityGroupsHierarchy < Array
|
||||
|
||||
hydrate(group)
|
||||
end
|
||||
|
||||
load_discomforts
|
||||
freeze
|
||||
end
|
||||
|
||||
def find(id)
|
||||
@ -37,8 +40,35 @@ class AffinityGroupsHierarchy < Array
|
||||
@references[id_a].distance_to_common_ancestor(@references[id_b])
|
||||
end
|
||||
|
||||
def discomfort(id_a, id_b)
|
||||
return 0 if id_a == id_b
|
||||
|
||||
@discomforts[uuid_to_int(id_a) + uuid_to_int(id_b)] || DEFAULT_DISCOMFORT
|
||||
end
|
||||
|
||||
def default_discomfort(id_a, id_b)
|
||||
return 0 if id_a == id_b
|
||||
|
||||
dist = distance(id_a, id_b)
|
||||
|
||||
return DEFAULT_DISCOMFORT if dist.nil?
|
||||
|
||||
Rational(dist, dist + 1)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_discomforts
|
||||
@load_discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id,
|
||||
:discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc|
|
||||
acc[uuid_to_int(id_a) + uuid_to_int(id_b)] = discomfort
|
||||
end
|
||||
end
|
||||
|
||||
def uuid_to_int(uuid)
|
||||
uuid.gsub('-', '').hex
|
||||
end
|
||||
|
||||
def hydrate(group)
|
||||
group.children.each do |child|
|
||||
register_child(group.id, child.id)
|
||||
|
@ -6,19 +6,10 @@
|
||||
|
||||
module Tables
|
||||
class DiscomfortCalculator
|
||||
class << self
|
||||
def cohesion_discomfort(id_a:, id_b:)
|
||||
distance = AffinityGroupsHierarchy.instance.distance(id_a, id_b)
|
||||
|
||||
return 1 if distance.nil?
|
||||
|
||||
Rational(distance, distance + 1)
|
||||
end
|
||||
end
|
||||
|
||||
private attr_reader :table
|
||||
def initialize(table:)
|
||||
private attr_reader :table, :hierarchy
|
||||
def initialize(table:, hierarchy: AffinityGroupsHierarchy.new)
|
||||
@table = table
|
||||
@hierarchy = hierarchy
|
||||
end
|
||||
|
||||
def calculate
|
||||
@ -59,7 +50,7 @@ module Tables
|
||||
#
|
||||
def cohesion_discomfort
|
||||
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
|
||||
count_a * count_b * self.class.cohesion_discomfort(id_a: a, id_b: b)
|
||||
count_a * count_b * hierarchy.discomfort(a, b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -13,6 +13,7 @@ module Tables
|
||||
def initialize(min_per_table:, max_per_table:)
|
||||
@min_per_table = min_per_table
|
||||
@max_per_table = max_per_table
|
||||
@hierarchy = AffinityGroupsHierarchy.new
|
||||
@tables = []
|
||||
end
|
||||
|
||||
@ -35,12 +36,6 @@ module Tables
|
||||
"#{@tables.count} tables, discomfort: #{discomfort}"
|
||||
end
|
||||
|
||||
def pretty_print
|
||||
@tables.map.with_index do |table, i|
|
||||
"Table #{i + 1} (#{table.count} ppl): (#{local_discomfort(table)}) #{table.map(&:name).join(', ')}"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def deep_dup
|
||||
self.class.new(min_per_table: @min_per_table, max_per_table: @max_per_table).tap do |new_distribution|
|
||||
new_distribution.tables = @tables.map(&:dup)
|
||||
@ -68,7 +63,7 @@ module Tables
|
||||
private
|
||||
|
||||
def local_discomfort(table)
|
||||
table.discomfort ||= DiscomfortCalculator.new(table:).calculate
|
||||
table.discomfort ||= DiscomfortCalculator.new(table:, hierarchy:).calculate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -75,159 +75,5 @@ module Tables
|
||||
it { expect(calculator.send(:table_size_penalty)).to eq(10) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cohesion_discomfort' do
|
||||
before do
|
||||
# Overridden in each test except trivial cases
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).and_call_original
|
||||
|
||||
%w[family friends work school].each do |group|
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(group, group).and_return(0)
|
||||
end
|
||||
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(family.id, friends.id).and_return(nil)
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(friends.id, work.id).and_return(1)
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(family.id, work.id).and_return(2)
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(family.id, school.id).and_return(3)
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(friends.id, school.id).and_return(4)
|
||||
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(work.id, school.id).and_return(5)
|
||||
end
|
||||
|
||||
context 'when the table contains just two guests' do
|
||||
context 'when they belong to the same group' do
|
||||
let(:table) { create_list(:guest, 2, group: family) }
|
||||
|
||||
it { expect(calculator.send(:cohesion_discomfort)).to eq(0) }
|
||||
end
|
||||
|
||||
context 'when they belong to completely unrelated groups' do
|
||||
let(:table) do
|
||||
[
|
||||
create(:guest, group: family),
|
||||
create(:guest, group: friends)
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(calculator.send(:cohesion_discomfort)).to eq(1) }
|
||||
end
|
||||
|
||||
context 'when they belong to groups at a distance of 1' do
|
||||
let(:table) do
|
||||
[
|
||||
create(:guest, group: friends),
|
||||
create(:guest, group: work)
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(calculator.send(:cohesion_discomfort)).to eq(0.5) }
|
||||
end
|
||||
|
||||
context 'when they belong to groups at a distance of 2' do
|
||||
let(:table) do
|
||||
[
|
||||
create(:guest, group: family),
|
||||
create(:guest, group: work)
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(calculator.send(:cohesion_discomfort)).to eq(Rational(2, 3)) }
|
||||
end
|
||||
|
||||
context 'when they belong to groups at a distance of 3' do
|
||||
let(:table) do
|
||||
[
|
||||
create(:guest, group: family),
|
||||
create(:guest, group: school)
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(calculator.send(:cohesion_discomfort)).to eq(Rational(3, 4)) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the table contains three guests' do
|
||||
let(:table) do
|
||||
[
|
||||
create(:guest, group: family),
|
||||
create(:guest, group: friends),
|
||||
create(:guest, group: work)
|
||||
]
|
||||
end
|
||||
|
||||
it 'returns the sum of the penalties for each pair of guests' do
|
||||
expect(calculator.send(:cohesion_discomfort)).to eq(1 + Rational(1, 2) + Rational(2, 3))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the table contains four guests of different groups' do
|
||||
let(:table) do
|
||||
[
|
||||
create(:guest, group: family),
|
||||
create(:guest, group: friends),
|
||||
create(:guest, group: work),
|
||||
create(:guest, group: school)
|
||||
]
|
||||
end
|
||||
|
||||
it 'returns the sum of the penalties for each pair of guests' do
|
||||
expect(calculator.send(:cohesion_discomfort))
|
||||
.to eq(1 + Rational(1, 2) + Rational(2, 3) + Rational(3, 4) + Rational(4, 5) + Rational(5, 6))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the table contains four guests of two evenly split groups' do
|
||||
let(:table) do
|
||||
[
|
||||
create_list(:guest, 2, group: family),
|
||||
create_list(:guest, 2, group: friends)
|
||||
].flatten
|
||||
end
|
||||
|
||||
it 'returns the sum of the penalties for each pair of guests' do
|
||||
expect(calculator.send(:cohesion_discomfort)).to eq(4)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the table contains six guests of two unevenly split groups' do
|
||||
let(:table) do
|
||||
[
|
||||
create_list(:guest, 2, group: family),
|
||||
create_list(:guest, 4, group: friends)
|
||||
].flatten
|
||||
end
|
||||
|
||||
it 'returns the sum of the penalties for each pair of guests' do
|
||||
expect(calculator.send(:cohesion_discomfort)).to eq(8)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the table contains six guests of three evenly split groups' do
|
||||
let(:table) do
|
||||
[
|
||||
create_list(:guest, 2, group: family),
|
||||
create_list(:guest, 2, group: friends),
|
||||
create_list(:guest, 2, group: work)
|
||||
].flatten
|
||||
end
|
||||
|
||||
it 'returns the sum of the penalties for each pair of guests' do
|
||||
expect(calculator.send(:cohesion_discomfort)).to eq((4 * 1) + (4 * Rational(1, 2)) + (4 * Rational(2, 3)))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the table contains six guests of three unevenly split groups' do
|
||||
let(:table) do
|
||||
[
|
||||
create_list(:guest, 3, group: family),
|
||||
create_list(:guest, 2, group: friends),
|
||||
create_list(:guest, 1, group: work)
|
||||
].flatten
|
||||
end
|
||||
|
||||
it 'returns the sum of the penalties for each pair of guests' do
|
||||
expect(calculator.send(:cohesion_discomfort)).to eq((6 * 1) + (2 * Rational(1, 2)) + (3 * Rational(2, 3)))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user