From 405e698a6f016f9a36c5ae4e75d71e629dec78c6 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Wed, 24 Jul 2024 20:14:58 +0200 Subject: [PATCH 01/17] Initial version of VNS algorithm --- Gemfile | 4 ++- Gemfile.lock | 7 +++++ app/services/tables/distribution.rb | 44 +++++++++++++++++++++++++++++ app/services/tables/swap.rb | 28 ++++++++++++++++++ db/seeds.rb | 16 ++--------- lib/tasks/vns.rake | 16 +++++++++++ 6 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 app/services/tables/distribution.rb create mode 100644 app/services/tables/swap.rb create mode 100644 lib/tasks/vns.rake diff --git a/Gemfile b/Gemfile index a3cbafd..7ff1257 100644 --- a/Gemfile +++ b/Gemfile @@ -64,4 +64,6 @@ group :development do end gem "money" -gem 'acts-as-taggable-on' \ No newline at end of file +gem 'acts-as-taggable-on' + +gem "vns", path: "../vns" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 6f951a1..8d5808f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,9 @@ +PATH + remote: ../vns + specs: + vns (0.4.0.pre.rc.1) + activesupport + GEM remote: https://rubygems.org/ specs: @@ -276,6 +282,7 @@ DEPENDENCIES stimulus-rails turbo-rails tzinfo-data + vns! web-console RUBY VERSION diff --git a/app/services/tables/distribution.rb b/app/services/tables/distribution.rb new file mode 100644 index 0000000..b8a9997 --- /dev/null +++ b/app/services/tables/distribution.rb @@ -0,0 +1,44 @@ +module Tables + class Distribution + attr_reader :tables + + def initialize(min_per_table:, max_per_table:) + @min_per_table = min_per_table + @max_per_table = max_per_table + end + + def random_distribution(people) + @tables = [] + + @tables << people.slice!(0..rand(@min_per_table..@max_per_table)) while people.any? + end + + def discomfort + @tables.map do |table| + local_discomfort(table) + end.sum + end + + def inspect + "#{@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(&:full_name).join(', ')}" + end.join("\n") + end + + private + + def local_discomfort(table) + 10 * (number_of_groups(table) - 1) + end + + def number_of_groups(table) + table.map do |person| + person.affinity_groups + end.flatten.uniq.count + end + end +end diff --git a/app/services/tables/swap.rb b/app/services/tables/swap.rb new file mode 100644 index 0000000..ddc3681 --- /dev/null +++ b/app/services/tables/swap.rb @@ -0,0 +1,28 @@ +module Tables + class Swap + private attr_reader :initial_solution + def initialize(initial_solution) + @initial_solution = initial_solution + end + + def each + @initial_solution.tables.combination(2) do |table_a, table_b| + table_a.product(table_b).each do |(person_a, person_b)| + table_a.delete(person_a) + table_b.delete(person_b) + + table_a << person_b + table_b << person_a + + yield(@initial_solution) + ensure + table_a.delete(person_b) + table_b.delete(person_a) + + table_a << person_a + table_b << person_b + end + end + end + end +end diff --git a/db/seeds.rb b/db/seeds.rb index e79ad9b..c5f684c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,12 +1,4 @@ -# This file should ensure the existence of records required to run the application in every environment (production, -# development, test). The code here should be idempotent so that it can be executed at any point in every environment. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Example: -# -# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| -# MovieGenre.find_or_create_by!(name: genre_name) -# end +NUMBER_OF_GUESTS = 50 Expense.delete_all Guest.delete_all @@ -28,10 +20,6 @@ Expense.create!(name: 'Transportation', amount: 3000, pricing_type: 'fixed') Expense.create!(name: 'Invitations', amount: 200, pricing_type: 'fixed') Expense.create!(name: 'Cake', amount: 500, pricing_type: 'fixed') -<<<<<<< HEAD - -======= ->>>>>>> 8fd0b7c (Modify seeds file to make sure every guest is part of a group) samples = { close_family: 10, family_1_group_a: 5, @@ -50,7 +38,7 @@ samples = { count.times { acc << affinity_group } end -300.times do +NUMBER_OF_GUESTS.times do guest = Guest.create!(first_name: Faker::Name.first_name, last_name: Faker::Name.last_name, email: Faker::Internet.email, diff --git a/lib/tasks/vns.rake b/lib/tasks/vns.rake new file mode 100644 index 0000000..dd5a942 --- /dev/null +++ b/lib/tasks/vns.rake @@ -0,0 +1,16 @@ +namespace :vns do + task distribute_tables: :environment do + engine = VNS::Engine.new + + engine.add_perturbation(Tables::Swap) + + initial_solution = Tables::Distribution.new(min_per_table: 8, max_per_table: 10) + initial_solution.random_distribution(Guest.all.shuffle) + + engine.initial_solution = initial_solution + + engine.target_function(&:discomfort) + + best_solution = engine.run + end +end -- 2.47.1 From 7926928feb1d82d184650cda56486d6b22f3cbe0 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Wed, 24 Jul 2024 20:24:01 +0200 Subject: [PATCH 02/17] Create models to store tables arrangements --- app/models/seat.rb | 4 ++++ app/models/tables_arrangement.rb | 2 ++ ...240724181756_create_tables_arrangements.rb | 8 ++++++++ db/migrate/20240724181853_create_seats.rb | 13 +++++++++++++ db/schema.rb | 19 ++++++++++++++++++- db/seeds.rb | 1 + spec/models/seat_spec.rb | 5 +++++ spec/models/tables_arrangement_spec.rb | 5 +++++ 8 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 app/models/seat.rb create mode 100644 app/models/tables_arrangement.rb create mode 100644 db/migrate/20240724181756_create_tables_arrangements.rb create mode 100644 db/migrate/20240724181853_create_seats.rb create mode 100644 spec/models/seat_spec.rb create mode 100644 spec/models/tables_arrangement_spec.rb diff --git a/app/models/seat.rb b/app/models/seat.rb new file mode 100644 index 0000000..e909587 --- /dev/null +++ b/app/models/seat.rb @@ -0,0 +1,4 @@ +class Seat < ApplicationRecord + belongs_to :guest + belongs_to :table_arrangement +end diff --git a/app/models/tables_arrangement.rb b/app/models/tables_arrangement.rb new file mode 100644 index 0000000..0bf9996 --- /dev/null +++ b/app/models/tables_arrangement.rb @@ -0,0 +1,2 @@ +class TablesArrangement < ApplicationRecord +end diff --git a/db/migrate/20240724181756_create_tables_arrangements.rb b/db/migrate/20240724181756_create_tables_arrangements.rb new file mode 100644 index 0000000..358c623 --- /dev/null +++ b/db/migrate/20240724181756_create_tables_arrangements.rb @@ -0,0 +1,8 @@ +class CreateTablesArrangements < ActiveRecord::Migration[7.1] + def change + create_table :tables_arrangements, id: :uuid do |t| + + t.timestamps + end + end +end diff --git a/db/migrate/20240724181853_create_seats.rb b/db/migrate/20240724181853_create_seats.rb new file mode 100644 index 0000000..74f5b7b --- /dev/null +++ b/db/migrate/20240724181853_create_seats.rb @@ -0,0 +1,13 @@ +class CreateSeats < ActiveRecord::Migration[7.1] + def change + create_table :seats, id: :uuid do |t| + t.references :guest, null: false, foreign_key: true, type: :uuid + t.references :tables_arrangement, null: false, type: :uuid + t.integer :table_number + + t.timestamps + end + + add_foreign_key :seats, :tables_arrangements, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 7410759..fa7e73b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_11_181632) do +ActiveRecord::Schema[7.1].define(version: 2024_07_24_181853) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -35,6 +35,21 @@ ActiveRecord::Schema[7.1].define(version: 2024_07_11_181632) do t.datetime "updated_at", null: false end + create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "guest_id", null: false + t.uuid "tables_arrangement_id", null: false + t.integer "table_number" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["guest_id"], name: "index_seats_on_guest_id" + t.index ["tables_arrangement_id"], name: "index_seats_on_tables_arrangement_id" + end + + create_table "tables_arrangements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "taggings", force: :cascade do |t| t.bigint "tag_id" t.string "taggable_type" @@ -66,5 +81,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_07_11_181632) do t.index ["name"], name: "index_tags_on_name", unique: true end + add_foreign_key "seats", "guests" + add_foreign_key "seats", "tables_arrangements", on_delete: :cascade add_foreign_key "taggings", "tags" end diff --git a/db/seeds.rb b/db/seeds.rb index c5f684c..be783d9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,5 +1,6 @@ NUMBER_OF_GUESTS = 50 +TablesArrangement.delete_all Expense.delete_all Guest.delete_all ActsAsTaggableOn::Tagging.delete_all diff --git a/spec/models/seat_spec.rb b/spec/models/seat_spec.rb new file mode 100644 index 0000000..bdcd95d --- /dev/null +++ b/spec/models/seat_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Seat, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/tables_arrangement_spec.rb b/spec/models/tables_arrangement_spec.rb new file mode 100644 index 0000000..71a09f5 --- /dev/null +++ b/spec/models/tables_arrangement_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe TablesArrangement, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end -- 2.47.1 From 191684a11cb965c6e4826d0601fd1e10663082c4 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Wed, 24 Jul 2024 20:37:14 +0200 Subject: [PATCH 03/17] Persist table distributions --- app/models/tables_arrangement.rb | 1 + app/services/tables/distribution.rb | 26 ++++++++++++++++++- ...240724181756_create_tables_arrangements.rb | 1 + db/schema.rb | 1 + lib/tasks/vns.rake | 4 +++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/models/tables_arrangement.rb b/app/models/tables_arrangement.rb index 0bf9996..1be3ec3 100644 --- a/app/models/tables_arrangement.rb +++ b/app/models/tables_arrangement.rb @@ -1,2 +1,3 @@ class TablesArrangement < ApplicationRecord + has_many :seats end diff --git a/app/services/tables/distribution.rb b/app/services/tables/distribution.rb index b8a9997..aa87de5 100644 --- a/app/services/tables/distribution.rb +++ b/app/services/tables/distribution.rb @@ -1,6 +1,6 @@ module Tables class Distribution - attr_reader :tables + attr_accessor :tables def initialize(min_per_table:, max_per_table:) @min_per_table = min_per_table @@ -29,6 +29,30 @@ module Tables 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) + end + end + + def save! + ActiveRecord::Base.transaction do + arrangement = TablesArrangement.create! + + records_to_store = [] + + tables.each_with_index do |table, table_number| + table.each do |person| + records_to_store << { guest_id: person.id, tables_arrangement_id: arrangement.id, table_number: } + end + end + + Seat.insert_all!(records_to_store) + + arrangement.update!(discomfort:) + end + end + private def local_discomfort(table) diff --git a/db/migrate/20240724181756_create_tables_arrangements.rb b/db/migrate/20240724181756_create_tables_arrangements.rb index 358c623..c05f6dc 100644 --- a/db/migrate/20240724181756_create_tables_arrangements.rb +++ b/db/migrate/20240724181756_create_tables_arrangements.rb @@ -1,6 +1,7 @@ class CreateTablesArrangements < ActiveRecord::Migration[7.1] def change create_table :tables_arrangements, id: :uuid do |t| + t.integer :discomfort t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index fa7e73b..08c4026 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -46,6 +46,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_07_24_181853) do end create_table "tables_arrangements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.integer "discomfort" t.datetime "created_at", null: false t.datetime "updated_at", null: false end diff --git a/lib/tasks/vns.rake b/lib/tasks/vns.rake index dd5a942..c33de67 100644 --- a/lib/tasks/vns.rake +++ b/lib/tasks/vns.rake @@ -12,5 +12,9 @@ namespace :vns do engine.target_function(&:discomfort) best_solution = engine.run + + binding.pry + + best_solution.save! end end -- 2.47.1 From 77be7d505a423886e1a3b07fca256e7164888b27 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Wed, 24 Jul 2024 20:43:44 +0200 Subject: [PATCH 04/17] Scaffold display of arrangements --- app/controllers/tables_arrangements_controller.rb | 9 +++++++++ app/helpers/tables_arrangements_helper.rb | 2 ++ app/views/tables_arrangements/index.html.erb | 9 +++++++++ app/views/tables_arrangements/show.html.erb | 0 config/routes.rb | 9 ++------- lib/tasks/vns.rake | 2 -- 6 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 app/controllers/tables_arrangements_controller.rb create mode 100644 app/helpers/tables_arrangements_helper.rb create mode 100644 app/views/tables_arrangements/index.html.erb create mode 100644 app/views/tables_arrangements/show.html.erb diff --git a/app/controllers/tables_arrangements_controller.rb b/app/controllers/tables_arrangements_controller.rb new file mode 100644 index 0000000..b247a6c --- /dev/null +++ b/app/controllers/tables_arrangements_controller.rb @@ -0,0 +1,9 @@ +class TablesArrangementsController < ApplicationController + def index + @tables_arrangements = TablesArrangement.all.order(discomfort: :asc).limit(10) + end + + def show + @tables_arrangement = TablesArrangement.find(params[:id]) + end +end diff --git a/app/helpers/tables_arrangements_helper.rb b/app/helpers/tables_arrangements_helper.rb new file mode 100644 index 0000000..c066a2e --- /dev/null +++ b/app/helpers/tables_arrangements_helper.rb @@ -0,0 +1,2 @@ +module TablesArrangementsHelper +end diff --git a/app/views/tables_arrangements/index.html.erb b/app/views/tables_arrangements/index.html.erb new file mode 100644 index 0000000..260e144 --- /dev/null +++ b/app/views/tables_arrangements/index.html.erb @@ -0,0 +1,9 @@ +

Tables arrangements

+ +
    + <% @tables_arrangements.each_with_index do |tables_arrangement, i| %> +
  1. +

    <%= link_to "Arrangement ##{i+1}", tables_arrangement_path(tables_arrangement) %> Discomfort: <%= tables_arrangement.discomfort %>

    +
  2. + <% end %> +
\ No newline at end of file diff --git a/app/views/tables_arrangements/show.html.erb b/app/views/tables_arrangements/show.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/config/routes.rb b/config/routes.rb index fb63987..2b43138 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,12 +1,7 @@ Rails.application.routes.draw do resources :guests resources :expenses - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html + resources :tables_arrangements, only: [:index, :show] - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. - # Can be used by load balancers and uptime monitors to verify that the app is live. - get "up" => "rails/health#show", as: :rails_health_check - - # Defines the root path route ("/") - # root "posts#index" + get 'up' => 'rails/health#show', as: :rails_health_check end diff --git a/lib/tasks/vns.rake b/lib/tasks/vns.rake index c33de67..c8e6b50 100644 --- a/lib/tasks/vns.rake +++ b/lib/tasks/vns.rake @@ -13,8 +13,6 @@ namespace :vns do best_solution = engine.run - binding.pry - best_solution.save! end end -- 2.47.1 From 45974fcf7efa471376afea82cd249e9d5e81d5b3 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 25 Jul 2024 09:39:34 +0200 Subject: [PATCH 05/17] Display table arrangements --- .../tables_arrangements_controller.rb | 1 + app/views/tables_arrangements/show.html.erb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/app/controllers/tables_arrangements_controller.rb b/app/controllers/tables_arrangements_controller.rb index b247a6c..d2fd9ac 100644 --- a/app/controllers/tables_arrangements_controller.rb +++ b/app/controllers/tables_arrangements_controller.rb @@ -5,5 +5,6 @@ class TablesArrangementsController < ApplicationController def show @tables_arrangement = TablesArrangement.find(params[:id]) + @seats = @tables_arrangement.seats.includes(:guest).group_by(&:table_number) end end diff --git a/app/views/tables_arrangements/show.html.erb b/app/views/tables_arrangements/show.html.erb index e69de29..f43b80d 100644 --- a/app/views/tables_arrangements/show.html.erb +++ b/app/views/tables_arrangements/show.html.erb @@ -0,0 +1,16 @@ +

ID: <%= @tables_arrangement.id %>

+ +

Discomfort: <%= @tables_arrangement.discomfort %>

+ +

Seats

+ +<% @seats.each do |table_number, seats| %> + +

Table <%= table_number %>

+ +
    + <% seats.each do |seat| %> +
  • <%= seat.guest.full_name %>
  • + <% end %> +
+<% end %> \ No newline at end of file -- 2.47.1 From d50ee7cb0653095374d4e4a62fb9676506e58f9a Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 25 Jul 2024 11:24:49 +0200 Subject: [PATCH 06/17] Add basic tests for the local_discomfort formula --- Gemfile | 1 + Gemfile.lock | 6 +++ spec/factories/guest.rb | 8 ++++ spec/rails_helper.rb | 1 + spec/services/tables/distribution_spec.rb | 45 +++++++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 spec/factories/guest.rb create mode 100644 spec/services/tables/distribution_spec.rb diff --git a/Gemfile b/Gemfile index 7ff1257..85d74c3 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ group :development, :test do gem 'rspec-rails', '~> 6.1.0' gem 'faker' gem 'pry' + gem "factory_bot_rails" end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 90a4379..44a43e5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,6 +100,11 @@ GEM diff-lcs (1.5.0) drb (2.2.1) erubi (1.13.0) + factory_bot (6.4.6) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) faker (3.1.1) i18n (>= 1.8.11, < 2) globalid (1.2.1) @@ -268,6 +273,7 @@ DEPENDENCIES acts-as-taggable-on bootsnap debug + factory_bot_rails faker importmap-rails jbuilder diff --git a/spec/factories/guest.rb b/spec/factories/guest.rb new file mode 100644 index 0000000..f761dd9 --- /dev/null +++ b/spec/factories/guest.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :guest do + first_name { Faker::Name.first_name } + last_name { Faker::Name.last_name } + email { Faker::Internet.email } + phone { Faker::PhoneNumber.cell_phone } + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index a15455f..c10abff 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -62,4 +62,5 @@ RSpec.configure do |config| config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") + config.include FactoryBot::Syntax::Methods end diff --git a/spec/services/tables/distribution_spec.rb b/spec/services/tables/distribution_spec.rb new file mode 100644 index 0000000..e9aca83 --- /dev/null +++ b/spec/services/tables/distribution_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +module Tables + RSpec.describe Distribution do + describe '#local_discomfort' do + let(:service) { described_class.new(min_per_table: 5, max_per_table: 5) } + before { service.tables = [table] } + + context 'when there is just one group in the table' do + let(:table) do + create_list(:guest, 3).each do |guest| + guest.affinity_group_list.add('family') + guest.save! + end + end + + it { expect(service.send(:local_discomfort, table)).to eq(0) } + end + + context 'when the table contains two groups' do + let(:table) do + guests = create_list(:guest, 3) + guests[0].affinity_group_list.add('family') + guests[1].affinity_group_list.add('friends') + guests[2].affinity_group_list.add('family') + guests.each(&:save!) + end + + it { expect(service.send(:local_discomfort, table)).to eq(10) } + end + + context 'when the table contains three groups' do + let(:table) do + guests = create_list(:guest, 3) + guests[0].affinity_group_list.add('family') + guests[1].affinity_group_list.add('friends') + guests[2].affinity_group_list.add('work') + guests.each(&:save!) + end + + it { expect(service.send(:local_discomfort, table)).to eq(20) } + end + end + end +end -- 2.47.1 From 95da6141ac9e1e196fb9d5090beb169edb4f6782 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 25 Jul 2024 11:25:22 +0200 Subject: [PATCH 07/17] Small refactor --- app/services/tables/distribution.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/services/tables/distribution.rb b/app/services/tables/distribution.rb index aa87de5..d94d690 100644 --- a/app/services/tables/distribution.rb +++ b/app/services/tables/distribution.rb @@ -60,9 +60,7 @@ module Tables end def number_of_groups(table) - table.map do |person| - person.affinity_groups - end.flatten.uniq.count + table.map(&:affinity_groups).flatten.uniq.count end end end -- 2.47.1 From df391a4f35e0435a7ef859b52209202da76309f4 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Wed, 31 Jul 2024 22:53:19 +0200 Subject: [PATCH 08/17] Extract a DiscomfortCalculator class --- app/services/tables/discomfort_calculator.rb | 22 +++++++++++++++++++ app/services/tables/distribution.rb | 8 +++---- ..._spec.rb => discomfort_calculator_spec.rb} | 14 +++++------- 3 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 app/services/tables/discomfort_calculator.rb rename spec/services/tables/{distribution_spec.rb => discomfort_calculator_spec.rb} (70%) diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb new file mode 100644 index 0000000..3ba39e7 --- /dev/null +++ b/app/services/tables/discomfort_calculator.rb @@ -0,0 +1,22 @@ +module Tables + class DiscomfortCalculator + private attr_reader :table + def initialize(table) + @table = table + end + + def calculate + group_merging + end + + private + + def group_merging + 10 * (number_of_groups - 1) + end + + def number_of_groups + table.map(&:affinity_groups).flatten.uniq.count + end + end +end diff --git a/app/services/tables/distribution.rb b/app/services/tables/distribution.rb index d94d690..d10d73f 100644 --- a/app/services/tables/distribution.rb +++ b/app/services/tables/distribution.rb @@ -1,3 +1,5 @@ +require_relative '../../extensions/tree_node_extension' + module Tables class Distribution attr_accessor :tables @@ -56,11 +58,7 @@ module Tables private def local_discomfort(table) - 10 * (number_of_groups(table) - 1) - end - - def number_of_groups(table) - table.map(&:affinity_groups).flatten.uniq.count + DiscomfortCalculator.new(table).calculate end end end diff --git a/spec/services/tables/distribution_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb similarity index 70% rename from spec/services/tables/distribution_spec.rb rename to spec/services/tables/discomfort_calculator_spec.rb index e9aca83..c997ac2 100644 --- a/spec/services/tables/distribution_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -1,10 +1,8 @@ require 'rails_helper' - module Tables - RSpec.describe Distribution do - describe '#local_discomfort' do - let(:service) { described_class.new(min_per_table: 5, max_per_table: 5) } - before { service.tables = [table] } + RSpec.describe DiscomfortCalculator do + describe '#group_merging' do + let(:calculator) { described_class.new(table) } context 'when there is just one group in the table' do let(:table) do @@ -14,7 +12,7 @@ module Tables end end - it { expect(service.send(:local_discomfort, table)).to eq(0) } + it { expect(calculator.send(:group_merging)).to eq(0) } end context 'when the table contains two groups' do @@ -26,7 +24,7 @@ module Tables guests.each(&:save!) end - it { expect(service.send(:local_discomfort, table)).to eq(10) } + it { expect(calculator.send(:group_merging)).to eq(10) } end context 'when the table contains three groups' do @@ -38,7 +36,7 @@ module Tables guests.each(&:save!) end - it { expect(service.send(:local_discomfort, table)).to eq(20) } + it { expect(calculator.send(:group_merging)).to eq(20) } end end end -- 2.47.1 From 8c6974a1e56e462d53789ecbd1d804c304e2c24f Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 18:43:56 +0200 Subject: [PATCH 09/17] Replace rule that calculates discomfort --- app/services/tables/discomfort_calculator.rb | 14 ++-- .../tables/discomfort_calculator_spec.rb | 64 +++++++++++-------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb index 3ba39e7..4a1f5c8 100644 --- a/app/services/tables/discomfort_calculator.rb +++ b/app/services/tables/discomfort_calculator.rb @@ -6,17 +6,19 @@ module Tables end def calculate - group_merging + cohesion_penalty end private - def group_merging - 10 * (number_of_groups - 1) - end + def cohesion_penalty + table.map { |guest| guest.affinity_group_list.first }.combination(2).sum do |a, b| + distance = AffinityGroupsHierarchy.instance.distance(a, b) + next 1 if distance.nil? + next 0 if distance.zero? - def number_of_groups - table.map(&:affinity_groups).flatten.uniq.count + Rational(distance, distance + 1) + end end end end diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index c997ac2..1e59103 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -1,42 +1,50 @@ require 'rails_helper' module Tables RSpec.describe DiscomfortCalculator do - describe '#group_merging' do - let(:calculator) { described_class.new(table) } - - context 'when there is just one group in the table' do + let(:calculator) { described_class.new(table) } + + describe '#cohesion_penalty' do + context 'when the table contains just two guests' do let(:table) do - create_list(:guest, 3).each do |guest| - guest.affinity_group_list.add('family') - guest.save! - end + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['friends']) + ] end - it { expect(calculator.send(:group_merging)).to eq(0) } - end - - context 'when the table contains two groups' do - let(:table) do - guests = create_list(:guest, 3) - guests[0].affinity_group_list.add('family') - guests[1].affinity_group_list.add('friends') - guests[2].affinity_group_list.add('family') - guests.each(&:save!) + before do + allow(AffinityGroupsHierarchy.instance).to receive(:distance).and_return(distance) end - it { expect(calculator.send(:group_merging)).to eq(10) } - end + context 'when they belong to the same group' do + let(:distance) { 0 } - context 'when the table contains three groups' do - let(:table) do - guests = create_list(:guest, 3) - guests[0].affinity_group_list.add('family') - guests[1].affinity_group_list.add('friends') - guests[2].affinity_group_list.add('work') - guests.each(&:save!) + it { expect(calculator.send(:cohesion_penalty)).to eq(0) } end - it { expect(calculator.send(:group_merging)).to eq(20) } + context 'when they belong to completely unrelated groups' do + let(:distance) { nil } + + it { expect(calculator.send(:cohesion_penalty)).to eq(1) } + end + + context 'when they belong to groups at a distance of 1' do + let(:distance) { 1 } + + it { expect(calculator.send(:cohesion_penalty)).to eq(0.5) } + end + + context 'when they belong to groups at a distance of 2' do + let(:distance) { 2 } + + it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(2, 3)) } + end + + context 'when they belong to groups at a distance of 3' do + let(:distance) { 3 } + + it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(3, 4)) } + end end end end -- 2.47.1 From f4c0cca3691f82a706ca1b12079349e864e82792 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 18:52:22 +0200 Subject: [PATCH 10/17] Include additional test case for three people --- .../tables/discomfort_calculator_spec.rb | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 1e59103..8888ef5 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' module Tables RSpec.describe DiscomfortCalculator do let(:calculator) { described_class.new(table) } - + describe '#cohesion_penalty' do context 'when the table contains just two guests' do let(:table) do @@ -46,6 +46,26 @@ module Tables it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(3, 4)) } end end + + context 'when the table contains three guests' do + let(:table) do + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['friends']), + create(:guest, affinity_group_list: ['work']) + ] + end + + before do + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'friends').and_return(nil) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'work').and_return(1) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'work').and_return(2) + end + + it 'returns the sum of the penalties for each pair of guests' do + expect(calculator.send(:cohesion_penalty)).to eq(1 + Rational(1, 2) + Rational(2, 3)) + end + end end end end -- 2.47.1 From d194b91244ceab76676f291d3abe7389bcae694b Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 18:53:46 +0200 Subject: [PATCH 11/17] Include additional test case for four people --- .../tables/discomfort_calculator_spec.rb | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 8888ef5..9f391f7 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -66,6 +66,31 @@ module Tables expect(calculator.send(:cohesion_penalty)).to eq(1 + Rational(1, 2) + Rational(2, 3)) end end + + context 'when the table contains four guests' do + let(:table) do + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['friends']), + create(:guest, affinity_group_list: ['work']), + create(:guest, affinity_group_list: ['school']) + ] + end + + before do + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'friends').and_return(nil) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'work').and_return(1) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'work').and_return(2) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'school').and_return(3) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'school').and_return(4) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('work', 'school').and_return(5) + end + + it 'returns the sum of the penalties for each pair of guests' do + expect(calculator.send(:cohesion_penalty)) + .to eq(1 + Rational(1, 2) + Rational(2, 3) + Rational(3, 4) + Rational(4, 5) + Rational(5, 6)) + end + end end end end -- 2.47.1 From 2a0a2d77dfda5384a3420e7fef9efb956916ae83 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 19:09:26 +0200 Subject: [PATCH 12/17] Refactor + include tests when there are two groups of guests --- app/services/tables/discomfort_calculator.rb | 1 + .../tables/discomfort_calculator_spec.rb | 89 ++++++++++++------- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb index 4a1f5c8..e1b413d 100644 --- a/app/services/tables/discomfort_calculator.rb +++ b/app/services/tables/discomfort_calculator.rb @@ -14,6 +14,7 @@ module Tables def cohesion_penalty table.map { |guest| guest.affinity_group_list.first }.combination(2).sum do |a, b| distance = AffinityGroupsHierarchy.instance.distance(a, b) + next 1 if distance.nil? next 0 if distance.zero? diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 9f391f7..939ebb4 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -4,44 +4,67 @@ module Tables let(:calculator) { described_class.new(table) } describe '#cohesion_penalty' 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', 'friends').and_return(nil) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'work').and_return(1) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'work').and_return(2) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'school').and_return(3) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'school').and_return(4) + allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('work', 'school').and_return(5) + end context 'when the table contains just two guests' do - let(:table) do - [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['friends']) - ] - end - - before do - allow(AffinityGroupsHierarchy.instance).to receive(:distance).and_return(distance) - end - context 'when they belong to the same group' do - let(:distance) { 0 } + let(:table) { create_list(:guest, 2, affinity_group_list: ['family']) } it { expect(calculator.send(:cohesion_penalty)).to eq(0) } end context 'when they belong to completely unrelated groups' do - let(:distance) { nil } - + let(:table) do + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['friends']) + ] + end it { expect(calculator.send(:cohesion_penalty)).to eq(1) } end context 'when they belong to groups at a distance of 1' do - let(:distance) { 1 } + let(:table) do + [ + create(:guest, affinity_group_list: ['friends']), + create(:guest, affinity_group_list: ['work']) + ] + end it { expect(calculator.send(:cohesion_penalty)).to eq(0.5) } end context 'when they belong to groups at a distance of 2' do - let(:distance) { 2 } + let(:table) do + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['work']) + ] + end it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(2, 3)) } end context 'when they belong to groups at a distance of 3' do - let(:distance) { 3 } + let(:table) do + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['school']) + ] + end it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(3, 4)) } end @@ -56,18 +79,12 @@ module Tables ] end - before do - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'friends').and_return(nil) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'work').and_return(1) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'work').and_return(2) - end - it 'returns the sum of the penalties for each pair of guests' do expect(calculator.send(:cohesion_penalty)).to eq(1 + Rational(1, 2) + Rational(2, 3)) end end - context 'when the table contains four guests' do + context 'when the table contains four guests of different groups' do let(:table) do [ create(:guest, affinity_group_list: ['family']), @@ -77,20 +94,26 @@ module Tables ] end - before do - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'friends').and_return(nil) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'work').and_return(1) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'work').and_return(2) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('family', 'school').and_return(3) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('friends', 'school').and_return(4) - allow(AffinityGroupsHierarchy.instance).to receive(:distance).with('work', 'school').and_return(5) - end - it 'returns the sum of the penalties for each pair of guests' do expect(calculator.send(:cohesion_penalty)) .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 split groups' do + let(:table) do + [ + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['family']), + create(:guest, affinity_group_list: ['friends']), + create(:guest, affinity_group_list: ['friends']) + ] + end + + it 'returns the sum of the penalties for each pair of guests' do + expect(calculator.send(:cohesion_penalty)).to eq(4) + end + end end end end -- 2.47.1 From c0aea2395413034533dd742ec075c49754daca2f Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 19:11:36 +0200 Subject: [PATCH 13/17] Include additional test case --- .../tables/discomfort_calculator_spec.rb | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 939ebb4..7560661 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -100,20 +100,31 @@ module Tables end end - context 'when the table contains four guests of two split groups' do + context 'when the table contains four guests of two evenly split groups' do let(:table) do [ - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['family']), - create(:guest, affinity_group_list: ['friends']), - create(:guest, affinity_group_list: ['friends']) - ] + create_list(:guest, 2, affinity_group_list: ['family']), + create_list(:guest, 2, affinity_group_list: ['friends']) + ].flatten end it 'returns the sum of the penalties for each pair of guests' do expect(calculator.send(:cohesion_penalty)).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, affinity_group_list: ['family']), + create_list(:guest, 4, affinity_group_list: ['friends']) + ].flatten + end + + it 'returns the sum of the penalties for each pair of guests' do + expect(calculator.send(:cohesion_penalty)).to eq(8) + end + end end end end -- 2.47.1 From 0836a9149883760a6f7e5f896fe6bc77386d2f9e Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 19:13:47 +0200 Subject: [PATCH 14/17] Include additional test case --- spec/services/tables/discomfort_calculator_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 7560661..28e52fe 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -125,6 +125,20 @@ module Tables expect(calculator.send(:cohesion_penalty)).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, affinity_group_list: ['family']), + create_list(:guest, 2, affinity_group_list: ['friends']), + create_list(:guest, 2, affinity_group_list: ['work']) + ].flatten + end + + it 'returns the sum of the penalties for each pair of guests' do + expect(calculator.send(:cohesion_penalty)).to eq(4 * 1 + 4 * Rational(1, 2) + 4 * Rational(2, 3)) + end + end end end end -- 2.47.1 From 41589af738f4608eac8884af46aad9f367ff9e6a Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 19:17:03 +0200 Subject: [PATCH 15/17] Add test with a table with three uneven groups --- spec/services/tables/discomfort_calculator_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 28e52fe..3764e7e 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -139,6 +139,20 @@ module Tables expect(calculator.send(:cohesion_penalty)).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, affinity_group_list: ['family']), + create_list(:guest, 2, affinity_group_list: ['friends']), + create_list(:guest, 1, affinity_group_list: ['work']) + ].flatten + end + + it 'returns the sum of the penalties for each pair of guests' do + expect(calculator.send(:cohesion_penalty)).to eq(6 * 1 + 2 * Rational(1, 2) + 3 * Rational(2, 3)) + end + end end end end -- 2.47.1 From 3971fab1965571861c78f090865b7b44579e33ac Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 19:24:29 +0200 Subject: [PATCH 16/17] Refactor implementation to make it more efficient --- app/services/tables/discomfort_calculator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb index e1b413d..69214bb 100644 --- a/app/services/tables/discomfort_calculator.rb +++ b/app/services/tables/discomfort_calculator.rb @@ -12,13 +12,13 @@ module Tables private def cohesion_penalty - table.map { |guest| guest.affinity_group_list.first }.combination(2).sum do |a, b| + table.map { |guest| guest.affinity_group_list.first }.tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)| distance = AffinityGroupsHierarchy.instance.distance(a, b) - next 1 if distance.nil? + next count_a * count_b if distance.nil? next 0 if distance.zero? - Rational(distance, distance + 1) + count_a * count_b * Rational(distance, distance + 1) end end end -- 2.47.1 From 95237165e1ee27e253f6793bc73b59cb00b1cf34 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Thu, 1 Aug 2024 20:22:51 +0200 Subject: [PATCH 17/17] Move VNS implementation to the project --- Gemfile | 1 - Gemfile.lock | 9 ------ app/services/vns/engine.rb | 48 ++++++++++++++++++++++++++++++ config/initializers/inflections.rb | 6 ++-- 4 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 app/services/vns/engine.rb diff --git a/Gemfile b/Gemfile index 478d4be..45a5913 100644 --- a/Gemfile +++ b/Gemfile @@ -67,5 +67,4 @@ end gem "money" gem 'acts-as-taggable-on' -gem "vns", path: "../vns" gem "rubytree" diff --git a/Gemfile.lock b/Gemfile.lock index a571f8a..5df9abb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,3 @@ -PATH - remote: ../vns - specs: - vns (0.4.0.pre.rc.1) - activesupport - GEM remote: https://rubygems.org/ specs: @@ -106,8 +100,6 @@ GEM factory_bot (~> 6.4) railties (>= 5.0.0) faker (3.1.1) - faker (3.4.2) - i18n (>= 1.8.11, < 2) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.5) @@ -293,7 +285,6 @@ DEPENDENCIES stimulus-rails turbo-rails tzinfo-data - vns! web-console RUBY VERSION diff --git a/app/services/vns/engine.rb b/app/services/vns/engine.rb new file mode 100644 index 0000000..5c260aa --- /dev/null +++ b/app/services/vns/engine.rb @@ -0,0 +1,48 @@ +module VNS + class Engine + def target_function(&function) + @target_function = function + end + + def add_perturbation(klass) + @perturbations ||= Set.new + @perturbations << klass + end + + attr_writer :initial_solution + + def run + raise 'No target function defined' unless @target_function + raise 'No perturbations defined' unless @perturbations + raise 'No initial solution defined' unless @initial_solution + + @best_solution = @initial_solution + @best_score = @target_function.call(@best_solution) + + puts "Initial score: #{@best_score.to_f}" + + @perturbations.each do |perturbation| + puts "Running perturbation: #{perturbation.name}" + optimize(perturbation.new(@best_solution)) + end + + @best_solution + end + + private + + def optimize(perturbation) + perturbation.each do |alternative_solution| + score = @target_function.call(alternative_solution) + next if score >= @best_score + + @best_solution = alternative_solution.deep_dup + @best_score = score + + puts "New lowest score: #{@best_score.to_f}" + + return optimize(perturbation.class.new(@best_solution)) + end + end + end +end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 3860f65..157a851 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -11,6 +11,6 @@ # end # These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym "RESTful" -# end +ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym 'VNS' +end -- 2.47.1