Source/AdvancedAlgorithms/Knapsack/GetKnapsack.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Reflection;
 
namespace Knapsack
{
    [Cmdlet("Get", "Knapsack")]
    public class GetKnapsack : PSCmdlet
    {
        #region Parameters
        [Parameter(Mandatory = true)]
        public int KnapsackSize { get; set; }
 
        [Parameter(Mandatory = true)]
        public PSObject[] Items { get; set; }
 
        [Parameter(Mandatory = true)]
        public string WeightPropertyName { get; set; }
 
        [Parameter(Mandatory = false)]
        public string ValuePropertyName { get; set; }
        #endregion
 
        #region Variables
        #endregion
 
        #region Cmdlet Functions
        protected override void ProcessRecord()
        {
            #region Parameter Checks
            // Check if knapsack size is 0
            if (KnapsackSize <= 0)
            {
                WriteError(new ErrorRecord((new Exception("Knapsack size should be greater than 0.")), null, ErrorCategory.InvalidArgument, null));
                return;
            }
 
            // Check if Items is null
            if ( !(Items != null))
            {
                WriteError(new ErrorRecord((new Exception("Input items cannot be null.")), null, ErrorCategory.InvalidArgument, null));
                return;
            }
            if (Items.Length == 0)
            {
                WriteError(new ErrorRecord((new Exception("Input item collection cannot be empty.")), null, ErrorCategory.InvalidArgument, null));
                return;
            }
 
            // Check if WeightPropertyName exists
            foreach(PSObject o in Items)
            {
                try
                {
                    GetPSObjectProperty(o, WeightPropertyName);
                }
                catch (Exception e)
                {
                    WriteError(new ErrorRecord((new Exception("Could not find Weight property " + WeightPropertyName + " on all input objects.")), null, ErrorCategory.InvalidArgument, null));
                    return;
                }
            }
 
            // Check if the value property name is supplied
            if (!(ValuePropertyName != null))
                ValuePropertyName = WeightPropertyName;
            else
            {
                foreach (PSObject o in Items)
                {
                    try
                    {
                        GetPSObjectProperty(o, ValuePropertyName);
                    }
                    catch (Exception e)
                    {
                        WriteError(new ErrorRecord((new Exception("Could not find Value property " + ValuePropertyName + " on all input objects.")), null, ErrorCategory.InvalidArgument, null));
                        return;
                    }
                }
            }
            #endregion
 
            #region Knapsack
            // Get the result
            List<PSObject> result = knapsack(KnapsackSize, Items, WeightPropertyName, ValuePropertyName, Items.Length);
 
            // Return the objects to select
            foreach (PSObject o in result)
                WriteObject(o);
            #endregion
 
        }
        #endregion
 
        #region Utility Functions
        /// <summary>
        /// Calculate the knapsack selection
        /// </summary>
        /// <param name="KnapsackSize"></param>
        /// <param name="Items"></param>
        /// <param name="WeightPropertyName"></param>
        /// <param name="ValuePropertyName"></param>
        /// <param name="n"></param>
        /// <returns></returns>
        protected static List<PSObject> knapsack(int KnapsackSize, PSObject[] Items, string WeightPropertyName, string ValuePropertyName, int n)
        {
            // Create seperate variables for the names of the properties in order to handle them as case-insensitive
            string weightPropertyName = string.Empty;
            string valuePropertyName = string.Empty;
 
            // Find the exact name of the properties to use
            foreach (System.Management.Automation.PSPropertyInfo pi in Items[0].Properties)
            {
                if (pi.Name.ToLower().Equals(WeightPropertyName.ToLower()))
                {
                    weightPropertyName = pi.Name;
                }
 
                if (pi.Name.ToLower().Equals(ValuePropertyName.ToLower()))
                {
                    valuePropertyName = pi.Name;
                }
            }
 
            // Create an array of weights to make calculations easier
            List<Int64> weightsList = new List<Int64>();
 
            // Create an array of values to make calculations easier
            List<Int64> valuesList = new List<Int64>();
 
            // Populate the lists
            foreach (PSObject o in Items)
            {
                // Get the values of the properties
                Int64 w1 = Convert.ToInt64(GetPSObjectPropertyValue(o, weightPropertyName));
                Int64 v1 = Convert.ToInt64(GetPSObjectPropertyValue(o, valuePropertyName));
 
                // Add the values to the lists
                weightsList.Add(w1);
                valuesList.Add(v1);
            }
 
            #region Knapsack Algorithm
            Int64 i, w;
            Int64[,] K = new Int64[n + 1, KnapsackSize + 1];
 
            // Build table K[][] in bottom up manner
            for (i = 0; i <= n; i++)
            {
                for (w = 0; w <= KnapsackSize; w++)
                {
                    if (i == 0 || w == 0)
                        K[i, w] = 0;
                    else if (weightsList[(int)(i - 1)] <= w)
                        K[i, w] = Math.Max(valuesList[(int)(i - 1)] +
                                K[i - 1, w - (Int64)weightsList[(int)(i - 1)]], K[i - 1, w]);
                    else
                        K[i, w] = K[i - 1, w];
                }
            }
 
            // Save the optimum result
            Int64 res = K[n, KnapsackSize];
 
            // Create a list to save the selected objects
            List<PSObject> selection = new List<PSObject>();
 
            w = KnapsackSize;
            for (i = n; i > 0 && res > 0; i--)
            {
 
                // either the result comes from the top
                // (K[i-1][w]) or from (val[i-1] + K[i-1]
                // [w-wt[i-1]]) as in Knapsack table. If
                // it comes from the latter one/ it means
                // the item is included.
                if (res == K[i - 1, w])
                    continue;
                else
                {
 
                    // This item is included.
                    //Console.Write(weightsList[(int)(i - 1)] + " ");
                    selection.Add(Items[(int)(i - 1)]);
 
                    // Since this weight is included its
                    // value is deducted
                    res = res - valuesList[(int)(i - 1)];
                    w = w - weightsList[(int)(i - 1)];
                }
            }
            #endregion
 
            // Return the list of selected objects
            return selection;
        }
 
        /// <summary>
        /// Get the information regarding a property of a Powershell object
        /// </summary>
        /// <param name="o"></param>
        /// <param name="PropertyName"></param>
        /// <returns></returns>
        protected static System.Management.Automation.PSPropertyInfo GetPSObjectProperty(PSObject o, string PropertyName)
        {
            foreach (System.Management.Automation.PSPropertyInfo pi in o.Properties)
            {
                if (pi.Name.ToLower().Equals(PropertyName.ToLower()))
                {
                    return pi;
                }
            }
 
            return null;
        }
 
        /// <summary>
        /// Get the value of a specific property of a Powershell object
        /// </summary>
        /// <param name="o"></param>
        /// <param name="PropertyName"></param>
        /// <returns></returns>
        protected static object GetPSObjectPropertyValue(PSObject o, string PropertyName)
        {
            foreach (System.Management.Automation.PSPropertyInfo pi in o.Properties)
            {
                if (pi.Name.ToLower().Equals(PropertyName.ToLower()))
                {
                    return pi.Value;
                }
            }
 
            return null;
        }
        #endregion
    }
}