I encountered the following problem today:
I have to sort items into slots, based on sizes, so that the slots are most full. If multiple solutions exist to this, then I have to take into account the achieved priority score of the sort. (So there are kind of 2 goal functions).
I came up with a backtracking solution, which takes the items (in ascending order by size) and the slots (in ascending order by size), and backtrack through them. Basically what we return is for each item, the index of the slot it should go in. There is an added candidate for each item, which is -1, as a non-possible index, meaning the item is not put in any of the slots.
It finds a solution perfectly, that fills up the slots optimally, but sadly the priority point system is somehow broken.
internal class Slot
{
internal int Size;
}
internal class Item
{
internal int Size;
internal PriorityEnum Priority;
//1 - Critical
//2 - High
//3 - Medium
//4 - Low
}
internal class SlotFillBackTrack {
private int LvlNum;
private int[] Candidates;
private Item[] UpForSortItems;
private Slot[] slots;
internal SlotFillBackTrack(Item[] items, Slot[] slots) {
LvlNum = items.Length;
Candidates = new int[slots.Length + 1];
this.slots = slots;
UpForSortItems = items;
for (int i = 0; i < Candidates.Length; i++) {
Candidates[i] = i - 1;
}
}
internal int[] StartSearch() {
if (LvlNum > 0) {
bool found = false;
List < int > Current = new List < int > ();
int[] Opt = new int[LvlNum];
BacktrackOpt(ref Current, ref found, ref Opt);
if (found) {
return Opt;
}
throw new Exception("No solution");
}
throw new Exception("Nothing to sort!");
}
private void BacktrackOpt(ref List < int > Current, ref bool found, ref int[] Opt) {
if (Current.Count == LvlNum) {
if (!found) {
Current.CopyTo(Opt, 0);
found = true;
} else if (LoadingScore(Current.ToArray()) > LoadingScore(Opt)) {
Current.CopyTo(Opt, 0);
} else if (LoadingScore(Current.ToArray()) == LoadingScore(Opt) &&
PriorityScore(Current.ToArray()) > PriorityScore(Opt)) {
Current.CopyTo(Opt, 0);
}
return;
}
for (int i = 0; i < Candidates.Length; i++) {
if (IsValidMove(Current.Count, Candidates[i], Current)) {
Current.Add(Candidates[i]);
BacktrackOpt(ref Current, ref found, ref Opt);
Current.Remove(Candidates[i]);
}
}
}
private bool IsValidMove(int level, int candidate, List < int > alreadyChosen) {
if (candidate < 0) {
return true;
}
int emptySpaceInSlot = slots[candidate].Size;
for (int i = 0; i < alreadyChosen.Count; i++) {
if (alreadyChosen[i] == candidate) {
emptySpaceInSlot -= UpForSortItems[i].Size;
}
}
return emptySpaceInSlot >= UpForSortItems[level].Size;
}
private int LoadingScore(int[] E) {
int usedSpace = 0;
int allSpace = 0;
for (int i = 0; i < slots.Length; i++) {
allSpace += slots[i].Size;
}
for (int i = 0; i < E.Length; i++) {
if (E[i] != -1) {
usedSpace += UpForSortItems[i].Size;
}
}
return usedSpace - allSpace;
}
private int PriorityScore(int[] E) {
int sum = 0;
for (int i = 0; i < E.Length; i++) {
if (E[i] != -1) {
sum += 5 - (int) UpForSortItems[i].Priority;
}
}
return sum;
}
}
For the following inputs:
Slots:
Slot1: Size = 20
Slot2: Size = 30
Slot3: Size = 60
Items
Item1 Size = 15; Priority = 2 (High)
Item2 Size = 20; Priority = 1 (Critical)
Item3 Size = 30; Priority = 1 (Critical)
Item4 Size = 45; Priority = 2 (High)
Item5 Size = 60; Priority = 1 (Critical)
Slot (Size) | Expected Item(s) (Size, Priority) | Recieved Item(s) (Size, Priority) |
---|---|---|
Slot1 (20) | Item2 (20, 1) | Item2 (20, 1) |
Slot2 (30) | Item3 (30, 1) | Item3 (30, 1) |
Slot2 (60) | Item4 (45, 2), Item1 (15, 2) | Item5 (60, 1) |
Priorty points (see in code) | 5-1+5-1+5-2+5-2 = 14 | 5-1+5-1+5-1 = 12 |
Can someone please point me in the direction of where this is wrong? Thanks in advance.