Skip to content

Issue 425 - Migrate to DotNet 8 - File-scoped namespaces: Algorithms,… #430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 132 additions & 133 deletions Algorithms/Knapsack/BranchAndBoundKnapsackSolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,164 +2,163 @@
using System.Collections.Generic;
using System.Linq;

namespace Algorithms.Knapsack
namespace Algorithms.Knapsack;

/// <summary>
/// Branch and bound Knapsack solver.
/// </summary>
/// <typeparam name="T">Type of items in knapsack.</typeparam>
public class BranchAndBoundKnapsackSolver<T>
{
/// <summary>
/// Branch and bound Knapsack solver.
/// Returns the knapsack containing the items that maximize value while not exceeding weight capacity.
/// Construct a tree structure with total number of items + 1 levels, each node have two child nodes,
/// starting with a dummy item root, each following levels are associated with 1 items, construct the
/// tree in breadth first order to identify the optimal item set.
/// </summary>
/// <typeparam name="T">Type of items in knapsack.</typeparam>
public class BranchAndBoundKnapsackSolver<T>
/// <param name="items">All items to choose from.</param>
/// <param name="capacity">The maximum weight capacity of the knapsack to be filled.</param>
/// <param name="weightSelector">
/// A function that returns the value of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <param name="valueSelector">
/// A function that returns the weight of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <returns>
/// The array of items that provides the maximum value of the
/// knapsack without exceeding the specified weight <paramref name="capacity">capacity</paramref>.
/// </returns>
public T[] Solve(T[] items, int capacity, Func<T, int> weightSelector, Func<T, double> valueSelector)
{
/// <summary>
/// Returns the knapsack containing the items that maximize value while not exceeding weight capacity.
/// Construct a tree structure with total number of items + 1 levels, each node have two child nodes,
/// starting with a dummy item root, each following levels are associated with 1 items, construct the
/// tree in breadth first order to identify the optimal item set.
/// </summary>
/// <param name="items">All items to choose from.</param>
/// <param name="capacity">The maximum weight capacity of the knapsack to be filled.</param>
/// <param name="weightSelector">
/// A function that returns the value of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <param name="valueSelector">
/// A function that returns the weight of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <returns>
/// The array of items that provides the maximum value of the
/// knapsack without exceeding the specified weight <paramref name="capacity">capacity</paramref>.
/// </returns>
public T[] Solve(T[] items, int capacity, Func<T, int> weightSelector, Func<T, double> valueSelector)
{
// This is required for greedy approach in upper bound calculation to work.
items = items.OrderBy(i => valueSelector(i) / weightSelector(i)).ToArray();
// This is required for greedy approach in upper bound calculation to work.
items = items.OrderBy(i => valueSelector(i) / weightSelector(i)).ToArray();

// nodesQueue --> used to construct tree in breadth first order
Queue<BranchAndBoundNode> nodesQueue = new();
// nodesQueue --> used to construct tree in breadth first order
Queue<BranchAndBoundNode> nodesQueue = new();

// maxCumulativeValue --> maximum value while not exceeding weight capacity.
var maxCumulativeValue = 0.0;
// maxCumulativeValue --> maximum value while not exceeding weight capacity.
var maxCumulativeValue = 0.0;

// starting node, associated with a temporary created dummy item
BranchAndBoundNode root = new(level: -1, taken: false);
// starting node, associated with a temporary created dummy item
BranchAndBoundNode root = new(level: -1, taken: false);

// lastNodeOfOptimalPat --> last item in the optimal item sets identified by this algorithm
BranchAndBoundNode lastNodeOfOptimalPath = root;
// lastNodeOfOptimalPat --> last item in the optimal item sets identified by this algorithm
BranchAndBoundNode lastNodeOfOptimalPath = root;

nodesQueue.Enqueue(root);
nodesQueue.Enqueue(root);

while (nodesQueue.Count != 0)
{
// parent --> parent node which represents the previous item, may or may not be taken into the knapsack
BranchAndBoundNode parent = nodesQueue.Dequeue();

while (nodesQueue.Count != 0)
// IF it is the last level, branching cannot be performed
if (parent.Level == items.Length - 1)
{
// parent --> parent node which represents the previous item, may or may not be taken into the knapsack
BranchAndBoundNode parent = nodesQueue.Dequeue();

// IF it is the last level, branching cannot be performed
if (parent.Level == items.Length - 1)
{
continue;
}

// create a child node where the associated item is taken into the knapsack
var left = new BranchAndBoundNode(parent.Level + 1, true, parent);

// create a child node where the associated item is not taken into the knapsack
var right = new BranchAndBoundNode(parent.Level + 1, false, parent);

// Since the associated item on current level is taken for the first node,
// set the cumulative weight of first node to cumulative weight of parent node + weight of the associated item,
// set the cumulative value of first node to cumulative value of parent node + value of current level's item.
left.CumulativeWeight = parent.CumulativeWeight + weightSelector(items[left.Level]);
left.CumulativeValue = parent.CumulativeValue + valueSelector(items[left.Level]);
right.CumulativeWeight = parent.CumulativeWeight;
right.CumulativeValue = parent.CumulativeValue;

// IF cumulative weight is smaller than the weight capacity of the knapsack AND
// current cumulative value is larger then the current maxCumulativeValue, update the maxCumulativeValue
if (left.CumulativeWeight <= capacity && left.CumulativeValue > maxCumulativeValue)
{
maxCumulativeValue = left.CumulativeValue;
lastNodeOfOptimalPath = left;
}

left.UpperBound = ComputeUpperBound(left, items, capacity, weightSelector, valueSelector);
right.UpperBound = ComputeUpperBound(right, items, capacity, weightSelector, valueSelector);

// IF upperBound of this node is larger than maxCumulativeValue,
// the current path is still possible to reach or surpass the maximum value,
// add current node to nodesQueue so that nodes below it can be further explored
if (left.UpperBound > maxCumulativeValue && left.CumulativeWeight < capacity)
{
nodesQueue.Enqueue(left);
}

// Cumulative weight is the same as for parent node and < capacity
if (right.UpperBound > maxCumulativeValue)
{
nodesQueue.Enqueue(right);
}
continue;
}

return GetItemsFromPath(items, lastNodeOfOptimalPath);
}
// create a child node where the associated item is taken into the knapsack
var left = new BranchAndBoundNode(parent.Level + 1, true, parent);

// determine items taken based on the path
private static T[] GetItemsFromPath(T[] items, BranchAndBoundNode lastNodeOfPath)
{
List<T> takenItems = new();
// create a child node where the associated item is not taken into the knapsack
var right = new BranchAndBoundNode(parent.Level + 1, false, parent);

// only bogus initial node has no parent
for (var current = lastNodeOfPath; current.Parent is not null; current = current.Parent)
// Since the associated item on current level is taken for the first node,
// set the cumulative weight of first node to cumulative weight of parent node + weight of the associated item,
// set the cumulative value of first node to cumulative value of parent node + value of current level's item.
left.CumulativeWeight = parent.CumulativeWeight + weightSelector(items[left.Level]);
left.CumulativeValue = parent.CumulativeValue + valueSelector(items[left.Level]);
right.CumulativeWeight = parent.CumulativeWeight;
right.CumulativeValue = parent.CumulativeValue;

// IF cumulative weight is smaller than the weight capacity of the knapsack AND
// current cumulative value is larger then the current maxCumulativeValue, update the maxCumulativeValue
if (left.CumulativeWeight <= capacity && left.CumulativeValue > maxCumulativeValue)
{
if(current.IsTaken)
{
takenItems.Add(items[current.Level]);
}
maxCumulativeValue = left.CumulativeValue;
lastNodeOfOptimalPath = left;
}

return takenItems.ToArray();
left.UpperBound = ComputeUpperBound(left, items, capacity, weightSelector, valueSelector);
right.UpperBound = ComputeUpperBound(right, items, capacity, weightSelector, valueSelector);

// IF upperBound of this node is larger than maxCumulativeValue,
// the current path is still possible to reach or surpass the maximum value,
// add current node to nodesQueue so that nodes below it can be further explored
if (left.UpperBound > maxCumulativeValue && left.CumulativeWeight < capacity)
{
nodesQueue.Enqueue(left);
}

// Cumulative weight is the same as for parent node and < capacity
if (right.UpperBound > maxCumulativeValue)
{
nodesQueue.Enqueue(right);
}
}

/// <summary>
/// Returns the upper bound value of a given node.
/// </summary>
/// <param name="aNode">The given node.</param>
/// <param name="items">All items to choose from.</param>
/// <param name="capacity">The maximum weight capacity of the knapsack to be filled.</param>
/// <param name="weightSelector">
/// A function that returns the value of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <param name="valueSelector">
/// A function that returns the weight of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <returns>
/// upper bound value of the given <paramref name="aNode">node</paramref>.
/// </returns>
private static double ComputeUpperBound(BranchAndBoundNode aNode, T[] items, int capacity, Func<T, int> weightSelector, Func<T, double> valueSelector)
return GetItemsFromPath(items, lastNodeOfOptimalPath);
}

// determine items taken based on the path
private static T[] GetItemsFromPath(T[] items, BranchAndBoundNode lastNodeOfPath)
{
List<T> takenItems = new();

// only bogus initial node has no parent
for (var current = lastNodeOfPath; current.Parent is not null; current = current.Parent)
{
var upperBound = aNode.CumulativeValue;
var availableWeight = capacity - aNode.CumulativeWeight;
var nextLevel = aNode.Level + 1;
if(current.IsTaken)
{
takenItems.Add(items[current.Level]);
}
}

return takenItems.ToArray();
}

while (availableWeight > 0 && nextLevel < items.Length)
/// <summary>
/// Returns the upper bound value of a given node.
/// </summary>
/// <param name="aNode">The given node.</param>
/// <param name="items">All items to choose from.</param>
/// <param name="capacity">The maximum weight capacity of the knapsack to be filled.</param>
/// <param name="weightSelector">
/// A function that returns the value of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <param name="valueSelector">
/// A function that returns the weight of the specified item
/// from the <paramref name="items">items</paramref> list.
/// </param>
/// <returns>
/// upper bound value of the given <paramref name="aNode">node</paramref>.
/// </returns>
private static double ComputeUpperBound(BranchAndBoundNode aNode, T[] items, int capacity, Func<T, int> weightSelector, Func<T, double> valueSelector)
{
var upperBound = aNode.CumulativeValue;
var availableWeight = capacity - aNode.CumulativeWeight;
var nextLevel = aNode.Level + 1;

while (availableWeight > 0 && nextLevel < items.Length)
{
if (weightSelector(items[nextLevel]) <= availableWeight)
{
if (weightSelector(items[nextLevel]) <= availableWeight)
{
upperBound += valueSelector(items[nextLevel]);
availableWeight -= weightSelector(items[nextLevel]);
}
else
{
upperBound += valueSelector(items[nextLevel]) / weightSelector(items[nextLevel]) * availableWeight;
availableWeight = 0;
}

nextLevel++;
upperBound += valueSelector(items[nextLevel]);
availableWeight -= weightSelector(items[nextLevel]);
}
else
{
upperBound += valueSelector(items[nextLevel]) / weightSelector(items[nextLevel]) * availableWeight;
availableWeight = 0;
}

return upperBound;
nextLevel++;
}

return upperBound;
}
}
41 changes: 20 additions & 21 deletions Algorithms/Knapsack/BranchAndBoundNode.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
namespace Algorithms.Knapsack
namespace Algorithms.Knapsack;

public class BranchAndBoundNode
{
public class BranchAndBoundNode
{
// isTaken --> true = the item where index = level is taken, vice versa
public bool IsTaken { get; }
// isTaken --> true = the item where index = level is taken, vice versa
public bool IsTaken { get; }

// cumulativeWeight --> um of weight of item associated in each nodes starting from root to this node (only item that is taken)
public int CumulativeWeight { get; set; }
// cumulativeWeight --> um of weight of item associated in each nodes starting from root to this node (only item that is taken)
public int CumulativeWeight { get; set; }

// cumulativeValue --> sum of value of item associated in each nodes starting from root to this node (only item that is taken)
public double CumulativeValue { get; set; }
// cumulativeValue --> sum of value of item associated in each nodes starting from root to this node (only item that is taken)
public double CumulativeValue { get; set; }

// upperBound --> largest possible value after taking/not taking the item associated to this node (fractional)
public double UpperBound { get; set; }
// upperBound --> largest possible value after taking/not taking the item associated to this node (fractional)
public double UpperBound { get; set; }

// level --> level of the node in the tree structure
public int Level { get; }
// level --> level of the node in the tree structure
public int Level { get; }

// parent node
public BranchAndBoundNode? Parent { get; }
// parent node
public BranchAndBoundNode? Parent { get; }

public BranchAndBoundNode(int level, bool taken, BranchAndBoundNode? parent = null)
{
Level = level;
IsTaken = taken;
Parent = parent;
}
public BranchAndBoundNode(int level, bool taken, BranchAndBoundNode? parent = null)
{
Level = level;
IsTaken = taken;
Parent = parent;
}
}
Loading