I feel like there is an elegant Python solution for this problem that I've been trying to solve with VBA. Can someone please help?
How can I generate a list of numbers that meet the following criteria:
- Are all integers
- The total is first split between types.
- The numbers are further split into subtypes and optimized to meet the required percentages.
- The total sum does not exceed the specified Total.
Example of this problem:
- You plan to build a maximum of 102 residential units.
- Construction type: 40% of them will be Studios and 60% will be Townhouse construction type. (Could have more or fewer types)
- There are two Priority lists for the future occupants for the apartments: Assisted and Unassisted
- The unit allocation percentages in the Assisted(A) list are required to be fully met and the Unassisted(U) list is flexible. We'd like the Unassisted(U) list to be up to 20% of the Total units if possible, but we definitely need a minimum of 80% of the Total units to be Assisted(A) units.
- The Assisted(A) units are a total of 102x80% = 81.6 units (already not an integer)
- A minimum of 10% of the Assisted(A) list units must be for elderly individuals.
- A minimum of 40% of the Assisted(A) list units must be for families.
- The remaining 50% of the Assisted(A) list units are for aspiring data scientists.
The remaining 20% of the Total units are Unassisted(U) occupancy units.
So:
- Total: 102 units.
Construction_type_categories=[.4, .6]
(102 units split into Studios and Townhouses: 40% and 60%)Assisted=[.1,.4,.5]
(80%+ of the total units are Assisted, further categorized as elderly, family, other occupancy according to the percentages in the list)- Unassisted units are the remaining units that were not part of the 80%+ of the Assisted list. (up to 20% of total units)
Result:
[4,16,12,8,7,25,19,11]
Breakdown:
- 4 Studio Units Assisted for elderly
- 16 Studio Units Assisted for families
- 12 Studio Units for other Assisted occupancy type
- 8 Studio Units (Unassisted)
- 7 Townhouse Units Assisted for elderly
- 25 Townhouse Units Assisted for families
- 19 Townhouse Units for other Assisted occupancy type
- 11 Townhouse Units (Unassisted).
I thought of first generating a preliminary array of rounded numbers and then looping through and making adjustments. It looked so tedious I've started considering generating a large matrix of numbers using numpy and filtering by the outlined criteria.
It has been time consuming to generate and optimize the numbers manually so I appreciate any help with a better solution.
import math
def is_even(x):
if x % 2 == 0:
return True
else:
return False
total=102
unassisted=.2
unassisted_unit_count= math.floor(total*unassisted)
assisted_unit_count=total- unassisted_unit_count
construction_type_categories=[.4, .6] #Must be even.
assisted_subcategories=[.1,.4,.5] #Last element here will be of least priority.
def Unit_Number_Getter(L):
if L[1]=='total_constr_type_amounts':
giventotal= total
if L[1]=='assisted_constr_type_amounts':
giventotal= assisted_unit_count
#Spliting by construction type (preliminary).
constr_type_amounts=[]
for ct in construction_type_categories:
constr_type_amounts.append(round(giventotal * ct))
#Making the unit counts even for the total construction type amounts (need to).
for p in constr_type_amounts:
if not is_even(p):
add_here=constr_type_amounts.index(p)
for f in constr_type_amounts:
if not is_even(f):
from_here= constr_type_amounts.index(f)
constr_type_amounts[add_here] +=1
constr_type_amounts[from_here] -=1
assert sum(constr_type_amounts)==giventotal
print L[1]
print(constr_type_amounts)
L[0]=constr_type_amounts
total_constr_type_amounts=0
assisted_constr_type_amounts=0
List_of_lists=[[total_constr_type_amounts,"total_constr_type_amounts"],[assisted_constr_type_amounts,"assisted_constr_type_amounts"]]
#Established the unit counts for each construction type (for Assisted and total units)
for L in List_of_lists:
Unit_Number_Getter(L)
#Getting a list of the unit counts for each assisted subcategory in each constr type.
testlist=[]
for c in List_of_lists[1][0]:
already_added=0
for a in assisted_subcategories[:-1]:
adding_now= math.ceil(c * a)
testlist.append(adding_now)
already_added+=adding_now
#^Added the priority assisted units (all but the last element).
#Now will add the last of the assisted units.
testlist.append(c-already_added)
#Now will add he least prioritized unassisted units up to the max.
Max_unassisted_units= List_of_lists[0][0][List_of_lists[1][0].index(c)]-c
testlist.append(Max_unassisted_units)
assert ((c+Max_unassisted_units)== List_of_lists[0][0][List_of_lists[1][0].index(c)])#all units present
print("Result: "+ "\n" + str(testlist))