-2

Say I have two colours of lollipops, and five lollipops of each colour (so ten in total), and I want to distribute these among my friends. We each submit some ranked preferences:

Ben: 1 - Orange, 2 - Green, 3 - Red

Joe: 1 - Green, 2 - Blue, 3 - Red

Tim: 1 - Orange, 2 - Red, 3 - Blue etc

Is there any software I can use that will "mimimise" the total sacrifices we have to make? (e.g. giving everybody as close to their first preference as possible)

Jack Nagy
  • 3
  • 1
  • 5
  • Did you try sum of squared errors?(assuming ben,joe and tim are equally important) or its weighted version(ben is more important for example) – huseyin tugrul buyukisik Jul 31 '16 at 21:28
  • 2
    The [help/on-topic] says very clearly that *Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam.* – Ken White Jul 31 '16 at 21:41

1 Answers1

0

This question is lacking a good model. Different formulations of the objective/losses will lead to completely different behaviour/solutions.

Here is a mixed-integer programming approach (as your problem is already np-hard):

Model A

  • First preference gives satisfaction X, second: Y, ... (9, 4, 1, 0) = inverse squared
    • This is equivalent to a least-squares solution / l2-norm solution (= bigger misses are harder penalized than smaller ones)
  • We want to maximize the total satisfaction

Effects of the model

  • No try of giving each person the same amount of lollies

Code

import math
import numpy as np
from cvxpy import *

# PROBLEM
# -------
# colors = orange, green, red, blue
n_colors = 4
n_lollies_per_color = 5
n_lollies = n_colors * n_lollies_per_color
prefs = np.asarray([[0, 1, 2, 3],
                    [1, 3, 2, 0],
                    [0, 2, 3, 1]])
n_persons = prefs.shape[0]
n_lollies_per_person = n_lollies / n_persons

# SOLVER
# ------
per_person_per_lolly_satisfaction_vector = np.asarray([[n_colors -1 - np.where(prefs[p, :] == c)[0]
                                                        for c in range(n_colors)]
                                                        for p in range(n_persons)]).reshape((3,4))
# Decision-vars
X = Int(n_persons, n_colors)

# Constraints
constraints = []
# non-zero lollies
constraints.append(X >= 0)
# exactly n_lollies_per_color are distributed
for c in range(n_colors):
  constraints.append(sum_entries(X[:,c]) == n_lollies_per_color)

# Objective
objective = Maximize(sum_entries(mul_elemwise(np.square(per_person_per_lolly_satisfaction_vector), X)))

problem = Problem(objective, constraints)
problem.solve(verbose=False)
print(problem.value)
print(np.round(X.value))  # Rows -> persons; Columns -> amount of lollies they got

Output

129.9999999837688
[[ 1.  0.  0.  0.]
 [ 0.  5.  0.  5.]
 [ 4.  0.  5.  0.]]

Observations

  • Person one only gets 1 lolly while the others get 9/10 (as expected)

Model B

  • Same as A, but we are only allowing a obtained-number-of-lollies-difference of 1 between each person -> this is hard-coded into the constraints (more powerful soft-constraints are much harder to implement -> keyword: convexity)

Effects

  • Fair distribution of lollies in regards to the amount of lollies obtained

Code Difference

Just add these constraints:

# lower and upper bound on lollies received -> at most 1 lolly less than      everyone else
for p in range(n_persons):
    constraints.append(sum_entries(X[p, :]) >= math.floor(n_lollies_per_person))
    constraints.append(sum_entries(X[p, :]) <= math.ceil(n_lollies_per_person))

Output

119.99999984107899
[[ 5.  0.  0.  1.]
 [-0.  5.  0.  2.]
 [ 0.  0.  5.  2.]]

Observations

  • We obtained a fair distribution at a cost:
    • We lost total satisfaction (120 vs. 130)
Community
  • 1
  • 1
sascha
  • 32,238
  • 6
  • 68
  • 110