/// <summary>
        /// Solves the Knapsack problem
        /// </summary>
        /// <param name="items">The items to put into the knapsack</param>
        /// <param name="maxWeight">The maximum weight the knapsack can hold</param>
        /// <returns>
        /// The items to put into the knapsack
        /// </returns>
        public IEnumerable<IItem> Solve(IEnumerable<IItem> items, long maxWeight)
        {
            IList<IItem> itemList = items.ToList();

            var smallerSolutionList = new OneDimensionalSparseArray<long>();
            var intermediateSolution = new OneDimensionalSparseArray<long>();
            var memoList = new OneDimensionalSparseArray<long>();
            var keepMatrix = new TwoDimensionalSparseMatrix<bool>();

            SolveUnboundedKnapsack(maxWeight, itemList, ref smallerSolutionList, ref intermediateSolution, ref memoList, ref keepMatrix);

            return Package(itemList, keepMatrix, maxWeight);
        }
        /// <summary>
        /// Solves the Knapsack problem
        /// </summary>
        /// <param name="items">The items to put into the knapsack</param>
        /// <param name="maxWeight">The maximum weight the knapsack can hold</param>
        /// <returns>
        /// The items to put into the knapsack
        /// </returns>
        public IEnumerable<IItem> Solve(IEnumerable<IItem> items, long maxWeight)
        {
            if (items.Any() == false)
            {
                return Enumerable.Empty<IItem>();
            }

            IList<IItem> itemList = items.ToList();

            var valueMatrix = new TwoDimensionalSparseMatrix<long>();
            var keepMatrix = new TwoDimensionalSparseMatrix<bool>();

            int itemCount = itemList.Count;

            SolveKnapsackProblem(maxWeight, itemList, valueMatrix, keepMatrix, itemCount);

            return Package(itemList, keepMatrix, maxWeight);
        }
        /// <summary>
        /// Packages the specified item list.
        /// </summary>
        /// <param name="itemList">The item list.</param>
        /// <param name="keepMatrix">The keep matrix.</param>
        /// <param name="maxWeight">The maximum weight.</param>
        /// <returns>The items packed up into a knapsack</returns>
        private static IEnumerable<IItem> Package(
			IList<IItem> itemList,
			TwoDimensionalSparseMatrix<bool> keepMatrix,
			long maxWeight)
        {
            int itemCount = itemList.Count;
            var knapsackItems = Enumerable.Empty<IItem>();
            long upperBound = maxWeight;
            for (int i = itemCount; i > 0; i--)
            {
                if (keepMatrix[i, upperBound])
                {
                    IItem currentItem = itemList[i - 1];
                    knapsackItems = knapsackItems.Append(currentItem);
                    upperBound -= currentItem.Weight;
                }
            }

            return knapsackItems;
        }
        /// <summary>
        /// Solves the knapsack problem.
        /// </summary>
        /// <param name="maxWeight">The maximum weight.</param>
        /// <param name="itemList">The item list.</param>
        /// <param name="valueMatrix">The value matrix.</param>
        /// <param name="keepMatrix">The keep matrix.</param>
        /// <param name="itemCount">The item count.</param>
        private static void SolveKnapsackProblem(
			long maxWeight, 
			IList<IItem> itemList, 
			TwoDimensionalSparseMatrix<long> valueMatrix, 
			TwoDimensionalSparseMatrix<bool> keepMatrix, 
			int itemCount)
        {
            for (var currentFileIndex = 1; currentFileIndex <= itemCount; currentFileIndex++)
            {
                var weightAtPreviousIndex = itemList[currentFileIndex - 1].Weight;
                var valueAtPreviousIndex = itemList[currentFileIndex - 1].Value;

                for (var currentWeight = 0; currentWeight <= maxWeight; currentWeight++)
                {
                    var newProspectiveValue = valueAtPreviousIndex + valueMatrix[currentFileIndex - 1, currentWeight - weightAtPreviousIndex];
                    var oldValue = valueMatrix[currentFileIndex - 1, currentWeight];
                    if (weightAtPreviousIndex <= currentWeight && newProspectiveValue > oldValue)
                    {
                        valueMatrix[currentFileIndex, currentWeight] = newProspectiveValue;
                        keepMatrix[currentFileIndex, currentWeight] = true;
                    }
                    else
                    {
                        valueMatrix[currentFileIndex, currentWeight] = oldValue;
                    }
                }
            }
        }
        /// <summary>
        /// Packages the specified item list.
        /// </summary>
        /// <param name="itemList">The item list.</param>
        /// <param name="keepMatrix">The keep matrix.</param>
        /// <param name="maxWeight">The maximum weight.</param>
        /// <returns>The items packed up into a knapsack</returns>
        private static IEnumerable<IItem> Package(
			IList<IItem> itemList,
			TwoDimensionalSparseMatrix<bool> keepMatrix,
			long maxWeight)
        {
            int itemCount = itemList.Count;
            var knapsackItems = Enumerable.Empty<IItem>();
            long currentWeightToCheck = maxWeight;
            while(true)
            {
                bool foundItem = false;
                for (int itemIndex = 0; itemIndex < itemList.Count; itemIndex++)
                {
                    if(keepMatrix[itemIndex, currentWeightToCheck])
                    {
                        IItem itemToTake = itemList[itemIndex];
                        knapsackItems = knapsackItems.Append(itemToTake);
                        currentWeightToCheck -= itemToTake.Weight;
                        foundItem = true;
                        break;
                    }
                }

                if (foundItem == false)
                {
                    break;
                }
            }

            return knapsackItems;
        }
        /// <summary>
        /// Solves the unbounded knapsack.
        /// </summary>
        /// <param name="maxWeight">The maximum weight.</param>
        /// <param name="itemList">The item list.</param>
        /// <param name="smallerSolutionList">The smaller solution list.</param>
        /// <param name="intermediateSolutionList">The intermediate solution list.</param>
        /// <param name="memoList">The memo list.</param>
        /// <param name="keepMatrix">The keep matrix.</param>
        private static void SolveUnboundedKnapsack(
			long maxWeight,
			IList<IItem> itemList,
			ref OneDimensionalSparseArray<long> smallerSolutionList,
			ref OneDimensionalSparseArray<long> intermediateSolutionList,
			ref OneDimensionalSparseArray<long> memoList,
			ref TwoDimensionalSparseMatrix<bool> keepMatrix)
        {
            for (long weight = 1; weight <= maxWeight; weight++)
            {
                for (int itemIndex = 0; itemIndex < itemList.Count; itemIndex++)
                {
                    IItem currentItem = itemList[itemIndex];
                    if (weight >= currentItem.Weight)
                    {
                        smallerSolutionList[itemIndex] = memoList[weight - currentItem.Weight];
                    }
                    else
                    {
                        smallerSolutionList[itemIndex] = 0;
                    }
                }

                for (int itemIndex = 0; itemIndex < itemList.Count; itemIndex++)
                {
                    IItem currentItem = itemList[itemIndex];
                    if (weight >= currentItem.Weight)
                    {
                        intermediateSolutionList[itemIndex] = smallerSolutionList[itemIndex] + currentItem.Value;
                    }
                    else
                    {
                        intermediateSolutionList[itemIndex] = 0;
                    }
                }

                long fileIndexOfMaxValue = 0;
                memoList[weight] = intermediateSolutionList[0];

                for (int itemIndex = 1; itemIndex < itemList.Count; itemIndex++)
                {
                    if (intermediateSolutionList[itemIndex] > memoList[weight])
                    {
                        memoList[weight] = intermediateSolutionList[itemIndex];
                        fileIndexOfMaxValue = itemIndex;
                    }
                }

                keepMatrix[fileIndexOfMaxValue, weight] = true;
            }
        }