tcollier-sommelier 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7aec6ab221e7b53607609c6a5f7b3f84945461bb
4
- data.tar.gz: a759cd1e83e4933366ae5b28d2fffb806d5c8ace
3
+ metadata.gz: b069aee84c44d4bde4c6482d81c47b8bfcf7ae65
4
+ data.tar.gz: 4f8b1044d8973656f19dd921a1e6051edd87869d
5
5
  SHA512:
6
- metadata.gz: 43f4525771fec89eb6fc7703eb0503d15439c8c78c4ae5c7867eadcaae80c28ad208cc04818074b51908231de9a06068d58b1a83f0df9c21d06b8823d54c072e
7
- data.tar.gz: c39b125857e32674f126970486399b3d9d98d47b44ab2960aade9c7cbcc920db8d6ab71a438094d1881a5ff29149c3ecfed166ae7c65d0b6b9ad306f9828b7af
6
+ metadata.gz: 459dc2eef3cddd6194f42ded392a32ed3e166759fb30b0edb4216215de9c47aef588fbcc1d51aed19ea5d6454cde51b2f6b995fce1a509977add818e73e609c7
7
+ data.tar.gz: 40fca90df92292e7f5e29810ae039094f155661f7e45a5ad04662b8a446515dedeefe98a11e282a82cb0630dfac2565f8415674449e5b48d04e5127e213dc20c
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.4.4
4
+ - 2.5.1
5
+ branches:
6
+ only: master
@@ -1,3 +1,7 @@
1
- Version 0.1.0, 2018-07-20
1
+ Version 0.2.0, 2018-07-21
2
2
  -------------------------
3
- e66dc58 Initial release with Gale-Shapely variant
3
+ fab29e5 Add rake task to apply Sommelier to a CSV
4
+
5
+ Version 0.1.0, 2018-07-21
6
+ -------------------------
7
+ b5c4925 Initial release with Gale-Shapely variant
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/tcollier/sommelier.svg?branch=master)](https://travis-ci.org/tcollier/sommelier)
2
+
1
3
  # Sommelier
2
4
 
3
5
  Sommelier provides a solution to a variant of the Stable Marriage problem in
@@ -11,7 +13,7 @@ restaurant menu and the brides-to-be are wines.
11
13
  The [Stable Marriage Problem](https://en.wikipedia.org/wiki/Stable_marriage_problem)
12
14
  is a mathematical problem that attempts to uniquely match a set of _N_ items
13
15
  (classically male suitors) with another set of _N_ items (classically females the
14
- suitors wish to marry). The final matches are referred to as proposals.
16
+ suitors wish to marry).
15
17
 
16
18
  ## Gale-Shapely Algorithm
17
19
 
@@ -23,17 +25,18 @@ this is not always practical.
23
25
 
24
26
  ## Variation
25
27
 
26
- The algorithm applied here has 2 primary differences from Gale-Shapely
28
+ The algorithm applied here has a few key differences from Gale-Shapely
27
29
 
28
- 1. Neither suitors nor suitees need to have a complete ranking of the other set.
29
- 2. The rankings are determined by a symmetrical match score. For example, the match
30
- score for Alice and Steve is the same as for Steve and Alice. Though scores are
31
- relative, so Alice's match score for Steve may be her highest scoring match, but
32
- that same score may only be the fifth highest scoring match for Steve.
30
+ 1. The number of dishes and wines do not need to be equal.
31
+ 2. Neither dishes nor wines need to have a complete ranking of the other set.
32
+ 3. The rankings are determined by a symmetrical match score. For example, the match
33
+ score for Eggplant and Cabernet is the same as for Cabernet and Eggplant. Though scores are
34
+ relative, so Eggplant's match score for Cabernet may be its highest scoring match, but
35
+ that same score may only be the fifth highest scoring match for Cabernet.
33
36
 
34
- Because of these differences, not every suitor or suitee is guaranteed to have
35
- a proposal. Swapping the suitor and suitee sets can have dramatic effects on the
36
- final set of proposals.
37
+ Because of these differences, not every dish or wine is guaranteed to be in the
38
+ pairings map. Swapping the dish and wine sets in the match catalog alter the final
39
+ set of pairings.
37
40
 
38
41
  ## Usage
39
42
 
@@ -51,16 +54,49 @@ sommelier = Sommelier.new
51
54
  sommelier.add_match('Asparagus', 'Pinot Noir', 0.366)
52
55
  sommelier.add_match('Asparagus', 'Sauvignon Blanc', 0.453)
53
56
  sommelier.add_match('Asparagus', 'Chardonnay', 0.245)
54
- sommelier.add_match('Tofu', 'Rose', 0.486)
57
+ sommelier.add_match('Tofu', 'Rosé', 0.486)
55
58
  sommelier.add_match('Tofu', 'Sauvignon Blanc', 0.304)
56
59
  sommelier.add_match('Eggplant', 'Sauvignon Blanc', 0.299)
57
60
  sommelier.add_match('Salmon', 'Sauvignon Blanc', 0.602)
58
61
  puts sommelier.pairings
59
62
  # {
60
63
  # "Salmon" => "Sauvignon Blanc",
61
- # "Tofu" => "Rose",
64
+ # "Tofu" => "Rosé",
62
65
  # "Asparagus" => "Pinot Noir"
63
66
  # }
64
67
 
65
68
  # Note: neither "Eggplant" nor "Chardonnay" were matched in the pairings map
66
69
  ```
70
+
71
+ ### CSV
72
+
73
+ This gem provides a rake task to apply the Sommelier algorithm to a CSV file.
74
+ The file must have a header row and the columns are expected to be in the following
75
+ order:
76
+
77
+ 1. `dish`
78
+ 2. `wine`
79
+ 3. `score`
80
+
81
+ Note: the header row is simply ignored, so the columns can be named anything.
82
+
83
+ Any additional columns will be ignored.
84
+
85
+ ```csv
86
+ # matches.csv
87
+ dish,wine,score
88
+ Asparagus,Pinot Noir,0.366
89
+ Asparagus,Sauvignon Blanc,0.453
90
+ Asparagus,Chardonnay,0.245
91
+ Tofu,Rosé,0.486
92
+ Tofu,Sauvignon Blanc,0.304
93
+ Eggplant,Sauvignon Blanc,0.299
94
+ Salmon,Sauvignon Blanc,0.602
95
+ ```
96
+
97
+ ```bash
98
+ rake sommelier:from_csv matches.csv
99
+ # Salmon => Sauvignon Blanc
100
+ # Tofu => Rosé
101
+ # Asparagus => Pinot Noir
102
+ ```
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'sommelier'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
@@ -1,3 +1,6 @@
1
+ require 'rake'
2
+ load 'tasks/sommelier.rake'
3
+
1
4
  require_relative 'sommelier/match_maker'
2
5
  require_relative 'sommelier/match_catalog'
3
6
 
@@ -11,14 +14,14 @@ require_relative 'sommelier/match_catalog'
11
14
  # sommelier.add_match('Asparagus', 'Pinot Noir', 0.366)
12
15
  # sommelier.add_match('Asparagus', 'Sauvignon Blanc', 0.453)
13
16
  # sommelier.add_match('Asparagus', 'Chardonnay', 0.245)
14
- # sommelier.add_match('Tofu', 'Rose', 0.486)
17
+ # sommelier.add_match('Tofu', 'Rosé', 0.486)
15
18
  # sommelier.add_match('Tofu', 'Sauvignon Blanc', 0.304)
16
19
  # sommelier.add_match('Eggplant', 'Sauvignon Blanc', 0.299)
17
20
  # sommelier.add_match('Salmon', 'Sauvignon Blanc', 0.602)
18
21
  # puts sommelier.pairings
19
22
  # # {
20
23
  # # "Salmon" => "Sauvignon Blanc",
21
- # # "Tofu" => "Rose",
24
+ # # "Tofu" => "Rosé",
22
25
  # # "Asparagus" => "Pinot Noir"
23
26
  # # }
24
27
  #
@@ -1,18 +1,22 @@
1
1
  class Sommelier
2
2
  class MatchMaker
3
+ # Decide a single dish's pairing request to accept for each wine
3
4
  class Decider
4
5
  def initialize(match_catalog)
5
6
  @match_catalog = match_catalog
6
7
  end
7
8
 
8
- # Decide a single dish's pairing request to accept for each wine
9
+ # Decide on which requested pairings to accepts (up to one per wine) and
10
+ # update the `accepted` and `reversed` input maps.
9
11
  #
10
12
  # @param requests [Hash<Object, Array<Object>>] mapping of wines to
11
- # the list of dishes that have requested pairing in the current round
13
+ # the list of dishes that have requested pairing in the current round.
12
14
  # @param accepted [Hash<Object, Object>] mapping of dishes to the wine
13
- # that has accepted its pairing request
15
+ # that has accepted its pairing request. Note: invoking this method may
16
+ # cause modifications to this object.
14
17
  # @param reversed [Hash<Object, Object>] mapping of wines to the dish
15
- # that has its pairing request accepted by wine
18
+ # that has its pairing request accepted by wine. Note: invoking this
19
+ # method may cause modifications to this object.
16
20
  def decide!(requests, accepted, reversed)
17
21
  requests.each do |wine, current_dishes|
18
22
  # Be sure to consider the full set of current dishes and potentially
@@ -1,16 +1,15 @@
1
1
  class Sommelier
2
2
  class MatchMaker
3
+ # Generate all of the requested pairings for a single round of the
4
+ # algorithm. Each dish that isn't currently locked in a pairing from a
5
+ # prior round and hasn't exhausted its list of preferred wines will
6
+ # request a pairing with the highest matching wine it has not yet
7
+ # requested.
3
8
  class Generator
4
9
  def initialize(match_catalog)
5
10
  @match_catalog = match_catalog
6
11
  end
7
12
 
8
- # Generate all of the requested pairings for a single round of the
9
- # algorithm. Each dish that isn't currently locked in a pairing from a
10
- # prior round and hasn't exhausted its list of preferred wines will
11
- # request a pairing with the highest matching wine it has not yet
12
- # requested.
13
- #
14
13
  # @param round [Integer] the round number
15
14
  # @param accepted [Hash<Object, Object>] mapping of dishes to the wine
16
15
  # that has accepted his pairing
@@ -1,3 +1,3 @@
1
1
  class Sommelier
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,20 @@
1
+ namespace :sommelier do
2
+ desc 'Apply Sommelier to a CSV with a headers row ["dish", "wine", "score"]'
3
+ task :from_csv do
4
+ unless ARGV.length == 2
5
+ raise ArgumentError, 'Expecting a file as the first argument'
6
+ end
7
+
8
+ require 'csv'
9
+ require 'sommelier'
10
+
11
+ csv = CSV.open(ARGV[1], headers: true)
12
+ sommelier = Sommelier.new
13
+ csv.each do |row|
14
+ sommelier.add_match(row[0], row[1], row[2].to_f)
15
+ end
16
+ sommelier.pairings.each do |dish, wine|
17
+ puts "#{dish} => #{wine}"
18
+ end
19
+ end
20
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcollier-sommelier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Collier
@@ -89,6 +89,7 @@ extra_rdoc_files: []
89
89
  files:
90
90
  - ".gitignore"
91
91
  - ".rspec"
92
+ - ".travis.yml"
92
93
  - CHANGELOG.txt
93
94
  - Gemfile
94
95
  - Gemfile.lock
@@ -103,6 +104,7 @@ files:
103
104
  - lib/sommelier/match_maker/generator.rb
104
105
  - lib/sommelier/preference.rb
105
106
  - lib/sommelier/version.rb
107
+ - lib/tasks/sommelier.rake
106
108
  - sommelier.gemspec
107
109
  homepage: https://github.com/tcollier/sommelier
108
110
  licenses: