I'm new to Python coding and need help to solve an unbounded knapsack problem with multiple bins (4 bins) and constraints.
The bins have weight constraints of 10.5, 10.5, 7, and 7 respectively.
Each bin can be filled with certain items only. For example, bin 0 can be filled only with items 0-9, bin 1 is can be filled only with items 10-19, and so forth.
The algorithm runs recursively until the total weight constraint of 3000 is reached.
I've modified the code available from or-tools as follows. The output is very close to what I require, but I'm having trouble with items 2 and 3.
from ortools.linear_solver import pywraplp
def main():
data = {}
data['weights'] = [
0.95,0.31,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.75,0.95,0.78,0.45,1.00,1.00,1.00,0.19,1.00,1.00,0.50,3.00,3.00,3.00,0.75,
1.25,5.00,3.00,1.00,3.00,1.00,1.00,0.63,3.00,1.00,0.55,1.00,1.00,0.75,0.67,0.38
]
data['values'] = [
0.8554,0.7855,0.7406,0.7282,0.7855,0.8653,0.8529,0.9177,0.8329,0.9077,0.7706,0.8928,0.8354,0.9077,0.8379,0.8055,0.8778,
0.8778,0.9252,0.9501,0.9451,0.9377,0.9252,0.9102,0.9252,0.9476,0.9426,0.9476,0.9377,0.9302,0.9401,0.8579,0.8678,0.8903,
0.8853,0.8105,0.9077,0.9152,0.9152,0.8579
]
assert len(data['weights']) == len(data['values'])
data['num_items'] = len(data['weights'])
data['all_items'] = range(data['num_items'])
data['bin_capacities'] = [10.5, 10.5, 7, 7]
data['num_bins'] = len(data['bin_capacities'])
data['all_bins'] = range(data['num_bins'])
# Create the mip solver with the SCIP backend.
solver = pywraplp.Solver.CreateSolver('SCIP')
if solver is None:
print('SCIP solver unavailable.')
return
# Variables.
# x[i, b] = 1 if item i is packed in bin b.
x = {}
for i in data['all_items']:
for b in data['all_bins']:
x[i, b] = solver.BoolVar(f'x_{i}_{b}')
# Constraints.
# Each item is assigned to at most one bin.
for i in data['all_items']:
solver.Add(sum(x[i, b] for b in data['all_bins']) <= 1)
# The amount packed in each bin cannot exceed its capacity.
for b in data['all_bins']:
solver.Add(
sum(x[i, b] * data['weights'][i]
for i in data['all_items']) <= data['bin_capacities'][b])
# Objective.
# Maximize total value of packed items.
objective = solver.Objective()
for i in data['all_items']:
for b in data['all_bins']:
objective.SetCoefficient(x[i, b], data['values'][i])
objective.SetMaximization()
status = solver.Solve()
if status == pywraplp.Solver.OPTIMAL:
print(f'Total packed value: {objective.Value()}')
total_weight = 0
for b in data['all_bins']:
print(f'Bin {b}')
bin_weight = 0
bin_value = 0
for i in data['all_items']:
if x[i, b].solution_value() > 0:
print(
f"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}"
)
bin_weight += data['weights'][i]
bin_value += data['values'][i]
print(f'Packed bin weight: {bin_weight}')
print(f'Packed bin value: {bin_value}\n')
total_weight += bin_weight
print(f'Total packed weight: {total_weight}')
else:
print('The problem does not have an optimal solution.')
if __name__ == '__main__':
main()
Related
I have a set i of customers and a set j of facilities. I have two binary variables: y ij which is 1 if client i is served by a primary facility, 0 otherwise; b ij is 1 if client i is served by a backup facility, 0 otherwise.
Given the starting matrix d:
-I must set y[i,j] = 1 based on the minimum distance of each row in the matrix (and this I have done);
I have to fix b[i,j] = 1 according to the second minimum distance of each row in the matrix (I don't know how to do this. I wrote max, but I don't have to do that). I've tried removing the first minimum from each row with the various pop, deleteat, splice, etc, but the solver gives me an error.
using JuMP
using Gurobi
using DelimitedFiles
import Random
import LinearAlgebra
import Plots
n = 3
m = 5
model = Model(Gurobi.Optimizer);
#variable(model, y[1:m,1:n] >= 0, Bin);
#variable(model, b[1:m,1:n] >= 0, Bin);
d = [
[80 20 40]
[71 55 24]
[56 47 81]
[10 20 30]
[31 41 21]
];
#PRIMARY ASSIGNMENTS
# 1) For each customer find the minimum d i-j and its position in matrix and create a vector V composed by all d i-j just founded
V = [];
for i = 1:m;
c = findmin(d[i,j] for j = 1:n);
push!(V,[c[1] ,c[2], i]);
end
println(V)
# 2) Sort vector's evelements from the smallest to the largest
S = sort(V)
println(S)
for i = 1:m
println(S[i][2])
println(S[i][3])
end
# 3) Fix primary assingnments for the first 50% of customers
for i = 1:3
fix(y[S[i][3], S[i][2]], 1.0, force = true);
end
# SECONDARY ASSIGNMENTS
# 1) For each customer find the second minimum d i-j and its position in matrix and create a vector W composed by all d i-j just founded
W = [];
for i = 1:m;
f = findmax(d[i,j] for j = 1:n);
push!(W,[f[1] ,f[2], i]);
end
println(W)
# 2) Sort vector's elements from the smallest to the largest
T = sort(W)
println(T)
for i = 1:3
println(T[i][2])
println(T[i][3])
end
# 3) Fix secondary assingnments for the first 50% of customers
for i = 1:3
fix(b[T[i][3], T[i][2]], 1.0, force = true);
end
optimize!(model)
I tried to find for each line the second minimum, but I could not.
I had an interview and couldn't think a clear/best solution for this problem.
Given 2 numbers A and B and we need to convert a number A to B with minimum number of the following operations:
Subtract 1
Add 1
Multiply 2
Divide 2
Multiply 3
Divide 3
For e.g. : if a=3 and b=7, the program should output 2.
1st operation : *2 -> 3*2 = 6.
2nd operation : +1 -> 6 + 1 =7.
For e.g. : if a=10 and b=60, the program should output 2.
1st operation: *2 -> 10*2 = 20.
2nd operation: *3 -> 20*3 = 60
As we can Change m (10) to n (60) after 2 operations, the answer is 2.
Tried to use dynamic programming and recursion approach but to no avail. Any tips?
As mentioned in other answers, this can be approached using BFS in a graph whose nodes correspond to numbers and whose edges correspond to operations.
Interestingly, sometimes, optimal paths need to contain quite large numbers (larger than 3 * max(A, B)).
Below is an example of an optimal paths with such large numbers within it:
a = 82, b = 73
optimal path:
[82, 164, 328, 656, 657, 219, 73] (6 operations)
optimal path if paths with values larger than 3 * max(a, b) are discarded:
[82, 81, 162, 54, 108, 216, 72, 73] (7 operations)
Below is a python implementation of this BFS solution:
def solve(a, b, max_n=None):
# the bfs queue
queue = []
# length[i] = length of the shortest
# path to get from `a' to `i'
length = {}
# previous[i] = previous value reached
# in the shortest path from `a' to `i'
previous = {}
# node with value `a' is the first in the path
queue.append(a)
length[a] = 0
previous[a] = None
while True:
val = queue.pop(0)
# add an element to the queue (if it was not
# already visited, and eventually not above
# some limit)
def try_add(next_val):
if max_n is not None and next_val > max_n:
return
if next_val in length:
return
queue.append(next_val)
length[next_val] = length[val] + 1
previous[next_val] = val
try_add(val + 1)
try_add(val - 1)
try_add(val * 2)
if val % 2 == 0:
try_add(val // 2)
try_add(val * 3)
if val % 3 == 0:
try_add(val // 3)
# check whether we already have a solution
if b in length:
break
path = [b]
while True:
if path[-1] == a:
break
else:
path.append(previous[path[-1]])
path.reverse()
return path
if __name__ == '__main__':
a = 82
b = 73
path = solve(a, b)
print(len(path), ': ', path)
path = solve(a, b, 3 * max(a, b))
print(len(path), ': ', path)
Treat numbers as nodes of a graph, and operations as edges. Use BFS to find the shortest path from A to B.
I think you can cap the nodes at 3 times the absolute value of A and B, to minimize the number of steps, but this is not necessary.
The space and time complexity is proportional to the answer, e.g. if the answer is 2, in the worst case we have to visit 6*2=12 nodes.
Here's a BFS Javascript solution:
const findPath = (ops) => (A, B) => {
const queue = new Set() .add ( [A, []] )
const paths = new Map()
while (queue .size !== 0 && !paths .has (B)) {
const next = [...queue] [0]
const [n, p] = next
ops.forEach((fn) => {
const m = fn(n);
if (Number.isInteger(m)) {
if (!paths.has(m)) {
queue.add([m, [...p, n]])
paths.set(m, [...p, n])
}
queue.delete(next)
}
})
}
return paths.get(B)
}
const ops = [n => n + 1, n => n - 1, n => 2 * n, n => 3 * n, n => n / 2, n => n / 3]
console .log (
findPath (ops) (82, 73)
)
We keep a queue of numbers still to process and a dictionary recording the paths for each number found, and keep testing them until the queue is empty (won't happen with these operations, but others might let us drain it) or we've found our target. For each number we run each operation and for integer results add it to our structures if it's not already found.
There is nothing in here to attempt to stop a chain from spiraling out of control. It's not clear how we would do that. And it would clearly be possible with different operations: if we had, say, add 2, subtract 2, and double, we'd never be able to get from 2 to 3. This algorithm would never stop.
While this could of course be converted to a recursive algorithm, the naive recursion is not likely to succeed as it would work depth-first and usually miss the value and never halt.
I had this problem for an entry test for a job. I did not pass the test. I am disguising the question in deference to the company.
Imagine you have N number of people in a park of A X B space. If a person has no other person within 50 feet, he enjoys his privacy. Otherwise, his personal space is violated. Given a set of (x, y), how many people will have their space violated?
For example, give this list in Python:
people = [(0,0), (1,1), (1000, 1000)]
We would find 2 people who are having their space violated: 1, 2.
We don't need to find all sets of people; just the total number of unique people.
You can't use a brute method to solve the problem. In other words, you can't use a simple array within an array.
I have been working on this problem off and on for a few weeks, and although I have gotten a solution faster than n^2, have not come up with a problem that scales.
I think the only correct way to solve this problem is by using Fortune's algorithm?
Here's what I have in Python (not using Fortune's algorithm):
import math
import random
random.seed(1) # Setting random number generator seed for repeatability
TEST = True
NUM_PEOPLE = 10000
PARK_SIZE = 128000 # Meters.
CONFLICT_RADIUS = 500 # Meters.
def _get_distance(x1, y1, x2, y2):
"""
require: x1, y1, x2, y2: all integers
return: a distance as a float
"""
distance = math.sqrt(math.pow((x1 - x2), 2) + math.pow((y1 - y2),2))
return distance
def check_real_distance(people1, people2, conflict_radius):
"""
determine if two people are too close
"""
if people2[1] - people1[1] > conflict_radius:
return False
d = _get_distance(people1[0], people1[1], people2[0], people2[1])
if d >= conflict_radius:
return False
return True
def check_for_conflicts(peoples, conflict_radius):
# sort people
def sort_func1(the_tuple):
return the_tuple[0]
_peoples = []
index = 0
for people in peoples:
_peoples.append((people[0], people[1], index))
index += 1
peoples = _peoples
peoples = sorted(peoples, key = sort_func1)
conflicts_dict = {}
i = 0
# use a type of sweep strategy
while i < len(peoples) - 1:
x_len = peoples[i + 1][0] - peoples[i][0]
conflict = False
conflicts_list =[peoples[i]]
j = i + 1
while x_len <= conflict_radius and j < len(peoples):
x_len = peoples[j][0] - peoples[i][0]
conflict = check_real_distance(peoples[i], peoples[j], conflict_radius)
if conflict:
people1 = peoples[i][2]
people2 = peoples[j][2]
conflicts_dict[people1] = True
conflicts_dict[people2] = True
j += 1
i += 1
return len(conflicts_dict.keys())
def gen_coord():
return int(random.random() * PARK_SIZE)
if __name__ == '__main__':
people_positions = [[gen_coord(), gen_coord()] for i in range(NUM_PEOPLE)]
conflicts = check_for_conflicts(people_positions, CONFLICT_RADIUS)
print("people in conflict: {}".format(conflicts))
As you can see from the comments, there's lots of approaches to this problem. In an interview situation you'd probably want to list as many as you can and say what the strengths and weaknesses of each one are.
For the problem as stated, where you have a fixed radius, the simplest approach is probably rounding and hashing. k-d trees and the like are powerful data structures, but they're also quite complex and if you don't need to repeatedly query them or add and remove objects they might be overkill for this. Hashing can achieve linear time, versus spatial trees which are n log n, although it might depend on the distribution of points.
To understand hashing and rounding, just think of it as partitioning your space up into a grid of squares with sides of length equal to the radius you want to check against. Each square is given it's own "zip code" which you can use as a hash key to store values in that square. You can compute the zip code of a point by dividing the x and y co-ordinates by the radius, and rounding down, like this:
def get_zip_code(x, y, radius):
return str(int(math.floor(x/radius))) + "_" + str(int(math.floor(y/radius)))
I'm using strings because it's simple, but you can use anything as long as you generate a unique zip code for each square.
Create a dictionary, where the keys are the zip codes, and the values are lists of all the people in that zip code. To check for conflicts, add the people one at a time, and before adding each one, test for conflicts with all the people in the same zip code, and the zip code's 8 neighbours. I've reused your method for keeping track of conflicts:
def check_for_conflicts(peoples, conflict_radius):
index = 0
d = {}
conflicts_dict = {}
for person in peoples:
# check for conflicts with people in this person's zip code
# and neighbouring zip codes:
for offset_x in range(-1, 2):
for offset_y in range(-1, 2):
offset_zip_code = get_zip_code(person[0] + (offset_x * conflict_radius), person[1] + (offset_y * conflict_radius), conflict_radius)
if offset_zip_code in d:
# get a list of people in this zip:
other_people = d[offset_zip_code]
# check for conflicts with each of them:
for other_person in other_people:
conflict = check_real_distance(person, other_person, conflict_radius)
if conflict:
people1 = index
people2 = other_person[2]
conflicts_dict[people1] = True
conflicts_dict[people2] = True
# add the new person to their zip code
zip_code = get_zip_code(person[0], person[1], conflict_radius)
if not zip_code in d:
d[zip_code] = []
d[zip_code].append([person[0], person[1], index])
index += 1
return len(conflicts_dict.keys())
The time complexity of this depends on a couple of things. If you increase the number of people, but don't increase the size of the space you are distributing them in, then it will be O(N2) because the number of conflicts is going to increase quadratically and you have to count them all. However if you increase the space along with the number of people, so that the density is the same, it will be closer to O(N).
If you're just counting unique people, you can keep a count if how many people in each zip code have at least 1 conflict. If its equal to everyone in the zip code, you can early out of the loop that checks for conflicts in a given zip after the first conflict with the new person, since no more uniques will be found. You could also loop through twice, adding all people on the first loop, and testing on the second, breaking out of the loop when you find the first conflict for each person.
You can see this topcoder link and section 'Closest pair'. You can modify the closest pair algorithm so that the distance h is always 50.
So , what you basically do is ,
Sort the people by X coordinate
Sweep from left to right.
Keep a balanced binary tree and keep all the points within 50 radii in the binary tree. The key of the binary tree would be the Y coordinates of the point
Select the points with Y-50 and Y+50 , this can be done with the binary tree in lg(n) time.
So the overall complexity becomes nlg(n)
Be sure to mark the points you find to skip those points in the future.
You can use set in C++ as the binary tree.But I couldn't find if python set supports range query or upper_bound and lower_bound.If someone knows , please point that out in the comments.
Here's my solution to this interesting problem:
from math import sqrt
import math
import random
class Person():
def __init__(self, x, y, conflict_radius=500):
self.position = [x, y]
self.valid = True
self.radius = conflict_radius**2
def validate_people(self, people):
P0 = self.position
for p in reversed(people):
P1 = p.position
dx = P1[0] - P0[0]
dy = P1[1] - P0[1]
dx2 = (dx * dx)
if dx2 > self.radius:
break
dy2 = (dy * dy)
d = dx2 + dy2
if d <= self.radius:
self.valid = False
p.valid = False
def __str__(self):
p = self.position
return "{0}:{1} - {2}".format(p[0], p[1], self.valid)
class Park():
def __init__(self, num_people=10000, park_size=128000):
random.seed(1)
self.num_people = num_people
self.park_size = park_size
def gen_coord(self):
return int(random.random() * self.park_size)
def generate(self):
return [[self.gen_coord(), self.gen_coord()] for i in range(self.num_people)]
def naive_solution(data):
sorted_data = sorted(data, key=lambda x: x[0])
len_sorted_data = len(sorted_data)
result = []
for index, pos in enumerate(sorted_data):
print "{0}/{1} - {2}".format(index, len_sorted_data, len(result))
p = Person(pos[0], pos[1])
p.validate_people(result)
result.append(p)
return result
if __name__ == '__main__':
people_positions = Park().generate()
with_conflicts = len(filter(lambda x: x.valid, naive_solution(people_positions)))
without_conflicts = len(filter(lambda x: not x.valid, naive_solution(people_positions)))
print("people with conflicts: {}".format(with_conflicts))
print("people without conflicts: {}".format(without_conflicts))
I'm sure the code can be still optimized further
I found a relatively solution to the problem. Sort the list of coordinates by the X value. Then look at each X value, one at a time. Sweep right, checking the position with the next position, until the end of the sweep area is reached (500 meters), or a conflict is found.
If no conflict is found, sweep left in the same manner. This method avoids unnecessary checks. For example, if there are 1,000,000 people in the park, then all of them will be in conflict. The algorithm will only check each person one time: once a conflict is found the search stops.
My time seems to be O(N).
Here is the code:
import math
import random
random.seed(1) # Setting random number generator seed for repeatability
NUM_PEOPLE = 10000
PARK_SIZE = 128000 # Meters.
CONFLICT_RADIUS = 500 # Meters.
check_real_distance = lambda conflict_radius, people1, people2: people2[1] - people1[1] <= conflict_radius \
and math.pow(people1[0] - people2[0], 2) + math.pow(people1[1] - people2[1], 2) <= math.pow(conflict_radius, 2)
def check_for_conflicts(peoples, conflict_radius):
peoples.sort(key = lambda x: x[0])
conflicts_dict = {}
i = 0
num_checks = 0
# use a type of sweep strategy
while i < len(peoples) :
conflict = False
j = i + 1
#sweep right
while j < len(peoples) and peoples[j][0] - peoples[i][0] <= conflict_radius \
and not conflict and not conflicts_dict.get(i):
num_checks += 1
conflict = check_real_distance(conflict_radius, peoples[i], peoples[j])
if conflict:
conflicts_dict[i] = True
conflicts_dict[j] = True
j += 1
j = i - 1
#sweep left
while j >= 0 and peoples[i][0] - peoples[j][0] <= conflict_radius \
and not conflict and not conflicts_dict.get(i):
num_checks += 1
conflict = check_real_distance(conflict_radius, peoples[j], peoples[i])
if conflict:
conflicts_dict[i] = True
conflicts_dict[j] = True
j -= 1
i += 1
print("num checks is {0}".format(num_checks))
print("num checks per size is is {0}".format(num_checks/ NUM_PEOPLE))
return len(conflicts_dict.keys())
def gen_coord():
return int(random.random() * PARK_SIZE)
if __name__ == '__main__':
people_positions = [[gen_coord(), gen_coord()] for i in range(NUM_PEOPLE)]
conflicts = check_for_conflicts(people_positions, CONFLICT_RADIUS)
print("people in conflict: {}".format(conflicts))
I cam up with an answer that seems to take O(N) time. The strategy is to sort the array by X values. For each X value, sweep left until a conflict is found, or the distance exceeds the conflict distance (500 M). If no conflict is found, sweep left in the same manner. With this technique, you limit the amount of searching.
Here is the code:
import math
import random
random.seed(1) # Setting random number generator seed for repeatability
NUM_PEOPLE = 10000
PARK_SIZE = 128000 # Meters.
CONFLICT_RADIUS = 500 # Meters.
check_real_distance = lambda conflict_radius, people1, people2: people2[1] - people1[1] <= conflict_radius \
and math.pow(people1[0] - people2[0], 2) + math.pow(people1[1] - people2[1], 2) <= math.pow(conflict_radius, 2)
def check_for_conflicts(peoples, conflict_radius):
peoples.sort(key = lambda x: x[0])
conflicts_dict = {}
i = 0
num_checks = 0
# use a type of sweep strategy
while i < len(peoples) :
conflict = False
j = i + 1
#sweep right
while j < len(peoples) and peoples[j][0] - peoples[i][0] <= conflict_radius \
and not conflict and not conflicts_dict.get(i):
num_checks += 1
conflict = check_real_distance(conflict_radius, peoples[i], peoples[j])
if conflict:
conflicts_dict[i] = True
conflicts_dict[j] = True
j += 1
j = i - 1
#sweep left
while j >= 0 and peoples[i][0] - peoples[j][0] <= conflict_radius \
and not conflict and not conflicts_dict.get(i):
num_checks += 1
conflict = check_real_distance(conflict_radius, peoples[j], peoples[i])
if conflict:
conflicts_dict[i] = True
conflicts_dict[j] = True
j -= 1
i += 1
print("num checks is {0}".format(num_checks))
print("num checks per size is is {0}".format(num_checks/ NUM_PEOPLE))
return len(conflicts_dict.keys())
def gen_coord():
return int(random.random() * PARK_SIZE)
if __name__ == '__main__':
people_positions = [[gen_coord(), gen_coord()] for i in range(NUM_PEOPLE)]
conflicts = check_for_conflicts(people_positions, CONFLICT_RADIUS)
print("people in conflict: {}".format(conflicts))
I have a 2-dimensional array which describes the distance between objects:
A B C
A 0 1 2
B 1 0 3
C 2 3 0
for example distance(A,B) = 1, distance(B,C) = 3, distance(A,C) = 2,
distance(x,y) = distance(x,y). I do not know anything more about this distance, it is not Euclides Distance or any commonly known distance function.
How to find number of groups and partition points (x,y)?
I have found solution:
D =[x][y] #two dimencion array with distances between x and y
sorted_distance = sorted_distance(D) # all values apears in D, delete duplicates and sort from max to min value
for distance in sorted_distance:
V = D.keys()
E = []
for x in V:
for y in V:
if x==y: continue
if D[x][y]<=distance:
E.append((x,y))
G = Grapth(V,E)
connected_components = get_connected_components(G)
if len(connected_components)>1: # this value could be increase if result is not rewarding
return connected_components
There are M students from N classes, A[i] is the number of students from class_i, sum(A[i]) == M. All these students will sit in a row with M seats, and there are no 2 students from the same class sit next to each other.
How many valid ways can these M students be seated in a row?
For example,
if N = 2, A = {1, 2},
the output should be 2;
if N = 2, A = {1, 3},
the output should be 0;
if N = 3, A = {1, 2, 3},
the output should be 120.
Technical Specification:
N < 47;
A[i] < 47;
sum(A) < 447;
if the output is greater than 1000000007, than output (result % 1000000007).
This solution might not be optimal, but I think it is a good start.
There are two components to this problem:
Labeling each seat to a class (X ways)
Assigning a seat to a student (Y ways)
Final answer is equal to X*Y.
Part 2 is very easy. Y = A(1)!A(2)!...*A(N)!
Calculating first part is tricky though. You can use DP to solve it. Complexity = N*A(1)A(2)...*A(N) (which is too expensive for my taste)
DP problem is:
F(a1,a2,..,an,last_letter=1) = F(a1-1,a2,..,an,last_letter!=1)+F(a1,a2-1,..,an,last_letter!=1)+...+F(a1,a2,..,an-1,last_letter!=1)
Consider the following python code:
import math
mem={}
def get_id(A, forbidden):
count = {}
for a in A:
if a<>forbidden:
n = A[a]
count[n] = count.get(n,0)+1
return frozenset( [A.get(forbidden, 0)] + count.items() )
def class_seatings(A, forbidden=None):
if sum(A.values())==1:
if forbidden in A:
return 0
else:
return 1
ssum = 0
for a in A:
if a <> forbidden:
n = A[a]
A2 = dict(A)
if n==1:
del A2[a]
else:
A2[a] -= 1
id = get_id(A2, a)
if id not in mem:
mem[id] = class_seatings(A2, a)
cs = mem[id]
ssum += cs
return ssum
def student_seatings(A):
assert all(map(lambda x: x>0, A.values()))
facts = map(lambda x: math.factorial(x), A.values())
mult = reduce(lambda x,y: x*y, facts, 1)
return mult*class_seatings(A) % 1000000007
It looks like it gets the basic cases right:
>>> student_seatings( {1:1, 2:2} )
2
>>> student_seatings( {1:1, 2:2, 3:3} )
120
>>> student_seatings( {1:1, 2:3} )
0
>>> student_seatings( {1:2, 2:2} )
8
However the basic memoization scheme using mem and get_id starts to crumble before the requirements you mention. To see this, watch the progression
mem={}
for upper in range(2,11):
A = dict( (x,x) for x in range(1,upper) )
print len(A), student_seatings(A)
print len(mem)
which yields
1 1
0
2 2
4
3 120
20
4 309312
83
5 579005048
329
6 462179000
1286
7 481882817
5004
8 678263090
19447
9 992777307
75581
Anyone care to improve it?
First notice that given a valid seating arrangement for input list A such that sum(A)=n, it is easy to compute the number of valid seating arrangements when a new class of size m arrives:
Fit the m new students in all possible n+1 valid positions (there are n old students, which means n+1 valid positions). This is the exact definition of combinations, and there are C(n+1,m) such combinations.
Then permute the m new students to get all possible valid seating arrangements.
Therefore, the arrival of the new class of size m has multiplied the number of valid seating arrangements by C(n+1,m) * m!. This suggests the following algorithm (in Python):
import math
import scipy.misc
def f(A) :
if len(A) == 2 :
a,b = A
if a == b :
# two solutions o+o+ and +o+o without order consideration, then multiply by 2! * 2! to get all possible orders within classes
return math.factorial(a) * math.factorial(b) * 2
elif abs( a - b ) == 1 :
# o+o+o without order consideration, then multiply by 2! * 3! to get all possible orders within classes
return math.factorial(a) * math.factorial(b)
else : # no solution
return 0
else : # the number of valid arrangement is multiplied by C(n+1,m) * m!
return sum( f(A[:i]+A[i+1:]) * scipy.misc.comb( sum(A)-ai + 1, ai ) * math.factorial(ai) for i, ai in enumerate(A) )
Let's check that we get the same results as the OP's examples:
f( [ 1,2 ] )
2
f( [ 1,3 ] )
0
f( [ 1, 2, 3 ] )
120.0
It works! Let's try with three classes of 30 students:
f( [ 30, 30, 30 ] )
2.6058794190003256e+115 # this is greater than the number of baryons in the universe!