The owner of a thriving restaurant full of reservations asked me to ** maximize sales **. We will ask the candidate reservations for one day which ones are OK and which ones are cancelled. Such problems can also be solved with Combinatorial Optimization (http://qiita.com/Tsutomu-KKE@github/items/bfbf4c185ed7004b5721).
For reservations, it is assumed that the time, number of people and unit price are known as shown below. It is assumed that one line corresponds to one reservation, visits only [number of people] at [start time], eats over [reservation time], and pays [unit price] x [number of people] to return.
td> | Start time td> | Reservation time td> | Number of people td> | Unit price td> tr> |
0 | 13 | 2 | 2 | 2000 |
1 | 11 | 1 | 3 | 2800 |
2 | 16 | 3 | 3 | 2800 |
... | ... | ... | ... | ... |
The restaurant seats up to 16 people, 4 tables for 2 people and 2 tables for 4 people.
You can also use the two-person table and the four-person table together. There are 13 ways to attach them (hereinafter referred to as table groups). (Number is table number)
[0]
[1]
[2]
[3]
[4]
[5]
[0, 1]
[1, 2]
[2, 3]
[4, 5]
[0, 1, 2]
[1, 2, 3]
[0, 1, 2, 3]
$ \ mbox {objective} $ td> | $ \ sum_i {\ sum_j {number of people_i unit price_i x_ {ij}}} $ td> | total sales < / td> tr> |
$ \ mbox {variables} $ td> | $ x_ {ij} \ in \ {0, 1 \} ~ \ forall i, j $ td> | Reserved $ Whether i $ uses table group $ j $ td> tr> |
$ \ mbox {subject to} $ td> | $ \ sum_j {x_ {ij}} \ le 1 ~ \ forall i $ td> | Any table group td> tr> |
$ Number of seats in table group j \ lt When the number of reservation i is $ td> | Number limit td> tr> | |
$x_{ij} = 0 ~ \forall i, j$ | ||
Only one set can be reserved at the same time at the same table td> tr> |
However, the meaning of the subscript is as follows.
i: Reservation j: table group s: table t: time
First, create a reservation table (a) with random numbers.
python
import numpy as np, numpy.random as rnd, pandas as pd, matplotlib.pyplot as plt
from pulp import *
def addvar(lowBound=0, count=[0], *args, **kwargs):
count[0] += 1
return LpVariable('v%d' % count[0], lowBound=lowBound, *args, **kwargs)
rnd.seed(5)
a = pd.DataFrame([(rnd.randint(10, 17), rnd.choice([1, 2, 2, 3]),
max(1, min(8, int(rnd.lognormal(1.2, 0.5)))), rnd.randint(10, 16) * 200)
for _ in range(60)], columns=['Start time', 'Reservation time', 'Number of people', 'unit price'])
cap = [2, 2, 2, 2, 4, 4] #Number of seats by table
sps = [[0], [1], [2], [3], [4], [5], [0, 1], [1, 2], [2, 3],
[4, 5], [0, 1, 2], [1, 2, 3], [0, 1, 2, 3]] #Table list by table group
ns, nt = len(sps), 19 - 10 #Number of table groups, number of times
Formulate and solve.
python
m = LpProblem(sense=LpMaximize) #Mathematical model
p = [[[] for t in range(nt)] for _ in range(6)] #Variable list by time by table
a['Var'] = [[addvar(cat=LpBinary) for j in range(ns)] for i, r in a.iterrows()]
m += lpDot(a.Number of people* a.unit price, a.Var.apply(lpSum)) #Objective function(Total sales)
for i, r in a.iterrows():
m += lpSum(r.Var) <= 1 #Any table group
for j, sp in enumerate(sps):
if sum(cap[s] for s in sp) < r.Number of people:
m += r.Var[j] == 0 #Number of people limit
for s in sp:
for t in range(r.Reservation time):
p[s][r.Start time- 10 + t].append(r.Var[j])
for s in range(6):
for t in range(nt):
if p[s][t]:
m += lpSum(p[s][t]) <= 1 #Only one group can accept reservations at the same time at the same table
m.solve()
a['Val'] = a.Var.apply(lambda v: int(value(lpDot(range(1, ns+1), v))))
print('%s %d people%.2f 10,000 yen' % (LpStatus[m.status],
sum(a[a.Val > 0].Number of people), value(m.objective) / 10000))
>>>
Optimal 8 3 people 20.440,000 yen
Sales are over 200,000. Shows the successful reservations.
python
print('Hours Number of people Price table')
for i, r in a.iterrows():
if r.Val:
print('%2d-%2d %d %d %s' % (r.Start time, r.Start time + r.Reservation time- 1,
r.Number of people, r.Number of people * r.unit price, sps[r.Val-1]))
>>>
Hours Number of people Price table
11-11 3 8400 [2, 3]
16-18 3 8400 [5]
13-13 4 11200 [4]
16-17 2 4800 [2]
16-17 4 11200 [4]
12-12 3 7200 [5]
14-15 8 20800 [4, 5]
14-14 2 4800 [2]
10-10 1 3000 [0]
16-18 2 6000 [3]
15-15 8 16000 [0, 1, 2, 3]
11-11 3 8400 [0, 1]
10-10 2 4000 [4]
11-12 4 9600 [4]
16-18 4 8000 [0, 1]
13-13 2 5600 [0]
12-12 6 13200 [1, 2, 3]
10-11 4 8000 [5]
10-10 3 6600 [1, 2]
13-13 4 10400 [5]
13-13 2 6000 [1]
13-13 4 10400 [2, 3]
14-14 2 5200 [3]
14-14 3 7200 [0, 1]
that's all
Recommended Posts