Repository: TheAlgorithms/C-Sharp Branch: master Commit: 96e2905cab7b Files: 627 Total size: 1.9 MB Directory structure: gitextract_zwg869o3/ ├── .devcontainer/ │ └── devcontainer.json ├── .editorconfig ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── pull_request_template.md │ └── workflows/ │ ├── ci.yml │ └── stale.yml ├── .gitignore ├── Algorithms/ │ ├── Algorithms.csproj │ ├── Crypto/ │ │ ├── Digests/ │ │ │ ├── AsconDigest.cs │ │ │ ├── IDigest.cs │ │ │ └── Md2Digest.cs │ │ ├── Exceptions/ │ │ │ ├── CryptoException.cs │ │ │ ├── DataLengthException.cs │ │ │ └── OutputLengthException.cs │ │ ├── Paddings/ │ │ │ ├── IBlockCipherPadding.cs │ │ │ ├── Iso10126D2Padding.cs │ │ │ ├── Iso7816D4Padding.cs │ │ │ ├── Pkcs7Padding.cs │ │ │ ├── TbcPadding.cs │ │ │ └── X932Padding.cs │ │ └── Utils/ │ │ ├── ByteEncodingUtils.cs │ │ ├── LongUtils.cs │ │ └── ValidationUtils.cs │ ├── DataCompression/ │ │ ├── BurrowsWheelerTransform.cs │ │ ├── HuffmanCompressor.cs │ │ ├── ShannonFanoCompressor.cs │ │ └── Translator.cs │ ├── Encoders/ │ │ ├── AutokeyEncorder.cs │ │ ├── BlowfishEncoder.cs │ │ ├── CaesarEncoder.cs │ │ ├── FeistelCipher.cs │ │ ├── HillEncoder.cs │ │ ├── IEncoder.cs │ │ ├── NysiisEncoder.cs │ │ ├── SoundexEncoder.cs │ │ └── VigenereEncoder.cs │ ├── Financial/ │ │ └── PresentValue.cs │ ├── GlobalUsings.cs │ ├── Graph/ │ │ ├── ArticulationPoints.cs │ │ ├── BellmanFord.cs │ │ ├── BipartiteGraph.cs │ │ ├── BreadthFirstSearch.cs │ │ ├── BreadthFirstTreeTraversal.cs │ │ ├── Bridges.cs │ │ ├── DepthFirstSearch.cs │ │ ├── Dijkstra/ │ │ │ ├── DijkstraAlgorithm.cs │ │ │ └── DistanceModel.cs │ │ ├── FloydWarshall.cs │ │ ├── IGraphSearch.cs │ │ ├── Kosaraju.cs │ │ ├── MinimumSpanningTree/ │ │ │ ├── Kruskal.cs │ │ │ └── PrimMatrix.cs │ │ ├── TarjanStronglyConnectedComponents.cs │ │ └── TopologicalSort.cs │ ├── Knapsack/ │ │ ├── BranchAndBoundKnapsackSolver.cs │ │ ├── BranchAndBoundNode.cs │ │ ├── DynamicProgrammingKnapsackSolver.cs │ │ ├── IHeuristicKnapsackSolver.cs │ │ ├── IKnapsackSolver.cs │ │ └── NaiveKnapsackSolver.cs │ ├── LinearAlgebra/ │ │ ├── Distances/ │ │ │ ├── Chebyshev.cs │ │ │ ├── Euclidean.cs │ │ │ ├── Manhattan.cs │ │ │ └── Minkowski.cs │ │ └── Eigenvalue/ │ │ └── PowerIteration.cs │ ├── MachineLearning/ │ │ ├── KNearestNeighbors.cs │ │ ├── LinearRegression.cs │ │ └── LogisticRegression.cs │ ├── ModularArithmetic/ │ │ ├── ChineseRemainderTheorem.cs │ │ ├── ExtendedEuclideanAlgorithm.cs │ │ └── ModularMultiplicativeInverse.cs │ ├── NewtonSquareRoot.cs │ ├── Numeric/ │ │ ├── Abs.cs │ │ ├── AdditionWithoutArithmetic.cs │ │ ├── AliquotSumCalculator.cs │ │ ├── AmicableNumbersChecker.cs │ │ ├── AutomorphicNumber.cs │ │ ├── BinomialCoefficient.cs │ │ ├── Ceil.cs │ │ ├── Decomposition/ │ │ │ ├── LU.cs │ │ │ └── ThinSVD.cs │ │ ├── DoubleFactorial.cs │ │ ├── EulerMethod.cs │ │ ├── Factorial.cs │ │ ├── Factorization/ │ │ │ ├── IFactorizer.cs │ │ │ └── TrialDivisionFactorizer.cs │ │ ├── Floor.cs │ │ ├── GaussJordanElimination.cs │ │ ├── GreatestCommonDivisor/ │ │ │ ├── BinaryGreatestCommonDivisorFinder.cs │ │ │ ├── EuclideanGreatestCommonDivisorFinder.cs │ │ │ └── IGreatestCommonDivisorFinder.cs │ │ ├── JosephusProblem.cs │ │ ├── KeithNumberChecker.cs │ │ ├── KrishnamurthyNumberChecker.cs │ │ ├── MillerRabinPrimalityChecker.cs │ │ ├── ModularExponentiation.cs │ │ ├── NarcissisticNumberChecker.cs │ │ ├── PerfectCubeChecker.cs │ │ ├── PerfectNumberChecker.cs │ │ ├── PerfectSquareChecker.cs │ │ ├── PrimeChecker.cs │ │ ├── Pseudoinverse/ │ │ │ └── PseudoInverse.cs │ │ ├── Relu.cs │ │ ├── RungeKuttaMethod.cs │ │ ├── Series/ │ │ │ └── Maclaurin.cs │ │ ├── Sigmoid.cs │ │ ├── SoftMax.cs │ │ ├── SumOfDigits.cs │ │ └── Tanh.cs │ ├── Other/ │ │ ├── BoyerMooreMajorityVote.cs │ │ ├── DecisionsConvolutions.cs │ │ ├── FermatPrimeChecker.cs │ │ ├── FloodFill.cs │ │ ├── GaussOptimization.cs │ │ ├── GeoLocation.cs │ │ ├── Geofence.cs │ │ ├── Geohash.cs │ │ ├── Int2Binary.cs │ │ ├── JulianEaster.cs │ │ ├── KadanesAlgorithm.cs │ │ ├── KochSnowflake.cs │ │ ├── Luhn.cs │ │ ├── Mandelbrot.cs │ │ ├── ParetoOptimization.cs │ │ ├── PollardsRhoFactorizing.cs │ │ ├── RGBHSVConversion.cs │ │ ├── SieveOfEratosthenes.cs │ │ ├── Triangulator.cs │ │ └── WelfordsVariance.cs │ ├── Problems/ │ │ ├── DynamicProgramming/ │ │ │ ├── CoinChange/ │ │ │ │ └── DynamicCoinChangeSolver.cs │ │ │ └── LevenshteinDistance/ │ │ │ └── LevenshteinDistance.cs │ │ ├── GraphColoring/ │ │ │ └── GraphColoringSolver.cs │ │ ├── JobScheduling/ │ │ │ ├── IntervalSchedulingSolver.cs │ │ │ └── Job.cs │ │ ├── KnightTour/ │ │ │ └── OpenKnightTour.cs │ │ ├── NQueens/ │ │ │ └── BacktrackingNQueensSolver.cs │ │ ├── StableMarriage/ │ │ │ ├── Accepter.cs │ │ │ ├── GaleShapley.cs │ │ │ └── Proposer.cs │ │ └── TravelingSalesman/ │ │ └── TravelingSalesmanSolver.cs │ ├── RecommenderSystem/ │ │ ├── CollaborativeFiltering.cs │ │ └── ISimilarityCalculator.cs │ ├── Search/ │ │ ├── AStar/ │ │ │ ├── AStar.cs │ │ │ ├── Node.cs │ │ │ ├── NodeState.cs │ │ │ ├── PathfindingException.cs │ │ │ ├── PriorityQueue.cs │ │ │ └── VecN.cs │ │ ├── BinarySearcher.cs │ │ ├── BoyerMoore.cs │ │ ├── FastSearcher.cs │ │ ├── FibonacciSearcher.cs │ │ ├── InterpolationSearch.cs │ │ ├── JumpSearcher.cs │ │ ├── LinearSearcher.cs │ │ └── RecursiveBinarySearcher.cs │ ├── Sequences/ │ │ ├── AllOnesSequence.cs │ │ ├── AllThreesSequence.cs │ │ ├── AllTwosSequence.cs │ │ ├── BinaryPrimeConstantSequence.cs │ │ ├── BinomialSequence.cs │ │ ├── CakeNumbersSequence.cs │ │ ├── CatalanSequence.cs │ │ ├── CentralPolygonalNumbersSequence.cs │ │ ├── CubesSequence.cs │ │ ├── DivisorsCountSequence.cs │ │ ├── EuclidNumbersSequence.cs │ │ ├── EulerTotientSequence.cs │ │ ├── FactorialSequence.cs │ │ ├── FermatNumbersSequence.cs │ │ ├── FermatPrimesSequence.cs │ │ ├── FibonacciSequence.cs │ │ ├── GolombsSequence.cs │ │ ├── ISequence.cs │ │ ├── KolakoskiSequence.cs │ │ ├── KolakoskiSequence2.cs │ │ ├── KummerNumbersSequence.cs │ │ ├── LucasNumbersBeginningAt2Sequence.cs │ │ ├── MakeChangeSequence.cs │ │ ├── MatchstickTriangleSequence.cs │ │ ├── NaturalSequence.cs │ │ ├── NegativeIntegersSequence.cs │ │ ├── NumberOfBooleanFunctionsSequence.cs │ │ ├── NumberOfPrimesByNumberOfDigitsSequence.cs │ │ ├── NumberOfPrimesByPowersOf10Sequence.cs │ │ ├── OnesCountingSequence.cs │ │ ├── PowersOf10Sequence.cs │ │ ├── PowersOf2Sequence.cs │ │ ├── PrimePiSequence.cs │ │ ├── PrimesSequence.cs │ │ ├── PrimorialNumbersSequence.cs │ │ ├── RecamansSequence.cs │ │ ├── SquaresSequence.cs │ │ ├── TetrahedralSequence.cs │ │ ├── TetranacciNumbersSequence.cs │ │ ├── ThreeNPlusOneStepsSequence.cs │ │ ├── TribonacciNumbersSequence.cs │ │ ├── VanEcksSequence.cs │ │ └── ZeroSequence.cs │ ├── Shufflers/ │ │ ├── FisherYatesShuffler.cs │ │ ├── IShuffler.cs │ │ ├── LINQShuffler.cs │ │ ├── NaiveShuffler.cs │ │ └── RecursiveShuffler.cs │ ├── Sorters/ │ │ ├── Comparison/ │ │ │ ├── BasicTimSorter.cs │ │ │ ├── BinaryInsertionSorter.cs │ │ │ ├── BogoSorter.cs │ │ │ ├── BubbleSorter.cs │ │ │ ├── CocktailSorter.cs │ │ │ ├── CombSorter.cs │ │ │ ├── CycleSorter.cs │ │ │ ├── ExchangeSorter.cs │ │ │ ├── GnomeSorter.cs │ │ │ ├── HeapSorter.cs │ │ │ ├── IComparisonSorter.cs │ │ │ ├── InsertionSorter.cs │ │ │ ├── MedianOfThreeQuickSorter.cs │ │ │ ├── MergeSorter.cs │ │ │ ├── MiddlePointQuickSorter.cs │ │ │ ├── PancakeSorter.cs │ │ │ ├── QuickSorter.cs │ │ │ ├── RandomPivotQuickSorter.cs │ │ │ ├── SelectionSorter.cs │ │ │ ├── ShellSorter.cs │ │ │ ├── TimSorter.cs │ │ │ └── TimSorterSettings.cs │ │ ├── External/ │ │ │ ├── ExternalMergeSorter.cs │ │ │ ├── IExternalSorter.cs │ │ │ ├── ISequentialStorage.cs │ │ │ ├── ISequentialStorageReader.cs │ │ │ ├── ISequentialStorageWriter.cs │ │ │ └── Storages/ │ │ │ ├── IntFileStorage.cs │ │ │ └── IntInMemoryStorage.cs │ │ ├── Integer/ │ │ │ ├── BucketSorter.cs │ │ │ ├── CountingSorter.cs │ │ │ ├── IIntegerSorter.cs │ │ │ └── RadixSorter.cs │ │ ├── String/ │ │ │ ├── IStringSorter.cs │ │ │ └── MsdRadixStringSorter.cs │ │ └── Utils/ │ │ └── GallopingStrategy.cs │ ├── Stack/ │ │ ├── BalancedParenthesesChecker.cs │ │ ├── InfixToPostfix.cs │ │ ├── NextGreaterElement.cs │ │ └── ReverseStack.cs │ └── Strings/ │ ├── GeneralStringAlgorithms.cs │ ├── ManachersAlgorithm.cs │ ├── Palindrome.cs │ ├── PatternMatching/ │ │ ├── Bitap.cs │ │ ├── BoyerMoore.cs │ │ ├── KnuthMorrisPrattSearcher.cs │ │ ├── NaiveStringSearch.cs │ │ ├── RabinKarp.cs │ │ ├── WildCardMatcher.cs │ │ └── ZblockSubstringSearch.cs │ ├── Permutation.cs │ └── Similarity/ │ ├── CosineSimilarity.cs │ ├── DamerauLevenshteinDistance.cs │ ├── HammingDistance.cs │ ├── JaccardDistance.cs │ ├── JaccardSimilarity.cs │ ├── JaroSimilarity.cs │ ├── JaroWinklerDistance.cs │ └── OptimalStringAlignment.cs ├── Algorithms.Tests/ │ ├── Algorithms.Tests.csproj │ ├── AssemblyInfo.cs │ ├── Compressors/ │ │ ├── BurrowsWheelerTransformTests.cs │ │ ├── HuffmanCompressorTests.cs │ │ ├── ShannonFanoCompressorTests.cs │ │ └── TranslatorTests.cs │ ├── Crypto/ │ │ ├── Digests/ │ │ │ ├── AsconDigestTests.cs │ │ │ └── Md2DigestTests.cs │ │ ├── Exceptions/ │ │ │ ├── CryptoExceptionTests.cs │ │ │ ├── DataLengthExceptionTests.cs │ │ │ └── OutputLengthExceptionTests.cs │ │ ├── Paddings/ │ │ │ ├── Iso10126D2PaddingTests.cs │ │ │ ├── Iso7816D4PaddingTests.cs │ │ │ ├── Pkcs7PaddingTests.cs │ │ │ ├── TbcPaddingTests.cs │ │ │ └── X932PaddingTests.cs │ │ └── Utils/ │ │ ├── ByteEncodingUtils.cs │ │ ├── LongUtilsTests.cs │ │ └── ValidationUtilsTests.cs │ ├── Encoders/ │ │ ├── AutokeyEncoderTests.cs │ │ ├── BlowfishEncoderTests.cs │ │ ├── CaesarEncoderTests.cs │ │ ├── FeistelCipherTest.cs │ │ ├── HillEnconderTests.cs │ │ ├── NysiisEncoderTests.cs │ │ ├── SoundexEncoderTest.cs │ │ └── VigenereEncoderTests.cs │ ├── Financial/ │ │ └── PresentValueTests.cs │ ├── GlobalUsings.cs │ ├── Graph/ │ │ ├── ArticulationPointsTests.cs │ │ ├── BellmanFordTests.cs │ │ ├── BipartiteGraphTests.cs │ │ ├── BreadthFirstSearchTests.cs │ │ ├── BreadthFirstTreeTraversalTests.cs │ │ ├── BridgesTests.cs │ │ ├── DepthFirstSearchTests.cs │ │ ├── Dijkstra/ │ │ │ └── DijkstraTests.cs │ │ ├── FloydWarshallTests.cs │ │ ├── KosarajuTests.cs │ │ ├── MinimumSpanningTree/ │ │ │ ├── KruskalTests.cs │ │ │ └── PrimMatrixTests.cs │ │ ├── TarjanStronglyConnectedComponentsTests.cs │ │ └── TopologicalSortTests.cs │ ├── Helpers/ │ │ ├── IntComparer.cs │ │ └── RandomHelper.cs │ ├── Knapsack/ │ │ ├── BranchAndBoundKnapsackSolverTests.cs │ │ ├── DynamicProgrammingKnapsackSolverTests.cs │ │ └── NaiveKnapsackSolverTests.cs │ ├── LinearAlgebra/ │ │ ├── Distances/ │ │ │ ├── ChebyshevTests.cs │ │ │ ├── EuclideanTests.cs │ │ │ ├── ManhattanTests.cs │ │ │ └── MinkowskiTests.cs │ │ └── Eigenvalue/ │ │ └── PowerIterationTests.cs │ ├── MachineLearning/ │ │ ├── KNearestNeighborsTests.cs │ │ ├── LinearRegressionTests.cs │ │ └── LogisticRegressionTests.cs │ ├── ModularArithmetic/ │ │ ├── ChineseRemainderTheoremTest.cs │ │ ├── ExtendedEuclideanAlgorithmTest.cs │ │ └── ModularMultiplicativeInverseTest.cs │ ├── Numeric/ │ │ ├── AbsTests.cs │ │ ├── AdditionWithoutArithmeticsTests.cs │ │ ├── AliquotSumCalculatorTests.cs │ │ ├── AmicableNumbersTest.cs │ │ ├── AutomorphicNumberTests.cs │ │ ├── BinomialCoefficientTests.cs │ │ ├── CeilTests.cs │ │ ├── Decomposition/ │ │ │ ├── LUTests.cs │ │ │ ├── MaclaurinTests.cs │ │ │ └── SVDTests.cs │ │ ├── DoubleFactorialTests.cs │ │ ├── EulerMethodTest.cs │ │ ├── FactorialTests.cs │ │ ├── Factorization/ │ │ │ └── TrialDivisionFactorizerTests.cs │ │ ├── FloorTests.cs │ │ ├── GaussJordanEliminationTests.cs │ │ ├── GreatestCommonDivisor/ │ │ │ ├── BinaryGreatestCommonDivisorFinderTests.cs │ │ │ └── EuclideanGreatestCommonDivisorFinderTests.cs │ │ ├── JosephusProblemTest.cs │ │ ├── KeithNumberTest.cs │ │ ├── KrishnamurthyNumberCheckerTests.cs │ │ ├── MillerRabinPrimalityTest.cs │ │ ├── ModularExponentiationTest.cs │ │ ├── NarcissisticNumberTest.cs │ │ ├── NewtonSquareRootTests.cs │ │ ├── PerfectCubeTests.cs │ │ ├── PerfectNumberTest.cs │ │ ├── PerfectSquareTest.cs │ │ ├── PrimeNumberTest.cs │ │ ├── PseudoInverse/ │ │ │ └── PseudoInverseTests.cs │ │ ├── ReluTest.cs │ │ ├── RungeKuttaMethodTest.cs │ │ ├── SigmoidTests.cs │ │ ├── SoftMaxTests.cs │ │ ├── SumOfDigitsTest.cs │ │ └── TanhTest.cs │ ├── Other/ │ │ ├── BoyerMooreMajorityVoteTests.cs │ │ ├── DecisionsConvolutionsTest.cs │ │ ├── FermatPrimeCheckerTests.cs │ │ ├── FloodFillTest.cs │ │ ├── GaussOptimizationTest.cs │ │ ├── GeoLocationTests.cs │ │ ├── GeofenceTests.cs │ │ ├── GeohashTests.cs │ │ ├── Int2BinaryTests.cs │ │ ├── JulianEasterTests.cs │ │ ├── KadanesAlgorithmTests.cs │ │ ├── KochSnowflakeTest.cs │ │ ├── LuhnTests.cs │ │ ├── MandelbrotTest.cs │ │ ├── ParetoOptimizationTests.cs │ │ ├── PollardsRhoFactorizingTests.cs │ │ ├── RGBHSVConversionTest.cs │ │ ├── SieveOfEratosthenesTests.cs │ │ ├── TriangulatorTests.cs │ │ └── WelfordsVarianceTest.cs │ ├── Problems/ │ │ ├── DynamicProgramming/ │ │ │ ├── CoinChange/ │ │ │ │ ├── GenerateChangesDictionaryTests.cs │ │ │ │ ├── GenerateSingleCoinChangesTests.cs │ │ │ │ ├── GetMinimalNextCoinTests.cs │ │ │ │ └── MakeCoinChangeDynamicTests.cs │ │ │ └── LevenshteinDistance/ │ │ │ └── LevenshteinDistanceTests.cs │ │ ├── GraphColoring/ │ │ │ └── GraphColoringSolverTests.cs │ │ ├── JobScheduling/ │ │ │ └── IntervalSchedulingSolverTests.cs │ │ ├── KnightTour/ │ │ │ └── OpenKnightTourTests.cs │ │ ├── NQueens/ │ │ │ └── BacktrackingNQueensSolverTests.cs │ │ ├── StableMarriage/ │ │ │ └── GaleShapleyTests.cs │ │ └── TravelingSalesman/ │ │ └── TravelingSalesmanSolverTests.cs │ ├── RecommenderSystem/ │ │ └── CollaborativeFilteringTests.cs │ ├── Search/ │ │ ├── AStarTests.cs │ │ ├── BinarySearcherTests.cs │ │ ├── BoyerMooreTests.cs │ │ ├── FastSearcherTests.cs │ │ ├── FibonacciSearcherTests.cs │ │ ├── Helper.cs │ │ ├── InterpolationSearchTests.cs │ │ ├── JumpSearcherTests.cs │ │ ├── LinearSearcherTests.cs │ │ └── RecursiveBinarySearcherTests.cs │ ├── Sequences/ │ │ ├── AllOnesSequenceTests.cs │ │ ├── AllThreesSequenceTests.cs │ │ ├── AllTwosSequenceTests.cs │ │ ├── BinaryPrimeConstantSequenceTests.cs │ │ ├── BinomialSequenceTests.cs │ │ ├── CakeNumbersSequenceTests.cs │ │ ├── CatalanSequenceTest.cs │ │ ├── CentralPolygonalNumbersSequenceTests.cs │ │ ├── CubesSequenceTests.cs │ │ ├── DivisorsCountSequenceTests.cs │ │ ├── EuclidNumbersSequenceTests.cs │ │ ├── EulerTotientSequenceTests.cs │ │ ├── FactorialSequenceTest.cs │ │ ├── FermatNumbersSequenceTests.cs │ │ ├── FermatPrimesSequenceTests.cs │ │ ├── FibonacciSequenceTests.cs │ │ ├── GolombsSequenceTests.cs │ │ ├── KolakoskiSequenceTests.cs │ │ ├── KummerNumbersSequenceTests.cs │ │ ├── LucasNumbersBeginningAt2SequenceTests.cs │ │ ├── MakeChangeSequenceTests.cs │ │ ├── MatchstickTriangleSequenceTests.cs │ │ ├── NaturalSequenceTests.cs │ │ ├── NegativeIntegersSequenceTests.cs │ │ ├── NumberOfBooleanFunctionsSequenceTests.cs │ │ ├── NumberOfPrimesByNumberOfDigitsSequenceTests.cs │ │ ├── NumberOfPrimesByPowersOf10SequenceTests.cs │ │ ├── OnesCountingSequenceTest.cs │ │ ├── PowersOf10SequenceTests.cs │ │ ├── PowersOf2SequenceTests.cs │ │ ├── PrimePiSequenceTests.cs │ │ ├── PrimesSequenceTests.cs │ │ ├── PrimorialNumbersSequenceTests.cs │ │ ├── RecamansSequenceTests.cs │ │ ├── SquaresSequenceTests.cs │ │ ├── TetrahedralSequenceTests.cs │ │ ├── TetranacciNumbersSequenceTests.cs │ │ ├── ThreeNPlusOneStepsSequenceTests.cs │ │ ├── TribonacciNumbersSequenceTests.cs │ │ ├── VanEcksSequenceTests.cs │ │ └── ZeroSequenceTests.cs │ ├── Shufflers/ │ │ ├── FisherYatesShufflerTests.cs │ │ ├── LINQShufflerTests.cs │ │ ├── NaiveShufflerTests.cs │ │ └── RecursiveShufflerTests.cs │ ├── Sorters/ │ │ ├── Comparison/ │ │ │ ├── BasicTeamSorterTests.cs │ │ │ ├── BinaryInsertionSorterTests.cs │ │ │ ├── BogoSorterTests.cs │ │ │ ├── BubbleSorterTests.cs │ │ │ ├── CocktailSorterTests.cs │ │ │ ├── CombSorterTests.cs │ │ │ ├── CycleSorterTests.cs │ │ │ ├── ExchangeSorterTests.cs │ │ │ ├── GnomeSorterTests.cs │ │ │ ├── HeapSorterTests.cs │ │ │ ├── InsertionSorterTests.cs │ │ │ ├── MedianOfThreeQuickSorterTests.cs │ │ │ ├── MergeSorterTests.cs │ │ │ ├── MiddlePointQuickSorterTests.cs │ │ │ ├── PancakeSorterTests.cs │ │ │ ├── RandomPivotQuickSorterTests.cs │ │ │ ├── SelectionSorterTests.cs │ │ │ ├── ShellSorterTests.cs │ │ │ └── TimSorterTests.cs │ │ ├── External/ │ │ │ └── ExternalMergeSorterTests.cs │ │ ├── Integer/ │ │ │ ├── BucketSorterTests.cs │ │ │ ├── CountingSorterTests.cs │ │ │ └── RadixSorterTests.cs │ │ ├── String/ │ │ │ └── MsdRadixStringSorterTests.cs │ │ └── Utils/ │ │ └── GallopingStrategyTests.cs │ ├── Stack/ │ │ ├── BalancedParenthesesCheckerTests.cs │ │ ├── InfixToPostfixTests.cs │ │ ├── NextGreaterElementTests.cs │ │ └── ReverseStackTests.cs │ └── Strings/ │ ├── GeneralStringAlgorithmsTests.cs │ ├── ManachersAlgorithmTests.cs │ ├── PalindromeTests.cs │ ├── PatternMatching/ │ │ ├── BitapTests.cs │ │ ├── BoyerMoreTests.cs │ │ ├── KnuthMorrisPrattSearcherTests.cs │ │ ├── NaiveStringSearchTests.cs │ │ ├── RabinKarpTests.cs │ │ ├── WildCardMatcherTests.cs │ │ └── ZblockSubstringSearchTest.cs │ ├── PermutationTests.cs │ └── Similarity/ │ ├── CosineSimilarityTests.cs │ ├── DamerauLevenshteinDistanceTests.cs │ ├── HammingDistanceTests.cs │ ├── JaccardDistanceTests.cs │ ├── JaccardSimilarityTests.cs │ ├── JaroSimilarityTests.cs │ ├── JaroWinklerDistanceTests.cs │ └── OptimalStringAlignmentTests.cs ├── C-Sharp.sln ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DataStructures/ │ ├── AATree/ │ │ ├── AATree.cs │ │ └── AATreeNode.cs │ ├── AVLTree/ │ │ ├── AVLTree.cs │ │ └── AVLTreeNode.cs │ ├── BTree/ │ │ ├── BTree.cs │ │ └── BTreeNode.cs │ ├── Bag/ │ │ ├── Bag.cs │ │ └── BagNode.cs │ ├── BinarySearchTree/ │ │ ├── BinarySearchTree.cs │ │ └── BinarySearchTreeNode.cs │ ├── BitArray.cs │ ├── Cache/ │ │ ├── LfuCache.cs │ │ └── LruCache.cs │ ├── DataStructures.csproj │ ├── Deque/ │ │ └── Deque.cs │ ├── DisjointSet/ │ │ ├── DisjointSet.cs │ │ └── Node.cs │ ├── Fenwick/ │ │ └── BinaryIndexedTree.cs │ ├── GlobalUsings.cs │ ├── Graph/ │ │ ├── DirectedWeightedGraph.cs │ │ ├── IDirectedWeightedGraph.cs │ │ └── Vertex.cs │ ├── Hashing/ │ │ ├── Entry.cs │ │ ├── HashTable.cs │ │ └── NumberTheory/ │ │ └── PrimeNumber.cs │ ├── Heap/ │ │ ├── BinaryHeap.cs │ │ ├── FibonacciHeap/ │ │ │ ├── FHeapNode.cs │ │ │ └── FibonacciHeap.cs │ │ ├── MinMaxHeap.cs │ │ └── PairingHeap/ │ │ ├── PairingHeap.cs │ │ ├── PairingHeapNode.cs │ │ ├── PairingNodeComparer.cs │ │ └── Sorting.cs │ ├── InvertedIndex.cs │ ├── LinkedList/ │ │ ├── CircularLinkedList/ │ │ │ ├── CircularLinkedList.cs │ │ │ └── CircularLinkedListNode.cs │ │ ├── DoublyLinkedList/ │ │ │ ├── DoublyLinkedList.cs │ │ │ └── DoublyLinkedListNode.cs │ │ ├── SinglyLinkedList/ │ │ │ ├── SinglyLinkedList.cs │ │ │ └── SinglyLinkedListNode.cs │ │ └── SkipList/ │ │ ├── SkipList.cs │ │ └── SkipListNode.cs │ ├── Probabilistic/ │ │ ├── BloomFilter.cs │ │ ├── CountMinSketch.cs │ │ └── HyperLogLog.cs │ ├── Queue/ │ │ ├── ArrayBasedQueue.cs │ │ ├── ListBasedQueue.cs │ │ └── StackBasedQueue.cs │ ├── RedBlackTree/ │ │ ├── RedBlackTree.cs │ │ └── RedBlackTreeNode.cs │ ├── ScapegoatTree/ │ │ ├── Extensions.cs │ │ ├── Node.cs │ │ └── ScapegoatTree.cs │ ├── SegmentTrees/ │ │ ├── SegmentTree.cs │ │ ├── SegmentTreeApply.cs │ │ └── SegmentTreeUpdate.cs │ ├── SortedList.cs │ ├── Stack/ │ │ ├── ArrayBasedStack.cs │ │ ├── ListBasedStack.cs │ │ └── QueueBasedStack.cs │ ├── Timeline.cs │ ├── Tries/ │ │ ├── Trie.cs │ │ └── TrieNode.cs │ └── UnrolledList/ │ ├── UnrolledLinkedList.cs │ └── UnrolledLinkedListNode.cs ├── DataStructures.Tests/ │ ├── AATreeTests.cs │ ├── AVLTreeTests.cs │ ├── BTreeTests.cs │ ├── BagTests.cs │ ├── BinarySearchTreeTests.cs │ ├── BitArrayTests.cs │ ├── Cache/ │ │ ├── LfuCacheTests.cs │ │ └── LruCacheTests.cs │ ├── DataStructures.Tests.csproj │ ├── Deque/ │ │ └── DequeTests.cs │ ├── DisjointSet/ │ │ └── DisjointSetTests.cs │ ├── Fenwick/ │ │ └── BinaryIndexedTreeTests.cs │ ├── GlobalUsings.cs │ ├── Graph/ │ │ └── DirectedWeightedGraphTests.cs │ ├── Hashing/ │ │ ├── HashTableTests.cs │ │ └── NumberTheory/ │ │ └── PrimeNumberTests.cs │ ├── Heap/ │ │ ├── BinaryHeapTests.cs │ │ ├── FibonacciHeaps/ │ │ │ └── FibonacciHeapTests.cs │ │ ├── MinMaxHeapTests.cs │ │ └── PairingHeap/ │ │ ├── PairingHeapComparerTests.cs │ │ └── PairingHeapTests.cs │ ├── InvertedIndexTests.cs │ ├── LinkedList/ │ │ ├── CircularLinkedListTests.cs │ │ ├── DoublyLinkedListTests.cs │ │ ├── LinkedListTests.cs │ │ └── SkipListTests.cs │ ├── Probabilistic/ │ │ ├── BloomFilterTests.cs │ │ ├── CountMinSketchTests.cs │ │ └── HyperLogLogTest.cs │ ├── Queue/ │ │ ├── ArrayBasedQueueTests.cs │ │ ├── ListBasedQueueTests.cs │ │ └── StackBasedQueueTests.cs │ ├── RedBlackTreeTests.cs │ ├── ScapegoatTree/ │ │ ├── ExtensionsTests.cs │ │ ├── ScapegoatTreeNodeTests.cs │ │ └── ScapegoatTreeTests.cs │ ├── SegmentTrees/ │ │ ├── SegmentTreeApplyTests.cs │ │ ├── SegmentTreeTests.cs │ │ └── SegmentTreeUpdateTest.cs │ ├── SortedListTests.cs │ ├── Stack/ │ │ ├── ArrayBasedStackTests.cs │ │ ├── ListBasedStackTests.cs │ │ └── QueueBasedStackTests.cs │ ├── TimelineTests.cs │ ├── Tries/ │ │ └── TrieTests.cs │ └── UnrolledList/ │ ├── UnrolledLinkedListNodeTests.cs │ └── UnrolledLinkedListTests.cs ├── LICENSE ├── README.md ├── Utilities/ │ ├── Exceptions/ │ │ └── ItemNotFoundException.cs │ ├── Extensions/ │ │ ├── DictionaryExtensions.cs │ │ ├── MatrixExtensions.cs │ │ ├── RandomExtensions.cs │ │ └── VectorExtensions.cs │ ├── GlobalUsings.cs │ └── Utilities.csproj ├── Utilities.Tests/ │ ├── Extensions/ │ │ ├── DictionaryExtensionsTests.cs │ │ ├── MatrixExtensionsTests.cs │ │ ├── RandomExtensionsTests.cs │ │ └── VectorExtensionsTests.cs │ ├── GlobalUsings.cs │ └── Utilities.Tests.csproj ├── stylecop.json └── stylecop.ruleset ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainer/devcontainer.json ================================================ { "name": "The Algorithms C#", "image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm", "customizations": { "vscode": { "extensions": [ "ms-dotnettools.csharp", "ms-dotnettools.csdevkit", "nunit.nunit-adapter", "fluentassertions.fluentassertions" ] } }, "postCreateCommand": "sudo chown -R $(whoami) /workspaces" } ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 # Microsoft .NET properties csharp_new_line_before_members_in_object_initializers = false csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion csharp_space_after_cast = false csharp_style_var_elsewhere = true:suggestion csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_constants_rule.severity = warning dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols dotnet_naming_rule.private_instance_fields_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_instance_fields_rule.severity = warning dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols dotnet_naming_rule.private_static_fields_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_static_fields_rule.severity = warning dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style_1 dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined dotnet_naming_rule.private_static_readonly_rule.severity = warning dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef dotnet_naming_rule.unity_serialized_field_rule.severity = warning dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_style.lower_camel_case_style_1.capitalization = camel_case dotnet_naming_style.lower_camel_case_style_1.required_prefix = _ dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field dotnet_naming_symbols.private_constants_symbols.required_modifiers = const dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion dotnet_style_qualification_for_event = false:suggestion dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # ReSharper properties resharper_autodetect_indent_settings = true resharper_braces_for_dowhile = required_for_multiline_statement resharper_braces_for_fixed = required_for_multiline_statement resharper_braces_for_for = required_for_multiline_statement resharper_braces_for_foreach = required_for_multiline_statement resharper_braces_for_ifelse = required_for_multiline_statement resharper_braces_for_lock = required_for_multiline_statement resharper_braces_for_using = required_for_multiline_statement resharper_braces_for_while = required_for_multiline_statement resharper_constructor_or_destructor_body = expression_body resharper_csharp_insert_final_newline = true resharper_csharp_wrap_after_declaration_lpar = true resharper_csharp_wrap_lines = false resharper_local_function_body = expression_body resharper_method_or_operator_body = expression_body resharper_new_line_before_while = true resharper_place_attribute_on_same_line = false resharper_space_after_cast = false resharper_space_within_single_line_array_initializer_braces = true resharper_trailing_comma_in_multiline_lists = true resharper_use_indent_from_vs = false # ReSharper inspection severities resharper_arrange_redundant_parentheses_highlighting = hint resharper_arrange_this_qualifier_highlighting = hint resharper_arrange_trailing_comma_in_multiline_lists_highlighting = suggestion resharper_arrange_type_member_modifiers_highlighting = hint resharper_arrange_type_modifiers_highlighting = hint resharper_built_in_type_reference_style_for_member_access_highlighting = hint resharper_built_in_type_reference_style_highlighting = hint resharper_redundant_base_qualifier_highlighting = warning resharper_suggest_var_or_type_built_in_types_highlighting = hint resharper_suggest_var_or_type_elsewhere_highlighting = hint resharper_suggest_var_or_type_simple_types_highlighting = hint resharper_web_config_module_not_resolved_highlighting = warning resharper_web_config_type_not_resolved_highlighting = warning resharper_web_config_wrong_module_highlighting = warning [{*.har,*.inputactions,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] indent_style = space indent_size = 2 [{*.yaml,*.yml}] indent_style = space indent_size = 2 [*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] indent_style = space indent_size = 4 tab_width = 4 ================================================ FILE: .github/CODEOWNERS ================================================ * @siriak ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: Something works wrong labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** How to reproduce the behavior. Initial conditions, parameters, etc. **Expected behavior** A clear and concise description of what you expected to happen. **Actual behavior** A clear and concise description of what has happened. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: Add something cool labels: enhancement assignees: '' --- I propose to add [algorithm/data structure] [name]. It helps to solve problems such as [...]. It's best described in book(s), on website(s): [...]. ================================================ FILE: .github/pull_request_template.md ================================================ - [ ] I have performed a self-review of my code - [ ] My code follows the style guidelines of this project - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Comments in areas I changed are up to date - [ ] I have added comments to hard-to-understand areas of my code - [ ] I have made corresponding changes to the README.md ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: dotnet-version: 8.x - name: Restore run: dotnet restore - name: Build run: dotnet build --no-restore - name: Test run: dotnet test --no-restore --collect "XPlat Code Coverage" - name: Upload coverage to codecov (tokenless) if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository uses: codecov/codecov-action@v4 with: fail_ci_if_error: true - name: Upload coverage to codecov (with token) if: > github.repository == 'TheAlgorithms/C-Sharp' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true ================================================ FILE: .github/workflows/stale.yml ================================================ name: 'Close stale issues and PRs' on: schedule: - cron: '0 0 * * *' jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v4 with: stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.' close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.' close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' exempt-issue-labels: 'dont-close' exempt-pr-labels: 'dont-close' days-before-stale: 30 days-before-close: 7 ================================================ FILE: .gitignore ================================================ # Visual Studio cache/options directory .vs/ # Rider cache/options directory .idea/ # Rider user config file C-Sharp.sln.DotSettings.user # Build results bin/ obj/ TestResults/ ================================================ FILE: Algorithms/Algorithms.csproj ================================================ net8.0 ..\stylecop.ruleset true enable ./bin/Algorithms.xml all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: Algorithms/Crypto/Digests/AsconDigest.cs ================================================ using System.Runtime.CompilerServices; using Algorithms.Crypto.Utils; namespace Algorithms.Crypto.Digests; /// /// Implements the Ascon cryptographic hash algorithm, providing both the standard Ascon-Hash and the Ascon-HashA variants. /// /// /// The class implements the Ascon hash function, a lightweight cryptographic algorithm designed for /// resource-constrained environments such as IoT devices. It provides two variants: /// /// /// /// : The standard Ascon-Hash variant with 12 rounds of the permutation function for enhanced security. /// /// /// /// /// : A performance-optimized variant with 8 rounds of the permutation function, offering a trade-off between security and performance. /// /// /// ///
/// The AsconDigest processes data in 8-byte blocks, accumulating input until a block is complete, at which point it applies /// the permutation function to update the internal state. After all data has been processed, the hash value can be finalized /// and retrieved. ///
/// Ascon was designed to meet the requirements of lightweight cryptography, making it ideal for devices with limited computational power. ///
public class AsconDigest : IDigest { public enum AsconParameters { /// /// Represents the Ascon Hash variant, the standard cryptographic hashing function of the Ascon family. /// /// /// AsconHash is the primary hashing algorithm in the Ascon family. It is designed for efficiency and security /// in resource-constrained environments, such as IoT devices, and provides high resistance to cryptanalytic attacks. /// This variant uses 12 rounds of the permutation function for increased security. /// AsconHash, /// /// Represents the Ascon HashA variant, an alternative variant of the Ascon hashing function with fewer permutation rounds. /// /// /// AsconHashA is a variant of the Ascon hashing function that uses fewer rounds (8 rounds) of the permutation function, /// trading off some security for improved performance in specific scenarios. It is still designed to be secure for many /// applications, but it operates faster in environments where computational resources are limited. /// AsconHashA, } /// /// Specifies the Ascon variant being used (either Ascon-Hash or Ascon-HashA). This defines the cryptographic algorithm's behavior. /// private readonly AsconParameters asconParameters; /// /// The number of permutation rounds applied in the Ascon cryptographic process. This is determined by the selected Ascon variant. /// private readonly int asconPbRounds; /// /// Internal buffer that temporarily stores input data before it is processed in 8-byte blocks. The buffer is cleared after each block is processed. /// private readonly byte[] buffer = new byte[8]; /// /// Internal state variable x0 used in the cryptographic permutation function. This is updated continuously as input data is processed. /// private ulong x0; /// /// Internal state variable x1 used in the cryptographic permutation function. This, along with other state variables, is updated during each round. /// private ulong x1; /// /// Internal state variable x2 used in the cryptographic permutation function. It helps track the evolving state of the digest. /// private ulong x2; /// /// Internal state variable x3 used in the cryptographic permutation function, contributing to the mixing and non-linearity of the state. /// private ulong x3; /// /// Internal state variable x4 used in the cryptographic permutation function. This, along with x0 to x3, ensures cryptographic security. /// private ulong x4; /// /// Tracks the current position within the buffer array. When bufferPosition reaches 8, the buffer is processed and reset. /// private int bufferPosition; /// /// Initializes a new instance of the class with the specified Ascon parameters. /// /// The Ascon variant to use, either or . /// /// This constructor sets up the digest by selecting the appropriate number of permutation rounds based on the Ascon variant. /// /// For , 12 permutation rounds are used. /// For , 8 permutation rounds are used. /// /// If an unsupported parameter is provided, the constructor throws an to indicate that the parameter is invalid. /// The internal state of the digest is then reset to prepare for processing input data. /// /// Thrown when an invalid parameter setting is provided for Ascon Hash. public AsconDigest(AsconParameters parameters) { // Set the Ascon parameter (AsconHash or AsconHashA) for this instance. asconParameters = parameters; // Determine the number of permutation rounds based on the Ascon variant. asconPbRounds = parameters switch { AsconParameters.AsconHash => 12, // 12 rounds for Ascon-Hash variant. AsconParameters.AsconHashA => 8, // 8 rounds for Ascon-HashA variant. _ => throw new ArgumentException("Invalid parameter settings for Ascon Hash"), // Throw exception for invalid parameter. }; // Reset the internal state to prepare for new input. Reset(); } /// /// Gets the name of the cryptographic algorithm based on the selected Ascon parameter. /// /// /// A string representing the name of the algorithm variant, either "Ascon-Hash" or "Ascon-HashA". /// /// /// This property determines the algorithm name based on the selected Ascon variant when the instance was initialized. /// It supports two variants: /// /// "Ascon-Hash" for the variant. /// "Ascon-HashA" for the variant. /// /// If an unsupported or unknown parameter is used, the property throws an . /// /// Thrown if an unknown Ascon parameter is encountered. public string AlgorithmName { get { return asconParameters switch { AsconParameters.AsconHash => "Ascon-Hash", // Return "Ascon-Hash" for AsconHash variant. AsconParameters.AsconHashA => "Ascon-HashA", // Return "Ascon-HashA" for AsconHashA variant. _ => throw new InvalidOperationException(), // Throw an exception for unknown Ascon parameters. }; } } /// /// Gets the size of the resulting hash produced by the digest, in bytes. /// /// The size of the hash, which is 32 bytes (256 bits) for this digest implementation. /// /// This method returns the fixed size of the hash output produced by the digest algorithm. In this implementation, /// the digest produces a 256-bit hash, which corresponds to 32 bytes. This is typical for cryptographic hash functions /// that aim to provide a high level of security by generating a large output size. /// public int GetDigestSize() => 32; /// /// Gets the internal block size of the digest in bytes. /// /// The internal block size of the digest, which is 8 bytes (64 bits). /// /// This method returns the block size that the digest algorithm uses when processing input data. The input is processed /// in chunks (blocks) of 8 bytes at a time. This block size determines how the input data is split and processed in multiple /// steps before producing the final hash. /// public int GetByteLength() => 8; /// /// Updates the cryptographic state by processing a single byte of input and adding it to the internal buffer. /// /// The byte to be added to the internal buffer and processed. /// /// This method collects input bytes in an internal buffer. Once the buffer is filled (reaching 8 bytes), the buffer is processed /// by converting it into a 64-bit unsigned integer in big-endian format and XORing it with the internal state variable x0. /// After processing the buffer, the permutation function is applied to mix the internal state, and the buffer position is reset to zero. ///

/// If the buffer has not yet reached 8 bytes, the method simply adds the input byte to the buffer and waits for further input. ///
public void Update(byte input) { // Add the input byte to the buffer. buffer[bufferPosition] = input; // If the buffer is not full (less than 8 bytes), increment the buffer position and return early. if (++bufferPosition != 8) { return; // Wait for more input to fill the buffer before processing. } // Once the buffer is full (8 bytes), convert the buffer to a 64-bit integer (big-endian) and XOR it with the state. x0 ^= ByteEncodingUtils.BigEndianToUint64(buffer, 0); // Apply the permutation function to mix the state. P(asconPbRounds); // Reset the buffer position for the next block of input. bufferPosition = 0; } /// /// Updates the cryptographic state by processing a segment of input data from a byte array, starting at a specified offset and length. /// /// The byte array containing the input data to be processed. /// The offset in the input array where processing should begin. /// The number of bytes from the input array to process. /// /// This method ensures that the input data is valid by checking the array length, starting from the provided offset, /// and making sure it is long enough to accommodate the specified length. It then processes the data by converting /// the relevant section of the byte array to a and delegating the actual block update to /// the method for further processing. /// /// /// Thrown if the input data is too short, starting from and for the length . /// public void BlockUpdate(byte[] input, int inOff, int inLen) { // Validate the input data to ensure there is enough data to process from the specified offset and length. ValidationUtils.CheckDataLength(input, inOff, inLen, "input buffer too short"); // Convert the input byte array into a ReadOnlySpan and delegate the processing to the span-based method. BlockUpdate(input.AsSpan(inOff, inLen)); } /// /// Processes the input data by updating the internal cryptographic state, handling both partial and full blocks. /// /// A read-only span of bytes representing the input data to be processed. /// /// This method processes the input data in chunks of 8 bytes. It manages the internal buffer to accumulate data /// until there are enough bytes to process a full 8-byte block. When the buffer is full or enough input is provided, /// it XORs the buffered data with the internal state variable x0 and applies the permutation function /// to update the cryptographic state. ///

/// If the input contains more than 8 bytes, the method continues to process full 8-byte blocks in a loop until /// the input is exhausted. Any remaining bytes (less than 8) are stored in the internal buffer for future processing. ///
public void BlockUpdate(ReadOnlySpan input) { // Calculate the number of available bytes left in the buffer before it reaches 8 bytes. var available = 8 - bufferPosition; // If the input length is smaller than the remaining space in the buffer, copy the input into the buffer. if (input.Length < available) { input.CopyTo(buffer.AsSpan(bufferPosition)); // Copy the small input into the buffer. bufferPosition += input.Length; // Update the buffer position. return; // Return early since we don't have enough data to process a full block. } // If there is data in the buffer, but it isn't full, fill it and process the full 8-byte block. if (bufferPosition > 0) { // Copy enough bytes from the input to complete the buffer. input[..available].CopyTo(buffer.AsSpan(bufferPosition)); // XOR the full buffer with the internal state (x0) and apply the permutation. x0 ^= ByteEncodingUtils.BigEndianToUint64(buffer); P(asconPbRounds); // Apply the permutation rounds. // Update the input to exclude the bytes we've already processed from the buffer. input = input[available..]; } // Process full 8-byte blocks directly from the input. while (input.Length >= 8) { // XOR the next 8-byte block from the input with the internal state and apply the permutation. x0 ^= ByteEncodingUtils.BigEndianToUint64(input); P(asconPbRounds); // Move to the next 8-byte chunk in the input. input = input[8..]; } // Copy any remaining bytes (less than 8) into the buffer to store for future processing. input.CopyTo(buffer); bufferPosition = input.Length; // Update the buffer position to reflect the remaining unprocessed data. } /// /// Finalizes the cryptographic hash computation, absorbing any remaining data, applying the final permutation, /// and writing the resulting hash to the specified position in the provided output byte array. /// /// The byte array where the final 32-byte hash will be written. /// The offset in the output array at which to start writing the hash. /// The size of the hash (32 bytes). /// /// This method finalizes the hash computation by converting the output array to a and /// calling the method. It provides flexibility in placing the result in an /// existing byte array with a specified offset. /// /// Thrown if the output buffer is too small to hold the resulting hash. public int DoFinal(byte[] output, int outOff) { // Call the Span-based DoFinal method with the output byte array and offset. return DoFinal(output.AsSpan(outOff)); } /// /// Finalizes the cryptographic hash computation, absorbing any remaining data, applying the final permutation, and /// writing the resulting hash to the provided output buffer. /// /// A span of bytes where the final 32-byte hash will be written. /// The size of the hash (32 bytes). /// /// This method completes the hash computation by absorbing any remaining input data, applying the final permutation, /// and extracting the state variables to produce the final hash. The method processes the state in 8-byte chunks, /// writing the result into the output buffer in big-endian format. After the final permutation is applied, the internal /// state is reset to prepare for a new hashing session. /// /// Thrown if the output buffer is too small to hold the resulting hash. public int DoFinal(Span output) { // Validate that the output buffer is at least 32 bytes in length. ValidationUtils.CheckOutputLength(output, 32, "output buffer too short"); // Absorb any remaining input and apply the final permutation. AbsorbAndFinish(); // Convert the first part of the state (x0) to big-endian format and write it to the output. ByteEncodingUtils.UInt64ToBigEndian(x0, output); // Loop to process the remaining parts of the internal state (x1, x2, etc.). for (var i = 0; i < 3; ++i) { // Move to the next 8-byte segment in the output buffer. output = output[8..]; // Apply the permutation rounds to mix the state. P(asconPbRounds); // Convert the updated state variable (x0) to big-endian format and write it to the output. ByteEncodingUtils.UInt64ToBigEndian(x0, output); } // Reset the internal state for the next hash computation. Reset(); // Return the size of the hash (32 bytes). return 32; } /// /// Computes the cryptographic hash of the input byte array and returns the result as a lowercase hexadecimal string. /// /// The input byte array to be hashed. /// A string containing the computed hash in lowercase hexadecimal format. /// /// This method takes a byte array as input, processes it to compute the Ascon hash, and returns the result as a hexadecimal string. /// It internally converts the byte array to a and delegates the actual hashing to the /// method. /// public string Digest(byte[] input) { return Digest(input.AsSpan()); } /// /// Computes the cryptographic hash of the input span of bytes and returns the result as a lowercase hexadecimal string. /// /// A span of bytes representing the input data to be hashed. /// A string containing the computed hash in lowercase hexadecimal format. /// /// This method processes the input span using the Ascon cryptographic algorithm to compute the hash. It accumulates /// the input, applies the necessary permutations and internal state updates, and finally produces a hash in the form /// of a 32-byte array. The result is then converted into a lowercase hexadecimal string using . /// public string Digest(Span input) { // Update the internal state with the input data. BlockUpdate(input); // Create an array to hold the final hash output (32 bytes). var output = new byte[GetDigestSize()]; // Finalize the hash computation and store the result in the output array. DoFinal(output, 0); // Convert the hash (byte array) to a lowercase hexadecimal string. return BitConverter.ToString(output).Replace("-", string.Empty).ToLowerInvariant(); } /// /// Resets the internal state of the Ascon cryptographic hash algorithm to its initial state based on the selected variant. /// /// /// This method clears the internal buffer and resets the buffer position to zero. Depending on the specified /// Ascon variant ( or ), it also reinitializes /// the internal state variables (x0, x1, x2, x3, x4) to their starting values. ///

/// The reset is necessary to prepare the hash function for a new message. It ensures that previous messages do not /// affect the new one and that the internal state is consistent with the algorithm’s specification for the selected variant. ///
public void Reset() { // Clear the buffer to remove any leftover data from previous operations. Array.Clear(buffer, 0, buffer.Length); // Reset the buffer position to zero to start processing fresh input. bufferPosition = 0; // Initialize the internal state variables (x0, x1, x2, x3, x4) based on the selected Ascon variant. switch (asconParameters) { // If using the AsconHashA variant, set the specific initial state values for x0 through x4. case AsconParameters.AsconHashA: x0 = 92044056785660070UL; x1 = 8326807761760157607UL; x2 = 3371194088139667532UL; x3 = 15489749720654559101UL; x4 = 11618234402860862855UL; break; // If using the AsconHash variant, set the specific initial state values for x0 through x4. case AsconParameters.AsconHash: x0 = 17191252062196199485UL; x1 = 10066134719181819906UL; x2 = 13009371945472744034UL; x3 = 4834782570098516968UL; x4 = 3787428097924915520UL; break; // If an unknown Ascon variant is encountered, throw an exception. default: throw new InvalidOperationException(); } } /// /// Finalizes the absorption phase of the cryptographic hash by padding the buffer and applying the final permutation round. /// /// /// This method is called when the input data has been fully absorbed into the internal state, and it needs to be finalized. /// The buffer is padded with a specific value (0x80) to signify the end of the data, and the remaining portion of the buffer is /// XORed with the internal state variable x0. After padding, the final permutation round is applied using 12 rounds of /// the permutation function . This ensures the internal state is fully mixed and the cryptographic hash /// is securely finalized. /// private void AbsorbAndFinish() { // Pad the buffer with 0x80 to indicate the end of the data. buffer[bufferPosition] = 0x80; // XOR the buffer (after padding) with the internal state x0, but only the relevant portion of the buffer is considered. // The (56 - (bufferPosition << 3)) shifts ensure that only the unprocessed part of the buffer is XORed into x0. x0 ^= ByteEncodingUtils.BigEndianToUint64(buffer, 0) & (ulong.MaxValue << (56 - (bufferPosition << 3))); // Apply 12 rounds of the permutation function to fully mix and finalize the internal state. P(12); } /// /// Executes the cryptographic permutation function by applying a sequence of rounds that transform the internal state variables. /// /// /// The number of rounds to execute. If set to 12, additional rounds are performed with specific constants to enhance the security of the transformation. /// /// /// In the Ascon cryptographic algorithm, the permutation function P transforms the internal state over multiple rounds. /// This method applies a set of round constants, each of which alters the state variables (x0, x1, x2, x3, x4) differently, /// ensuring that the transformation introduces non-linearity and diffusion, which are essential for cryptographic security. ///

/// When is set to 12, the method first applies four unique round constants. /// Afterward, it applies a fixed set of six additional constants regardless of the number of rounds. ///
private void P(int numberOfRounds) { if (numberOfRounds == 12) { Round(0xf0UL); Round(0xe1UL); Round(0xd2UL); Round(0xc3UL); } Round(0xb4UL); Round(0xa5UL); Round(0x96UL); Round(0x87UL); Round(0x78UL); Round(0x69UL); Round(0x5aUL); Round(0x4bUL); } /// /// Executes a single round of the cryptographic permutation function, transforming the internal state /// variables x0, x1, x2, x3, and x4 using XOR, AND, and NOT operations, along with circular bit rotations. /// This function is designed to introduce diffusion and non-linearity into the state for cryptographic security. /// /// /// A 64-bit unsigned integer constant that influences the round's transformation. Each round uses a unique value of this constant /// to ensure that the transformation applied to the state differs for each round. /// /// /// The Round function uses a series of bitwise operations (XOR, AND, NOT) and circular bit rotations to mix /// the internal state. Each transformation step introduces non-linearity and ensures that small changes in the input or state /// variables propagate widely across the internal state, enhancing the security of the cryptographic process. ///

/// The round constant () plays a crucial role in altering the state at each round, ensuring /// that each round contributes uniquely to the overall cryptographic transformation. Circular rotations are applied using /// to spread bits throughout the 64-bit word. ///
[MethodImpl(MethodImplOptions.AggressiveInlining)] private void Round(ulong circles) { // Step 1: Perform XOR and AND operations to mix inputs and state variables var t0 = x0 ^ x1 ^ x2 ^ x3 ^ circles ^ (x1 & (x0 ^ x2 ^ x4 ^ circles)); var t1 = x0 ^ x2 ^ x3 ^ x4 ^ circles ^ ((x1 ^ x2 ^ circles) & (x1 ^ x3)); var t2 = x1 ^ x2 ^ x4 ^ circles ^ (x3 & x4); var t3 = x0 ^ x1 ^ x2 ^ circles ^ (~x0 & (x3 ^ x4)); var t4 = x1 ^ x3 ^ x4 ^ ((x0 ^ x4) & x1); // Step 2: Apply circular right shifts and update the internal state variables x0 = t0 ^ LongUtils.RotateRight(t0, 19) ^ LongUtils.RotateRight(t0, 28); x1 = t1 ^ LongUtils.RotateRight(t1, 39) ^ LongUtils.RotateRight(t1, 61); x2 = ~(t2 ^ LongUtils.RotateRight(t2, 1) ^ LongUtils.RotateRight(t2, 6)); x3 = t3 ^ LongUtils.RotateRight(t3, 10) ^ LongUtils.RotateRight(t3, 17); x4 = t4 ^ LongUtils.RotateRight(t4, 7) ^ LongUtils.RotateRight(t4, 41); } } ================================================ FILE: Algorithms/Crypto/Digests/IDigest.cs ================================================ namespace Algorithms.Crypto.Digests; /// /// Interface for message digest algorithms, providing methods to update, finalize, and reset the digest state. /// public interface IDigest { /// /// Gets the name of the digest algorithm (e.g., "SHA-256"). /// string AlgorithmName { get; } /// /// Gets the size of the digest in bytes (e.g., 32 bytes for SHA-256). /// /// The size of the digest in bytes. int GetDigestSize(); /// /// Gets the byte length of the internal buffer used by the digest. /// /// The byte length of the internal buffer. int GetByteLength(); /// /// Updates the digest with a single byte of input data. /// /// The byte to add to the digest. void Update(byte input); /// /// Updates the digest with a portion of a byte array. /// /// The byte array containing the input data. /// The offset within the array to start reading from. /// The length of data to read from the array. void BlockUpdate(byte[] input, int inOff, int inLen); /// /// Updates the digest with a portion of input data from a of bytes. /// /// The containing the input data. void BlockUpdate(ReadOnlySpan input); /// /// Completes the digest calculation and stores the result in the specified byte array. /// /// The byte array to store the final digest. /// The offset within the array to start writing the digest. /// The number of bytes written to the output array. int DoFinal(byte[] output, int outOff); /// /// Completes the digest calculation and stores the result in the specified of bytes. /// /// The to store the final digest. /// The number of bytes written to the output span. int DoFinal(Span output); string Digest(byte[] input); string Digest(Span input); /// /// Resets the digest to its initial state, clearing all data accumulated so far. /// void Reset(); } ================================================ FILE: Algorithms/Crypto/Digests/Md2Digest.cs ================================================ namespace Algorithms.Crypto.Digests; /// /// MD2 is a cryptographic hash function that takes an input message and produces a 128-bit output, also called a message /// digest or a hash. /// /// A hash function has two main properties: it is easy to compute the hash from the input, but it is hard to find the /// input from the hash or to find two different inputs that produce the same hash. /// /// /// MD2 works by first padding the input message to a multiple of 16 bytes and adding a 16-byte checksum to it. Then, it /// uses a 48-byte auxiliary block and a 256-byte S-table (a fixed permutation of the numbers 0 to 255) to process the /// message in 16-byte blocks. /// /// /// For each block, it updates the auxiliary block by XORing it with the message block and then applying the S-table 18 /// times. After all blocks are processed, the first 16 bytes of the auxiliary block become the hash value. /// /// public class Md2Digest { // The S-table is a set of constants generated by shuffling the integers 0 through 255 using a variant of // Durstenfeld's algorithm with a pseudorandom number generator based on decimal digits of pi. private static readonly byte[] STable = [ 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, 31, 26, 219, 153, 141, 51, 159, 17, 131, 20, ]; // The X buffer is a 48-byte auxiliary block used to compute the message digest. private readonly byte[] xBuffer = new byte[48]; // The M buffer is a 16-byte auxiliary block that keeps 16 byte blocks from the input data. private readonly byte[] mBuffer = new byte[16]; // The checksum buffer private readonly byte[] checkSum = new byte[16]; private int xBufferOffset; private int mBufferOffset; /// /// Computes the MD2 hash of the input byte array. /// /// The input byte array to be hashed. /// The MD2 hash as a byte array. public byte[] Digest(byte[] input) { Update(input, 0, input.Length); // Pad the input to a multiple of 16 bytes. var paddingByte = (byte)(mBuffer.Length - mBufferOffset); for (var i = mBufferOffset; i < mBuffer.Length; i++) { mBuffer[i] = paddingByte; } // Process the checksum of the padded input. ProcessCheckSum(mBuffer); // Process the first block of the padded input. ProcessBlock(mBuffer); // Process the second block of the padded input, which is the checksum. ProcessBlock(checkSum); // Copy the first 16 bytes of the auxiliary block to the output. var digest = new byte[16]; xBuffer.AsSpan(xBufferOffset, 16).CopyTo(digest); // Reset the internal state for reuse. Reset(); return digest; } /// /// Resets the engine to its initial state. /// private void Reset() { xBufferOffset = 0; for (var i = 0; i != xBuffer.Length; i++) { xBuffer[i] = 0; } mBufferOffset = 0; for (var i = 0; i != mBuffer.Length; i++) { mBuffer[i] = 0; } for (var i = 0; i != checkSum.Length; i++) { checkSum[i] = 0; } } /// /// Performs the compression step of MD2 hash algorithm. /// /// The 16 bytes block to be compressed. /// /// the compression step is designed to achieve diffusion and confusion, two properties that make it hard to reverse /// or analyze the hash function. Diffusion means that changing one bit of the input affects many bits of the output, /// and confusion means that there is no apparent relation between the input and the output. /// private void ProcessBlock(byte[] block) { // Copying and XORing: The input block is copied to the second and third parts of the internal state, while XORing // the input block with the first part of the internal state. // By copying the input block to the second and third parts of the internal state, the compression step ensures // that each input block contributes to the final output digest. // By XORing the input block with the first part of the internal state, the compression step introduces a non-linear // transformation that depends on both the input and the previous state. This makes it difficult to deduce the input // or the state from the output, or vice versa. for (var i = 0; i < 16; i++) { xBuffer[i + 16] = block[i]; xBuffer[i + 32] = (byte)(block[i] ^ xBuffer[i]); } var tmp = 0; // Mixing: The internal state is mixed using the substitution table for 18 rounds. Each round consists of looping // over the 48 bytes of the internal state and updating each byte by XORing it with a value from the substitution table. // The mixing process ensures that each byte of the internal state is affected by every byte of the input block and // every byte of the substitution table. This creates a high degree of diffusion and confusion, which makes it hard // to find collisions or preimages for the hash function. for (var j = 0; j < 18; j++) { for (var k = 0; k < 48; k++) { tmp = xBuffer[k] ^= STable[tmp]; tmp &= 0xff; } tmp = (tmp + j) % 256; } } /// /// Performs the checksum step of MD2 hash algorithm. /// /// The 16 bytes block to calculate the checksum. /// /// The checksum step ensures that changing any bit of the input message will change about half of the bits of the /// checksum, making it harder to find collisions or preimages. /// private void ProcessCheckSum(byte[] block) { // Assign the last element of checksum to the variable last. This is the initial value of the checksum. var last = checkSum[15]; for (var i = 0; i < 16; i++) { // Compute the XOR of the current element of the mBuffer array and the last value, and uses it as an index // to access an element of STable. This is a substitution operation that maps each byte to another byte using // the STable. var map = STable[(mBuffer[i] ^ last) & 0xff]; // Compute the XOR of the current element of checkSum and the substituted byte, and stores it back to the // checksum. This is a mixing operation that updates the checksum value with the input data. checkSum[i] ^= map; // Assign the updated element of checksum to last. This is to keep track of the last checksum value for the // next iteration. last = checkSum[i]; } } /// /// Update the message digest with a single byte. /// /// The input byte to digest. private void Update(byte input) { mBuffer[mBufferOffset++] = input; } /// /// Update the message digest with a block of bytes. /// /// The byte array containing the data. /// The offset into the byte array where the data starts. /// The length of the data. private void Update(byte[] input, int inputOffset, int length) { // process whole words while (length >= 16) { Array.Copy(input, inputOffset, mBuffer, 0, 16); ProcessCheckSum(mBuffer); ProcessBlock(mBuffer); length -= 16; inputOffset += 16; } while (length > 0) { Update(input[inputOffset]); inputOffset++; length--; } } } ================================================ FILE: Algorithms/Crypto/Exceptions/CryptoException.cs ================================================ namespace Algorithms.Crypto.Exceptions; /// /// Represents errors that occur during cryptographic operations. /// public class CryptoException : Exception { /// /// Initializes a new instance of the class. /// public CryptoException() { } /// /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. public CryptoException(string message) : base(message) { } /// /// Initializes a new instance of the class with a specified error message /// and a reference to the inner exception that is the cause of this exception. /// /// The message that describes the error. /// The exception that is the cause of the current exception. public CryptoException(string message, Exception inner) : base(message, inner) { } } ================================================ FILE: Algorithms/Crypto/Exceptions/DataLengthException.cs ================================================ namespace Algorithms.Crypto.Exceptions; /// /// Represents errors that occur when the length of data in a cryptographic operation is invalid or incorrect. /// public class DataLengthException : CryptoException { /// /// Initializes a new instance of the class. /// public DataLengthException() { } /// /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. public DataLengthException(string message) : base(message) { } /// /// Initializes a new instance of the class with a specified error message /// and a reference to the inner exception that is the cause of this exception. /// /// The message that describes the error. /// The exception that is the cause of the current exception. public DataLengthException(string message, Exception inner) : base(message, inner) { } } ================================================ FILE: Algorithms/Crypto/Exceptions/OutputLengthException.cs ================================================ namespace Algorithms.Crypto.Exceptions; /// /// Represents an exception that is thrown when the output buffer length is insufficient for a cryptographic operation. /// /// /// The is a specific subclass of . It is used in cryptographic /// operations to signal that the provided output buffer does not have enough space to store the required output. This exception is /// typically thrown when encryption, hashing, or other cryptographic operations require more space than what has been allocated in /// the output buffer. ///
/// This exception provides constructors for creating the exception with a custom message, an inner exception, or both. By inheriting /// from , it can be handled similarly in cases where both input and output length issues may arise. ///
public class OutputLengthException : DataLengthException { /// /// Initializes a new instance of the class. /// /// /// This constructor initializes a new instance of the class without any additional message or inner exception. /// It is commonly used when a generic output length issue needs to be raised without specific details. /// public OutputLengthException() { } /// /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. /// /// This constructor allows for a custom error message to be provided, giving more detail about the specific issue with the output length. /// public OutputLengthException(string message) : base(message) { } /// /// Initializes a new instance of the class with a specified error message /// and a reference to the inner exception that is the cause of this exception. /// /// The message that describes the error. /// The exception that is the cause of the current exception. /// /// This constructor allows for both a custom message and an inner exception, which can be useful for propagating /// the underlying cause of the error. For example, if the output buffer length is too short due to incorrect calculations, /// the root cause (e.g., an ) can be passed in as the inner exception. /// public OutputLengthException(string message, Exception inner) : base(message, inner) { } } ================================================ FILE: Algorithms/Crypto/Paddings/IBlockCipherPadding.cs ================================================ namespace Algorithms.Crypto.Paddings; /// /// A common interface that all block cipher padding schemes should follow. /// public interface IBlockCipherPadding { /// /// Adds padding bytes to the end of the given block of the data and returns the number of bytes that were added. /// /// The input data array that needs padding. /// The offset in the input array where the padding should start. /// The number of bytes added. /// /// This method expects that the input parameter contains the last block of plain text /// that needs to be padded. This means that the value of has to have the same value as /// the last block of plain text. The reason for this is that some modes such as the base the /// padding value on the last byte of the plain text. /// public int AddPadding(byte[] inputData, int inputOffset); /// /// Removes the padding bytes from the given block of data and returns the original data as a new array. /// /// The input data array containing the padding. /// The input data without the padding as a new byte array. /// Thrown when the input data has invalid padding. public byte[] RemovePadding(byte[] inputData); /// /// Gets the number of padding bytes in the input data. /// /// The input data array that has padding. /// The number of padding bytes in the input data. /// Thrown when the input data has invalid padding. public int GetPaddingCount(byte[] input); } ================================================ FILE: Algorithms/Crypto/Paddings/Iso10126D2Padding.cs ================================================ namespace Algorithms.Crypto.Paddings; /// /// /// This class implements the ISO10126d2 padding scheme, which is a standard way of padding data to fit a certain block /// size. /// /// /// ISO10126d2 padding adds N-1 random bytes and one byte of value N to the end of the data, where N is the number of /// bytes needed to reach the block size. For example, if the block size is 16 bytes, and the data is 10 bytes long, then /// 5 random bytes and a byte with value 6 will be added to the end of data. This way the padded data will be 16 bytes /// long and can be encrypted or decrypted by a block cipher algorithm. /// /// /// The padding can easily be removed after decryption by looking at the last byte and discarding that many bytes from /// the end of the data. /// /// public class Iso10126D2Padding : IBlockCipherPadding { /// /// Adds random padding to the input data array to make it a multiple of the block size according to the /// ISO10126d2 standard. /// /// The input data array that needs to be padded. /// The offset in the input data array where the padding should start. /// The number of bytes added as padding. /// /// Thrown when there is not enough space in the input array for padding. /// public int AddPadding(byte[] inputData, int inputOffset) { // Calculate how many bytes need to be added to reach the next multiple of block size. var code = (byte)(inputData.Length - inputOffset); if (code == 0 || inputOffset + code > inputData.Length) { throw new ArgumentException("Not enough space in input array for padding"); } // Add the padding. while (inputOffset < (inputData.Length - 1)) { inputData[inputOffset] = (byte)RandomNumberGenerator.GetInt32(255); inputOffset++; } // Set the last byte of the array to the size of the padding added. inputData[inputOffset] = code; return code; } /// /// Removes the padding from the input data array and returns the original data. /// /// /// The input data with ISO10126d2 padding. Must not be null and must have a valid length and padding. /// /// /// The input data without the padding as a new byte array. /// /// /// Thrown when the padding length is invalid. /// public byte[] RemovePadding(byte[] inputData) { // Get the size of the padding from the last byte of the input data. var paddingLength = inputData[^1]; // Check if the padding size is valid. if (paddingLength < 1 || paddingLength > inputData.Length) { throw new ArgumentException("Invalid padding length"); } // Create a new array to hold the original data. var output = new byte[inputData.Length - paddingLength]; // Copy the original data into the new array. Array.Copy(inputData, 0, output, 0, output.Length); return output; } /// /// Gets the number of padding bytes from the input data array. /// /// The input data array that has been padded. /// The number of padding bytes. /// Thrown when the input is null. /// Thrown when the padding block is corrupted. public int GetPaddingCount(byte[] input) { if (input == null) { throw new ArgumentNullException(nameof(input), "Input cannot be null"); } // Get the last byte of the input data as the padding value. var lastByte = input[^1]; var paddingCount = lastByte & 0xFF; // Calculate the index where the padding starts. var paddingStartIndex = input.Length - paddingCount; var paddingCheckFailed = 0; // The paddingCheckFailed will be non-zero under the following circumstances: // 1. When paddingStartIndex is negative: This happens when paddingCount (the last byte of the input array) is // greater than the length of the input array. In other words, the padding count is claiming that there are more // padding bytes than there are bytes in the array, which is not a valid scenario. // 2. When paddingCount - 1 is negative: This happens when paddingCount is zero or less. Since paddingCount // represents the number of padding bytes and is derived from the last byte of the input array, it should always // be a positive number. If it's zero or less, it means that either there's no padding, or an invalid negative // padding count has shomehow encoded into the last byte of the input array. paddingCheckFailed = (paddingStartIndex | (paddingCount - 1)) >> 31; if (paddingCheckFailed != 0) { throw new ArgumentException("Padding block is corrupted"); } return paddingCount; } } ================================================ FILE: Algorithms/Crypto/Paddings/Iso7816D4Padding.cs ================================================ namespace Algorithms.Crypto.Paddings; /// /// /// ISO 7816-4 padding is a padding scheme that is defined in the ISO/IEC 7816-4 documentation. /// /// /// It is used for adding data to the end of a message that needs to be encrypted or decrypted by a block cipher. /// /// ISO 7816-4 padding works as follows: /// /// The first byte of the padding is 0x80, which is the hexadecimal representation of the binary value 10000000. This /// byte indicates the start of the padding. /// /// /// All other bytes of the padding are 0x00, which is the hexadecimal representation of the binary value 00000000. These /// bytes fill up the remaining space in the last block. /// /// /// The padding can be of any size, from 1 byte to the block size. For example, if the block size is 8 bytes and the /// message has 5 bytes, then 3 bytes of padding are needed. The padding would be 0x80 0x00 0x00. /// /// /// ISO 7816-4 padding is also known as bit padding,because it simply places a single 1 bit after the plaintext, followed /// by 0 valued bits up to the block size. It works for both byte-oriented and bit-oriented protocols, as it does not /// depend on any specific character encoding or representation. /// /// public class Iso7816D4Padding : IBlockCipherPadding { /// /// Adds padding to the input data according to the ISO 7816-4 standard. /// /// The input data array that needs padding. /// The offset in the input data array where the padding should start. /// The number of bytes added as padding. /// /// Thrown when there is not enough space in the input array for padding or when the input offset is invalid. /// public int AddPadding(byte[] inputData, int inputOffset) { // Calculate the number of padding bytes based on the input data length and offset. var code = (byte)(inputData.Length - inputOffset); // Check if the padding bytes are valid and fit in the input array. if (code == 0 || inputOffset + code > inputData.Length) { throw new ArgumentException("Not enough space in input array for padding"); } // Set the first padding byte to 80. This marks the start of padding in the ISO 7816-4 standard. inputData[inputOffset] = 80; inputOffset++; // Set the remaining padding bytes to 0. while (inputOffset < inputData.Length) { inputData[inputOffset] = 0; inputOffset++; } // Return the number of padding bytes. return code; } /// /// Removes the padding from the input data array and returns the original data. /// /// /// The input data with ISO 7816-4 padding. Must not be null and must have a valid length and padding. /// /// The input data without the padding as a new byte array. /// /// Thrown when the input data has invalid padding. /// public byte[] RemovePadding(byte[] inputData) { // Find the index of the first padding byte by scanning from the end of the input. var paddingIndex = inputData.Length - 1; // Skip all the padding bytes that are 0. while (paddingIndex >= 0 && inputData[paddingIndex] == 0) { paddingIndex--; } // Check if the first padding byte is 0x80. if (paddingIndex < 0 || inputData[paddingIndex] != 0x80) { throw new ArgumentException("Invalid padding"); } // Create a new array to store the unpadded data. var unpaddedData = new byte[paddingIndex]; // Copy the unpadded data from the input data to the new array. Array.Copy(inputData, 0, unpaddedData, 0, paddingIndex); // Return the unpadded data array. return unpaddedData; } /// /// Gets the number of padding bytes in the input data according to the ISO 7816-4 standard. /// /// The input data array that has padding. /// The number of padding bytes in the input data. /// Thrown when the input data has invalid padding. public int GetPaddingCount(byte[] input) { // Initialize the index of the first padding byte to -1. var paddingStartIndex = -1; // Initialize a mask to indicate if the current byte is still part of the padding. var stillPaddingMask = -1; // Initialize the current index to the end of the input data. var currentIndex = input.Length; // Loop backwards through the input data. while (--currentIndex >= 0) { // Get the current byte as an unsigned integer. var currentByte = input[currentIndex] & 0xFF; // Compute a mask to indicate if the current byte is 0x00. var isZeroMask = (currentByte - 1) >> 31; // Compute a mask to indicate if the current byte is 0x80. var isPaddingStartMask = ((currentByte ^ 0x80) - 1) >> 31; // Update the index of the first padding byte using bitwise operations. // If the current byte is 0x80 and still part of the padding, set the index to the current index. // Otherwise, keep the previous index. paddingStartIndex ^= (currentIndex ^ paddingStartIndex) & (stillPaddingMask & isPaddingStartMask); // Update the mask to indicate if the current byte is still part of the padding using bitwise operations. // If the current byte is 0x00, keep the previous mask. // Otherwise, set the mask to 0. stillPaddingMask &= isZeroMask; } // Check if the index of the first padding byte is valid. if (paddingStartIndex < 0) { throw new ArgumentException("Pad block corrupted"); } // Return the number of padding bytes. return input.Length - paddingStartIndex; } } ================================================ FILE: Algorithms/Crypto/Paddings/Pkcs7Padding.cs ================================================ namespace Algorithms.Crypto.Paddings; /// /// /// This class implements the PKCS7 padding scheme, which is a standard way of padding data to fit a certain block size. /// /// /// PKCS7 padding adds N bytes of value N to the end of the data, where N is the number of bytes needed to reach the block size. /// For example, if the block size is 16 bytes, and the data is 11 bytes long, then 5 bytes of value 5 will be added to the /// end of the data. This way, the padded data will be 16 bytes long and can be encrypted or decrypted by a block cipher algorithm. /// /// /// The padding can be easily removed after decryption by looking at the last byte and subtracting that many bytes from the /// end of the data. /// /// /// This class supports any block size from 1 to 255 bytes, and can be used with any encryption algorithm that requires /// padding, such as AES. /// /// public class Pkcs7Padding : IBlockCipherPadding { private readonly int blockSize; public Pkcs7Padding(int blockSize) { if (blockSize is < 1 or > 255) { throw new ArgumentOutOfRangeException(nameof(blockSize), $"Invalid block size: {blockSize}"); } this.blockSize = blockSize; } /// /// Adds padding to the end of a byte array according to the PKCS#7 standard. /// /// The byte array to be padded. /// The offset from which to start padding. /// The padding value that was added to each byte. /// /// If the input array does not have enough space to add blockSize bytes as padding. /// /// /// The padding value is equal to the number of of bytes that are added to the array. /// For example, if the input array has a length of 16 and the input offset is 10, /// then 6 bytes with the value 6 will be added to the end of the array. /// public int AddPadding(byte[] input, int inputOffset) { // Calculate how many bytes need to be added to reach the next multiple of block size. var code = (byte)((blockSize - (input.Length % blockSize)) % blockSize); // If no padding is needed, add a full block of padding. if (code == 0) { code = (byte)blockSize; } if (inputOffset + code > input.Length) { throw new ArgumentException("Not enough space in input array for padding"); } // Add the padding for (var i = 0; i < code; i++) { input[inputOffset + i] = code; } return code; } /// /// Removes the PKCS7 padding from the given input data. /// /// The input data with PKCS7 padding. Must not be null and must have a valid length and padding. /// The input data without the padding as a new byte array. /// /// Thrown if the input data is null, has an invalid length, or has an invalid padding. /// public byte[] RemovePadding(byte[] input) { // Check if input length is a multiple of blockSize if (input.Length % blockSize != 0) { throw new ArgumentException("Input length must be a multiple of block size"); } // Get the padding length from the last byte of input var paddingLength = input[^1]; // Check if padding length is valid if (paddingLength < 1 || paddingLength > blockSize) { throw new ArgumentException("Invalid padding length"); } // Check if all padding bytes have the correct value for (var i = 0; i < paddingLength; i++) { if (input[input.Length - 1 - i] != paddingLength) { throw new ArgumentException("Invalid padding"); } } // Create a new array with the size of input minus the padding length var output = new byte[input.Length - paddingLength]; // Copy the data without the padding into the output array Array.Copy(input, output, output.Length); return output; } /// /// Gets the number of padding bytes in the given input data according to the PKCS7 padding scheme. /// /// The input data with PKCS7 padding. Must not be null and must have a valid padding. /// The number of padding bytes in the input data. /// /// Thrown if the input data is null or has an invalid padding. /// /// /// This method uses bitwise operations to avoid branching. /// public int GetPaddingCount(byte[] input) { if (input == null) { throw new ArgumentNullException(nameof(input), "Input cannot be null"); } // Get the last byte of the input data as the padding value. var lastByte = input[^1]; var paddingCount = lastByte & 0xFF; // Calculate the index where the padding starts var paddingStartIndex = input.Length - paddingCount; var paddingCheckFailed = 0; // Check if the padding start index is negative or greater than the input length. // This is done by using bitwise operations to avoid branching. // If the padding start index is negative, then its most significant bit will be 1. // If the padding count is greater than the block size, then its most significant bit will be 1. // By ORing these two cases, we can get a non-zero value rif either of them is true. // By shifting this value right by 31 bits, we can get either 0 or -1 as the result. paddingCheckFailed = (paddingStartIndex | (paddingCount - 1)) >> 31; for (var i = 0; i < input.Length; i++) { // Check if each byte matches the padding value. // This is done by using bitwise operations to avoid branching. // If a byte does not match the padding value, then XORing them will give a non-zero value. // If a byte is before the padding start index, then we want to ignore it. // This is done by using bitwise operations to create a mask that is either all zeros or all ones. // If i is less than the padding start index, then subtracting them will give a negative value. // By shifting this value right by 31 bits, we can get either -1 or 0 as the mask. // By negating this mask, we can get either 0 or -1 as the mask. // By ANDing this mask with the XOR result, we can get either 0 or the XOR result as the final result. // By ORing this final result with the previous padding check result, we can accumulate any non-zero values. paddingCheckFailed |= (input[i] ^ lastByte) & ~((i - paddingStartIndex) >> 31); } // Check if the padding check failed. if (paddingCheckFailed != 0) { throw new ArgumentException("Padding block is corrupted"); } // Return the number of padding bytes. return paddingCount; } } ================================================ FILE: Algorithms/Crypto/Paddings/TbcPadding.cs ================================================ namespace Algorithms.Crypto.Paddings; /// /// /// Trailing-Bit-Complement padding is a padding scheme that is defined in the ISO/IEC 9797-1 standard. /// /// /// It is used for adding data to the end of a message that needs to be encrypted or decrypted by a block cipher. /// /// /// The padding bytes are either 0x00 or 0xFF, depending on the last bit of the original data. For example, if the last /// bit of the original data is 0, then the padding bytes are 0xFF; if the last bit is 1, then the padding bytes are 0x00. /// The padding bytes are added at the end of the data block until the desired length is reached. /// /// public class TbcPadding : IBlockCipherPadding { /// /// Adds padding to the input array according to the TBC standard. /// /// The input array to be padded. /// The offset in the input array where the padding starts. /// The number of bytes that were added. /// Thrown when the input array does not have enough space for padding. public int AddPadding(byte[] input, int inputOffset) { // Calculate the number of bytes to be padded. var count = input.Length - inputOffset; byte code; // Check if the input array has enough space for padding. if (count < 0) { throw new ArgumentException("Not enough space in input array for padding"); } if (inputOffset > 0) { // Get the last bit of the previous byte. var lastBit = input[inputOffset - 1] & 0x01; // Set the padding code to 0xFF if the last bit is 0, or 0x00 if the last bit is 1. code = (byte)(lastBit == 0 ? 0xff : 0x00); } else { // Get the last bit of the last byte in the input array. var lastBit = input[^1] & 0x01; // Set the padding code to 0xff if the last bit is 0, or 0x00 if the last bit is 1. code = (byte)(lastBit == 0 ? 0xff : 0x00); } while (inputOffset < input.Length) { // Set each byte to the padding code. input[inputOffset] = code; inputOffset++; } // Return the number of bytes that were padded. return count; } /// /// Removes the padding from a byte array according to the Trailing-Bit-Complement padding algorithm. /// /// The byte array to remove the padding from. /// A new byte array without the padding. /// /// This method assumes that the input array has padded with either 0x00 or 0xFF bytes, depending on the last bit of /// the original data. The method works by finding the last byte that does not match the padding code and copying all /// the bytes up to that point into a new array. If the input array is not padded or has an invalid padding, the /// method may return incorrect results. /// public byte[] RemovePadding(byte[] input) { if (input.Length == 0) { return Array.Empty(); } // Get the last byte of the input array. var lastByte = input[^1]; // Determine the byte code var code = (byte)((lastByte & 0x01) == 0 ? 0x00 : 0xff); // Start from the end of the array and move towards the front. int i; for (i = input.Length - 1; i >= 0; i--) { // If the current byte does not match the padding code, stop. if (input[i] != code) { break; } } // Create a new array of the appropriate length. var unpadded = new byte[i + 1]; // Copy the unpadded data into the new array. Array.Copy(input, unpadded, i + 1); // Return the new array. return unpadded; } /// /// Returns the number of padding bytes in a byte array according to the Trailing-Bit-Complement padding algorithm. /// /// The byte array to check for padding. /// The number of padding bytes in the input array. /// /// This method assumes that the input array has been padded with either 0x00 or 0xFF bytes, depending on the last /// bit of the original data. The method works by iterating backwards from the end of the array and counting the /// number of bytes that match the padding code. The method uses bitwise operations to optimize the performance and /// avoid branching. If the input array is not padded or has an invalid padding, the method may return incorrect /// results. /// public int GetPaddingCount(byte[] input) { var length = input.Length; if (length == 0) { throw new ArgumentException("No padding found."); } // Get the value of the last byte as the padding value var paddingValue = input[--length] & 0xFF; var paddingCount = 1; // Start count at 1 for the last byte var countingMask = -1; // Initialize counting mask // Check if there is no padding if (paddingValue != 0 && paddingValue != 0xFF) { throw new ArgumentException("No padding found"); } // Loop backwards through the array for (var i = length - 1; i >= 0; i--) { var currentByte = input[i] & 0xFF; // Calculate matchMask. If currentByte equals paddingValue, matchMask will be 0, otherwise -1 var matchMask = ((currentByte ^ paddingValue) - 1) >> 31; // Update countingMask. Once a non-matching byte is found, countingMask will remain -1 countingMask &= matchMask; // Increment count only if countingMask is 0 (i.e., currentByte matches paddingValue) paddingCount -= countingMask; } return paddingCount; } } ================================================ FILE: Algorithms/Crypto/Paddings/X932Padding.cs ================================================ namespace Algorithms.Crypto.Paddings; /// /// /// X9.32 padding is a padding scheme for symmetric encryption algorithms that is based on the ANSI X9.32 standard. /// /// /// It adds bytes with value equal to 0 up to the end of the plaintext. For example if the plaintext is 13 bytes long /// and the block size is 16 bytes, then 2 bytes with value 0 will be added as padding. The last byte indicates the /// number of padding bytes. /// /// /// If random padding mode is selected then random bytes are added before the padding bytes. For example, if the plaintext /// is 13 bytes long, then 2 random bytes will be added as padding. Again the last byte indicates the number of padding /// bytes. /// /// /// /// Initializes a new instance of the class with the specified padding mode. /// /// A boolean value that indicates whether to use random bytes as padding or not. public class X932Padding(bool useRandomPadding) : IBlockCipherPadding { private readonly bool useRandomPadding = useRandomPadding; /// /// Adds padding to the input data according to the X9.23 padding scheme. /// /// The input data array to be padded. /// The offset in the input data array where the padding should start. /// The number of padding bytes added. /// /// Thrown when the input offset is greater than or equal to the input data length. /// public int AddPadding(byte[] inputData, int inputOffset) { // Check if the input offset is valid. if (inputOffset >= inputData.Length) { throw new ArgumentException("Not enough space in input array for padding"); } // Calculate the number of padding bytes needed. var code = (byte)(inputData.Length - inputOffset); // Fill the remaining bytes with random or zero bytes while (inputOffset < inputData.Length - 1) { if (!useRandomPadding) { // Use zero bytes if random padding is disabled. inputData[inputOffset] = 0; } else { // Use random bytes if random padding is enabled. inputData[inputOffset] = (byte)RandomNumberGenerator.GetInt32(255); } inputOffset++; } // Set the last byte to the number of padding bytes. inputData[inputOffset] = code; // Return the number of padding bytes. return code; } /// /// Removes padding from the input data according to the X9.23 padding scheme. /// /// The input data array to be unpadded. /// The unpadded data array. /// /// Thrown when the input data is empty or has an invalid padding length. /// public byte[] RemovePadding(byte[] inputData) { // Check if the array is empty. if (inputData.Length == 0) { return Array.Empty(); } // Get the padding length from the last byte of the input data. var paddingLength = inputData[^1]; // Check if the padding length is valid. if (paddingLength < 1 || paddingLength > inputData.Length) { throw new ArgumentException("Invalid padding length"); } // Create a new array for the output data. var output = new byte[inputData.Length - paddingLength]; // Copy the input data without the padding bytes to the output array. Array.Copy(inputData, output, output.Length); // Return the output array. return output; } /// /// Gets the number of padding bytes in the input data according to the X9.23 padding scheme. /// /// The input data array to be checked. /// The number of padding bytes in the input data. /// /// Thrown when the input data has a corrupted padding block. /// public int GetPaddingCount(byte[] input) { // Get the last byte of the input data, which is the padding length. var count = input[^1] & 0xFF; // Calculate the position of the first padding byte. var position = input.Length - count; // Check if the position and count are valid using bitwise operations. // If either of them is negative or zero, the result will be negative. var failed = (position | (count - 1)) >> 31; // Throw an exception if the result is negative. if (failed != 0) { throw new ArgumentException("Pad block corrupted"); } // Return the padding length. return count; } } ================================================ FILE: Algorithms/Crypto/Utils/ByteEncodingUtils.cs ================================================ using System.Buffers.Binary; using System.Runtime.CompilerServices; namespace Algorithms.Crypto.Utils; /// /// Provides utility methods for converting between byte arrays and 64-bit unsigned integers using big-endian byte order. /// /// /// The class contains static methods that assist in reading and writing 64-bit unsigned integers /// from and to byte arrays or spans in big-endian format. These methods are optimized for cryptographic operations where byte /// encoding is critical for consistency and security. /// public static class ByteEncodingUtils { /// /// Converts an 8-byte segment from a byte array (starting at the specified offset) into a 64-bit unsigned integer using big-endian format. /// /// The byte array containing the input data. /// The offset within the byte array to start reading from. /// A 64-bit unsigned integer representing the big-endian interpretation of the byte array segment. /// Thrown if the specified offset is out of range of the byte array. /// /// This method reads 8 bytes from the specified offset within the byte array and converts them to a 64-bit unsigned integer /// in big-endian format. Big-endian format stores the most significant byte first, followed by the less significant bytes. /// public static ulong BigEndianToUint64(byte[] byteStream, int offset) { return BinaryPrimitives.ReadUInt64BigEndian(byteStream.AsSpan(offset)); } /// /// Converts a read-only span of bytes into a 64-bit unsigned integer using big-endian format. /// /// A read-only span containing the input data. /// A 64-bit unsigned integer representing the big-endian interpretation of the span of bytes. /// /// This method is optimized for performance using the attribute to encourage /// inlining by the compiler. It reads exactly 8 bytes from the input span and converts them into a 64-bit unsigned integer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong BigEndianToUint64(ReadOnlySpan byteStream) { return BinaryPrimitives.ReadUInt64BigEndian(byteStream); } /// /// Writes a 64-bit unsigned integer to a span of bytes using big-endian format. /// /// The 64-bit unsigned integer to write. /// The span of bytes where the value will be written. /// /// This method writes the 64-bit unsigned integer into the span in big-endian format, where the most significant byte is written first. /// The method is optimized using the attribute to improve performance in scenarios /// where frequent byte-to-integer conversions are required, such as cryptographic algorithms. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void UInt64ToBigEndian(ulong value, Span byteStream) { BinaryPrimitives.WriteUInt64BigEndian(byteStream, value); } } ================================================ FILE: Algorithms/Crypto/Utils/LongUtils.cs ================================================ namespace Algorithms.Crypto.Utils; /// /// Provides utility methods for performing bitwise rotation operations (left and right) on 64-bit integers. /// /// /// The class contains methods to rotate 64-bit signed and unsigned integers to the left or right. /// These rotations are crucial in various cryptographic algorithms, where circular shifts are used to mix data and /// introduce non-linearity. The methods use the underlying for efficient, /// hardware-supported bitwise rotations. /// public static class LongUtils { /// /// Rotates the bits of a 64-bit signed integer to the left by a specified number of bits. /// /// The 64-bit signed integer to rotate. /// The number of bits to rotate the integer to the left. /// The result of rotating the integer to the left by the specified distance. /// /// This method uses the underlying method, converting the signed integer to an unsigned integer /// for the rotation, then casting it back to a signed integer. The rotation is performed in a circular manner, where bits shifted /// out of the most significant bit are reintroduced into the least significant bit. /// public static long RotateLeft(long i, int distance) { return (long)BitOperations.RotateLeft((ulong)i, distance); } /// /// Rotates the bits of a 64-bit unsigned integer to the left by a specified number of bits. /// /// The 64-bit unsigned integer to rotate. /// The number of bits to rotate the integer to the left. /// The result of rotating the integer to the left by the specified distance. /// /// The rotation is performed circularly, meaning bits shifted out of the most significant bit are reintroduced into /// the least significant bit. This method is optimized for performance using hardware-supported operations through /// . /// public static ulong RotateLeft(ulong i, int distance) { return BitOperations.RotateLeft(i, distance); } /// /// Rotates the bits of a 64-bit signed integer to the right by a specified number of bits. /// /// The 64-bit signed integer to rotate. /// The number of bits to rotate the integer to the right. /// The result of rotating the integer to the right by the specified distance. /// /// Similar to the left rotation, this method uses to perform the rotation. /// The signed integer is cast to an unsigned integer for the operation and cast back to a signed integer afterward. /// The rotation wraps bits shifted out of the least significant bit into the most significant bit. /// public static long RotateRight(long i, int distance) { return (long)BitOperations.RotateRight((ulong)i, distance); } /// /// Rotates the bits of a 64-bit unsigned integer to the right by a specified number of bits. /// /// The 64-bit unsigned integer to rotate. /// The number of bits to rotate the integer to the right. /// The result of rotating the integer to the right by the specified distance. /// /// This method performs the rotation circularly, where bits shifted out of the least significant bit are reintroduced /// into the most significant bit. The operation uses hardware-supported instructions via . /// public static ulong RotateRight(ulong i, int distance) { return BitOperations.RotateRight(i, distance); } } ================================================ FILE: Algorithms/Crypto/Utils/ValidationUtils.cs ================================================ using Algorithms.Crypto.Exceptions; namespace Algorithms.Crypto.Utils; /// /// Provides utility methods for validating the lengths of input and output data in cryptographic operations. /// /// /// The class contains static methods to validate the length and position of data buffers used in /// cryptographic operations. These methods throw appropriate exceptions such as or /// when the validation fails. These are critical for ensuring that cryptographic computations /// do not run into buffer overflows, underflows, or incorrect input/output buffer lengths. /// public static class ValidationUtils { /// /// Validates that the specified offset and length fit within the bounds of the given buffer. /// /// The byte array to validate. /// The offset into the byte array where validation should start. /// The number of bytes to validate from the specified offset. /// The message that describes the error if the exception is thrown. /// Thrown if the offset and length exceed the bounds of the buffer. /// /// This method ensures that the specified offset and length fit within the bounds of the buffer. If the offset and length /// go out of bounds, a is thrown with the provided error message. /// public static void CheckDataLength(byte[] buffer, int offset, int length, string message) { if (offset > (buffer.Length - length)) { throw new DataLengthException(message); } } /// /// Throws an if the specified condition is true. /// /// A boolean condition indicating whether the exception should be thrown. /// The message that describes the error if the exception is thrown. /// Thrown if the condition is true. /// /// This method performs a simple conditional check for output length validation. If the condition is true, an /// is thrown with the provided message. /// public static void CheckOutputLength(bool condition, string message) { if (condition) { throw new OutputLengthException(message); } } /// /// Validates that the specified offset and length fit within the bounds of the output buffer. /// /// The byte array to validate. /// The offset into the byte array where validation should start. /// The number of bytes to validate from the specified offset. /// The message that describes the error if the exception is thrown. /// Thrown if the offset and length exceed the bounds of the buffer. /// /// This method ensures that the specified offset and length do not exceed the bounds of the output buffer. If the /// validation fails, an is thrown with the provided message. /// public static void CheckOutputLength(byte[] buffer, int offset, int length, string message) { if (offset > (buffer.Length - length)) { throw new OutputLengthException(message); } } /// /// Validates that the length of the output span does not exceed the specified length. /// /// The type of elements in the span. /// The span to validate. /// The maximum allowed length for the output span. /// The message that describes the error if the exception is thrown. /// Thrown if the length of the output span exceeds the specified length. /// /// This method checks that the span does not exceed the specified length. If the span length exceeds the allowed length, /// an is thrown with the provided error message. /// public static void CheckOutputLength(Span output, int length, string message) { if (output.Length > length) { throw new OutputLengthException(message); } } } ================================================ FILE: Algorithms/DataCompression/BurrowsWheelerTransform.cs ================================================ namespace Algorithms.DataCompression; /// /// The Burrows–Wheeler transform (BWT) rearranges a character string into runs of similar characters. /// This is useful for compression, since it tends to be easy to compress a string that has runs of repeated /// characters. /// See here for more info. /// public class BurrowsWheelerTransform { /// /// Encodes the input string using BWT and returns encoded string and the index of original string in the sorted /// rotation matrix. /// /// Input string. public (string Encoded, int Index) Encode(string s) { if (s.Length == 0) { return (string.Empty, 0); } var rotations = GetRotations(s); Array.Sort(rotations, StringComparer.Ordinal); var lastColumn = rotations .Select(x => x[^1]) .ToArray(); var encoded = new string(lastColumn); return (encoded, Array.IndexOf(rotations, s)); } /// /// Decodes the input string and returns original string. /// /// Encoded string. /// Index of original string in the sorted rotation matrix. public string Decode(string s, int index) { if (s.Length == 0) { return string.Empty; } var rotations = new string[s.Length]; for (var i = 0; i < s.Length; i++) { for (var j = 0; j < s.Length; j++) { rotations[j] = s[j] + rotations[j]; } Array.Sort(rotations, StringComparer.Ordinal); } return rotations[index]; } private string[] GetRotations(string s) { var result = new string[s.Length]; for (var i = 0; i < s.Length; i++) { result[i] = s.Substring(i) + s.Substring(0, i); } return result; } } ================================================ FILE: Algorithms/DataCompression/HuffmanCompressor.cs ================================================ using Algorithms.Sorters.Comparison; namespace Algorithms.DataCompression; /// /// Greedy lossless compression algorithm. /// public class HuffmanCompressor(IComparisonSorter sorter, Translator translator) { // TODO: Use partial sorter private readonly IComparisonSorter sorter = sorter; private readonly Translator translator = translator; /// /// Given an input string, returns a new compressed string /// using huffman encoding. /// /// Text message to compress. /// Compressed string and keys to decompress it. public (string CompressedText, Dictionary DecompressionKeys) Compress(string uncompressedText) { if (string.IsNullOrEmpty(uncompressedText)) { return (string.Empty, []); } if (uncompressedText.Distinct().Count() == 1) { var dict = new Dictionary { ["1"] = uncompressedText[0].ToString(), }; return (new string('1', uncompressedText.Length), dict); } var nodes = GetListNodesFromText(uncompressedText); var tree = GenerateHuffmanTree(nodes); var (compressionKeys, decompressionKeys) = GetKeys(tree); return (translator.Translate(uncompressedText, compressionKeys), decompressionKeys); } /// /// Finds frequency for each character in the text. /// /// Symbol-frequency array. private static ListNode[] GetListNodesFromText(string text) { var occurenceCounts = new Dictionary(); foreach (var ch in text) { if (!occurenceCounts.ContainsKey(ch)) { occurenceCounts.Add(ch, 0); } occurenceCounts[ch]++; } return occurenceCounts.Select(kvp => new ListNode(kvp.Key, 1d * kvp.Value / text.Length)).ToArray(); } private (Dictionary CompressionKeys, Dictionary DecompressionKeys) GetKeys( ListNode tree) { var compressionKeys = new Dictionary(); var decompressionKeys = new Dictionary(); if (tree.HasData) { compressionKeys.Add(tree.Data.ToString(), string.Empty); decompressionKeys.Add(string.Empty, tree.Data.ToString()); return (compressionKeys, decompressionKeys); } if (tree.LeftChild is not null) { var (lsck, lsdk) = GetKeys(tree.LeftChild); compressionKeys.AddMany(lsck.Select(kvp => (kvp.Key, "0" + kvp.Value))); decompressionKeys.AddMany(lsdk.Select(kvp => ("0" + kvp.Key, kvp.Value))); } if (tree.RightChild is not null) { var (rsck, rsdk) = GetKeys(tree.RightChild); compressionKeys.AddMany(rsck.Select(kvp => (kvp.Key, "1" + kvp.Value))); decompressionKeys.AddMany(rsdk.Select(kvp => ("1" + kvp.Key, kvp.Value))); return (compressionKeys, decompressionKeys); } return (compressionKeys, decompressionKeys); } private ListNode GenerateHuffmanTree(ListNode[] nodes) { var comparer = new ListNodeComparer(); while (nodes.Length > 1) { sorter.Sort(nodes, comparer); var left = nodes[0]; var right = nodes[1]; var newNodes = new ListNode[nodes.Length - 1]; Array.Copy(nodes, 2, newNodes, 1, nodes.Length - 2); newNodes[0] = new ListNode(left, right); nodes = newNodes; } return nodes[0]; } /// /// Represents tree structure for the algorithm. /// public class ListNode { public ListNode(char data, double frequency) { HasData = true; Data = data; Frequency = frequency; } public ListNode(ListNode leftChild, ListNode rightChild) { LeftChild = leftChild; RightChild = rightChild; Frequency = leftChild.Frequency + rightChild.Frequency; } public char Data { get; } public bool HasData { get; } public double Frequency { get; } public ListNode? RightChild { get; } public ListNode? LeftChild { get; } } public class ListNodeComparer : IComparer { public int Compare(ListNode? x, ListNode? y) { if (x is null || y is null) { return 0; } return x.Frequency.CompareTo(y.Frequency); } } } ================================================ FILE: Algorithms/DataCompression/ShannonFanoCompressor.cs ================================================ using Algorithms.Knapsack; namespace Algorithms.DataCompression; /// /// Greedy lossless compression algorithm. /// public class ShannonFanoCompressor( IHeuristicKnapsackSolver<(char Symbol, double Frequency)> splitter, Translator translator) { private readonly IHeuristicKnapsackSolver<(char Symbol, double Frequency)> splitter = splitter; private readonly Translator translator = translator; /// /// Given an input string, returns a new compressed string /// using Shannon-Fano encoding. /// /// Text message to compress. /// Compressed string and keys to decompress it. public (string CompressedText, Dictionary DecompressionKeys) Compress(string uncompressedText) { if (string.IsNullOrEmpty(uncompressedText)) { return (string.Empty, new Dictionary()); } if (uncompressedText.Distinct().Count() == 1) { var dict = new Dictionary { { "1", uncompressedText[0].ToString() }, }; return (new string('1', uncompressedText.Length), dict); } var node = GetListNodeFromText(uncompressedText); var tree = GenerateShannonFanoTree(node); var (compressionKeys, decompressionKeys) = GetKeys(tree); return (translator.Translate(uncompressedText, compressionKeys), decompressionKeys); } private (Dictionary CompressionKeys, Dictionary DecompressionKeys) GetKeys( ListNode tree) { var compressionKeys = new Dictionary(); var decompressionKeys = new Dictionary(); if (tree.Data.Length == 1) { compressionKeys.Add(tree.Data[0].Symbol.ToString(), string.Empty); decompressionKeys.Add(string.Empty, tree.Data[0].Symbol.ToString()); return (compressionKeys, decompressionKeys); } if (tree.LeftChild is not null) { var (lsck, lsdk) = GetKeys(tree.LeftChild); compressionKeys.AddMany(lsck.Select(kvp => (kvp.Key, "0" + kvp.Value))); decompressionKeys.AddMany(lsdk.Select(kvp => ("0" + kvp.Key, kvp.Value))); } if (tree.RightChild is not null) { var (rsck, rsdk) = GetKeys(tree.RightChild); compressionKeys.AddMany(rsck.Select(kvp => (kvp.Key, "1" + kvp.Value))); decompressionKeys.AddMany(rsdk.Select(kvp => ("1" + kvp.Key, kvp.Value))); } return (compressionKeys, decompressionKeys); } private ListNode GenerateShannonFanoTree(ListNode node) { if (node.Data.Length == 1) { return node; } var left = splitter.Solve(node.Data, 0.5 * node.Data.Sum(x => x.Frequency), x => x.Frequency, _ => 1); var right = node.Data.Except(left).ToArray(); node.LeftChild = GenerateShannonFanoTree(new ListNode(left)); node.RightChild = GenerateShannonFanoTree(new ListNode(right)); return node; } /// /// Finds frequency for each character in the text. /// /// Symbol-frequency array. private ListNode GetListNodeFromText(string text) { var occurenceCounts = new Dictionary(); for (var i = 0; i < text.Length; i++) { var ch = text[i]; if (!occurenceCounts.ContainsKey(ch)) { occurenceCounts.Add(ch, 0); } occurenceCounts[ch]++; } return new ListNode(occurenceCounts.Select(kvp => (kvp.Key, 1d * kvp.Value / text.Length)).ToArray()); } /// /// Represents tree structure for the algorithm. /// public class ListNode((char Symbol, double Frequency)[] data) { public (char Symbol, double Frequency)[] Data { get; } = data; public ListNode? RightChild { get; set; } public ListNode? LeftChild { get; set; } } } ================================================ FILE: Algorithms/DataCompression/Translator.cs ================================================ namespace Algorithms.DataCompression; /// /// Provides method for text conversion by key mapping. /// public class Translator { /// /// Converts the input text according to the translation keys. /// /// Input text. /// Translation keys used for text matching. /// Converted text according to the translation keys. public string Translate(string text, Dictionary translationKeys) { var sb = new StringBuilder(); var start = 0; for (var i = 0; i < text.Length; i++) { var key = text.Substring(start, i - start + 1); if (translationKeys.ContainsKey(key)) { _ = sb.Append(translationKeys[key]); start = i + 1; } } return sb.ToString(); } } ================================================ FILE: Algorithms/Encoders/AutokeyEncorder.cs ================================================ namespace Algorithms.Encoders { /// /// Class for AutoKey encoding strings. /// public class AutokeyEncorder { /// /// Autokey Cipher is a type of polyalphabetic cipher. /// This works by choosing a key (a word or short phrase), /// then you append the plaintext to itself to form a longer key. /// /// The string to be appended to the key. /// The string to be appended to the plaintext. /// The Autokey encoded string (All Uppercase). public string Encode(string plainText, string keyword) { plainText = Regex.Replace(plainText.ToUpper(CultureInfo.InvariantCulture), "[^A-Z]", string.Empty); keyword = keyword.ToUpper(CultureInfo.InvariantCulture); keyword += plainText; StringBuilder cipherText = new StringBuilder(); for (int i = 0; i < plainText.Length; i++) { char plainCharacter = plainText[i]; char keyCharacter = keyword[i]; int encryptedCharacter = (plainCharacter - 'A' + keyCharacter - 'A') % 26 + 'A'; cipherText.Append((char)encryptedCharacter); } return cipherText.ToString(); } /// /// Removed the key from the encoded string. /// /// The encoded string. /// The key to be removed from the encoded string. /// The plaintext (All Uppercase). public string Decode(string cipherText, string keyword) { cipherText = Regex.Replace(cipherText.ToUpper(CultureInfo.InvariantCulture), "[^A-Z]", string.Empty); keyword = keyword.ToUpper(CultureInfo.InvariantCulture); StringBuilder plainText = new StringBuilder(); StringBuilder extendedKeyword = new StringBuilder(keyword); for (int i = 0; i < cipherText.Length; i++) { char cipherCharacter = cipherText[i]; char keywordCharacter = extendedKeyword[i]; int decryptedCharacter = (cipherCharacter - 'A' - (keywordCharacter - 'A') + 26) % 26 + 'A'; plainText.Append((char)decryptedCharacter); extendedKeyword.Append((char)decryptedCharacter); } return plainText.ToString(); } } } ================================================ FILE: Algorithms/Encoders/BlowfishEncoder.cs ================================================ namespace Algorithms.Encoders; /// /// /// The Blowfish algorithm is a symmetric-key block cipher, which means it uses the same secret key to encrypt and /// decrypt data. It was designed by Bruce Schneier in 1993. /// /// /// The blowfish algorithm works on 64-bit blocks of data, which are divided into two 32-bit halves: left and right. /// It uses a variable-length key, from 32 bits to 448 bits, to generate 18 subkeys and four S-boxes, which are arrays /// of 256 32-bit words. The subkeys and the S-boxes are key-dependent, meaning that they change according to the secret key. /// /// /// The blowfish algorithm performs 16 rounds of encryption or decryption on each block of data, using a Feistel network /// structure. In each round, the left half is XORed with a subkey, then passed through a function F that applies four /// S-box lookups and two XOR operations. The output of F is then XORed with the right half. The left and right halves /// are swapped at the end of each round, except for the last one. The final output is XORed with two more subkeys to /// produce the encrypted or decrypted block. /// /// Blowfish on Wikipedia. /// public class BlowfishEncoder { // Initialize modVal to 2^32 private const ulong ModVal = 4294967296L; // Initialize the substitution boxes private readonly string[][] s = [ [ "d1310ba6", "98dfb5ac", "2ffd72db", "d01adfb7", "b8e1afed", "6a267e96", "ba7c9045", "f12c7f99", "24a19947", "b3916cf7", "0801f2e2", "858efc16", "636920d8", "71574e69", "a458fea3", "f4933d7e", "0d95748f", "728eb658", "718bcd58", "82154aee", "7b54a41d", "c25a59b5", "9c30d539", "2af26013", "c5d1b023", "286085f0", "ca417918", "b8db38ef", "8e79dcb0", "603a180e", "6c9e0e8b", "b01e8a3e", "d71577c1", "bd314b27", "78af2fda", "55605c60", "e65525f3", "aa55ab94", "57489862", "63e81440", "55ca396a", "2aab10b6", "b4cc5c34", "1141e8ce", "a15486af", "7c72e993", "b3ee1411", "636fbc2a", "2ba9c55d", "741831f6", "ce5c3e16", "9b87931e", "afd6ba33", "6c24cf5c", "7a325381", "28958677", "3b8f4898", "6b4bb9af", "c4bfe81b", "66282193", "61d809cc", "fb21a991", "487cac60", "5dec8032", "ef845d5d", "e98575b1", "dc262302", "eb651b88", "23893e81", "d396acc5", "0f6d6ff3", "83f44239", "2e0b4482", "a4842004", "69c8f04a", "9e1f9b5e", "21c66842", "f6e96c9a", "670c9c61", "abd388f0", "6a51a0d2", "d8542f68", "960fa728", "ab5133a3", "6eef0b6c", "137a3be4", "ba3bf050", "7efb2a98", "a1f1651d", "39af0176", "66ca593e", "82430e88", "8cee8619", "456f9fb4", "7d84a5c3", "3b8b5ebe", "e06f75d8", "85c12073", "401a449f", "56c16aa6", "4ed3aa62", "363f7706", "1bfedf72", "429b023d", "37d0d724", "d00a1248", "db0fead3", "49f1c09b", "075372c9", "80991b7b", "25d479d8", "f6e8def7", "e3fe501a", "b6794c3b", "976ce0bd", "04c006ba", "c1a94fb6", "409f60c4", "5e5c9ec2", "196a2463", "68fb6faf", "3e6c53b5", "1339b2eb", "3b52ec6f", "6dfc511f", "9b30952c", "cc814544", "af5ebd09", "bee3d004", "de334afd", "660f2807", "192e4bb3", "c0cba857", "45c8740f", "d20b5f39", "b9d3fbdb", "5579c0bd", "1a60320a", "d6a100c6", "402c7279", "679f25fe", "fb1fa3cc", "8ea5e9f8", "db3222f8", "3c7516df", "fd616b15", "2f501ec8", "ad0552ab", "323db5fa", "fd238760", "53317b48", "3e00df82", "9e5c57bb", "ca6f8ca0", "1a87562e", "df1769db", "d542a8f6", "287effc3", "ac6732c6", "8c4f5573", "695b27b0", "bbca58c8", "e1ffa35d", "b8f011a0", "10fa3d98", "fd2183b8", "4afcb56c", "2dd1d35b", "9a53e479", "b6f84565", "d28e49bc", "4bfb9790", "e1ddf2da", "a4cb7e33", "62fb1341", "cee4c6e8", "ef20cada", "36774c01", "d07e9efe", "2bf11fb4", "95dbda4d", "ae909198", "eaad8e71", "6b93d5a0", "d08ed1d0", "afc725e0", "8e3c5b2f", "8e7594b7", "8ff6e2fb", "f2122b64", "8888b812", "900df01c", "4fad5ea0", "688fc31c", "d1cff191", "b3a8c1ad", "2f2f2218", "be0e1777", "ea752dfe", "8b021fa1", "e5a0cc0f", "b56f74e8", "18acf3d6", "ce89e299", "b4a84fe0", "fd13e0b7", "7cc43b81", "d2ada8d9", "165fa266", "80957705", "93cc7314", "211a1477", "e6ad2065", "77b5fa86", "c75442f5", "fb9d35cf", "ebcdaf0c", "7b3e89a0", "d6411bd3", "ae1e7e49", "00250e2d", "2071b35e", "226800bb", "57b8e0af", "2464369b", "f009b91e", "5563911d", "59dfa6aa", "78c14389", "d95a537f", "207d5ba2", "02e5b9c5", "83260376", "6295cfa9", "11c81968", "4e734a41", "b3472dca", "7b14a94a", "1b510052", "9a532915", "d60f573f", "bc9bc6e4", "2b60a476", "81e67400", "08ba6fb5", "571be91f", "f296ec6b", "2a0dd915", "b6636521", "e7b9f9b6", "ff34052e", "c5855664", "53b02d5d", "a99f8fa1", "08ba4799", "6e85076a", ], [ "4b7a70e9", "b5b32944", "db75092e", "c4192623", "ad6ea6b0", "49a7df7d", "9cee60b8", "8fedb266", "ecaa8c71", "699a17ff", "5664526c", "c2b19ee1", "193602a5", "75094c29", "a0591340", "e4183a3e", "3f54989a", "5b429d65", "6b8fe4d6", "99f73fd6", "a1d29c07", "efe830f5", "4d2d38e6", "f0255dc1", "4cdd2086", "8470eb26", "6382e9c6", "021ecc5e", "09686b3f", "3ebaefc9", "3c971814", "6b6a70a1", "687f3584", "52a0e286", "b79c5305", "aa500737", "3e07841c", "7fdeae5c", "8e7d44ec", "5716f2b8", "b03ada37", "f0500c0d", "f01c1f04", "0200b3ff", "ae0cf51a", "3cb574b2", "25837a58", "dc0921bd", "d19113f9", "7ca92ff6", "94324773", "22f54701", "3ae5e581", "37c2dadc", "c8b57634", "9af3dda7", "a9446146", "0fd0030e", "ecc8c73e", "a4751e41", "e238cd99", "3bea0e2f", "3280bba1", "183eb331", "4e548b38", "4f6db908", "6f420d03", "f60a04bf", "2cb81290", "24977c79", "5679b072", "bcaf89af", "de9a771f", "d9930810", "b38bae12", "dccf3f2e", "5512721f", "2e6b7124", "501adde6", "9f84cd87", "7a584718", "7408da17", "bc9f9abc", "e94b7d8c", "ec7aec3a", "db851dfa", "63094366", "c464c3d2", "ef1c1847", "3215d908", "dd433b37", "24c2ba16", "12a14d43", "2a65c451", "50940002", "133ae4dd", "71dff89e", "10314e55", "81ac77d6", "5f11199b", "043556f1", "d7a3c76b", "3c11183b", "5924a509", "f28fe6ed", "97f1fbfa", "9ebabf2c", "1e153c6e", "86e34570", "eae96fb1", "860e5e0a", "5a3e2ab3", "771fe71c", "4e3d06fa", "2965dcb9", "99e71d0f", "803e89d6", "5266c825", "2e4cc978", "9c10b36a", "c6150eba", "94e2ea78", "a5fc3c53", "1e0a2df4", "f2f74ea7", "361d2b3d", "1939260f", "19c27960", "5223a708", "f71312b6", "ebadfe6e", "eac31f66", "e3bc4595", "a67bc883", "b17f37d1", "018cff28", "c332ddef", "be6c5aa5", "65582185", "68ab9802", "eecea50f", "db2f953b", "2aef7dad", "5b6e2f84", "1521b628", "29076170", "ecdd4775", "619f1510", "13cca830", "eb61bd96", "0334fe1e", "aa0363cf", "b5735c90", "4c70a239", "d59e9e0b", "cbaade14", "eecc86bc", "60622ca7", "9cab5cab", "b2f3846e", "648b1eaf", "19bdf0ca", "a02369b9", "655abb50", "40685a32", "3c2ab4b3", "319ee9d5", "c021b8f7", "9b540b19", "875fa099", "95f7997e", "623d7da8", "f837889a", "97e32d77", "11ed935f", "16681281", "0e358829", "c7e61fd6", "96dedfa1", "7858ba99", "57f584a5", "1b227263", "9b83c3ff", "1ac24696", "cdb30aeb", "532e3054", "8fd948e4", "6dbc3128", "58ebf2ef", "34c6ffea", "fe28ed61", "ee7c3c73", "5d4a14d9", "e864b7e3", "42105d14", "203e13e0", "45eee2b6", "a3aaabea", "db6c4f15", "facb4fd0", "c742f442", "ef6abbb5", "654f3b1d", "41cd2105", "d81e799e", "86854dc7", "e44b476a", "3d816250", "cf62a1f2", "5b8d2646", "fc8883a0", "c1c7b6a3", "7f1524c3", "69cb7492", "47848a0b", "5692b285", "095bbf00", "ad19489d", "1462b174", "23820e00", "58428d2a", "0c55f5ea", "1dadf43e", "233f7061", "3372f092", "8d937e41", "d65fecf1", "6c223bdb", "7cde3759", "cbee7460", "4085f2a7", "ce77326e", "a6078084", "19f8509e", "e8efd855", "61d99735", "a969a7aa", "c50c06c2", "5a04abfc", "800bcadc", "9e447a2e", "c3453484", "fdd56705", "0e1e9ec9", "db73dbd3", "105588cd", "675fda79", "e3674340", "c5c43465", "713e38d8", "3d28f89e", "f16dff20", "153e21e7", "8fb03d4a", "e6e39f2b", "db83adf7", ], [ "e93d5a68", "948140f7", "f64c261c", "94692934", "411520f7", "7602d4f7", "bcf46b2e", "d4a20068", "d4082471", "3320f46a", "43b7d4b7", "500061af", "1e39f62e", "97244546", "14214f74", "bf8b8840", "4d95fc1d", "96b591af", "70f4ddd3", "66a02f45", "bfbc09ec", "03bd9785", "7fac6dd0", "31cb8504", "96eb27b3", "55fd3941", "da2547e6", "abca0a9a", "28507825", "530429f4", "0a2c86da", "e9b66dfb", "68dc1462", "d7486900", "680ec0a4", "27a18dee", "4f3ffea2", "e887ad8c", "b58ce006", "7af4d6b6", "aace1e7c", "d3375fec", "ce78a399", "406b2a42", "20fe9e35", "d9f385b9", "ee39d7ab", "3b124e8b", "1dc9faf7", "4b6d1856", "26a36631", "eae397b2", "3a6efa74", "dd5b4332", "6841e7f7", "ca7820fb", "fb0af54e", "d8feb397", "454056ac", "ba489527", "55533a3a", "20838d87", "fe6ba9b7", "d096954b", "55a867bc", "a1159a58", "cca92963", "99e1db33", "a62a4a56", "3f3125f9", "5ef47e1c", "9029317c", "fdf8e802", "04272f70", "80bb155c", "05282ce3", "95c11548", "e4c66d22", "48c1133f", "c70f86dc", "07f9c9ee", "41041f0f", "404779a4", "5d886e17", "325f51eb", "d59bc0d1", "f2bcc18f", "41113564", "257b7834", "602a9c60", "dff8e8a3", "1f636c1b", "0e12b4c2", "02e1329e", "af664fd1", "cad18115", "6b2395e0", "333e92e1", "3b240b62", "eebeb922", "85b2a20e", "e6ba0d99", "de720c8c", "2da2f728", "d0127845", "95b794fd", "647d0862", "e7ccf5f0", "5449a36f", "877d48fa", "c39dfd27", "f33e8d1e", "0a476341", "992eff74", "3a6f6eab", "f4f8fd37", "a812dc60", "a1ebddf8", "991be14c", "db6e6b0d", "c67b5510", "6d672c37", "2765d43b", "dcd0e804", "f1290dc7", "cc00ffa3", "b5390f92", "690fed0b", "667b9ffb", "cedb7d9c", "a091cf0b", "d9155ea3", "bb132f88", "515bad24", "7b9479bf", "763bd6eb", "37392eb3", "cc115979", "8026e297", "f42e312d", "6842ada7", "c66a2b3b", "12754ccc", "782ef11c", "6a124237", "b79251e7", "06a1bbe6", "4bfb6350", "1a6b1018", "11caedfa", "3d25bdd8", "e2e1c3c9", "44421659", "0a121386", "d90cec6e", "d5abea2a", "64af674e", "da86a85f", "bebfe988", "64e4c3fe", "9dbc8057", "f0f7c086", "60787bf8", "6003604d", "d1fd8346", "f6381fb0", "7745ae04", "d736fccc", "83426b33", "f01eab71", "b0804187", "3c005e5f", "77a057be", "bde8ae24", "55464299", "bf582e61", "4e58f48f", "f2ddfda2", "f474ef38", "8789bdc2", "5366f9c3", "c8b38e74", "b475f255", "46fcd9b9", "7aeb2661", "8b1ddf84", "846a0e79", "915f95e2", "466e598e", "20b45770", "8cd55591", "c902de4c", "b90bace1", "bb8205d0", "11a86248", "7574a99e", "b77f19b6", "e0a9dc09", "662d09a1", "c4324633", "e85a1f02", "09f0be8c", "4a99a025", "1d6efe10", "1ab93d1d", "0ba5a4df", "a186f20f", "2868f169", "dcb7da83", "573906fe", "a1e2ce9b", "4fcd7f52", "50115e01", "a70683fa", "a002b5c4", "0de6d027", "9af88c27", "773f8641", "c3604c06", "61a806b5", "f0177a28", "c0f586e0", "006058aa", "30dc7d62", "11e69ed7", "2338ea63", "53c2dd94", "c2c21634", "bbcbee56", "90bcb6de", "ebfc7da1", "ce591d76", "6f05e409", "4b7c0188", "39720a3d", "7c927c24", "86e3725f", "724d9db9", "1ac15bb4", "d39eb8fc", "ed545578", "08fca5b5", "d83d7cd3", "4dad0fc4", "1e50ef5e", "b161e6f8", "a28514d9", "6c51133c", "6fd5c7e7", "56e14ec4", "362abfce", "ddc6c837", "d79a3234", "92638212", "670efa8e", "406000e0", ], [ "3a39ce37", "d3faf5cf", "abc27737", "5ac52d1b", "5cb0679e", "4fa33742", "d3822740", "99bc9bbe", "d5118e9d", "bf0f7315", "d62d1c7e", "c700c47b", "b78c1b6b", "21a19045", "b26eb1be", "6a366eb4", "5748ab2f", "bc946e79", "c6a376d2", "6549c2c8", "530ff8ee", "468dde7d", "d5730a1d", "4cd04dc6", "2939bbdb", "a9ba4650", "ac9526e8", "be5ee304", "a1fad5f0", "6a2d519a", "63ef8ce2", "9a86ee22", "c089c2b8", "43242ef6", "a51e03aa", "9cf2d0a4", "83c061ba", "9be96a4d", "8fe51550", "ba645bd6", "2826a2f9", "a73a3ae1", "4ba99586", "ef5562e9", "c72fefd3", "f752f7da", "3f046f69", "77fa0a59", "80e4a915", "87b08601", "9b09e6ad", "3b3ee593", "e990fd5a", "9e34d797", "2cf0b7d9", "022b8b51", "96d5ac3a", "017da67d", "d1cf3ed6", "7c7d2d28", "1f9f25cf", "adf2b89b", "5ad6b472", "5a88f54c", "e029ac71", "e019a5e6", "47b0acfd", "ed93fa9b", "e8d3c48d", "283b57cc", "f8d56629", "79132e28", "785f0191", "ed756055", "f7960e44", "e3d35e8c", "15056dd4", "88f46dba", "03a16125", "0564f0bd", "c3eb9e15", "3c9057a2", "97271aec", "a93a072a", "1b3f6d9b", "1e6321f5", "f59c66fb", "26dcf319", "7533d928", "b155fdf5", "03563482", "8aba3cbb", "28517711", "c20ad9f8", "abcc5167", "ccad925f", "4de81751", "3830dc8e", "379d5862", "9320f991", "ea7a90c2", "fb3e7bce", "5121ce64", "774fbe32", "a8b6e37e", "c3293d46", "48de5369", "6413e680", "a2ae0810", "dd6db224", "69852dfd", "09072166", "b39a460a", "6445c0dd", "586cdecf", "1c20c8ae", "5bbef7dd", "1b588d40", "ccd2017f", "6bb4e3bb", "dda26a7e", "3a59ff45", "3e350a44", "bcb4cdd5", "72eacea8", "fa6484bb", "8d6612ae", "bf3c6f47", "d29be463", "542f5d9e", "aec2771b", "f64e6370", "740e0d8d", "e75b1357", "f8721671", "af537d5d", "4040cb08", "4eb4e2cc", "34d2466a", "0115af84", "e1b00428", "95983a1d", "06b89fb4", "ce6ea048", "6f3f3b82", "3520ab82", "011a1d4b", "277227f8", "611560b1", "e7933fdc", "bb3a792b", "344525bd", "a08839e1", "51ce794b", "2f32c9b7", "a01fbac9", "e01cc87e", "bcc7d1f6", "cf0111c3", "a1e8aac7", "1a908749", "d44fbd9a", "d0dadecb", "d50ada38", "0339c32a", "c6913667", "8df9317c", "e0b12b4f", "f79e59b7", "43f5bb3a", "f2d519ff", "27d9459c", "bf97222c", "15e6fc2a", "0f91fc71", "9b941525", "fae59361", "ceb69ceb", "c2a86459", "12baa8d1", "b6c1075e", "e3056a0c", "10d25065", "cb03a442", "e0ec6e0e", "1698db3b", "4c98a0be", "3278e964", "9f1f9532", "e0d392df", "d3a0342b", "8971f21e", "1b0a7441", "4ba3348c", "c5be7120", "c37632d8", "df359f8d", "9b992f2e", "e60b6f47", "0fe3f11d", "e54cda54", "1edad891", "ce6279cf", "cd3e7e6f", "1618b166", "fd2c1d05", "848fd2c5", "f6fb2299", "f523f357", "a6327623", "93a83531", "56cccd02", "acf08162", "5a75ebb5", "6e163697", "88d273cc", "de966292", "81b949d0", "4c50901b", "71c65614", "e6c6c7bd", "327a140a", "45e1d006", "c3f27b9a", "c9aa53fd", "62a80f00", "bb25bfe2", "35bdd2f6", "71126905", "b2040222", "b6cbcf7c", "cd769c2b", "53113ec0", "1640e3d3", "38abbd60", "2547adf0", "ba38209c", "f746ce76", "77afa1c5", "20756060", "85cbfe4e", "8ae88dd8", "7aaaf9b0", "4cf9aa7e", "1948c25c", "02fb8a8c", "01c36ae4", "d6ebe1f9", "90d4f869", "a65cdea0", "3f09252d", "c208e69f", "b74e6132", "ce77e25b", "578fdfe3", "3ac372e6", ], ]; // Initialize the P-array sub-keys private readonly string[] p = [ "243f6a88", "85a308d3", "13198a2e", "03707344", "a4093822", "299f31d0", "082efa98", "ec4e6c89", "452821e6", "38d01377", "be5466cf", "34e90c6c", "c0ac29b7", "c97c50dd", "3f84d5b5", "b5470917", "9216d5d9", "8979fb1b", ]; /// /// Generate a key for the encryption algorithm based on the given string parameter. /// /// The key to generate the subkey from. public void GenerateKey(string key) { var j = 0; for (var i = 0; i < p.Length; i++) { // Perform the key expansion var subKey = key.Substring(j % key.Length, 8); p[i] = Xor(p[i], subKey); j += 8; } } /// /// Encrypts a string using the blowfish algorithm. /// /// The string to be encrypted, represented as a hexadecimal string. /// The encrypted string, represented as a hexadecimal string. public string Encrypt(string plainText) { // Perform the 16 rounds of the blowfish algorithm on the plainText. for (var i = 0; i < 16; i++) { plainText = Round(i, plainText); } // Swap the left and right parts of the plainText. var left = plainText.Substring(8, 8); var right = plainText[..8]; // XOR the left half with the last subkey of the P-array. left = Xor(left, p[17]); // XOR the right half with the second to last subkey from the P-array. right = Xor(right, p[16]); // Return the encrypted string as a concatenated string. return left + right; } /// /// Decrypts a string using the blowfish algorithm. /// /// The string to be decrypted, represented as a hexadecimal string. /// The decrypted string, represented as a hexadecimal string. public string Decrypt(string cipherText) { // Perform 16 rounds of the blowfish algorithm on the cipherText in reverse order. for (var i = 17; i > 1; i--) { cipherText = Round(i, cipherText); } // Swap the left and right halves of the cipherText. var left = cipherText.Substring(8, 8); var right = cipherText.Substring(0, 8); // XOR the left half with the first subkey from the P-array. left = Xor(left, p[0]); // XOR the right half with the second subkey from the P-array. right = Xor(right, p[1]); // Return the decrypted string as a concatenated string. return left + right; } /// /// Converts a hexadecimal string to a binary string. /// /// The hexadecimal string to convert. /// A multiple of 4 binary string representing the hexadecimal input. private string HexadecimalToBinary(string hex) { return hex.Select(t => // Convert each character to an integer using base 16 Convert.ToString(Convert.ToInt32(t.ToString(), 16), 2)) // Pad each binary string with leading zeros to make it 4 bits long .Select(fourBitBinary => fourBitBinary.PadLeft(4, '0')) // Concatenate all the binary strings into one .Aggregate(string.Empty, (current, fourBitBinary) => current + fourBitBinary); } /// /// Converts a binary string to a hexadecimal string. /// /// The multiple of 4 binary string to convert. /// A hexadecimal string representing the binary input. private string BinaryToHexadecimal(string binaryInput) { return string.Concat( Enumerable.Range(0, binaryInput.Length / 4) // Select each group of 4 bits .Select(index => binaryInput.Substring(index * 4, 4)) // Convert each group to an integer using base 2 .Select(fourBitBinary => Convert.ToInt32(fourBitBinary, 2) // Convert each integer to a hexadecimal character using base 16 .ToString("x"))); } /// /// Performs a bitwise XOR operation on two hexadecimal strings and returns the result. /// /// The first hexadecimal string to XOR. /// The second hexadecimal string to XOR. /// A hexadecimal string representing the XOR of the inputs. private string Xor(string left, string right) { // Convert the hexadecimal strings to binary strings using a helper method left = HexadecimalToBinary(left); right = HexadecimalToBinary(right); var xor = new StringBuilder(); // Loop through each bit in the binary strings for (var i = 0; i < left.Length; i++) { // Perform a bitwise XOR operation on the corresponding bits and append the result to xor xor.Append((char)(((left[i] - '0') ^ (right[i] - '0')) + '0')); } // Convert the binary string to a hexadecimal string var result = BinaryToHexadecimal(xor.ToString()); return result; } /// /// Adds two hexadecimal strings and returns the result modulo _modVal. /// /// The first hexadecimal string to add. /// The second hexadecimal string to add. /// A hexadecimal string representing the sum of the inputs modulo _modVal. private string AddAndMod(string left, string right) { // Convert the hexadecimal strings to unsigned 64-bit integers using base 16 var leftNumber = Convert.ToUInt64(left, 16); var rightNumber = Convert.ToUInt64(right, 16); // Add the two integers and calculate the remainder after dividing by _modVal var total = (leftNumber + rightNumber) % ModVal; // Convert the result to a hexadecimal string using base 16 var result = total.ToString("x"); // Pad the result with leading zeros to make it 8 characters long result = "00000000" + result; // Return the last 8 characters of the result return result[^8..]; } /// /// Performs the F function on a 32-bit input and returns a 32-bit output. /// /// The 32-bit hexadecimal input to the F function. /// The 32-bit hexadecimal output of the F function. /// /// The F function is a non-linear function that operates on a 32-bit input and produces a 32-bit output. It is used /// to generate the sub-keys and to perform the encryption and decryption of the data blocks. /// private string F(string plainText) { var a = new string[4]; for (var i = 0; i < 8; i += 2) { var col = Convert.ToUInt64(HexadecimalToBinary(plainText.Substring(i, 2)), 2); a[i / 2] = s[i / 2][col]; } var answer = AddAndMod(a[0], a[1]); answer = Xor(answer, a[2]); answer = AddAndMod(answer, a[3]); return answer; } /// /// Performs one round of the blowfish encryption on a 64-bit block of data. /// /// The round number, from 0 to 15, indicating which subkey from the P-array to use. /// The 64-bit block of data to be encrypted or decrypted, represented as a hexadecimal string. /// The encrypted or decrypted block of data, represented as a hexadecimal string. private string Round(int feistelRound, string plainText) { // Split the plainText into two 32-bit halves. var left = plainText[..8]; var right = plainText.Substring(8, 8); // XOR the left half with the subkey from the P-array. left = Xor(left, p[feistelRound]); // Apply the F function to the left half. var fOutput = F(left); // XOR the output of the F function with the right half. right = Xor(fOutput, right); // Swap the left and right halves and return them as a concatenated string. return right + left; } } ================================================ FILE: Algorithms/Encoders/CaesarEncoder.cs ================================================ namespace Algorithms.Encoders; /// /// Encodes using caesar cypher. /// public class CaesarEncoder : IEncoder { /// /// Encodes text using specified key, /// time complexity: O(n), /// space complexity: O(n), /// where n - text length. /// /// Text to be encoded. /// Key that will be used to encode the text. /// Encoded text. public string Encode(string text, int key) => Cipher(text, key); /// /// Decodes text that was encoded using specified key, /// time complexity: O(n), /// space complexity: O(n), /// where n - text length. /// /// Text to be decoded. /// Key that was used to encode the text. /// Decoded text. public string Decode(string text, int key) => Cipher(text, -key); private static string Cipher(string text, int key) { var newText = new StringBuilder(text.Length); for (var i = 0; i < text.Length; i++) { if (!char.IsLetter(text[i])) { _ = newText.Append(text[i]); continue; } var letterA = char.IsUpper(text[i]) ? 'A' : 'a'; var letterZ = char.IsUpper(text[i]) ? 'Z' : 'z'; var c = text[i] + key; c -= c > letterZ ? 26 * (1 + (c - letterZ - 1) / 26) : 0; c += c < letterA ? 26 * (1 + (letterA - c - 1) / 26) : 0; _ = newText.Append((char)c); } return newText.ToString(); } } ================================================ FILE: Algorithms/Encoders/FeistelCipher.cs ================================================ namespace Algorithms.Encoders; /// /// Encodes using Feistel cipher. /// https://en.wikipedia.org/wiki/Feistel_cipher /// In cryptography, a Feistel cipher (also known as Luby–Rackoff block cipher) /// is a symmetric structure used in the construction of block ciphers, /// named after the German-born physicist and cryptographer Horst Feistel /// who did pioneering research while working for IBM (USA) /// A large proportion of block ciphers use the scheme, including the US DES, /// the Soviet/Russian GOST and the more recent Blowfish and Twofish ciphers. /// public class FeistelCipher : IEncoder { // number of rounds to transform data block, each round a new "round" key is generated. private const int Rounds = 32; /// /// Encodes text using specified key, /// where n - text length. /// /// Text to be encoded. /// Key that will be used to encode the text. /// Error: key should be more than 0x00001111 for better encoding, key=0 will throw DivideByZero exception. /// Encoded text. public string Encode(string text, uint key) { List blocksListPlain = SplitTextToBlocks(text); StringBuilder encodedText = new(); foreach (ulong block in blocksListPlain) { uint temp = 0; // decompose a block to two subblocks 0x0123456789ABCDEF => 0x01234567 & 0x89ABCDEF uint rightSubblock = (uint)(block & 0x00000000FFFFFFFF); uint leftSubblock = (uint)(block >> 32); uint roundKey; // Feistel "network" itself for (int round = 0; round < Rounds; round++) { roundKey = GetRoundKey(key, round); temp = rightSubblock ^ BlockModification(leftSubblock, roundKey); rightSubblock = leftSubblock; leftSubblock = temp; } // compile text string formating the block value to text (hex based), length of the output = 16 byte always ulong encodedBlock = leftSubblock; encodedBlock = (encodedBlock << 32) | rightSubblock; encodedText.Append(string.Format("{0:X16}", encodedBlock)); } return encodedText.ToString(); } /// /// Decodes text that was encoded using specified key. /// /// Text to be decoded. /// Key that was used to encode the text. /// Error: key should be more than 0x00001111 for better encoding, key=0 will throw DivideByZero exception. /// Error: The length of text should be divisible by 16 as it the block lenght is 16 bytes. /// Decoded text. public string Decode(string text, uint key) { // The plain text will be padded to fill the size of block (16 bytes) if (text.Length % 16 != 0) { throw new ArgumentException($"The length of {nameof(key)} should be divisible by 16"); } List blocksListEncoded = GetBlocksFromEncodedText(text); StringBuilder decodedTextHex = new(); foreach (ulong block in blocksListEncoded) { uint temp = 0; // decompose a block to two subblocks 0x0123456789ABCDEF => 0x01234567 & 0x89ABCDEF uint rightSubblock = (uint)(block & 0x00000000FFFFFFFF); uint leftSubblock = (uint)(block >> 32); // Feistel "network" - decoding, the order of rounds and operations on the blocks is reverted uint roundKey; for (int round = Rounds - 1; round >= 0; round--) { roundKey = GetRoundKey(key, round); temp = leftSubblock ^ BlockModification(rightSubblock, roundKey); leftSubblock = rightSubblock; rightSubblock = temp; } // compose decoded block ulong decodedBlock = leftSubblock; decodedBlock = (decodedBlock << 32) | rightSubblock; for (int i = 0; i < 8; i++) { ulong a = (decodedBlock & 0xFF00000000000000) >> 56; // it's a trick, the code works with non zero characters, if your text has ASCII code 0x00 it will be skipped. if (a != 0) { decodedTextHex.Append((char)a); } decodedBlock = decodedBlock << 8; } } return decodedTextHex.ToString(); } // Using the size of block = 8 bytes this function splts the text and returns set of 8 bytes (ulong) blocks // the last block is extended up to 8 bytes if the tail of the text is smaller than 8 bytes private static List SplitTextToBlocks(string text) { List blocksListPlain = []; byte[] textArray = Encoding.ASCII.GetBytes(text); int offset = 8; for (int i = 0; i < text.Length; i += 8) { // text not always has len%16 == 0, that's why the offset should be adjusted for the last part of the text if (i > text.Length - 8) { offset = text.Length - i; } string block = Convert.ToHexString(textArray, i, offset); blocksListPlain.Add(Convert.ToUInt64(block, 16)); } return blocksListPlain; } // convert the encoded text to the set of ulong values (blocks for decoding) private static List GetBlocksFromEncodedText(string text) { List blocksListPlain = []; for (int i = 0; i < text.Length; i += 16) { ulong block = Convert.ToUInt64(text.Substring(i, 16), 16); blocksListPlain.Add(block); } return blocksListPlain; } // here might be any deterministic math formula private static uint BlockModification(uint block, uint key) { for (int i = 0; i < 32; i++) { // 0x55555555 for the better distribution 0 an 1 in the block block = ((block ^ 0x55555555) * block) % key; block = block ^ key; } return block; } // There are many ways to generate a round key, any deterministic math formula does work private static uint GetRoundKey(uint key, int round) { // "round + 2" - to avoid a situation when pow(key,1) ^ key = key ^ key = 0 uint a = (uint)Math.Pow((double)key, round + 2); return a ^ key; } } ================================================ FILE: Algorithms/Encoders/HillEncoder.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Encoders; /// /// Lester S. Hill's polygraphic substitution cipher, /// without representing letters using mod26, using /// corresponding "(char)value" instead. /// public class HillEncoder : IEncoder { private readonly GaussJordanElimination linearEquationSolver; public HillEncoder() => linearEquationSolver = new GaussJordanElimination(); // TODO: add DI public string Encode(string text, double[,] key) { var preparedText = FillGaps(text); var chunked = ChunkTextToArray(preparedText); var splitted = SplitToCharArray(chunked); var ciphered = new double[chunked.Length][]; for (var i = 0; i < chunked.Length; i++) { var vector = new double[3]; Array.Copy(splitted, i * 3, vector, 0, 3); var product = MatrixCipher(vector, key); ciphered[i] = product; } var merged = MergeArrayList(ciphered); return BuildStringFromArray(merged); } public string Decode(string text, double[,] key) { var chunked = ChunkTextToArray(text); var split = SplitToCharArray(chunked); var deciphered = new double[chunked.Length][]; for (var i = 0; i < chunked.Length; i++) { var vector = new double[3]; Array.Copy(split, i * 3, vector, 0, 3); var product = MatrixDeCipher(vector, key); deciphered[i] = product; } var merged = MergeArrayList(deciphered); var str = BuildStringFromArray(merged); return UnFillGaps(str); } /// /// Converts elements from the array to their corresponding Unicode characters. /// /// array of vectors. /// Message. private static string BuildStringFromArray(double[] arr) => new(arr.Select(c => (char)c).ToArray()); /// /// Multiplies the key for the given scalar. /// /// list of splitted words as numbers. /// Cipher selected key. /// Ciphered vector. private static double[] MatrixCipher(double[] vector, double[,] key) { var multiplied = new double[vector.Length]; for (var i = 0; i < key.GetLength(1); i++) { for (var j = 0; j < key.GetLength(0); j++) { multiplied[i] += key[i, j] * vector[j]; } } return multiplied; } /// /// Given a list of vectors, returns a single array of elements. /// /// List of ciphered arrays. /// unidimensional list. private static double[] MergeArrayList(double[][] list) { var merged = new double[list.Length * 3]; for (var i = 0; i < list.Length; i++) { Array.Copy(list[i], 0, merged, i * 3, list[0].Length); } return merged; } /// /// Splits the input text message as chunks of words. /// /// chunked words list. /// spliiter char array. private static char[] SplitToCharArray(string[] chunked) { var splitted = new char[chunked.Length * 3]; for (var i = 0; i < chunked.Length; i++) { for (var j = 0; j < 3; j++) { splitted[i * 3 + j] = chunked[i].ToCharArray()[j]; } } return splitted; } /// /// Chunks the input text message. /// /// text message. /// array of words. private static string[] ChunkTextToArray(string text) { // To split the message into chunks var div = text.Length / 3; var chunks = new string[div]; for (var i = 0; i < div; i++) { chunks.SetValue(text.Substring(i * 3, 3), i); } return chunks; } /// /// Fills a text message with spaces at the end /// to enable a simple split by 3-length-word. /// /// Text Message. /// Modified text Message. private static string FillGaps(string text) { var remainder = text.Length % 3; return remainder == 0 ? text : text + new string(' ', 3 - remainder); } /// /// Removes the extra spaces included on the cipher phase. /// /// Text message. /// Deciphered Message. private static string UnFillGaps(string text) => text.TrimEnd(); /// /// Finds the inverse of the given matrix using a linear equation solver. /// /// Splitted words vector. /// Key used for the cipher. /// TODO. private double[] MatrixDeCipher(double[] vector, double[,] key) { // To augment the original key with the given vector. var augM = new double[3, 4]; for (var i = 0; i < key.GetLength(0); i++) { for (var j = 0; j < key.GetLength(1); j++) { augM[i, j] = key[i, j]; } } for (var k = 0; k < vector.Length; k++) { augM[k, 3] = vector[k]; } _ = linearEquationSolver.Solve(augM); return [augM[0, 3], augM[1, 3], augM[2, 3]]; } } ================================================ FILE: Algorithms/Encoders/IEncoder.cs ================================================ namespace Algorithms.Encoders; /// /// Encodes and decodes text based on specified key. /// /// Type of the key. public interface IEncoder { /// /// Encodes text using specified key. /// /// Text to be encoded. /// Key that will be used to encode the text. /// Encoded text. string Encode(string text, TKey key); /// /// Decodes text that was encoded using specified key. /// /// Text to be decoded. /// Key that was used to encode the text. /// Decoded text. string Decode(string text, TKey key); } ================================================ FILE: Algorithms/Encoders/NysiisEncoder.cs ================================================ namespace Algorithms.Encoders; /// /// Class for NYSIIS encoding strings. /// public class NysiisEncoder { private static readonly char[] Vowels = ['A', 'E', 'I', 'O', 'U']; /// /// Encodes a string using the NYSIIS Algorithm. /// /// The string to encode. /// The NYSIIS encoded string (all uppercase). public string Encode(string text) { text = text.ToUpper(CultureInfo.CurrentCulture); text = TrimSpaces(text); text = StartReplace(text); text = EndReplace(text); for (var i = 1; i < text.Length; i++) { text = ReplaceStep(text, i); } text = RemoveDuplicates(text); return TrimEnd(text); } private string TrimSpaces(string text) => text.Replace(" ", string.Empty); private string RemoveDuplicates(string text) { var sb = new StringBuilder(); sb.Append(text[0]); foreach (var c in text) { if (sb[^1] != c) { sb.Append(c); } } return sb.ToString(); } private string TrimEnd(string text) { var checks = new (string From, string To)?[] { ("S", string.Empty), ("AY", "Y"), ("A", string.Empty), }; var replacement = checks.FirstOrDefault(t => text.EndsWith(t!.Value.From)); if (replacement is { }) { var (from, to) = replacement!.Value; text = Replace(text, text.Length - from.Length, from.Length, to); } return text; } private string ReplaceStep(string text, int i) { (string From, string To)[] replacements = [ ("EV", "AF"), ("E", "A"), ("I", "A"), ("O", "A"), ("U", "A"), ("Q", "G"), ("Z", "S"), ("M", "N"), ("KN", "NN"), ("K", "C"), ("SCH", "SSS"), ("PH", "FF"), ]; var replaced = TryReplace(text, i, replacements, out text); if (replaced) { return text; } // H[vowel] or [vowel]H -> text[i-1] if (text[i] == 'H') { if (!Vowels.Contains(text[i - 1])) { return ReplaceWithPrevious(); } if (i < text.Length - 1 && !Vowels.Contains(text[i + 1])) { return ReplaceWithPrevious(); } } // [vowel]W -> [vowel] if (text[i] == 'W' && Vowels.Contains(text[i - 1])) { return ReplaceWithPrevious(); } return text; string ReplaceWithPrevious() => Replace(text, i, 1, text[i - 1].ToString()); } private bool TryReplace(string text, int index, (string, string)[] opts, out string result) { for (var i = 0; i < opts.Length; i++) { var check = opts[i].Item1; var repl = opts[i].Item2; if (text.Length >= index + check.Length && text.Substring(index, check.Length) == check) { result = Replace(text, index, check.Length, repl); return true; } } result = text; return false; } private string StartReplace(string start) { var checks = new (string From, string To)?[] { ("MAC", "MCC"), ("KN", "NN"), ("K", "C"), ("PH", "FF"), ("PF", "FF"), ("SCH", "SSS"), }; var replacement = checks.FirstOrDefault(t => start.StartsWith(t!.Value.From)); if (replacement is { }) { var (from, to) = replacement!.Value; start = Replace(start, 0, from.Length, to); } return start; } private string EndReplace(string end) { var checks = new (string From, string To)?[] { ("EE", "Y"), ("IE", "Y"), ("DT", "D"), ("RT", "D"), ("NT", "D"), ("ND", "D"), }; var replacement = checks.FirstOrDefault(t => end.EndsWith(t!.Value.From)); if (replacement is { }) { var (from, to) = replacement!.Value; end = Replace(end, end.Length - from.Length, from.Length, to); } return end; } private string Replace(string text, int index, int length, string substitute) => text[..index] + substitute + text[(index + length)..]; } ================================================ FILE: Algorithms/Encoders/SoundexEncoder.cs ================================================ namespace Algorithms.Encoders; /// /// Class for Soundex encoding strings. /// public class SoundexEncoder { private static readonly Dictionary CharacterMapping = new() { ['a'] = 0, ['e'] = 0, ['i'] = 0, ['o'] = 0, ['u'] = 0, ['y'] = 0, ['h'] = 8, ['w'] = 8, ['b'] = 1, ['f'] = 1, ['p'] = 1, ['v'] = 1, ['c'] = 2, ['g'] = 2, ['j'] = 2, ['k'] = 2, ['q'] = 2, ['s'] = 2, ['x'] = 2, ['z'] = 2, ['d'] = 3, ['t'] = 3, ['l'] = 4, ['m'] = 5, ['n'] = 5, ['r'] = 6, }; /// /// Encodes a string using the Soundex Algorithm. /// /// The string to encode. /// The Soundex encoded string (one uppercase character and three digits). public string Encode(string text) { text = text.ToLowerInvariant(); var chars = OmitHAndW(text); IEnumerable numbers = ProduceNumberCoding(chars); numbers = CollapseDoubles(numbers); numbers = OmitVowels(numbers); numbers = CollapseLeadingDigit(numbers, text[0]); numbers = numbers.Take(3); numbers = PadTo3Numbers(numbers); var final = numbers.ToArray(); return $"{text.ToUpperInvariant()[0]}{final[0]}{final[1]}{final[2]}"; } private IEnumerable CollapseLeadingDigit(IEnumerable numbers, char c) { using var enumerator = numbers.GetEnumerator(); enumerator.MoveNext(); if (enumerator.Current == MapToNumber(c)) { enumerator.MoveNext(); } do { yield return enumerator.Current; } while (enumerator.MoveNext()); } private IEnumerable PadTo3Numbers(IEnumerable numbers) { using var enumerator = numbers.GetEnumerator(); for (var i = 0; i < 3; i++) { yield return enumerator.MoveNext() ? enumerator.Current : 0; } } private IEnumerable OmitVowels(IEnumerable numbers) => numbers.Where(i => i != 0); private IEnumerable OmitHAndW(string text) => text.Where(c => c != 'h' && c != 'w'); private IEnumerable CollapseDoubles(IEnumerable numbers) { var previous = int.MinValue; foreach (var i in numbers) { if (previous != i) { yield return i; previous = i; } } } private IEnumerable ProduceNumberCoding(IEnumerable text) => text.Select(MapToNumber); private int MapToNumber(char ch) { return CharacterMapping[ch]; } } ================================================ FILE: Algorithms/Encoders/VigenereEncoder.cs ================================================ namespace Algorithms.Encoders; /// /// Encodes using vigenere cypher. /// public class VigenereEncoder : IEncoder { private readonly CaesarEncoder caesarEncoder = new(); /// /// Encodes text using specified key, /// time complexity: O(n), /// space complexity: O(n), /// where n - text length. /// /// Text to be encoded. /// Key that will be used to encode the text. /// Encoded text. public string Encode(string text, string key) => Cipher(text, key, caesarEncoder.Encode); /// /// Decodes text that was encoded using specified key, /// time complexity: O(n), /// space complexity: O(n), /// where n - text length. /// /// Text to be decoded. /// Key that was used to encode the text. /// Decoded text. public string Decode(string text, string key) => Cipher(text, key, caesarEncoder.Decode); private string Cipher(string text, string key, Func symbolCipher) { key = AppendKey(key, text.Length); var encodedTextBuilder = new StringBuilder(text.Length); for (var i = 0; i < text.Length; i++) { if (!char.IsLetter(text[i])) { _ = encodedTextBuilder.Append(text[i]); continue; } var letterZ = char.IsUpper(key[i]) ? 'Z' : 'z'; var encodedSymbol = symbolCipher(text[i].ToString(), letterZ - key[i]); _ = encodedTextBuilder.Append(encodedSymbol); } return encodedTextBuilder.ToString(); } private string AppendKey(string key, int length) { if (string.IsNullOrEmpty(key)) { throw new ArgumentOutOfRangeException($"{nameof(key)} must be non-empty string"); } var keyBuilder = new StringBuilder(key, length); while (keyBuilder.Length < length) { _ = keyBuilder.Append(key); } return keyBuilder.ToString(); } } ================================================ FILE: Algorithms/Financial/PresentValue.cs ================================================ namespace Algorithms.Financial; /// /// PresentValue is the value of an expected income stream determined as of the date of valuation. /// public static class PresentValue { public static double Calculate(double discountRate, List cashFlows) { if (discountRate < 0) { throw new ArgumentException("Discount rate cannot be negative"); } if (cashFlows.Count == 0) { throw new ArgumentException("Cash flows list cannot be empty"); } double presentValue = cashFlows.Select((t, i) => t / Math.Pow(1 + discountRate, i)).Sum(); return Math.Round(presentValue, 2); } } ================================================ FILE: Algorithms/GlobalUsings.cs ================================================ // ----------------------------------------------------------------------------- // Global using directives for the C-Sharp solution. // These namespaces are imported globally so they don’t need to be repeatedly declared // in individual files, improving readability and reducing boilerplate. // // Guidelines: // - Keep only the most commonly used namespaces here. // - Add project-specific namespaces (e.g., Utilities.Extensions) only if they are // required across the majority of files in the project. // - Avoid placing rarely used namespaces here to maintain clarity. // ----------------------------------------------------------------------------- global using System; // Core base classes and fundamental types global using System.Collections.Generic; // Generic collection types (List, Dictionary, etc.) global using System.Globalization; // Culture-related information (dates, numbers, formatting) global using System.Linq; // LINQ query operators for collections global using System.Numerics; // Numeric types such as BigInteger and Complex global using System.Security.Cryptography; // Cryptographic services (hashing, encryption, random numbers) global using System.Text; // Text encoding, StringBuilder, etc. global using System.Text.RegularExpressions; // Regular expression support global using Utilities.Extensions; // Common extension methods used across the solution ================================================ FILE: Algorithms/Graph/ArticulationPoints.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.Graph; /// /// Finds articulation points (cut vertices) in an undirected graph. /// An articulation point is a vertex whose removal increases the number of connected components. /// public static class ArticulationPoints { /// /// Finds all articulation points in an undirected graph. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// Set of articulation points. public static HashSet Find( IEnumerable vertices, Func> getNeighbors) where T : notnull { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } if (getNeighbors == null) { throw new ArgumentNullException(nameof(getNeighbors)); } var vertexList = vertices.ToList(); if (vertexList.Count == 0) { return new HashSet(); } var articulationPoints = new HashSet(); var visited = new HashSet(); var discoveryTime = new Dictionary(); var low = new Dictionary(); var parent = new Dictionary(); var time = 0; foreach (var vertex in vertexList) { if (!visited.Contains(vertex)) { var state = new DfsState { Visited = visited, DiscoveryTime = discoveryTime, Low = low, Parent = parent, ArticulationPoints = articulationPoints, }; Dfs(vertex, ref time, state, getNeighbors); } } return articulationPoints; } /// /// Checks if a vertex is an articulation point. /// /// Type of vertex. /// Vertex to check. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// True if vertex is an articulation point. public static bool IsArticulationPoint( T vertex, IEnumerable vertices, Func> getNeighbors) where T : notnull { var articulationPoints = Find(vertices, getNeighbors); return articulationPoints.Contains(vertex); } /// /// Counts the number of articulation points in the graph. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// Number of articulation points. public static int Count( IEnumerable vertices, Func> getNeighbors) where T : notnull { return Find(vertices, getNeighbors).Count; } private static void Dfs( T u, ref int time, DfsState state, Func> getNeighbors) where T : notnull { state.Visited.Add(u); state.DiscoveryTime[u] = time; state.Low[u] = time; time++; int children = 0; foreach (var v in getNeighbors(u)) { if (!state.Visited.Contains(v)) { children++; state.Parent[v] = u; Dfs(v, ref time, state, getNeighbors); state.Low[u] = Math.Min(state.Low[u], state.Low[v]); // Check if u is an articulation point bool isRoot = !state.Parent.ContainsKey(u); if (isRoot && children > 1) { state.ArticulationPoints.Add(u); } bool isNonRootArticulation = state.Parent.ContainsKey(u) && state.Low[v] >= state.DiscoveryTime[u]; if (isNonRootArticulation) { state.ArticulationPoints.Add(u); } } else if (!EqualityComparer.Default.Equals(v, state.Parent.GetValueOrDefault(u))) { // Back edge: update low value state.Low[u] = Math.Min(state.Low[u], state.DiscoveryTime[v]); } else { // Edge to parent: no action needed } } } /// /// Encapsulates the state for DFS traversal in articulation point detection. /// /// Type of vertex. private sealed class DfsState where T : notnull { /// /// Gets set of visited vertices. /// public required HashSet Visited { get; init; } /// /// Gets discovery time for each vertex. /// public required Dictionary DiscoveryTime { get; init; } /// /// Gets lowest discovery time reachable from each vertex. /// public required Dictionary Low { get; init; } /// /// Gets parent vertex in DFS tree. /// public required Dictionary Parent { get; init; } /// /// Gets set of detected articulation points. /// public required HashSet ArticulationPoints { get; init; } } } ================================================ FILE: Algorithms/Graph/BellmanFord.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; /// /// Bellman-Ford algorithm on directed weighted graph. /// /// Generic type of data in the graph. public class BellmanFord(DirectedWeightedGraph graph, Dictionary, double> distances, Dictionary, Vertex?> predecessors) { private readonly DirectedWeightedGraph graph = graph; private readonly Dictionary, double> distances = distances; private readonly Dictionary, Vertex?> predecessors = predecessors; /// /// Runs the Bellman-Ford algorithm to find the shortest distances from the source vertex to all other vertices. /// /// Source vertex for shortest path calculation. /// /// A dictionary containing the shortest distances from the source vertex to all other vertices. /// If a vertex is unreachable from the source, it will have a value of double.PositiveInfinity. /// public Dictionary, double> Run(Vertex sourceVertex) { InitializeDistances(sourceVertex); RelaxEdges(); CheckForNegativeCycles(); return distances; } private void InitializeDistances(Vertex sourceVertex) { foreach (var vertex in graph.Vertices) { if (vertex != null) { distances[vertex] = double.PositiveInfinity; predecessors[vertex] = null; } } distances[sourceVertex] = 0; } private void RelaxEdges() { int vertexCount = graph.Count; for (int i = 0; i < vertexCount - 1; i++) { foreach (var vertex in graph.Vertices) { if (vertex != null) { RelaxEdgesForVertex(vertex); } } } } private void RelaxEdgesForVertex(Vertex u) { foreach (var neighbor in graph.GetNeighbors(u)) { if (neighbor == null) { continue; } var v = neighbor; var weight = graph.AdjacentDistance(u, v); if (distances[u] + weight < distances[v]) { distances[v] = distances[u] + weight; predecessors[v] = u; } } } private void CheckForNegativeCycles() { foreach (var vertex in graph.Vertices) { if (vertex != null) { CheckForNegativeCyclesForVertex(vertex); } } } private void CheckForNegativeCyclesForVertex(Vertex u) { foreach (var neighbor in graph.GetNeighbors(u)) { if (neighbor == null) { continue; } var v = neighbor; var weight = graph.AdjacentDistance(u, v); if (distances[u] + weight < distances[v]) { throw new InvalidOperationException("Graph contains a negative weight cycle."); } } } } ================================================ FILE: Algorithms/Graph/BipartiteGraph.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.Graph; /// /// Checks if a graph is bipartite (2-colorable). /// A bipartite graph can be divided into two independent sets where no two vertices /// within the same set are adjacent. /// public static class BipartiteGraph { /// /// Checks if a graph is bipartite using BFS-based coloring. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// True if graph is bipartite, false otherwise. public static bool IsBipartite( IEnumerable vertices, Func> getNeighbors) where T : notnull { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } if (getNeighbors == null) { throw new ArgumentNullException(nameof(getNeighbors)); } var vertexList = vertices.ToList(); if (vertexList.Count == 0) { return true; // Empty graph is bipartite } var colors = new Dictionary(); // Check each connected component foreach (var start in vertexList) { if (colors.ContainsKey(start)) { continue; // Already colored } if (!BfsColor(start, colors, getNeighbors)) { return false; } } return true; } /// /// Gets the two partitions of a bipartite graph. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// Tuple of two sets representing the partitions, or null if not bipartite. public static (HashSet SetA, HashSet SetB)? GetPartitions( IEnumerable vertices, Func> getNeighbors) where T : notnull { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } if (getNeighbors == null) { throw new ArgumentNullException(nameof(getNeighbors)); } var vertexList = vertices.ToList(); if (vertexList.Count == 0) { return (new HashSet(), new HashSet()); } var colors = new Dictionary(); // Color all components foreach (var start in vertexList) { if (colors.ContainsKey(start)) { continue; } if (!BfsColor(start, colors, getNeighbors)) { return null; // Not bipartite } } // Split into two sets based on color var setA = new HashSet(); var setB = new HashSet(); foreach (var vertex in vertexList) { if (colors[vertex] == 0) { setA.Add(vertex); } else { setB.Add(vertex); } } return (setA, setB); } /// /// Checks if a graph is bipartite using DFS-based coloring. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// True if graph is bipartite, false otherwise. public static bool IsBipartiteDfs( IEnumerable vertices, Func> getNeighbors) where T : notnull { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } if (getNeighbors == null) { throw new ArgumentNullException(nameof(getNeighbors)); } var vertexList = vertices.ToList(); if (vertexList.Count == 0) { return true; } var colors = new Dictionary(); foreach (var start in vertexList) { if (colors.ContainsKey(start)) { continue; } if (!DfsColor(start, 0, colors, getNeighbors)) { return false; } } return true; } private static bool BfsColor( T start, Dictionary colors, Func> getNeighbors) where T : notnull { var queue = new Queue(); queue.Enqueue(start); colors[start] = 0; while (queue.Count > 0) { var current = queue.Dequeue(); var currentColor = colors[current]; var nextColor = 1 - currentColor; foreach (var neighbor in getNeighbors(current)) { if (!colors.ContainsKey(neighbor)) { colors[neighbor] = nextColor; queue.Enqueue(neighbor); } else if (colors[neighbor] == currentColor) { return false; // Same color as current - not bipartite } else { // Different color - valid } } } return true; } private static bool DfsColor( T vertex, int color, Dictionary colors, Func> getNeighbors) where T : notnull { colors[vertex] = color; var nextColor = 1 - color; foreach (var neighbor in getNeighbors(vertex)) { if (!colors.ContainsKey(neighbor)) { if (!DfsColor(neighbor, nextColor, colors, getNeighbors)) { return false; } } else if (colors[neighbor] == color) { return false; // Same color - not bipartite } else { // Different color - valid } } return true; } } ================================================ FILE: Algorithms/Graph/BreadthFirstSearch.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; /// /// Breadth First Search - algorithm for traversing graph. /// Algorithm starts from root node that is selected by the user. /// Algorithm explores all nodes at the present depth. /// /// Vertex data type. public class BreadthFirstSearch : IGraphSearch where T : IComparable { /// /// Traverses graph from start vertex. /// /// Graph instance. /// Vertex that search starts from. /// Action that needs to be executed on each graph vertex. public void VisitAll(IDirectedWeightedGraph graph, Vertex startVertex, Action>? action = default) { Bfs(graph, startVertex, action, []); } /// /// Traverses graph from start vertex. /// /// Graph instance. /// Vertex that search starts from. /// Action that needs to be executed on each graph vertex. /// Hash set with visited vertices. private void Bfs(IDirectedWeightedGraph graph, Vertex startVertex, Action>? action, HashSet> visited) { var queue = new Queue>(); queue.Enqueue(startVertex); while (queue.Count > 0) { var currentVertex = queue.Dequeue(); if (currentVertex == null || visited.Contains(currentVertex)) { continue; } foreach (var vertex in graph.GetNeighbors(currentVertex)) { queue.Enqueue(vertex!); } action?.Invoke(currentVertex); visited.Add(currentVertex); } } } ================================================ FILE: Algorithms/Graph/BreadthFirstTreeTraversal.cs ================================================ using DataStructures.BinarySearchTree; namespace Algorithms.Graph; /// /// Breadth first tree traversal traverses through a binary tree /// by iterating through each level first. /// time complexity: O(n). /// space complexity: O(w) where w is the max width of a binary tree. /// /// Type of key held in binary search tree. public static class BreadthFirstTreeTraversal { /// /// Level Order Traversal returns an array of integers in order /// of each level of a binary tree. It uses a queue to iterate /// through each node following breadth first search traversal. /// /// Passes the binary tree to traverse. /// Returns level order traversal. public static TKey[] LevelOrderTraversal(BinarySearchTree tree) { BinarySearchTreeNode? root = tree.Root; TKey[] levelOrder = new TKey[tree.Count]; if (root is null) { return Array.Empty(); } Queue> breadthTraversal = new Queue>(); breadthTraversal.Enqueue(root); for (int i = 0; i < levelOrder.Length; i++) { BinarySearchTreeNode current = breadthTraversal.Dequeue(); levelOrder[i] = current.Key; if (current.Left is not null) { breadthTraversal.Enqueue(current.Left); } if (current.Right is not null) { breadthTraversal.Enqueue(current.Right); } } return levelOrder; } /// /// Deepest Node return the deepest node in a binary tree. If more /// than one node is on the deepest level, it is defined as the /// right-most node of a binary tree. Deepest node uses breadth /// first traversal to reach the end. /// /// Tree passed to find deepest node. /// Returns the deepest node in the tree. public static TKey? DeepestNode(BinarySearchTree tree) { BinarySearchTreeNode? root = tree.Root; if (root is null) { return default(TKey); } Queue> breadthTraversal = new Queue>(); breadthTraversal.Enqueue(root); TKey deepest = root.Key; while (breadthTraversal.Count > 0) { BinarySearchTreeNode current = breadthTraversal.Dequeue(); if (current.Left is not null) { breadthTraversal.Enqueue(current.Left); } if (current.Right is not null) { breadthTraversal.Enqueue(current.Right); } deepest = current.Key; } return deepest; } } ================================================ FILE: Algorithms/Graph/Bridges.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.Graph; /// /// Finds bridges (cut edges) in an undirected graph. /// A bridge is an edge whose removal increases the number of connected components. /// public static class Bridges { /// /// Finds all bridges in an undirected graph. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// Set of bridges as tuples of vertices. public static HashSet<(T From, T To)> Find( IEnumerable vertices, Func> getNeighbors) where T : notnull { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } if (getNeighbors == null) { throw new ArgumentNullException(nameof(getNeighbors)); } var vertexList = vertices.ToList(); if (vertexList.Count == 0) { return new HashSet<(T, T)>(); } var bridges = new HashSet<(T, T)>(); var visited = new HashSet(); var discoveryTime = new Dictionary(); var low = new Dictionary(); var parent = new Dictionary(); var time = 0; foreach (var vertex in vertexList) { if (!visited.Contains(vertex)) { var state = new DfsState { Visited = visited, DiscoveryTime = discoveryTime, Low = low, Parent = parent, Bridges = bridges, }; Dfs(vertex, ref time, state, getNeighbors); } } return bridges; } /// /// Checks if an edge is a bridge. /// /// Type of vertex. /// Source vertex. /// Destination vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// True if edge is a bridge. public static bool IsBridge( T from, T to, IEnumerable vertices, Func> getNeighbors) where T : notnull { var bridges = Find(vertices, getNeighbors); return bridges.Contains((from, to)) || bridges.Contains((to, from)); } /// /// Counts the number of bridges in the graph. /// /// Type of vertex. /// All vertices in the graph. /// Function to get neighbors of a vertex. /// Number of bridges. public static int Count( IEnumerable vertices, Func> getNeighbors) where T : notnull { return Find(vertices, getNeighbors).Count; } private static void Dfs( T u, ref int time, DfsState state, Func> getNeighbors) where T : notnull { state.Visited.Add(u); state.DiscoveryTime[u] = time; state.Low[u] = time; time++; foreach (var v in getNeighbors(u)) { if (!state.Visited.Contains(v)) { state.Parent[v] = u; Dfs(v, ref time, state, getNeighbors); state.Low[u] = Math.Min(state.Low[u], state.Low[v]); // Check if edge u-v is a bridge if (state.Low[v] > state.DiscoveryTime[u]) { state.Bridges.Add((u, v)); } } else if (!EqualityComparer.Default.Equals(v, state.Parent.GetValueOrDefault(u))) { // Back edge: update low value state.Low[u] = Math.Min(state.Low[u], state.DiscoveryTime[v]); } else { // Edge to parent: no action needed } } } /// /// Encapsulates the state for DFS traversal in bridge detection. /// /// Type of vertex. private sealed class DfsState where T : notnull { /// /// Gets set of visited vertices. /// public required HashSet Visited { get; init; } /// /// Gets discovery time for each vertex. /// public required Dictionary DiscoveryTime { get; init; } /// /// Gets lowest discovery time reachable from each vertex. /// public required Dictionary Low { get; init; } /// /// Gets parent vertex in DFS tree. /// public required Dictionary Parent { get; init; } /// /// Gets set of detected bridges. /// public required HashSet<(T From, T To)> Bridges { get; init; } } } ================================================ FILE: Algorithms/Graph/DepthFirstSearch.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; /// /// Depth First Search - algorithm for traversing graph. /// Algorithm starts from root node that is selected by the user. /// Algorithm explores as far as possible along each branch before backtracking. /// /// Vertex data type. public class DepthFirstSearch : IGraphSearch where T : IComparable { /// /// Traverses graph from start vertex. /// /// Graph instance. /// Vertex that search starts from. /// Action that needs to be executed on each graph vertex. public void VisitAll(IDirectedWeightedGraph graph, Vertex startVertex, Action>? action = default) { Dfs(graph, startVertex, action, []); } /// /// Traverses graph from start vertex. /// /// Graph instance. /// Vertex that search starts from. /// Action that needs to be executed on each graph vertex. /// Hash set with visited vertices. private void Dfs(IDirectedWeightedGraph graph, Vertex startVertex, Action>? action, HashSet> visited) { action?.Invoke(startVertex); visited.Add(startVertex); foreach (var vertex in graph.GetNeighbors(startVertex)) { if (vertex == null || visited.Contains(vertex)) { continue; } Dfs(graph, vertex!, action, visited); } } } ================================================ FILE: Algorithms/Graph/Dijkstra/DijkstraAlgorithm.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph.Dijkstra; public static class DijkstraAlgorithm { /// /// Implementation of the Dijkstra shortest path algorithm for cyclic graphs. /// https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm. /// /// Graph instance. /// Starting vertex instance. /// Generic Parameter. /// List of distances from current vertex to all other vertices. /// Exception thrown in case when graph is null or start /// vertex does not belong to graph instance. public static DistanceModel[] GenerateShortestPath(DirectedWeightedGraph graph, Vertex startVertex) { ValidateGraphAndStartVertex(graph, startVertex); var visitedVertices = new List>(); var distanceArray = InitializeDistanceArray(graph, startVertex); var distanceRecord = new PriorityQueue, double>(); distanceRecord.Enqueue(distanceArray[0], distanceArray[0].Distance); while (visitedVertices.Count != distanceArray.Length && distanceRecord.Count != 0) { while (visitedVertices.Contains(distanceRecord.Peek().Vertex!)) { distanceRecord.Dequeue(); } var minDistance = distanceRecord.Dequeue(); var currentPath = minDistance.Distance; visitedVertices.Add(minDistance.Vertex!); var neighborVertices = graph .GetNeighbors(minDistance.Vertex!) .Where(x => x != null && !visitedVertices.Contains(x)) .ToList(); foreach (var vertex in neighborVertices) { var adjacentDistance = graph.AdjacentDistance(minDistance.Vertex!, vertex!); var distance = distanceArray[vertex!.Index]; var fullDistance = currentPath + adjacentDistance; if (distance.Distance > fullDistance) { distance.Distance = fullDistance; distance.PreviousVertex = minDistance.Vertex; distanceRecord.Enqueue(distance, fullDistance); } } } return distanceArray; } private static DistanceModel[] InitializeDistanceArray( IDirectedWeightedGraph graph, Vertex startVertex) { var distArray = new DistanceModel[graph.Count]; distArray[startVertex.Index] = new DistanceModel(startVertex, startVertex, 0); foreach (var vertex in graph.Vertices.Where(x => x != null && !x.Equals(startVertex))) { distArray[vertex!.Index] = new DistanceModel(vertex, null, double.MaxValue); } return distArray; } private static void ValidateGraphAndStartVertex(DirectedWeightedGraph graph, Vertex startVertex) { if (graph is null) { throw new ArgumentNullException(nameof(graph)); } if (startVertex.Graph != null && !startVertex.Graph.Equals(graph)) { throw new ArgumentNullException(nameof(graph)); } } } ================================================ FILE: Algorithms/Graph/Dijkstra/DistanceModel.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph.Dijkstra; /// /// Entity which represents the Dijkstra shortest distance. /// Contains: Vertex, Previous Vertex and minimal distance from start vertex. /// /// Generic parameter. public class DistanceModel(Vertex? vertex, Vertex? previousVertex, double distance) { public Vertex? Vertex { get; } = vertex; public Vertex? PreviousVertex { get; set; } = previousVertex; public double Distance { get; set; } = distance; public override string ToString() => $"Vertex: {Vertex} - Distance: {Distance} - Previous: {PreviousVertex}"; } ================================================ FILE: Algorithms/Graph/FloydWarshall.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; /// /// Floyd Warshall algorithm on directed weighted graph. /// /// generic type of data in graph. public class FloydWarshall { /// /// runs the algorithm. /// /// graph upon which to run. /// /// a 2D array of shortest paths between any two vertices. /// where there is no path between two vertices - double.PositiveInfinity is placed. /// public double[,] Run(DirectedWeightedGraph graph) { var distances = SetupDistances(graph); var vertexCount = distances.GetLength(0); for (var k = 0; k < vertexCount; k++) { for (var i = 0; i < vertexCount; i++) { for (var j = 0; j < vertexCount; j++) { distances[i, j] = distances[i, j] > distances[i, k] + distances[k, j] ? distances[i, k] + distances[k, j] : distances[i, j]; } } } return distances; } /// /// setup adjacency matrix for use by main algorithm run. /// /// graph to dissect adjacency matrix from. /// the adjacency matrix in the format mentioned in Run. private double[,] SetupDistances(DirectedWeightedGraph graph) { var distances = new double[graph.Count, graph.Count]; for (int i = 0; i < distances.GetLength(0); i++) { for (var j = 0; j < distances.GetLength(0); j++) { var dist = graph.AdjacentDistance(graph.Vertices[i]!, graph.Vertices[j]!); distances[i, j] = dist != 0 ? dist : double.PositiveInfinity; } } for (var i = 0; i < distances.GetLength(0); i++) { distances[i, i] = 0; } return distances; } } ================================================ FILE: Algorithms/Graph/IGraphSearch.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; public interface IGraphSearch { /// /// Traverses graph from start vertex. /// /// Graph instance. /// Vertex that search starts from. /// Action that needs to be executed on each graph vertex. void VisitAll(IDirectedWeightedGraph graph, Vertex startVertex, Action>? action = null); } ================================================ FILE: Algorithms/Graph/Kosaraju.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; /// /// Implementation of Kosaraju-Sharir's algorithm (also known as Kosaraju's algorithm) to find the /// strongly connected components (SCC) of a directed graph. /// See https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm. /// /// Vertex data type. public static class Kosaraju { /// /// First DFS for Kosaraju algorithm: traverse the graph creating a reverse order explore list . /// /// Vertex to explore. /// Graph instance. /// List of already visited vertex. /// Reversed list of vertex for the second DFS. public static void Visit(Vertex v, IDirectedWeightedGraph graph, HashSet> visited, Stack> reversed) { if (visited.Contains(v)) { return; } // Set v as visited visited.Add(v); // Push v in the stack. // This can also be done with a List, inserting v at the begining of the list // after visit the neighbors. reversed.Push(v); // Visit neighbors foreach (var u in graph.GetNeighbors(v)) { Visit(u!, graph, visited, reversed); } } /// /// Second DFS for Kosaraju algorithm. Traverse the graph in reversed order /// assigning a root vertex for every vertex that belong to the same SCC. /// /// Vertex to assign. /// Root vertext, representative of the SCC. /// Graph with vertex and edges. /// /// Dictionary that assigns to each vertex the root of the SCC to which it corresponds. /// public static void Assign(Vertex v, Vertex root, IDirectedWeightedGraph graph, Dictionary, Vertex> roots) { // If v already has a representative vertex (root) already assigned, do nothing. if (roots.ContainsKey(v)) { return; } // Assign the root to the vertex. roots.Add(v, root); // Assign the current root vertex to v neighbors. foreach (var u in graph.GetNeighbors(v)) { Assign(u!, root, graph, roots); } } /// /// Find the representative vertex of the SCC for each vertex on the graph. /// /// Graph to explore. /// A dictionary that assigns to each vertex a root vertex of the SCC they belong. public static Dictionary, Vertex> GetRepresentatives(IDirectedWeightedGraph graph) { HashSet> visited = []; Stack> reversedL = new Stack>(); Dictionary, Vertex> representatives = []; foreach (var v in graph.Vertices) { if (v != null) { Visit(v, graph, visited, reversedL); } } visited.Clear(); while (reversedL.Count > 0) { Vertex v = reversedL.Pop(); Assign(v, v, graph, representatives); } return representatives; } /// /// Get the Strongly Connected Components for the graph. /// /// Graph to explore. /// An array of SCC. public static IEnumerable>[] GetScc(IDirectedWeightedGraph graph) { var representatives = GetRepresentatives(graph); Dictionary, List>> scc = []; foreach (var kv in representatives) { // Assign all vertex (key) that have the seem root (value) to a single list. if (scc.ContainsKey(kv.Value)) { scc[kv.Value].Add(kv.Key); } else { scc.Add(kv.Value, [kv.Key]); } } return scc.Values.ToArray(); } } ================================================ FILE: Algorithms/Graph/MinimumSpanningTree/Kruskal.cs ================================================ using DataStructures.DisjointSet; namespace Algorithms.Graph.MinimumSpanningTree; /// /// Algorithm to determine the minimum spanning forest of an undirected graph. /// /// /// Kruskal's algorithm is a greedy algorithm that can determine the /// minimum spanning tree or minimum spanning forest of any undirected /// graph. Unlike Prim's algorithm, Kruskal's algorithm will work on /// graphs that are unconnected. This algorithm will always have a /// running time of O(E log V) where E is the number of edges and V is /// the number of vertices/nodes. /// More information: https://en.wikipedia.org/wiki/Kruskal%27s_algorithm . /// Pseudocode and analysis: https://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/primAlgor.htm . /// public static class Kruskal { /// /// Determine the minimum spanning tree/forest of the given graph. /// /// Adjacency matrix representing the graph. /// Adjacency matrix of the minimum spanning tree/forest. public static float[,] Solve(float[,] adjacencyMatrix) { ValidateGraph(adjacencyMatrix); var numNodes = adjacencyMatrix.GetLength(0); var set = new DisjointSet(); var nodes = new Node[numNodes]; var edgeWeightList = new List(); var nodeConnectList = new List<(int, int)>(); // Add nodes to disjoint set for (var i = 0; i < numNodes; i++) { nodes[i] = set.MakeSet(i); } // Create lists with edge weights and associated connectivity for (var i = 0; i < numNodes - 1; i++) { for (var j = i + 1; j < numNodes; j++) { if (float.IsFinite(adjacencyMatrix[i, j])) { edgeWeightList.Add(adjacencyMatrix[i, j]); nodeConnectList.Add((i, j)); } } } var edges = Solve(set, nodes, edgeWeightList.ToArray(), nodeConnectList.ToArray()); // Initialize minimum spanning tree var mst = new float[numNodes, numNodes]; for (var i = 0; i < numNodes; i++) { mst[i, i] = float.PositiveInfinity; for (var j = i + 1; j < numNodes; j++) { mst[i, j] = float.PositiveInfinity; mst[j, i] = float.PositiveInfinity; } } foreach (var (node1, node2) in edges) { mst[node1, node2] = adjacencyMatrix[node1, node2]; mst[node2, node1] = adjacencyMatrix[node1, node2]; } return mst; } /// /// Determine the minimum spanning tree/forest of the given graph. /// /// Adjacency list representing the graph. /// Adjacency list of the minimum spanning tree/forest. public static Dictionary[] Solve(Dictionary[] adjacencyList) { ValidateGraph(adjacencyList); var numNodes = adjacencyList.Length; var set = new DisjointSet(); var nodes = new Node[numNodes]; var edgeWeightList = new List(); var nodeConnectList = new List<(int, int)>(); // Add nodes to disjoint set and create list of edge weights and associated connectivity for (var i = 0; i < numNodes; i++) { nodes[i] = set.MakeSet(i); foreach (var (node, weight) in adjacencyList[i]) { edgeWeightList.Add(weight); nodeConnectList.Add((i, node)); } } var edges = Solve(set, nodes, edgeWeightList.ToArray(), nodeConnectList.ToArray()); // Create minimum spanning tree var mst = new Dictionary[numNodes]; for (var i = 0; i < numNodes; i++) { mst[i] = []; } foreach (var (node1, node2) in edges) { mst[node1].Add(node2, adjacencyList[node1][node2]); mst[node2].Add(node1, adjacencyList[node1][node2]); } return mst; } /// /// Ensure that the given graph is undirected. /// /// Adjacency matrix of graph to check. private static void ValidateGraph(float[,] adj) { if (adj.GetLength(0) != adj.GetLength(1)) { throw new ArgumentException("Matrix must be square!"); } for (var i = 0; i < adj.GetLength(0) - 1; i++) { for (var j = i + 1; j < adj.GetLength(1); j++) { if (Math.Abs(adj[i, j] - adj[j, i]) > 1e-6) { throw new ArgumentException("Matrix must be symmetric!"); } } } } /// /// Ensure that the given graph is undirected. /// /// Adjacency list of graph to check. private static void ValidateGraph(Dictionary[] adj) { for (var i = 0; i < adj.Length; i++) { foreach (var edge in adj[i]) { if (!adj[edge.Key].ContainsKey(i) || Math.Abs(edge.Value - adj[edge.Key][i]) > 1e-6) { throw new ArgumentException("Graph must be undirected!"); } } } } /// /// Determine the minimum spanning tree/forest. /// /// Disjoint set needed for set operations. /// List of nodes in disjoint set associated with each node. /// Weights of each edge. /// Nodes associated with each item in the parameter. /// Array of edges in the minimum spanning tree/forest. private static (int, int)[] Solve(DisjointSet set, Node[] nodes, float[] edgeWeights, (int, int)[] connections) { var edges = new List<(int, int)>(); Array.Sort(edgeWeights, connections); foreach (var (node1, node2) in connections) { if (set.FindSet(nodes[node1]) != set.FindSet(nodes[node2])) { set.UnionSet(nodes[node1], nodes[node2]); edges.Add((node1, node2)); } } return edges.ToArray(); } } ================================================ FILE: Algorithms/Graph/MinimumSpanningTree/PrimMatrix.cs ================================================ namespace Algorithms.Graph.MinimumSpanningTree; /// /// Class that uses Prim's (Jarnik's algorithm) to determine the minimum /// spanning tree (MST) of a given graph. Prim's algorithm is a greedy /// algorithm that can determine the MST of a weighted undirected graph /// in O(V^2) time where V is the number of nodes/vertices when using an /// adjacency matrix representation. /// More information: https://en.wikipedia.org/wiki/Prim%27s_algorithm /// Pseudocode and runtime analysis: https://www.personal.kent.edu/~rmuhamma/Algorithms/MyAlgorithms/GraphAlgor/primAlgor.htm . /// public static class PrimMatrix { /// /// Determine the minimum spanning tree for a given weighted undirected graph. /// /// Adjacency matrix for graph to find MST of. /// Node to start search from. /// Adjacency matrix of the found MST. public static float[,] Solve(float[,] adjacencyMatrix, int start) { ValidateMatrix(adjacencyMatrix); var numNodes = adjacencyMatrix.GetLength(0); // Create array to represent minimum spanning tree var mst = new float[numNodes, numNodes]; // Create array to keep track of which nodes are in the MST already var added = new bool[numNodes]; // Create array to keep track of smallest edge weight for node var key = new float[numNodes]; // Create array to store parent of node var parent = new int[numNodes]; for (var i = 0; i < numNodes; i++) { mst[i, i] = float.PositiveInfinity; key[i] = float.PositiveInfinity; for (var j = i + 1; j < numNodes; j++) { mst[i, j] = float.PositiveInfinity; mst[j, i] = float.PositiveInfinity; } } // Ensures that the starting node is added first key[start] = 0; // Keep looping until all nodes are in tree for (var i = 0; i < numNodes - 1; i++) { GetNextNode(adjacencyMatrix, key, added, parent); } // Build adjacency matrix for tree for (var i = 0; i < numNodes; i++) { if (i == start) { continue; } mst[i, parent[i]] = adjacencyMatrix[i, parent[i]]; mst[parent[i], i] = adjacencyMatrix[i, parent[i]]; } return mst; } /// /// Ensure that the given adjacency matrix represents a weighted undirected graph. /// /// Adjacency matric to check. private static void ValidateMatrix(float[,] adjacencyMatrix) { // Matrix should be square if (adjacencyMatrix.GetLength(0) != adjacencyMatrix.GetLength(1)) { throw new ArgumentException("Adjacency matrix must be square!"); } // Graph needs to be undirected and connected for (var i = 0; i < adjacencyMatrix.GetLength(0); i++) { var connection = false; for (var j = 0; j < adjacencyMatrix.GetLength(0); j++) { if (Math.Abs(adjacencyMatrix[i, j] - adjacencyMatrix[j, i]) > 1e-6) { throw new ArgumentException("Adjacency matrix must be symmetric!"); } if (!connection && float.IsFinite(adjacencyMatrix[i, j])) { connection = true; } } if (!connection) { throw new ArgumentException("Graph must be connected!"); } } } /// /// Determine which node should be added next to the MST. /// /// Adjacency matrix of graph. /// Currently known minimum edge weight connected to each node. /// Whether or not a node has been added to the MST. /// The node that added the node to the MST. Used for building MST adjacency matrix. private static void GetNextNode(float[,] adjacencyMatrix, float[] key, bool[] added, int[] parent) { var numNodes = adjacencyMatrix.GetLength(0); var minWeight = float.PositiveInfinity; var node = -1; // Find node with smallest node with known edge weight not in tree. Will always start with starting node for (var i = 0; i < numNodes; i++) { if (!added[i] && key[i] < minWeight) { minWeight = key[i]; node = i; } } // Add node to mst added[node] = true; // Update smallest found edge weights and parent for adjacent nodes for (var i = 0; i < numNodes; i++) { if (!added[i] && adjacencyMatrix[node, i] < key[i]) { key[i] = adjacencyMatrix[node, i]; parent[i] = node; } } } } ================================================ FILE: Algorithms/Graph/TarjanStronglyConnectedComponents.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.Graph; /// /// Tarjan's algorithm for finding strongly connected components in a directed graph. /// Uses depth-first search with a stack to identify SCCs in O(V + E) time. /// public class TarjanStronglyConnectedComponents { private readonly List[] graph; private readonly int[] ids; private readonly int[] low; private readonly bool[] onStack; private readonly Stack stack; private readonly List> sccs; private int id; public TarjanStronglyConnectedComponents(int vertices) { graph = new List[vertices]; ids = new int[vertices]; low = new int[vertices]; onStack = new bool[vertices]; stack = new Stack(); sccs = new List>(); for (int i = 0; i < vertices; i++) { graph[i] = new List(); ids[i] = -1; } } /// /// Adds a directed edge from u to v. /// public void AddEdge(int u, int v) { if (u < 0 || u >= graph.Length || v < 0 || v >= graph.Length) { throw new ArgumentOutOfRangeException(nameof(u), "Vertex indices must be within valid range."); } graph[u].Add(v); } /// /// Finds all strongly connected components. /// /// List of SCCs, where each SCC is a list of vertex indices. public List> FindSCCs() { for (int i = 0; i < graph.Length; i++) { if (ids[i] == -1) { Dfs(i); } } return sccs; } /// /// Gets the number of strongly connected components. /// public int GetSccCount() => sccs.Count; /// /// Checks if two vertices are in the same SCC. /// public bool InSameScc(int u, int v) { if (sccs.Count == 0) { FindSCCs(); } foreach (var scc in sccs) { if (scc.Contains(u) && scc.Contains(v)) { return true; } } return false; } /// /// Gets the SCC containing the given vertex. /// public List? GetScc(int vertex) { if (sccs.Count == 0) { FindSCCs(); } return sccs.FirstOrDefault(scc => scc.Contains(vertex)); } /// /// Builds the condensation graph (DAG of SCCs). /// public List[] BuildCondensationGraph() { if (sccs.Count == 0) { FindSCCs(); } var sccIndex = new int[graph.Length]; for (int i = 0; i < sccs.Count; i++) { foreach (var vertex in sccs[i]) { sccIndex[vertex] = i; } } var condensation = new List[sccs.Count]; for (int i = 0; i < sccs.Count; i++) { condensation[i] = new List(); } var edges = new HashSet<(int, int)>(); for (int u = 0; u < graph.Length; u++) { foreach (var v in graph[u]) { int sccU = sccIndex[u]; int sccV = sccIndex[v]; if (sccU != sccV && !edges.Contains((sccU, sccV))) { condensation[sccU].Add(sccV); edges.Add((sccU, sccV)); } } } return condensation; } private void Dfs(int at) { stack.Push(at); onStack[at] = true; ids[at] = low[at] = id++; foreach (var to in graph[at]) { if (ids[to] == -1) { Dfs(to); } if (onStack[to]) { low[at] = Math.Min(low[at], low[to]); } } if (ids[at] == low[at]) { var scc = new List(); while (true) { int node = stack.Pop(); onStack[node] = false; scc.Add(node); if (node == at) { break; } } sccs.Add(scc); } } } ================================================ FILE: Algorithms/Graph/TopologicalSort.cs ================================================ using DataStructures.Graph; namespace Algorithms.Graph; /// /// Topological Sort is a linear ordering of vertices in a Directed Acyclic Graph (DAG) /// such that for every directed edge (u, v), vertex u comes before vertex v in the ordering. /// /// KEY CONCEPTS: /// 1. Only applicable to Directed Acyclic Graphs (DAGs) - graphs with no cycles. /// 2. A DAG can have multiple valid topological orderings. /// 3. Used in dependency resolution, task scheduling, build systems, and course prerequisites. /// /// ALGORITHM APPROACHES: /// 1. DFS-based (Depth-First Search): Uses post-order traversal and reverses the result. /// 2. Kahn's Algorithm: Uses in-degree counting and processes vertices with zero in-degree. /// /// TIME COMPLEXITY: O(V + E) where V is vertices and E is edges. /// SPACE COMPLEXITY: O(V) for the visited set and result stack. /// /// Reference: "Introduction to Algorithms" (CLRS) by Cormen, Leiserson, Rivest, and Stein. /// Also covered in "Algorithm Design Manual" by Steven Skiena. /// /// Vertex data type. public class TopologicalSort where T : IComparable { /// /// Performs topological sort on a directed acyclic graph using DFS-based approach. /// /// ALGORITHM STEPS (DFS-based approach): /// 1. Initialize a visited set to track processed vertices. /// 2. Initialize a stack to store the topological ordering. /// 3. For each unvisited vertex in the graph: /// a) Perform DFS from that vertex. /// b) After visiting all descendants, push the vertex to the stack. /// 4. The stack now contains vertices in reverse topological order. /// 5. Pop all vertices from the stack to get the topological ordering. /// /// WHY IT WORKS: /// - In DFS, we push a vertex to the stack only after visiting all its descendants. /// - This ensures that all vertices that depend on the current vertex are processed first. /// - Reversing this order gives us the topological sort. /// /// EXAMPLE: /// Graph: A → B → C /// A → D /// D → C /// Valid topological orderings: [A, B, D, C] or [A, D, B, C]. /// /// USE CASES: /// - Build systems (compile dependencies). /// - Task scheduling with dependencies. /// - Course prerequisite ordering. /// - Package dependency resolution. /// /// The directed acyclic graph to sort. /// A list of vertices in topological order. /// /// Thrown when the graph contains a cycle (not a DAG). /// public List> Sort(IDirectedWeightedGraph graph) { // Stack to store vertices in reverse topological order. // We use a stack because DFS naturally gives us reverse topological order. var stack = new Stack>(); // Track visited vertices to avoid reprocessing and detect cycles. var visited = new HashSet>(); // Track vertices currently in the recursion stack to detect cycles. // If we encounter a vertex that's in the recursion stack, we have a cycle. var recursionStack = new HashSet>(); // Process all vertices in the graph. // We need to iterate through all vertices because the graph might be disconnected. for (int i = 0; i < graph.Count; i++) { var vertex = graph.Vertices[i]; // Skip null vertices (shouldn't happen in a well-formed graph). if (vertex == null) { continue; } // If vertex hasn't been visited, perform DFS from it. if (!visited.Contains(vertex)) { DfsTopologicalSort(graph, vertex, visited, recursionStack, stack); } } // Convert stack to list. The stack contains vertices in reverse topological order, // so we need to reverse it to get the correct topological ordering. var result = new List>(stack.Count); while (stack.Count > 0) { result.Add(stack.Pop()); } return result; } /// /// Performs topological sort using Kahn's Algorithm (BFS-based approach). /// /// ALGORITHM STEPS (Kahn's Algorithm): /// 1. Calculate in-degree (number of incoming edges) for each vertex. /// 2. Add all vertices with in-degree 0 to a queue. /// 3. While the queue is not empty: /// a) Remove a vertex from the queue and add it to the result. /// b) For each neighbor of this vertex: /// - Decrease its in-degree by 1. /// - If in-degree becomes 0, add it to the queue. /// 4. If all vertices are processed, return the result. /// 5. If not all vertices are processed, the graph has a cycle. /// /// WHY IT WORKS: /// - Vertices with in-degree 0 have no dependencies and can be processed first. /// - After processing a vertex, we "remove" its outgoing edges by decreasing /// the in-degree of its neighbors. /// - This gradually reveals more vertices with in-degree 0. /// /// ADVANTAGES OVER DFS: /// - More intuitive for understanding dependencies. /// - Easier to detect cycles (if not all vertices are processed). /// - Better for parallel processing scenarios. /// /// The directed acyclic graph to sort. /// A list of vertices in topological order. /// /// Thrown when the graph contains a cycle (not a DAG). /// public List> SortKahn(IDirectedWeightedGraph graph) { // Calculate in-degree for each vertex. var inDegree = CalculateInDegrees(graph); // Queue to process vertices with in-degree 0. var queue = InitializeQueueWithZeroInDegreeVertices(inDegree); // Process vertices in topological order. var result = ProcessVerticesInTopologicalOrder(graph, inDegree, queue); // Verify all vertices were processed (no cycles). ValidateNoCycles(graph, result); return result; } /// /// Calculates the in-degree for each vertex in the graph. /// In-degree is the number of incoming edges to a vertex. /// /// The graph to analyze. /// Dictionary mapping each vertex to its in-degree. private Dictionary, int> CalculateInDegrees(IDirectedWeightedGraph graph) { var inDegree = new Dictionary, int>(); // Initialize in-degree for all vertices to 0. for (int i = 0; i < graph.Count; i++) { var vertex = graph.Vertices[i]; if (vertex != null) { inDegree[vertex] = 0; } } // Calculate actual in-degrees by examining all edges. for (int i = 0; i < graph.Count; i++) { var vertex = graph.Vertices[i]; if (vertex != null) { IncrementNeighborInDegrees(graph, vertex, inDegree); } } return inDegree; } /// /// Increments the in-degree for all neighbors of a given vertex. /// /// The graph containing the vertices. /// The vertex whose neighbors' in-degrees should be incremented. /// Dictionary tracking in-degrees. private void IncrementNeighborInDegrees( IDirectedWeightedGraph graph, Vertex vertex, Dictionary, int> inDegree) { foreach (var neighbor in graph.GetNeighbors(vertex)) { if (neighbor != null) { inDegree[neighbor]++; } } } /// /// Initializes a queue with all vertices that have in-degree 0. /// These vertices have no dependencies and can be processed first. /// /// Dictionary mapping vertices to their in-degrees. /// Queue containing all vertices with in-degree 0. private Queue> InitializeQueueWithZeroInDegreeVertices(Dictionary, int> inDegree) { var queue = new Queue>(); foreach (var kvp in inDegree) { if (kvp.Value == 0) { queue.Enqueue(kvp.Key); } } return queue; } /// /// Processes vertices in topological order using Kahn's algorithm. /// Dequeues vertices with in-degree 0 and decreases in-degrees of their neighbors. /// /// The graph being sorted. /// Dictionary tracking in-degrees. /// Queue of vertices with in-degree 0. /// List of vertices in topological order. private List> ProcessVerticesInTopologicalOrder( IDirectedWeightedGraph graph, Dictionary, int> inDegree, Queue> queue) { var result = new List>(); while (queue.Count > 0) { var vertex = queue.Dequeue(); result.Add(vertex); ProcessNeighbors(graph, vertex, inDegree, queue); } return result; } /// /// Processes neighbors of a vertex by decreasing their in-degrees. /// If a neighbor's in-degree becomes 0, it's added to the queue. /// /// The graph being sorted. /// The vertex whose neighbors are being processed. /// Dictionary tracking in-degrees. /// Queue of vertices with in-degree 0. private void ProcessNeighbors( IDirectedWeightedGraph graph, Vertex vertex, Dictionary, int> inDegree, Queue> queue) { foreach (var neighbor in graph.GetNeighbors(vertex)) { if (neighbor != null) { inDegree[neighbor]--; if (inDegree[neighbor] == 0) { queue.Enqueue(neighbor); } } } } /// /// Validates that all vertices were processed, ensuring no cycles exist. /// /// The graph being sorted. /// The list of processed vertices. /// /// Thrown when not all vertices were processed (cycle detected). /// private void ValidateNoCycles(IDirectedWeightedGraph graph, List> result) { if (result.Count != graph.Count) { throw new InvalidOperationException( "Graph contains a cycle. Topological sort is only possible for Directed Acyclic Graphs (DAGs)."); } } /// /// Helper method for DFS-based topological sort. /// Recursively visits vertices and adds them to the stack in post-order. /// /// POST-ORDER TRAVERSAL: /// - Visit all descendants first. /// - Then process the current vertex. /// - This ensures dependencies are processed before dependents. /// /// CYCLE DETECTION: /// - We maintain a recursion stack to track the current DFS path. /// - If we encounter a vertex that's already in the recursion stack, /// we've found a back edge, indicating a cycle. /// /// The graph being sorted. /// The current vertex being processed. /// Set of all visited vertices. /// Set of vertices in the current DFS path. /// Stack to store vertices in reverse topological order. /// /// Thrown when a cycle is detected. /// private void DfsTopologicalSort( IDirectedWeightedGraph graph, Vertex vertex, HashSet> visited, HashSet> recursionStack, Stack> stack) { // CYCLE DETECTION: // If the vertex is in the recursion stack, we've encountered it again // in the current DFS path, which means there's a cycle. if (recursionStack.Contains(vertex)) { throw new InvalidOperationException( $"Graph contains a cycle involving vertex: {vertex}. " + "Topological sort is only possible for Directed Acyclic Graphs (DAGs)."); } // If already visited, no need to process again. if (visited.Contains(vertex)) { return; } // Mark vertex as visited and add to recursion stack. visited.Add(vertex); recursionStack.Add(vertex); // Recursively visit all neighbors (descendants). // This ensures all dependencies are processed first. foreach (var neighbor in graph.GetNeighbors(vertex)) { if (neighbor != null) { DfsTopologicalSort(graph, neighbor, visited, recursionStack, stack); } } // Remove from recursion stack as we're done with this DFS path. recursionStack.Remove(vertex); // POST-ORDER: Add vertex to stack after visiting all descendants. // This ensures that all vertices that depend on the current vertex // are already in the stack (deeper in the stack). stack.Push(vertex); } } ================================================ FILE: Algorithms/Knapsack/BranchAndBoundKnapsackSolver.cs ================================================ namespace Algorithms.Knapsack; /// /// Branch and bound Knapsack solver. /// /// Type of items in knapsack. public class BranchAndBoundKnapsackSolver { /// /// 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. /// /// All items to choose from. /// The maximum weight capacity of the knapsack to be filled. /// /// A function that returns the value of the specified item /// from the items list. /// /// /// A function that returns the weight of the specified item /// from the items list. /// /// /// The array of items that provides the maximum value of the /// knapsack without exceeding the specified weight capacity. /// public T[] Solve(T[] items, int capacity, Func weightSelector, Func valueSelector) { // 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 nodesQueue = new(); // 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); // lastNodeOfOptimalPat --> last item in the optimal item sets identified by this algorithm BranchAndBoundNode lastNodeOfOptimalPath = 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(); // 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); } } return GetItemsFromPath(items, lastNodeOfOptimalPath); } // determine items taken based on the path private static T[] GetItemsFromPath(T[] items, BranchAndBoundNode lastNodeOfPath) { List takenItems = []; // only bogus initial node has no parent for (var current = lastNodeOfPath; current.Parent is not null; current = current.Parent) { if (current.IsTaken) { takenItems.Add(items[current.Level]); } } return takenItems.ToArray(); } /// /// Returns the upper bound value of a given node. /// /// The given node. /// All items to choose from. /// The maximum weight capacity of the knapsack to be filled. /// /// A function that returns the value of the specified item /// from the items list. /// /// /// A function that returns the weight of the specified item /// from the items list. /// /// /// upper bound value of the given node. /// private static double ComputeUpperBound(BranchAndBoundNode aNode, T[] items, int capacity, Func weightSelector, Func 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) { upperBound += valueSelector(items[nextLevel]); availableWeight -= weightSelector(items[nextLevel]); } else { upperBound += valueSelector(items[nextLevel]) / weightSelector(items[nextLevel]) * availableWeight; availableWeight = 0; } nextLevel++; } return upperBound; } } ================================================ FILE: Algorithms/Knapsack/BranchAndBoundNode.cs ================================================ namespace Algorithms.Knapsack; public class BranchAndBoundNode(int level, bool taken, BranchAndBoundNode? parent = null) { // isTaken --> true = the item where index = level is taken, vice versa public bool IsTaken { get; } = taken; // 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; } // 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; // parent node public BranchAndBoundNode? Parent { get; } = parent; } ================================================ FILE: Algorithms/Knapsack/DynamicProgrammingKnapsackSolver.cs ================================================ namespace Algorithms.Knapsack; /// /// Dynamic Programming Knapsack solver. /// /// Type of items in knapsack. public class DynamicProgrammingKnapsackSolver { /// /// Returns the knapsack containing the items that /// maximize value while not exceeding weight capacity. /// /// The list of items from which we select ones to be in the knapsack. /// /// The maximum weight capacity of the knapsack /// to be filled. Only integer values of this capacity are tried. If /// a greater resolution is needed, multiply the /// weights/capacity by a factor of 10. /// /// /// A function that returns the value of the specified item /// from the items list. /// /// /// A function that returns the weight of the specified item /// from the items list. /// /// /// The array of items that provides the maximum value of the /// knapsack without exceeding the specified weight capacity. /// public T[] Solve(T[] items, int capacity, Func weightSelector, Func valueSelector) { var cache = Tabulate(items, weightSelector, valueSelector, capacity); return GetOptimalItems(items, weightSelector, cache, capacity); } private static T[] GetOptimalItems(T[] items, Func weightSelector, double[,] cache, int capacity) { var currentCapacity = capacity; var result = new List(); for (var i = items.Length - 1; i >= 0; i--) { if (cache[i + 1, currentCapacity] > cache[i, currentCapacity]) { var item = items[i]; result.Add(item); currentCapacity -= weightSelector(item); } } result.Reverse(); // we added items back to front return result.ToArray(); } private static double[,] Tabulate( T[] items, Func weightSelector, Func valueSelector, int maxCapacity) { // Store the incremental results in a bottom up manner var n = items.Length; var results = new double[n + 1, maxCapacity + 1]; for (var i = 0; i <= n; i++) { for (var w = 0; w <= maxCapacity; w++) { if (i == 0 || w == 0) { // If we have no items to take, or // if we have no capacity in our knapsack // we cannot possibly have any value results[i, w] = 0; } else if (weightSelector(items[i - 1]) <= w) { // Decide if it is better to take or not take this item var iut = items[i - 1]; // iut = Item under test var vut = valueSelector(iut); // vut = Value of item under test var wut = weightSelector(iut); // wut = Weight of item under test var valueIfTaken = vut + results[i - 1, w - wut]; var valueIfNotTaken = results[i - 1, w]; results[i, w] = Math.Max(valueIfTaken, valueIfNotTaken); } else { // There is not enough room to take this item results[i, w] = results[i - 1, w]; } } } return results; } } ================================================ FILE: Algorithms/Knapsack/IHeuristicKnapsackSolver.cs ================================================ namespace Algorithms.Knapsack; /// /// Solves knapsack problem using some heuristics /// Sum of values of taken items -> max /// Sum of weights of taken items. <= capacity. /// /// Type of items in knapsack. public interface IHeuristicKnapsackSolver { /// /// Solves knapsack problem using some heuristics /// Sum of values of taken items -> max /// Sum of weights of taken items. <= capacity. /// /// All items to choose from. /// How much weight we can take. /// Maps item to its weight. /// Maps item to its value. /// Items that were chosen. T[] Solve(T[] items, double capacity, Func weightSelector, Func valueSelector); } ================================================ FILE: Algorithms/Knapsack/IKnapsackSolver.cs ================================================ namespace Algorithms.Knapsack; /// /// Solves knapsack problem: /// to maximize sum of values of taken items, /// while sum of weights of taken items is less than capacity. /// /// Type of items in knapsack. public interface IKnapsackSolver : IHeuristicKnapsackSolver { } ================================================ FILE: Algorithms/Knapsack/NaiveKnapsackSolver.cs ================================================ namespace Algorithms.Knapsack; /// /// Greedy heurictic solver. /// /// Type of items in knapsack. public class NaiveKnapsackSolver : IHeuristicKnapsackSolver { /// /// Solves the knapsack problem using a naive greedy approach. /// Items are added in order until capacity is reached. /// /// Array of items to consider for the knapsack. /// Maximum weight capacity of the knapsack. /// Function to get the weight of an item. /// Function to get the value of an item. /// Array of items that fit in the knapsack. public T[] Solve(T[] items, double capacity, Func weightSelector, Func valueSelector) { var weight = 0d; var left = new List(); foreach (var item in items) { var weightDelta = weightSelector(item); if (weight + weightDelta <= capacity) { weight += weightDelta; left.Add(item); } } return left.ToArray(); } } ================================================ FILE: Algorithms/LinearAlgebra/Distances/Chebyshev.cs ================================================ namespace Algorithms.LinearAlgebra.Distances; /// /// Implementation of Chebyshev distance. /// It is the maximum absolute difference between the measures in all dimensions of two points. /// In other words, it is the maximum distance one has to travel along any coordinate axis to get from one point to another. /// /// It is commonly used in various fields such as chess, warehouse logistics, and more. /// public static class Chebyshev { /// /// Calculate Chebyshev distance for two N-Dimensional points. /// /// First N-Dimensional point. /// Second N-Dimensional point. /// Calculated Chebyshev distance. public static double Distance(double[] point1, double[] point2) { if (point1.Length != point2.Length) { throw new ArgumentException("Both points should have the same dimensionality"); } // distance = max(|x1-y1|, |x2-y2|, ..., |xn-yn|) return point1.Zip(point2, (x1, x2) => Math.Abs(x1 - x2)).Max(); } } ================================================ FILE: Algorithms/LinearAlgebra/Distances/Euclidean.cs ================================================ namespace Algorithms.LinearAlgebra.Distances; /// /// Implementation for Euclidean distance. /// public static class Euclidean { /// /// Calculate Euclidean distance for two N-Dimensional points. /// /// First N-Dimensional point. /// Second N-Dimensional point. /// Calculated Euclidean distance. public static double Distance(double[] point1, double[] point2) { if (point1.Length != point2.Length) { throw new ArgumentException("Both points should have the same dimensionality"); } // distance = sqrt((x1-y1)^2 + (x2-y2)^2 + ... + (xn-yn)^2) return Math.Sqrt(point1.Zip(point2, (x1, x2) => (x1 - x2) * (x1 - x2)).Sum()); } } ================================================ FILE: Algorithms/LinearAlgebra/Distances/Manhattan.cs ================================================ namespace Algorithms.LinearAlgebra.Distances; /// /// Implementation fo Manhattan distance. /// It is the sum of the lengths of the projections of the line segment between the points onto the coordinate axes. /// In other words, it is the sum of absolute difference between the measures in all dimensions of two points. /// /// Its commonly used in regression analysis. /// public static class Manhattan { /// /// Calculate Manhattan distance for two N-Dimensional points. /// /// First N-Dimensional point. /// Second N-Dimensional point. /// Calculated Manhattan distance. public static double Distance(double[] point1, double[] point2) { if (point1.Length != point2.Length) { throw new ArgumentException("Both points should have the same dimensionality"); } // distance = |x1-y1| + |x2-y2| + ... + |xn-yn| return point1.Zip(point2, (x1, x2) => Math.Abs(x1 - x2)).Sum(); } } ================================================ FILE: Algorithms/LinearAlgebra/Distances/Minkowski.cs ================================================ namespace Algorithms.LinearAlgebra.Distances; /// /// Implementation of Minkowski distance. /// It is the sum of the lengths of the projections of the line segment between the points onto the /// coordinate axes, raised to the power of the order and then taking the p-th root. /// For the case of order = 1, the Minkowski distance degenerates to the Manhattan distance, /// for order = 2, the usual Euclidean distance is obtained and for order = infinity, the Chebyshev distance is obtained. /// public static class Minkowski { /// /// Calculate Minkowski distance for two N-Dimensional points. /// /// First N-Dimensional point. /// Second N-Dimensional point. /// Order of the Minkowski distance. /// Calculated Minkowski distance. public static double Distance(double[] point1, double[] point2, int order) { if (order < 1) { throw new ArgumentException("The order must be greater than or equal to 1."); } if (point1.Length != point2.Length) { throw new ArgumentException("Both points should have the same dimensionality"); } // distance = (|x1-y1|^p + |x2-y2|^p + ... + |xn-yn|^p)^(1/p) return Math.Pow(point1.Zip(point2, (x1, x2) => Math.Pow(Math.Abs(x1 - x2), order)).Sum(), 1.0 / order); } } ================================================ FILE: Algorithms/LinearAlgebra/Eigenvalue/PowerIteration.cs ================================================ namespace Algorithms.LinearAlgebra.Eigenvalue; /// /// Power iteration method - eigenvalue numeric algorithm, based on recurrent relation: /// Li+1 = (A * Li) / || A * Li ||, where Li - eigenvector approximation. /// public static class PowerIteration { /// /// Returns approximation of the dominant eigenvalue and eigenvector of matrix. /// /// /// /// The algorithm will not converge if the start vector is orthogonal to the eigenvector. /// /// /// The matrix must be square-shaped. /// /// /// Source square-shaped matrix. /// Start vector. /// Accuracy of the result. /// Dominant eigenvalue and eigenvector pair. /// The matrix is not square-shaped. /// The length of the start vector doesn't equal the size of the source matrix. public static (double Eigenvalue, double[] Eigenvector) Dominant( double[,] source, double[] startVector, double error = 0.00001) { if (source.GetLength(0) != source.GetLength(1)) { throw new ArgumentException("The source matrix is not square-shaped."); } if (source.GetLength(0) != startVector.Length) { throw new ArgumentException( "The length of the start vector doesn't equal the size of the source matrix."); } double eigenNorm; double[] previousEigenVector; double[] currentEigenVector = startVector; do { previousEigenVector = currentEigenVector; currentEigenVector = source.Multiply( previousEigenVector.ToColumnVector()) .ToRowVector(); eigenNorm = currentEigenVector.Magnitude(); currentEigenVector = currentEigenVector.Select(x => x / eigenNorm).ToArray(); } while (Math.Abs(currentEigenVector.Dot(previousEigenVector)) < 1.0 - error); var eigenvalue = source.Multiply(currentEigenVector.ToColumnVector()).ToRowVector().Magnitude(); return (eigenvalue, Eigenvector: currentEigenVector); } /// /// Returns approximation of the dominant eigenvalue and eigenvector of matrix. /// Random normalized vector is used as the start vector to decrease chance of orthogonality to the eigenvector. /// /// /// /// The algorithm will not converge if the start vector is orthogonal to the eigenvector. /// /// /// The matrix should be square-shaped. /// /// /// Source square-shaped matrix. /// Accuracy of the result. /// Dominant eigenvalue and eigenvector pair. /// The matrix is not square-shaped. /// The length of the start vector doesn't equal the size of the source matrix. public static (double Eigenvalue, double[] Eigenvector) Dominant(double[,] source, double error = 0.00001) => Dominant(source, new Random().NextVector(source.GetLength(1)), error); } ================================================ FILE: Algorithms/MachineLearning/KNearestNeighbors.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.MachineLearning; /// /// K-Nearest Neighbors (KNN) classifier implementation. /// This algorithm classifies data points based on the majority label of their k nearest neighbors. /// /// /// The type of the label used for classification. This can be any type that represents the class or category of a sample. /// public class KNearestNeighbors { private readonly List<(double[] Features, TLabel Label)> trainingData = new(); private readonly int k; /// /// Initializes a new instance of the classifier. /// /// Number of neighbors to consider for classification. /// Thrown if k is less than 1. public KNearestNeighbors(int k) { if (k < 1) { throw new ArgumentOutOfRangeException(nameof(k), "k must be at least 1."); } this.k = k; } /// /// Calculates the Euclidean distance between two feature vectors. /// /// First feature vector. /// Second feature vector. /// Euclidean distance. /// Thrown if vectors are of different lengths. public static double EuclideanDistance(double[] a, double[] b) { if (a.Length != b.Length) { throw new ArgumentException("Feature vectors must be of the same length."); } double sum = 0; for (int i = 0; i < a.Length; i++) { double diff = a[i] - b[i]; sum += diff * diff; } return Math.Sqrt(sum); } /// /// Adds a training sample to the classifier. /// /// Feature vector of the sample. /// Label of the sample. public void AddSample(double[] features, TLabel label) { if (features == null) { throw new ArgumentNullException(nameof(features)); } trainingData.Add((features, label)); } /// /// Predicts the label for a given feature vector using the KNN algorithm. /// /// Feature vector to classify. /// Predicted label. /// Thrown if there is no training data. public TLabel Predict(double[] features) { if (trainingData.Count == 0) { throw new InvalidOperationException("No training data available."); } if (features == null) { throw new ArgumentNullException(nameof(features)); } // Compute distances to all training samples var distances = trainingData .Select(td => (Label: td.Label, Distance: EuclideanDistance(features, td.Features))) .OrderBy(x => x.Distance) .Take(k) .ToList(); // Majority vote var labelCounts = distances .GroupBy(x => x.Label) .Select(g => new { Label = g.Key, Count = g.Count(), MinDistance = g.Min(item => item.Distance) }) .OrderByDescending(x => x.Count) .ThenBy(x => x.MinDistance) .ToList(); return labelCounts.First().Label; } } ================================================ FILE: Algorithms/MachineLearning/LinearRegression.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.MachineLearning; /// /// Implements simple linear regression for one independent variable (univariate). /// Linear regression is a supervised learning algorithm used to model the relationship /// between a scalar dependent variable (Y) and an independent variable (X). /// The model fits a line: Y = a + bX, where 'a' is the intercept and 'b' is the slope. /// public class LinearRegression { // Intercept (a) and slope (b) of the fitted line public double Intercept { get; private set; } public double Slope { get; private set; } public bool IsFitted { get; private set; } /// /// Fits the linear regression model to the provided data. /// /// List of independent variable values. /// List of dependent variable values. /// Thrown if input lists are null, empty, or of different lengths. public void Fit(IList x, IList y) { if (x == null || y == null) { throw new ArgumentException("Input data cannot be null."); } if (x.Count == 0 || y.Count == 0) { throw new ArgumentException("Input data cannot be empty."); } if (x.Count != y.Count) { throw new ArgumentException("Input lists must have the same length."); } // Calculate means double xMean = x.Average(); double yMean = y.Average(); // Calculate slope (b) and intercept (a) double numerator = 0.0; double denominator = 0.0; for (int i = 0; i < x.Count; i++) { numerator += (x[i] - xMean) * (y[i] - yMean); denominator += (x[i] - xMean) * (x[i] - xMean); } const double epsilon = 1e-12; if (Math.Abs(denominator) < epsilon) { throw new ArgumentException("Variance of X must not be zero."); } Slope = numerator / denominator; Intercept = yMean - Slope * xMean; IsFitted = true; } /// /// Predicts the output value for a given input using the fitted model. /// /// Input value. /// Predicted output value. /// Thrown if the model is not fitted. public double Predict(double x) { if (!IsFitted) { throw new InvalidOperationException("Model must be fitted before prediction."); } return Intercept + Slope * x; } /// /// Predicts output values for a list of inputs using the fitted model. /// /// List of input values. /// List of predicted output values. /// Thrown if the model is not fitted. public IList Predict(IList xValues) { if (!IsFitted) { throw new InvalidOperationException("Model must be fitted before prediction."); } return xValues.Select(Predict).ToList(); } } ================================================ FILE: Algorithms/MachineLearning/LogisticRegression.cs ================================================ using System; using System.Linq; namespace Algorithms.MachineLearning; /// /// Logistic Regression for binary classification. /// public class LogisticRegression { private double[] weights = []; private double bias; public int FeatureCount => weights.Length; /// /// Fit the model using gradient descent. /// /// 2D array of features (samples x features). /// Array of labels (0 or 1). /// Number of iterations. /// Step size. public void Fit(double[][] x, int[] y, int epochs = 1000, double learningRate = 0.01) { if (x.Length == 0 || x[0].Length == 0) { throw new ArgumentException("Input features cannot be empty."); } if (x.Length != y.Length) { throw new ArgumentException("Number of samples and labels must match."); } int nSamples = x.Length; int nFeatures = x[0].Length; weights = new double[nFeatures]; bias = 0; for (int epoch = 0; epoch < epochs; epoch++) { double[] dw = new double[nFeatures]; double db = 0; for (int i = 0; i < nSamples; i++) { double linear = Dot(x[i], weights) + bias; double pred = Sigmoid(linear); double error = pred - y[i]; for (int j = 0; j < nFeatures; j++) { dw[j] += error * x[i][j]; } db += error; } for (int j = 0; j < nFeatures; j++) { weights[j] -= learningRate * dw[j] / nSamples; } bias -= learningRate * db / nSamples; } } /// /// Predict probability for a single sample. /// public double PredictProbability(double[] x) { if (x.Length != weights.Length) { throw new ArgumentException("Feature count mismatch."); } return Sigmoid(Dot(x, weights) + bias); } /// /// Predict class label (0 or 1) for a single sample. /// public int Predict(double[] x) => PredictProbability(x) >= 0.5 ? 1 : 0; private static double Sigmoid(double z) => 1.0 / (1.0 + Math.Exp(-z)); private static double Dot(double[] a, double[] b) => a.Zip(b).Sum(pair => pair.First * pair.Second); } ================================================ FILE: Algorithms/ModularArithmetic/ChineseRemainderTheorem.cs ================================================ namespace Algorithms.ModularArithmetic; /// /// Chinese Remainder Theorem: https://en.wikipedia.org/wiki/Chinese_remainder_theorem. /// public static class ChineseRemainderTheorem { /// /// The Chinese Remainder Theorem is used to compute x for given set of pairs of integers (a_i, n_i) with: /// /// x = a_0 mod n_0 /// x = a_1 mod n_1 /// ... /// x = a_k mod n_k /// /// for 0 <= i < k, for some positive integer k. Additional requirements are: /// /// n_i > 1 for 0 <= i < k /// n_i and n_j are coprime, for all 0 <= i < j < k /// 0 <= a_i < n_i, for all 0 <= i < k /// 0 <= x < n_0 * n_1 * ... * n_(k-1) /// /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// The value x. /// If any of the requirements is not fulfilled. public static long Compute(List listOfAs, List listOfNs) { // Check the requirements for this algorithm: CheckRequirements(listOfAs, listOfNs); // For performance-reasons compute the product of all n_i as prodN, because we're going to need access to (prodN / n_i) for all i: var prodN = 1L; foreach (var n in listOfNs) { prodN *= n; } var result = 0L; for (var i = 0; i < listOfNs.Count; i++) { var a_i = listOfAs[i]; var n_i = listOfNs[i]; var modulus_i = prodN / n_i; var bezout_modulus_i = ExtendedEuclideanAlgorithm.Compute(n_i, modulus_i).BezoutB; result += a_i * bezout_modulus_i * modulus_i; } // Make sure, result is in [0, prodN). result %= prodN; if (result < 0) { result += prodN; } return result; } /// /// The Chinese Remainder Theorem is used to compute x for given set of pairs of integers (a_i, n_i) with: /// /// x = a_0 mod n_0 /// x = a_1 mod n_1 /// ... /// x = a_k mod n_k /// /// for 0 <= i < k, for some positive integer k. Additional requirements are: /// /// n_i > 1 for 0 <= i < k /// n_i and n_j are coprime, for all 0 <= i < j < k /// 0 <= a_i < n_i, for all 0 <= i < k /// 0 <= x < n_0 * n_1 * ... * n_(k-1) /// /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// The value x. /// If any of the requirements is not fulfilled. public static BigInteger Compute(List listOfAs, List listOfNs) { // Check the requirements for this algorithm: CheckRequirements(listOfAs, listOfNs); // For performance-reasons compute the product of all n_i as prodN, because we're going to need access to (prodN / n_i) for all i: var prodN = BigInteger.One; foreach (var n in listOfNs) { prodN *= n; } var result = BigInteger.Zero; for (var i = 0; i < listOfNs.Count; i++) { var a_i = listOfAs[i]; var n_i = listOfNs[i]; var modulus_i = prodN / n_i; var bezout_modulus_i = ExtendedEuclideanAlgorithm.Compute(n_i, modulus_i).BezoutB; result += a_i * bezout_modulus_i * modulus_i; } // Make sure, result is in [0, prodN). result %= prodN; if (result < 0) { result += prodN; } return result; } /// /// Checks the requirements for the algorithm and throws an ArgumentException if they are not being met. /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// If any of the requirements is not fulfilled. private static void CheckRequirements(List listOfAs, List listOfNs) { if (listOfAs == null || listOfNs == null || listOfAs.Count != listOfNs.Count) { throw new ArgumentException("The parameters 'listOfAs' and 'listOfNs' must not be null and have to be of equal length!"); } if (listOfNs.Any(x => x <= 1)) { throw new ArgumentException($"The value {listOfNs.First(x => x <= 1)} for some n_i is smaller than or equal to 1."); } if (listOfAs.Any(x => x < 0)) { throw new ArgumentException($"The value {listOfAs.First(x => x < 0)} for some a_i is smaller than 0."); } // Check if all pairs of (n_i, n_j) are coprime: for (var i = 0; i < listOfNs.Count; i++) { for (var j = i + 1; j < listOfNs.Count; j++) { long gcd; if ((gcd = ExtendedEuclideanAlgorithm.Compute(listOfNs[i], listOfNs[j]).Gcd) != 1L) { throw new ArgumentException($"The GCD of n_{i} = {listOfNs[i]} and n_{j} = {listOfNs[j]} equals {gcd} and thus these values aren't coprime."); } } } } /// /// Checks the requirements for the algorithm and throws an ArgumentException if they are not being met. /// /// An ordered list of a_0, a_1, ..., a_k. /// An ordered list of n_0, n_1, ..., n_k. /// If any of the requirements is not fulfilled. private static void CheckRequirements(List listOfAs, List listOfNs) { if (listOfAs == null || listOfNs == null || listOfAs.Count != listOfNs.Count) { throw new ArgumentException("The parameters 'listOfAs' and 'listOfNs' must not be null and have to be of equal length!"); } if (listOfNs.Any(x => x <= 1)) { throw new ArgumentException($"The value {listOfNs.First(x => x <= 1)} for some n_i is smaller than or equal to 1."); } if (listOfAs.Any(x => x < 0)) { throw new ArgumentException($"The value {listOfAs.First(x => x < 0)} for some a_i is smaller than 0."); } // Check if all pairs of (n_i, n_j) are coprime: for (var i = 0; i < listOfNs.Count; i++) { for (var j = i + 1; j < listOfNs.Count; j++) { BigInteger gcd; if ((gcd = ExtendedEuclideanAlgorithm.Compute(listOfNs[i], listOfNs[j]).Gcd) != BigInteger.One) { throw new ArgumentException($"The GCD of n_{i} = {listOfNs[i]} and n_{j} = {listOfNs[j]} equals {gcd} and thus these values aren't coprime."); } } } } } ================================================ FILE: Algorithms/ModularArithmetic/ExtendedEuclideanAlgorithm.cs ================================================ namespace Algorithms.ModularArithmetic; /// /// Extended Euclidean algorithm: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm. /// public static class ExtendedEuclideanAlgorithm { /// /// Computes the greatest common divisor (Gcd) of integers a and b, also the coefficients of Bézout's identity, /// which are integers x and y such that a*bezoutCoefficientOfA + b*bezoutCoefficientOfB = Gcd(a, b). /// /// Input number. /// Second input number. /// A record of ExtendedEuclideanAlgorithmResult containing the bezout coefficients of a and b as well as the Gcd(a,b). public static ExtendedEuclideanAlgorithmResult Compute(long a, long b) { long quotient; long tmp; var s = 0L; var bezoutOfA = 1L; var r = b; var gcd = a; var bezoutOfB = 0L; while (r != 0) { quotient = gcd / r; // integer division tmp = gcd; gcd = r; r = tmp - quotient * r; tmp = bezoutOfA; bezoutOfA = s; s = tmp - quotient * s; } if (b != 0) { bezoutOfB = (gcd - bezoutOfA * a) / b; // integer division } return new ExtendedEuclideanAlgorithmResult(bezoutOfA, bezoutOfB, gcd); } /// /// Computes the greatest common divisor (Gcd) of integers a and b, also the coefficients of Bézout's identity, /// which are integers x and y such that a*bezoutCoefficientOfA + b*bezoutCoefficientOfB = Gcd(a, b). /// /// Input number. /// Second input number. /// A record of ExtendedEuclideanAlgorithmResult containing the bezout coefficients of a and b as well as the Gcd(a,b). public static ExtendedEuclideanAlgorithmResult Compute(BigInteger a, BigInteger b) { BigInteger quotient; BigInteger tmp; var s = BigInteger.Zero; var bezoutOfA = BigInteger.One; var r = b; var gcd = a; var bezoutOfB = BigInteger.Zero; while (r != 0) { quotient = gcd / r; // integer division tmp = gcd; gcd = r; r = tmp - quotient * r; tmp = bezoutOfA; bezoutOfA = s; s = tmp - quotient * s; } if (b != 0) { bezoutOfB = (gcd - bezoutOfA * a) / b; // integer division } return new ExtendedEuclideanAlgorithmResult(bezoutOfA, bezoutOfB, gcd); } /// /// The result type for the computation of the Extended Euclidean Algorithm. /// /// The data type of the computation (i.e. long or BigInteger). /// The bezout coefficient of the parameter a to the computation. /// The bezout coefficient of the parameter b to the computation. /// The greatest common divisor of the parameters a and b to the computation. public record ExtendedEuclideanAlgorithmResult(T BezoutA, T BezoutB, T Gcd); } ================================================ FILE: Algorithms/ModularArithmetic/ModularMultiplicativeInverse.cs ================================================ namespace Algorithms.ModularArithmetic; /// /// Modular multiplicative inverse: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse. /// public static class ModularMultiplicativeInverse { /// /// Computes the modular multiplicative inverse of a in Z/nZ, if there is any (i.e. if a and n are coprime). /// /// The number a, of which to compute the multiplicative inverse. /// The modulus n. /// The multiplicative inverse of a in Z/nZ, a value in the interval [0, n). /// If there exists no multiplicative inverse of a in Z/nZ. public static long Compute(long a, long n) { var eeaResult = ExtendedEuclideanAlgorithm.Compute(a, n); // Check if there is an inverse: if (eeaResult.Gcd != 1) { throw new ArithmeticException($"{a} is not invertible in Z/{n}Z."); } // Make sure, inverseOfA (i.e. the bezout coefficient of a) is in the interval [0, n). var inverseOfA = eeaResult.BezoutA; if (inverseOfA < 0) { inverseOfA += n; } return inverseOfA; } /// /// Computes the modular multiplicative inverse of a in Z/nZ, if there is any (i.e. if a and n are coprime). /// /// The number a, of which to compute the multiplicative inverse. /// The modulus n. /// The multiplicative inverse of a in Z/nZ, a value in the interval [0, n). /// If there exists no multiplicative inverse of a in Z/nZ. public static BigInteger Compute(BigInteger a, BigInteger n) { var eeaResult = ExtendedEuclideanAlgorithm.Compute(a, n); // Check if there is an inverse: if (eeaResult.Gcd != 1) { throw new ArithmeticException($"{a} is not invertible in Z/{n}Z."); } // Make sure, inverseOfA (i.e. the bezout coefficient of a) is in the interval [0, n). var inverseOfA = eeaResult.BezoutA; if (inverseOfA < 0) { inverseOfA += n; } return inverseOfA; } } ================================================ FILE: Algorithms/NewtonSquareRoot.cs ================================================ namespace Algorithms; public static class NewtonSquareRoot { public static BigInteger Calculate(BigInteger number) { if (number < 0) { throw new ArgumentException("Cannot calculate the square root of a negative number."); } if (number == 0) { return BigInteger.Zero; } var bitLength = Convert.ToInt32(Math.Ceiling(BigInteger.Log(number, 2))); BigInteger root = BigInteger.One << (bitLength / 2); while (!IsSquareRoot(number, root)) { root += number / root; root /= 2; } return root; } private static bool IsSquareRoot(BigInteger number, BigInteger root) { var lowerBound = root * root; return number >= lowerBound && number <= lowerBound + root + root; } } ================================================ FILE: Algorithms/Numeric/Abs.cs ================================================ namespace Algorithms.Numeric; /// /// Find the absolute value of a number. /// public static class Abs { /// /// Returns the absolute value of a number. /// /// Type of number. /// Number to find the absolute value of. /// Absolute value of the number. public static T AbsVal(T inputNum) where T : INumber { return T.IsNegative(inputNum) ? -inputNum : inputNum; } /// /// Returns the number with the smallest absolute value on the input array. /// /// Type of number. /// Array of numbers to find the smallest absolute. /// Smallest absolute number. public static T AbsMin(T[] inputNums) where T : INumber { if (inputNums.Length == 0) { throw new ArgumentException("Array is empty."); } var min = inputNums[0]; for (var index = 1; index < inputNums.Length; index++) { var current = inputNums[index]; if (AbsVal(current).CompareTo(AbsVal(min)) < 0) { min = current; } } return min; } /// /// Returns the number with the largest absolute value on the input array. /// /// Type of number. /// Array of numbers to find the largest absolute. /// Largest absolute number. public static T AbsMax(T[] inputNums) where T : INumber { if (inputNums.Length == 0) { throw new ArgumentException("Array is empty."); } var max = inputNums[0]; for (var index = 1; index < inputNums.Length; index++) { var current = inputNums[index]; if (AbsVal(current).CompareTo(AbsVal(max)) > 0) { max = current; } } return max; } } ================================================ FILE: Algorithms/Numeric/AdditionWithoutArithmetic.cs ================================================ namespace Algorithms.Numeric; /// /// Add the integers without arithmetic operation. /// public static class AdditionWithoutArithmetic { /// /// Returns the sum of two integers. /// /// First number to add. /// Second number to add. /// Sum of the two numbers. public static int CalculateAdditionWithoutArithmetic(int first, int second) { while (second != 0) { int c = first & second; // Carry first ^= second; // Sum without carry second = c << 1; // Carry shifted left } return first; } } ================================================ FILE: Algorithms/Numeric/AliquotSumCalculator.cs ================================================ namespace Algorithms.Numeric; /// /// In number theory, the aliquot sum s(n) of a positive integer n is the sum of all proper divisors /// of n, that is, all divisors of n other than n itself. For example, the proper divisors of 15 /// (that is, the positive divisors of 15 that are not equal to 15) are 1, 3 and 5, so the aliquot /// sum of 15 is 9 i.e. (1 + 3 + 5). Wikipedia: https://en.wikipedia.org/wiki/Aliquot_sum. /// public static class AliquotSumCalculator { /// /// Finds the aliquot sum of an integer number. /// /// Positive number. /// The Aliquot Sum. /// Error number is not on interval (0.0; int.MaxValue). public static int CalculateAliquotSum(int number) { if (number < 0) { throw new ArgumentException($"{nameof(number)} cannot be negative"); } var sum = 0; for (int i = 1, limit = number / 2; i <= limit; ++i) { if (number % i == 0) { sum += i; } } return sum; } } ================================================ FILE: Algorithms/Numeric/AmicableNumbersChecker.cs ================================================ namespace Algorithms.Numeric; /// /// Amicable numbers are two different natural numbers related in such a way that the sum of the proper divisors of /// each is equal to the other number. That is, σ(a)=b+a and σ(b)=a+b, where σ(n) is equal to the sum of positive divisors of n (see also divisor function). /// See here for more info. /// public static class AmicableNumbersChecker { /// /// Checks if two numbers are amicable or not. /// /// First number to check. /// Second number to check. /// True if they are amicable numbers. False if not. public static bool AreAmicableNumbers(int x, int y) { return SumOfDivisors(x) == y && SumOfDivisors(y) == x; } private static int SumOfDivisors(int number) { var sum = 0; /* sum of its positive divisors */ for (var i = 1; i < number; ++i) { if (number % i == 0) { sum += i; } } return sum; } } ================================================ FILE: Algorithms/Numeric/AutomorphicNumber.cs ================================================ namespace Algorithms.Numeric; /// /// Calculates Automorphic numbers. A number is said to be an Automorphic number /// if the square of the number will contain the number itself at the end. /// public static class AutomorphicNumber { /// /// Generates a list of automorphic numbers that are between and /// inclusive. /// /// The lower bound of the list. /// The upper bound of the list. /// A list that contains all of the automorphic numbers between and inclusive. /// If the /// or is not greater than zero /// or is lower than the . public static IEnumerable GetAutomorphicNumbers(int lowerBound, int upperBound) { if (lowerBound < 1) { throw new ArgumentException($"Lower bound must be greater than 0."); } if (upperBound < 1) { throw new ArgumentException($"Upper bound must be greater than 0."); } if (lowerBound > upperBound) { throw new ArgumentException($"The lower bound must be less than or equal to the upper bound."); } return Enumerable.Range(lowerBound, upperBound).Where(IsAutomorphic); } /// /// Checks if a given natural number is automorphic or not. /// /// The number to check. /// True if the number is automorphic, false otherwise. /// If the number is non-positive. public static bool IsAutomorphic(int number) { if (number < 1) { throw new ArgumentException($"An automorphic number must always be positive."); } BigInteger square = BigInteger.Pow(number, 2); // Extract the last digits of both numbers while (number > 0) { if (number % 10 != square % 10) { return false; } number /= 10; square /= 10; } return true; } } ================================================ FILE: Algorithms/Numeric/BinomialCoefficient.cs ================================================ namespace Algorithms.Numeric; /// /// The binomial coefficients are the positive integers /// that occur as coefficients in the binomial theorem. /// public static class BinomialCoefficient { /// /// Calculates Binomial coefficients for given input. /// /// First number. /// Second number. /// Binimial Coefficients. public static BigInteger Calculate(BigInteger num, BigInteger k) { if (num < k || k < 0) { throw new ArgumentException("num ≥ k ≥ 0"); } // Tricks to gain performance: // 1. Because (num over k) equals (num over (num-k)), we can save multiplications and divisions // by replacing k with the minimum of k and (num - k). k = BigInteger.Min(k, num - k); // 2. We can simplify the computation of (num! / (k! * (num - k)!)) to ((num * (num - 1) * ... * (num - k + 1) / (k!)) // and thus save some multiplications and divisions. var numerator = BigInteger.One; for (var val = num - k + 1; val <= num; val++) { numerator *= val; } // 3. Typically multiplication is a lot faster than division, therefore compute the value of k! first (i.e. k - 1 multiplications) // and then divide the numerator by the denominator (i.e. 1 division); instead of performing k - 1 divisions (1 for each factor in k!). var denominator = BigInteger.One; for (var val = k; val > BigInteger.One; val--) { denominator *= val; } return numerator / denominator; } } ================================================ FILE: Algorithms/Numeric/Ceil.cs ================================================ namespace Algorithms.Numeric; /// /// Perform ceiling operation on a number. /// public static class Ceil { /// /// Returns the smallest integer greater than or equal to the number. /// /// Type of number. /// Number to find the ceiling of. /// Ceiling value of the number. public static T CeilVal(T inputNum) where T : INumber { T intPart = T.CreateChecked(Convert.ToInt32(inputNum)); return inputNum > intPart ? intPart + T.One : intPart; } } ================================================ FILE: Algorithms/Numeric/Decomposition/LU.cs ================================================ namespace Algorithms.Numeric.Decomposition; /// /// LU-decomposition factors the "source" matrix as the product of lower triangular matrix /// and upper triangular matrix. /// public static class Lu { /// /// Performs LU-decomposition on "source" matrix. /// Lower and upper matrices have same shapes as source matrix. /// Note: Decomposition can be applied only to square matrices. /// /// Square matrix to decompose. /// Tuple of lower and upper matrix. /// Source matrix is not square shaped. public static (double[,] L, double[,] U) Decompose(double[,] source) { if (source.GetLength(0) != source.GetLength(1)) { throw new ArgumentException("Source matrix is not square shaped."); } var pivot = source.GetLength(0); var lower = new double[pivot, pivot]; var upper = new double[pivot, pivot]; for (var i = 0; i < pivot; i++) { for (var k = i; k < pivot; k++) { double sum = 0; for (var j = 0; j < i; j++) { sum += lower[i, j] * upper[j, k]; } upper[i, k] = source[i, k] - sum; } for (var k = i; k < pivot; k++) { if (i == k) { lower[i, i] = 1; } else { double sum = 0; for (var j = 0; j < i; j++) { sum += lower[k, j] * upper[j, i]; } lower[k, i] = (source[k, i] - sum) / upper[i, i]; } } } return (L: lower, U: upper); } /// /// Eliminates linear equations system represented as A*x=b, using LU-decomposition, /// where A - matrix of equation coefficients, b - vector of absolute terms of equations. /// /// Matrix of equation coefficients. /// Vector of absolute terms of equations. /// Vector-solution for linear equations system. /// Matrix of equation coefficients is not square shaped. public static double[] Eliminate(double[,] matrix, double[] coefficients) { if (matrix.GetLength(0) != matrix.GetLength(1)) { throw new ArgumentException("Matrix of equation coefficients is not square shaped."); } var pivot = matrix.GetLength(0); var upperTransform = new double[pivot, 1]; // U * upperTransform = coefficients var solution = new double[pivot]; // L * solution = upperTransform (double[,] l, double[,] u) = Decompose(matrix); for (var i = 0; i < pivot; i++) { double pivotPointSum = 0; for (var j = 0; j < i; j++) { pivotPointSum += upperTransform[j, 0] * l[i, j]; } upperTransform[i, 0] = (coefficients[i] - pivotPointSum) / l[i, i]; } for (var i = pivot - 1; i >= 0; i--) { double pivotPointSum = 0; for (var j = i; j < pivot; j++) { pivotPointSum += solution[j] * u[i, j]; } solution[i] = (upperTransform[i, 0] - pivotPointSum) / u[i, i]; } return solution; } } ================================================ FILE: Algorithms/Numeric/Decomposition/ThinSVD.cs ================================================ namespace Algorithms.Numeric.Decomposition; /// /// Singular Vector Decomposition decomposes any general matrix into its /// singular values and a set of orthonormal bases. /// public static class ThinSvd { /// /// Computes a random unit vector. /// /// The dimensions of the required vector. /// The unit vector. public static double[] RandomUnitVector(int dimensions) { Random random = new(); double[] result = new double[dimensions]; for (var i = 0; i < dimensions; i++) { result[i] = 2 * random.NextDouble() - 1; } var magnitude = result.Magnitude(); result = result.Scale(1 / magnitude); return result; } /// /// Computes a single singular vector for the given matrix, corresponding to the largest singular value. /// /// The matrix. /// A singular vector, with dimension equal to number of columns of the matrix. public static double[] Decompose1D(double[,] matrix) => Decompose1D(matrix, 1E-5, 100); /// /// Computes a single singular vector for the given matrix, corresponding to the largest singular value. /// /// The matrix. /// The error margin. /// The maximum number of iterations. /// A singular vector, with dimension equal to number of columns of the matrix. public static double[] Decompose1D(double[,] matrix, double epsilon, int maxIterations) { var n = matrix.GetLength(1); var iterations = 0; double mag; double[] lastIteration; double[] currIteration = RandomUnitVector(n); double[,] b = matrix.Transpose().Multiply(matrix); do { lastIteration = currIteration.Copy(); currIteration = b.MultiplyVector(lastIteration); currIteration = currIteration.Scale(100); mag = currIteration.Magnitude(); if (mag > epsilon) { currIteration = currIteration.Scale(1 / mag); } iterations++; } while (lastIteration.Dot(currIteration) < 1 - epsilon && iterations < maxIterations); return currIteration; } public static (double[,] U, double[] S, double[,] V) Decompose(double[,] matrix) => Decompose(matrix, 1E-5, 100); /// /// Computes the SVD for the given matrix, with singular values arranged from greatest to least. /// /// The matrix. /// The error margin. /// The maximum number of iterations. /// The SVD. public static (double[,] U, double[] S, double[,] V) Decompose( double[,] matrix, double epsilon, int maxIterations) { var m = matrix.GetLength(0); var n = matrix.GetLength(1); var numValues = Math.Min(m, n); // sigmas is be a diagonal matrix, hence only a vector is needed double[] sigmas = new double[numValues]; double[,] us = new double[m, numValues]; double[,] vs = new double[n, numValues]; // keep track of progress double[,] remaining = matrix.Copy(); // for each singular value for (var i = 0; i < numValues; i++) { // compute the v singular vector double[] v = Decompose1D(remaining, epsilon, maxIterations); double[] u = matrix.MultiplyVector(v); // compute the contribution of this pair of singular vectors double[,] contrib = u.OuterProduct(v); // extract the singular value var s = u.Magnitude(); // v and u should be unit vectors if (s < epsilon) { u = new double[m]; v = new double[n]; } else { u = u.Scale(1 / s); } // save u, v and s into the result for (var j = 0; j < u.Length; j++) { us[j, i] = u[j]; } for (var j = 0; j < v.Length; j++) { vs[j, i] = v[j]; } sigmas[i] = s; // remove the contribution of this pair and compute the rest remaining = remaining.Subtract(contrib); } return (U: us, S: sigmas, V: vs); } } ================================================ FILE: Algorithms/Numeric/DoubleFactorial.cs ================================================ using System.Numerics; namespace Algorithms.Numeric; /// /// The double factorial of a positive integer n, denoted by n!!, /// is the product of all integers from 1 up to n that have the same parity (odd or even) as n. /// E.g., 5!! = 5 * 3 * 1 = 15, and 6!! = 6 * 4 * 2 = 48. /// public static class DoubleFactorial { /// /// Calculates the double factorial of a non-negative integer number. /// /// Non-negative integer input number. /// Double factorial of the integer input number. public static BigInteger Calculate(int inputNum) { // Don't calculate double factorial if input is a negative number. if (inputNum < 0) { throw new ArgumentException("Double factorial is only defined for non-negative integers (num >= 0)."); } // Base cases: 0!! = 1 and 1!! = 1 if (inputNum <= 1) { return BigInteger.One; } // Initialize result. BigInteger result = BigInteger.One; // Start the iteration from the input number and step down by 2. // This handles both odd (n, n-2, ..., 3, 1) and even (n, n-2, ..., 4, 2) cases naturally. BigInteger current = inputNum; while (current > BigInteger.Zero) { result *= current; // Decrease the current number by 2 for the next factor. current -= 2; } return result; } } ================================================ FILE: Algorithms/Numeric/EulerMethod.cs ================================================ namespace Algorithms.Numeric; /// /// In mathematics and computational science, the Euler method (also called forward Euler method) /// is a first-order numerical procedure for solving ordinary differential equations (ODEs) /// with a given initial value (aka. Cauchy problem). It is the most basic explicit method for numerical integration /// of ordinary differential equations. The method proceeds in a series of steps. At each step /// the y-value is calculated by evaluating the differential equation at the previous step, /// multiplying the result with the step-size and adding it to the last y-value: /// y_n+1 = y_n + stepSize * f(x_n, y_n). /// (description adapted from https://en.wikipedia.org/wiki/Euler_method ) /// (see also: https://www.geeksforgeeks.org/euler-method-solving-differential-equation/ ). /// public static class EulerMethod { /// /// Loops through all the steps until xEnd is reached, adds a point for each step and then /// returns all the points. /// /// Initial conditions x-value. /// Last x-value. /// Step-size on the x-axis. /// Initial conditions y-value. /// The right hand side of the differential equation. /// The solution of the Cauchy problem. public static List EulerFull( double xStart, double xEnd, double stepSize, double yStart, Func yDerivative) { if (xStart >= xEnd) { throw new ArgumentOutOfRangeException( nameof(xEnd), $"{nameof(xEnd)} should be greater than {nameof(xStart)}"); } if (stepSize <= 0) { throw new ArgumentOutOfRangeException( nameof(stepSize), $"{nameof(stepSize)} should be greater than zero"); } List points = []; double[] firstPoint = [xStart, yStart]; points.Add(firstPoint); var yCurrent = yStart; var xCurrent = xStart; while (xCurrent < xEnd) { yCurrent = EulerStep(xCurrent, stepSize, yCurrent, yDerivative); xCurrent += stepSize; double[] point = [xCurrent, yCurrent]; points.Add(point); } return points; } /// /// Calculates the next y-value based on the current value of x, y and the stepSize. /// /// Current x-value. /// Step-size on the x-axis. /// Current y-value. /// The right hand side of the differential equation. /// The next y-value. private static double EulerStep( double xCurrent, double stepSize, double yCurrent, Func yDerivative) { var yNext = yCurrent + stepSize * yDerivative(xCurrent, yCurrent); return yNext; } } ================================================ FILE: Algorithms/Numeric/Factorial.cs ================================================ namespace Algorithms.Numeric; /// /// The factorial of a positive integer n, denoted by n!, /// is the product of all positive integers less than or equal to n. /// public static class Factorial { /// /// Calculates factorial of a integer number. /// /// Integer Input number. /// Factorial of integer input number. public static BigInteger Calculate(int inputNum) { // Convert integer input to BigInteger BigInteger num = new BigInteger(inputNum); // Don't calculate factorial if input is a negative number. if (BigInteger.Compare(num, BigInteger.Zero) < 0) { throw new ArgumentException("Only for num >= 0"); } // Factorial of numbers greater than 0. BigInteger result = BigInteger.One; for (BigInteger i = BigInteger.One; BigInteger.Compare(i, num) <= 0; i = BigInteger.Add(i, BigInteger.One)) { result = BigInteger.Multiply(result, i); } return result; } } ================================================ FILE: Algorithms/Numeric/Factorization/IFactorizer.cs ================================================ namespace Algorithms.Numeric.Factorization; /// /// Finds a factor of a given number or returns false if it's prime. /// public interface IFactorizer { /// /// Finds a factor of a given number or returns false if it's prime. /// /// Integer to factor. /// Found factor. /// if factor is found, if is prime. bool TryFactor(int n, out int factor); } ================================================ FILE: Algorithms/Numeric/Factorization/TrialDivisionFactorizer.cs ================================================ namespace Algorithms.Numeric.Factorization; /// /// Factors number using trial division algorithm. /// public class TrialDivisionFactorizer : IFactorizer { /// /// Finds the smallest non trivial factor (i.e.: 1 < factor <= sqrt()) of a given number or returns false if it's prime. /// /// Integer to factor. /// Found factor. /// if factor is found, if is prime. public bool TryFactor(int n, out int factor) { n = Math.Abs(n); factor = Enumerable.Range(2, (int)Math.Sqrt(n) - 1).FirstOrDefault(i => n % i == 0); return factor != 0; } } ================================================ FILE: Algorithms/Numeric/Floor.cs ================================================ namespace Algorithms.Numeric; /// /// Perform floor operation on a number. /// public static class Floor { /// /// Returns the largest integer less than or equal to the number. /// /// Type of number. /// Number to find the floor of. /// Floor value of the number. public static T FloorVal(T inputNum) where T : INumber { T intPart = T.CreateChecked(Convert.ToInt32(inputNum)); return inputNum < intPart ? intPart - T.One : intPart; } } ================================================ FILE: Algorithms/Numeric/GaussJordanElimination.cs ================================================ namespace Algorithms.Numeric; /// /// Algorithm used to find the inverse of any matrix that can be inverted. /// public class GaussJordanElimination { private int RowCount { get; set; } /// /// Method to find a linear equation system using gaussian elimination. /// /// The key matrix to solve via algorithm. /// /// whether the input matrix has a unique solution or not. /// and solves on the given matrix. /// public bool Solve(double[,] matrix) { RowCount = matrix.GetUpperBound(0) + 1; if (!CanMatrixBeUsed(matrix)) { throw new ArgumentException("Please use a n*(n+1) matrix with Length > 0."); } var pivot = PivotMatrix(ref matrix); if (!pivot) { return false; } Elimination(ref matrix); return ElementaryReduction(ref matrix); } /// /// To make simple validation of the matrix to be used. /// /// Multidimensional array matrix. /// /// True: if algorithm can be use for given matrix; /// False: Otherwise. /// private bool CanMatrixBeUsed(double[,] matrix) => matrix?.Length == RowCount * (RowCount + 1) && RowCount > 1; /// /// To prepare given matrix by pivoting rows. /// /// Input matrix. /// Matrix. private bool PivotMatrix(ref double[,] matrix) { for (var col = 0; col + 1 < RowCount; col++) { if (matrix[col, col] == 0) { // To find a non-zero coefficient var rowToSwap = FindNonZeroCoefficient(ref matrix, col); if (matrix[rowToSwap, col] != 0) { var tmp = new double[RowCount + 1]; for (var i = 0; i < RowCount + 1; i++) { // To make the swap with the element above. tmp[i] = matrix[rowToSwap, i]; matrix[rowToSwap, i] = matrix[col, i]; matrix[col, i] = tmp[i]; } } else { // To return that the matrix doesn't have a unique solution. return false; } } } return true; } private int FindNonZeroCoefficient(ref double[,] matrix, int col) { var rowToSwap = col + 1; // To find a non-zero coefficient for (; rowToSwap < RowCount; rowToSwap++) { if (matrix[rowToSwap, col] != 0) { return rowToSwap; } } return col + 1; } /// /// Applies REF. /// /// Input matrix. private void Elimination(ref double[,] matrix) { for (var srcRow = 0; srcRow + 1 < RowCount; srcRow++) { for (var destRow = srcRow + 1; destRow < RowCount; destRow++) { var df = matrix[srcRow, srcRow]; var sf = matrix[destRow, srcRow]; for (var i = 0; i < RowCount + 1; i++) { matrix[destRow, i] = matrix[destRow, i] * df - matrix[srcRow, i] * sf; } } } } /// /// To continue reducing the matrix using RREF. /// /// Input matrix. /// True if it has a unique solution; false otherwise. private bool ElementaryReduction(ref double[,] matrix) { for (var row = RowCount - 1; row >= 0; row--) { var element = matrix[row, row]; if (element == 0) { return false; } for (var i = 0; i < RowCount + 1; i++) { matrix[row, i] /= element; } for (var destRow = 0; destRow < row; destRow++) { matrix[destRow, RowCount] -= matrix[destRow, row] * matrix[row, RowCount]; matrix[destRow, row] = 0; } } return true; } } ================================================ FILE: Algorithms/Numeric/GreatestCommonDivisor/BinaryGreatestCommonDivisorFinder.cs ================================================ namespace Algorithms.Numeric.GreatestCommonDivisor; /// /// Finds greatest common divisor for numbers u and v /// using binary algorithm. /// Wiki: https://en.wikipedia.org/wiki/Binary_GCD_algorithm. /// public class BinaryGreatestCommonDivisorFinder : IGreatestCommonDivisorFinder { public int FindGcd(int u, int v) { // GCD(0, 0) = 0 if (u == 0 && v == 0) { return 0; } // GCD(0, v) = v; GCD(u, 0) = u if (u == 0 || v == 0) { return u + v; } // GCD(-a, -b) = GCD(-a, b) = GCD(a, -b) = GCD(a, b) u = Math.Sign(u) * u; v = Math.Sign(v) * v; // Let shift := lg K, where K is the greatest power of 2 dividing both u and v var shift = 0; while (((u | v) & 1) == 0) { u >>= 1; v >>= 1; shift++; } while ((u & 1) == 0) { u >>= 1; } // From here on, u is always odd do { // Remove all factors of 2 in v as they are not common // v is not zero, so while will terminate while ((v & 1) == 0) { v >>= 1; } // Now u and v are both odd. Swap if necessary so u <= v, if (u > v) { var t = v; v = u; u = t; } // Here v >= u and v - u is even v -= u; } while (v != 0); // Restore common factors of 2 return u << shift; } } ================================================ FILE: Algorithms/Numeric/GreatestCommonDivisor/EuclideanGreatestCommonDivisorFinder.cs ================================================ namespace Algorithms.Numeric.GreatestCommonDivisor; /// /// Euclidean algorithm for finding the greatest common divisor. /// public class EuclideanGreatestCommonDivisorFinder : IGreatestCommonDivisorFinder { /// /// Finds greatest common divisor for numbers a and b /// using euclidean algorithm. /// /// First number. /// Second number. /// Greatest common divisor. public int FindGcd(int a, int b) { if (a == 0 && b == 0) { return int.MaxValue; } if (a == 0 || b == 0) { return a + b; } var aa = a; var bb = b; var cc = aa % bb; while (cc != 0) { aa = bb; bb = cc; cc = aa % bb; } return bb; } } ================================================ FILE: Algorithms/Numeric/GreatestCommonDivisor/IGreatestCommonDivisorFinder.cs ================================================ namespace Algorithms.Numeric.GreatestCommonDivisor; public interface IGreatestCommonDivisorFinder { int FindGcd(int a, int b); } ================================================ FILE: Algorithms/Numeric/JosephusProblem.cs ================================================ namespace Algorithms.Numeric; public static class JosephusProblem { /// /// Calculates the winner in the Josephus problem. /// /// The number of people in the initial circle. /// The count of each step. k-1 people are skipped and the k-th is executed. /// The 1-indexed position where the player must choose in order to win the game. public static long FindWinner(long n, long k) { if (k <= 0) { throw new ArgumentException("The step cannot be smaller than 1"); } if (k > n) { throw new ArgumentException("The step cannot be greater than the size of the group"); } long winner = 0; for (long stepIndex = 1; stepIndex <= n; ++stepIndex) { winner = (winner + k) % stepIndex; } return winner + 1; } } ================================================ FILE: Algorithms/Numeric/KeithNumberChecker.cs ================================================ namespace Algorithms.Numeric; /// /// In number theory, a Keith number or repfigit number is a natural number n in a given number base b with k digits such that /// when a sequence is created such that the first k terms are the k digits of n and each subsequent term is the sum of the /// previous k terms, n is part of the sequence. /// public static class KeithNumberChecker { /// /// Checks if a number is a Keith number or not. /// /// Number to check. /// True if it is a Keith number; False otherwise. public static bool IsKeithNumber(int number) { if (number < 0) { throw new ArgumentException($"{nameof(number)} cannot be negative"); } var tempNumber = number; var stringNumber = number.ToString(); var digitsInNumber = stringNumber.Length; /* storing the terms of the series */ var termsArray = new int[number]; for (var i = digitsInNumber - 1; i >= 0; i--) { termsArray[i] = tempNumber % 10; tempNumber /= 10; } var sum = 0; var k = digitsInNumber; while (sum < number) { sum = 0; for (var j = 1; j <= digitsInNumber; j++) { sum += termsArray[k - j]; } termsArray[k] = sum; k++; } return sum == number; } } ================================================ FILE: Algorithms/Numeric/KrishnamurthyNumberChecker.cs ================================================ namespace Algorithms.Numeric; /// /// A Krishnamurthy number is a number whose sum of the factorial of digits /// is equal to the number itself. /// /// For example, 145 is a Krishnamurthy number since: 1! + 4! + 5! = 1 + 24 + 120 = 145. /// public static class KrishnamurthyNumberChecker { /// /// Check if a number is Krishnamurthy number or not. /// /// The number to check. /// True if the number is Krishnamurthy, false otherwise. public static bool IsKMurthyNumber(int n) { int sumOfFactorials = 0; int tmp = n; if (n <= 0) { return false; } while (n != 0) { int factorial = (int)Factorial.Calculate(n % 10); sumOfFactorials += factorial; n = n / 10; } return tmp == sumOfFactorials; } } ================================================ FILE: Algorithms/Numeric/MillerRabinPrimalityChecker.cs ================================================ namespace Algorithms.Numeric; /// /// https://en.wikipedia.org/wiki/Miller-Rabin_primality_test /// The Miller–Rabin primality test or Rabin–Miller primality test is a probabilistic primality test: /// an algorithm which determines whether a given number is likely to be prime, /// similar to the Fermat primality test and the Solovay–Strassen primality test. /// It is of historical significance in the search for a polynomial-time deterministic primality test. /// Its probabilistic variant remains widely used in practice, as one of the simplest and fastest tests known. /// public static class MillerRabinPrimalityChecker { /// /// Run the probabilistic primality test. /// /// Number to check. /// Number of rounds, the parameter determines the accuracy of the test, recommended value is Log2(n). /// Seed for random number generator. /// True if is a highly likely prime number; False otherwise. /// Error: number should be more than 3. public static bool IsProbablyPrimeNumber(BigInteger n, BigInteger rounds, int? seed = null) { Random rand = seed is null ? new() : new(seed.Value); return IsProbablyPrimeNumber(n, rounds, rand); } private static bool IsProbablyPrimeNumber(BigInteger n, BigInteger rounds, Random rand) { if (n <= 3) { throw new ArgumentException($"{nameof(n)} should be more than 3"); } // Input #1: n > 3, an odd integer to be tested for primality // Input #2: k, the number of rounds of testing to perform, recommended k = Log2(n) // Output: false = “composite” // true = “probably prime” // write n as 2r·d + 1 with d odd(by factoring out powers of 2 from n − 1) BigInteger r = 0; BigInteger d = n - 1; while (d % 2 == 0) { r++; d /= 2; } // as there is no native random function for BigInteger we suppose a random int number is sufficient int nMaxValue = (n > int.MaxValue) ? int.MaxValue : (int)n; BigInteger a = rand.Next(2, nMaxValue - 2); // ; pick a random integer a in the range[2, n − 2] while (rounds > 0) { rounds--; var x = BigInteger.ModPow(a, d, n); if (x == 1 || x == (n - 1)) { continue; } BigInteger tempr = r - 1; while (tempr > 0 && (x != n - 1)) { tempr--; x = BigInteger.ModPow(x, 2, n); } if (x == n - 1) { continue; } return false; } return true; } } ================================================ FILE: Algorithms/Numeric/ModularExponentiation.cs ================================================ namespace Algorithms.Numeric; /// /// Modular exponentiation is a type of exponentiation performed over a modulus /// Modular exponentiation c is: c = b^e mod m where b is base, e is exponent, m is modulus /// (Wiki: https://en.wikipedia.org/wiki/Modular_exponentiation). /// public class ModularExponentiation { /// /// Performs Modular Exponentiation on b, e, m. /// /// Base. /// Exponent. /// Modulus. /// Modular Exponential. public int ModularPow(int b, int e, int m) { // initialize result in variable res int res = 1; if (m == 1) { // 1 divides every number return 0; } if (m <= 0) { // exponential not defined in this case throw new ArgumentException(string.Format("{0} is not a positive integer", m)); } for (int i = 0; i < e; i++) { res = (res * b) % m; } return res; } } ================================================ FILE: Algorithms/Numeric/NarcissisticNumberChecker.cs ================================================ namespace Algorithms.Numeric; /// /// A Narcissistic number is equal to the sum of the cubes of its digits. For example, 370 is a /// Narcissistic number because 3*3*3 + 7*7*7 + 0*0*0 = 370. /// public static class NarcissisticNumberChecker { /// /// Checks if a number is a Narcissistic number or not. /// /// Number to check. /// True if is a Narcissistic number; False otherwise. public static bool IsNarcissistic(int number) { var sum = 0; var temp = number; var numberOfDigits = 0; while (temp != 0) { numberOfDigits++; temp /= 10; } temp = number; while (number > 0) { var remainder = number % 10; var power = (int)Math.Pow(remainder, numberOfDigits); sum += power; number /= 10; } return sum == temp; } } ================================================ FILE: Algorithms/Numeric/PerfectCubeChecker.cs ================================================ namespace Algorithms.Numeric; /// /// A perfect cube is an element of algebraic structure that is equal to the cube of another element. /// public static class PerfectCubeChecker { /// /// Checks if a number is a perfect cube or not. /// /// Number to check. /// True if is a perfect cube; False otherwise. public static bool IsPerfectCube(int number) { if (number < 0) { number = -number; } var cubeRoot = Math.Round(Math.Pow(number, 1.0 / 3.0)); return Math.Abs(cubeRoot * cubeRoot * cubeRoot - number) < 1e-6; } /// /// Checks if a number is a perfect cube or not using binary search. /// /// Number to check. /// True if is a perfect cube; False otherwise. public static bool IsPerfectCubeBinarySearch(int number) { if (number < 0) { number = -number; } int left = 0; int right = number; while (left <= right) { int mid = left + (right - left) / 2; int midCubed = mid * mid * mid; if (midCubed == number) { return true; } else if (midCubed < number) { left = mid + 1; } else { right = mid - 1; } } return false; } } ================================================ FILE: Algorithms/Numeric/PerfectNumberChecker.cs ================================================ namespace Algorithms.Numeric; /// /// In number theory, a perfect number is a positive integer that is equal to the sum of its positive /// divisors, excluding the number itself.For instance, 6 has divisors 1, 2 and 3 (excluding /// itself), and 1 + 2 + 3 = 6, so 6 is a perfect number. /// public static class PerfectNumberChecker { /// /// Checks if a number is a perfect number or not. /// /// Number to check. /// True if is a perfect number; False otherwise. /// Error number is not on interval (0.0; int.MaxValue). public static bool IsPerfectNumber(int number) { if (number < 0) { throw new ArgumentException($"{nameof(number)} cannot be negative"); } var sum = 0; /* sum of its positive divisors */ for (var i = 1; i < number; ++i) { if (number % i == 0) { sum += i; } } return sum == number; } } ================================================ FILE: Algorithms/Numeric/PerfectSquareChecker.cs ================================================ namespace Algorithms.Numeric; /// /// A perfect square is an element of algebraic structure that is equal to the square of another element. /// public static class PerfectSquareChecker { /// /// Checks if a number is a perfect square or not. /// /// Number too check. /// True if is a perfect square; False otherwise. public static bool IsPerfectSquare(int number) { if (number < 0) { return false; } var sqrt = (int)Math.Sqrt(number); return sqrt * sqrt == number; } } ================================================ FILE: Algorithms/Numeric/PrimeChecker.cs ================================================ namespace Algorithms.Numeric; /// /// A prime number (or a prime) is a natural number greater than 1 that is not a product of two smaller natural numbers. /// public static class PrimeChecker { /// /// Checks if a number is a prime number or not using optimized trial division. /// This method avoids checking multiples of 2 and 3, optimizing the loop by checking /// divisors of the form 6k ± 1 up to the square root of the number. /// /// The integer number to check. /// True if the number is prime; False otherwise. public static bool IsPrime(int number) { // Numbers less than or equal to 1 are not prime. if (number <= 1) { return false; } // 2 and 3 are the first two prime numbers. if (number <= 3) { return true; } // Check for divisibility by 2 and 3. if (number % 2 == 0 || number % 3 == 0) { return false; } // Check for divisibility by numbers of the form 6k ± 1 up to sqrt(number). // The loop increments by 6 to skip known non-prime divisors. for (int i = 5; i <= number / i; i = i + 6) { // Check 6k - 1 if (number % i == 0) { return false; } // Check 6k + 1 if (number % (i + 2) == 0) { return false; } } // If no divisors are found, the number is prime. return true; } } ================================================ FILE: Algorithms/Numeric/Pseudoinverse/PseudoInverse.cs ================================================ using Algorithms.Numeric.Decomposition; namespace Algorithms.Numeric.Pseudoinverse; /// /// The Moore–Penrose pseudo-inverse A+ of a matrix A, /// is a general way to find the solution to the following system of linear equations: /// ~b = A ~y. ~b e R^m; ~y e R^n; A e Rm×n. /// There are various methods for construction the pseudo-inverse. /// This one is based on Singular Value Decomposition (SVD). /// public static class PseudoInverse { /// /// Return the pseudoinverse of a matrix based on the Moore-Penrose Algorithm. /// using Singular Value Decomposition (SVD). /// /// Input matrix to find its inverse to. /// The inverse matrix approximation of the input matrix. public static double[,] PInv(double[,] inMat) { // To compute the SVD of the matrix to find Sigma. var (u, s, v) = ThinSvd.Decompose(inMat); // To take the reciprocal of each non-zero element on the diagonal. var len = s.Length; var sigma = new double[len]; for (var i = 0; i < len; i++) { sigma[i] = Math.Abs(s[i]) < 0.0001 ? 0 : 1 / s[i]; } // To construct a diagonal matrix based on the vector result. var diag = sigma.ToDiagonalMatrix(); // To construct the pseudo-inverse using the computed information above. var matinv = u.Multiply(diag).Multiply(v.Transpose()); // To Transpose the result matrix. return matinv.Transpose(); } } ================================================ FILE: Algorithms/Numeric/Relu.cs ================================================ namespace Algorithms.Numeric; /// /// Implementation of the Rectified Linear Unit (ReLU) function. /// ReLU is defined as: ReLU(x) = max(0, x). /// It is commonly used as an activation function in neural networks. /// public static class Relu { /// /// Compute the Rectified Linear Unit (ReLU) for a single value. /// /// The input real number. /// The output real number (>= 0). public static double Compute(double input) { return Math.Max(0.0, input); } /// /// Compute the Rectified Linear Unit (ReLU) element-wise for a vector. /// /// The input vector of real numbers. /// The output vector where each element is max(0, input[i]). public static double[] Compute(double[] input) { if (input is null) { throw new ArgumentNullException(nameof(input)); } if (input.Length == 0) { throw new ArgumentException("Array is empty."); } var output = new double[input.Length]; for (var i = 0; i < input.Length; i++) { output[i] = Math.Max(0.0, input[i]); } return output; } } ================================================ FILE: Algorithms/Numeric/RungeKuttaMethod.cs ================================================ namespace Algorithms.Numeric; /// /// In numerical analysis, the Runge–Kutta methods are a family of implicit and explicit iterative methods, /// used in temporal discretization for the approximate solutions of simultaneous nonlinear equations. /// The most widely known member of the Runge–Kutta family is generally referred to as /// "RK4", the "classic Runge–Kutta method" or simply as "the Runge–Kutta method". /// public static class RungeKuttaMethod { /// /// Loops through all the steps until xEnd is reached, adds a point for each step and then /// returns all the points. /// /// Initial conditions x-value. /// Last x-value. /// Step-size on the x-axis. /// Initial conditions y-value. /// The right hand side of the differential equation. /// The solution of the Cauchy problem. public static List ClassicRungeKuttaMethod( double xStart, double xEnd, double stepSize, double yStart, Func function) { if (xStart >= xEnd) { throw new ArgumentOutOfRangeException( nameof(xEnd), $"{nameof(xEnd)} should be greater than {nameof(xStart)}"); } if (stepSize <= 0) { throw new ArgumentOutOfRangeException( nameof(stepSize), $"{nameof(stepSize)} should be greater than zero"); } List points = []; double[] firstPoint = [xStart, yStart]; points.Add(firstPoint); var yCurrent = yStart; var xCurrent = xStart; while (xCurrent < xEnd) { var k1 = function(xCurrent, yCurrent); var k2 = function(xCurrent + 0.5 * stepSize, yCurrent + 0.5 * stepSize * k1); var k3 = function(xCurrent + 0.5 * stepSize, yCurrent + 0.5 * stepSize * k2); var k4 = function(xCurrent + stepSize, yCurrent + stepSize * k3); yCurrent += (1.0 / 6.0) * stepSize * (k1 + 2 * k2 + 2 * k3 + k4); xCurrent += stepSize; double[] newPoint = [xCurrent, yCurrent]; points.Add(newPoint); } return points; } } ================================================ FILE: Algorithms/Numeric/Series/Maclaurin.cs ================================================ namespace Algorithms.Numeric.Series; /// /// Maclaurin series calculates nonlinear functions approximation /// starting from point x = 0 in a form of infinite power series: /// f(x) = f(0) + f'(0) * x + ... + (f'n(0) * (x ^ n)) / n! + ..., /// where n is natural number. /// public static class Maclaurin { /// /// Calculates approximation of e^x function: /// e^x = 1 + x + x^2 / 2! + ... + x^n / n! + ..., /// where n is number of terms (natural number), /// and x is given point (rational number). /// /// Given point. /// The number of terms in polynomial. /// Approximated value of the function in the given point. public static double Exp(double x, int n) => Enumerable.Range(0, n).Sum(i => ExpTerm(x, i)); /// /// Calculates approximation of sin(x) function: /// sin(x) = x - x^3 / 3! + ... + (-1)^n * x^(2*n + 1) / (2*n + 1)! + ..., /// where n is number of terms (natural number), /// and x is given point (rational number). /// /// Given point. /// The number of terms in polynomial. /// Approximated value of the function in the given point. public static double Sin(double x, int n) => Enumerable.Range(0, n).Sum(i => SinTerm(x, i)); /// /// Calculates approximation of cos(x) function: /// cos(x) = 1 - x^2 / 2! + ... + (-1)^n * x^(2*n) / (2*n)! + ..., /// where n is number of terms (natural number), /// and x is given point (rational number). /// /// Given point. /// The number of terms in polynomial. /// Approximated value of the function in the given point. public static double Cos(double x, int n) => Enumerable.Range(0, n).Sum(i => CosTerm(x, i)); /// /// Calculates approximation of e^x function: /// e^x = 1 + x + x^2 / 2! + ... + x^n / n! + ..., /// and x is given point (rational number). /// /// Given point. /// Last term error value. /// Approximated value of the function in the given point. /// Error value is not on interval (0.0; 1.0). public static double Exp(double x, double error = 0.00001) => ErrorTermWrapper(x, error, ExpTerm); /// /// Calculates approximation of sin(x) function: /// sin(x) = x - x^3 / 3! + ... + (-1)^n * x^(2*n + 1) / (2*n + 1)! + ..., /// and x is given point (rational number). /// /// Given point. /// Last term error value. /// Approximated value of the function in the given point. /// Error value is not on interval (0.0; 1.0). public static double Sin(double x, double error = 0.00001) => ErrorTermWrapper(x, error, SinTerm); /// /// Calculates approximation of cos(x) function: /// cos(x) = 1 - x^2 / 2! + ... + (-1)^n * x^(2*n) / (2*n)! + ..., /// and x is given point (rational number). /// /// Given point. /// Last term error value. /// Approximated value of the function in the given point. /// Error value is not on interval (0.0; 1.0). public static double Cos(double x, double error = 0.00001) => ErrorTermWrapper(x, error, CosTerm); /// /// Wrapper function for calculating approximation with estimated /// count of terms, where last term value is less than given error. /// /// Given point. /// Last term error value. /// Indexed term of approximation series. /// Approximated value of the function in the given point. /// Error value is not on interval (0.0; 1.0). private static double ErrorTermWrapper(double x, double error, Func term) { if (error <= 0.0 || error >= 1.0) { throw new ArgumentException("Error value is not on interval (0.0; 1.0)."); } var i = 0; var termCoefficient = 0.0; var result = 0.0; do { result += termCoefficient; termCoefficient = term(x, i); i++; } while (Math.Abs(termCoefficient) > error); return result; } /// /// Single term for e^x function approximation: x^i / i!. /// /// Given point. /// Term index from 0 to n. /// Single term value. private static double ExpTerm(double x, int i) => Math.Pow(x, i) / (long)Factorial.Calculate(i); /// /// Single term for sin(x) function approximation: (-1)^i * x^(2*i + 1) / (2*i + 1)!. /// /// Given point. /// Term index from 0 to n. /// Single term value. private static double SinTerm(double x, int i) => Math.Pow(-1, i) / ((long)Factorial.Calculate(2 * i + 1)) * Math.Pow(x, 2 * i + 1); /// /// Single term for cos(x) function approximation: (-1)^i * x^(2*i) / (2*i)!. /// /// Given point. /// Term index from 0 to n. /// Single term value. private static double CosTerm(double x, int i) => Math.Pow(-1, i) / ((long)Factorial.Calculate(2 * i)) * Math.Pow(x, 2 * i); } ================================================ FILE: Algorithms/Numeric/Sigmoid.cs ================================================ using System; namespace Algorithms.Numeric; /// /// Provides the Sigmoid (Logistic) function, commonly used as an activation /// function in neural networks to squash values into the range (0, 1). /// Formula: sigma(x) = 1 / (1 + e^(-x)). /// public static class Sigmoid { /// /// Calculates the value of the Sigmoid function for a given input x. /// /// The input number. /// The Sigmoid value, a double between 0 and 1. public static double Calculate(double x) { // The Sigmoid function is 1 / (1 + e^(-x)) // We use Math.Exp(-x) to calculate e^(-x) double exponent = Math.Exp(-x); // The result is 1.0 divided by (1.0 + exponent) return 1.0 / (1.0 + exponent); } } ================================================ FILE: Algorithms/Numeric/SoftMax.cs ================================================ namespace Algorithms.Numeric; /// /// Implementation of the SoftMax function. /// Its a function that takes as input a vector of K real numbers, and normalizes /// it into a probability distribution consisting of K probabilities proportional /// to the exponentials of the input numbers. After softmax, the elements of the vector always sum up to 1. /// https://en.wikipedia.org/wiki/Softmax_function. /// public static class SoftMax { /// /// Compute the SoftMax function. /// The SoftMax function is defined as: /// softmax(x_i) = exp(x_i) / sum(exp(x_j)) for j = 1 to n /// where x_i is the i-th element of the input vector. /// The elements of the output vector are the probabilities of the input vector, the output sums up to 1. /// /// The input vector of real numbers. /// The output vector of real numbers. public static double[] Compute(double[] input) { if (input.Length == 0) { throw new ArgumentException("Array is empty."); } var exponentVector = new double[input.Length]; var sum = 0.0; for (var index = 0; index < input.Length; index++) { exponentVector[index] = Math.Exp(input[index]); sum += exponentVector[index]; } for (var index = 0; index < input.Length; index++) { exponentVector[index] /= sum; } return exponentVector; } } ================================================ FILE: Algorithms/Numeric/SumOfDigits.cs ================================================ using System; namespace Algorithms.Numeric; /// /// Provides functionality to calculate the sum of the digits of an integer. /// public static class SumOfDigits { /// /// Calculates the sum of the digits of a non-negative integer. /// The method iteratively uses the modulus operator (%) to get the last digit /// and the division operator (/) to drop the last digit until the number is 0. /// /// The non-negative integer whose digits are to be summed. /// The sum of the digits of the input number. /// Thrown if the input number is negative. public static int Calculate(int number) { if (number < 0) { throw new ArgumentException("Input must be a non-negative integer.", nameof(number)); } if (number == 0) { return 0; } int sum = 0; int currentNumber = number; // Loop until the number becomes 0 while (currentNumber > 0) { // Get the last digit (e.g., 123 % 10 = 3) int digit = currentNumber % 10; // Add the digit to the sum sum += digit; // Remove the last digit (e.g., 123 / 10 = 12) currentNumber /= 10; } return sum; } } ================================================ FILE: Algorithms/Numeric/Tanh.cs ================================================ namespace Algorithms.Numeric; /// /// Implementation of the Hyperbolic Tangent (Tanh) function. /// Tanh is an activation function that takes a real number as input and squashes /// the output to a range between -1 and 1. /// It is defined as: tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)). /// https://en.wikipedia.org/wiki/Hyperbolic_function#Hyperbolic_tangent. /// public static class Tanh { /// /// Compute the Hyperbolic Tangent (Tanh) function for a single value. /// The Math.Tanh() method is used for efficient and accurate computation. /// /// The input real number. /// The output real number in the range [-1, 1]. public static double Compute(double input) { // For a single double, we can directly use the optimized Math.Tanh method. return Math.Tanh(input); } /// /// Compute the Hyperbolic Tangent (Tanh) function element-wise for a vector. /// /// The input vector of real numbers. /// The output vector of real numbers, where each element is in the range [-1, 1]. public static double[] Compute(double[] input) { if (input is null) { throw new ArgumentNullException(nameof(input)); } if (input.Length == 0) { throw new ArgumentException("Array is empty."); } var outputVector = new double[input.Length]; for (var index = 0; index < input.Length; index++) { // Apply Tanh to each element using the optimized Math.Tanh method. outputVector[index] = Math.Tanh(input[index]); } return outputVector; } } ================================================ FILE: Algorithms/Other/BoyerMooreMajorityVote.cs ================================================ namespace Algorithms.Other; /// /// Boyer-Moore Majority Vote algorithm. /// Finds element appearing more than n/2 times in O(n) time, O(1) space. /// public static class BoyerMooreMajorityVote { /// /// Finds the majority element. /// /// Input array. /// Majority element or null. public static int? FindMajority(int[] nums) { if (nums == null || nums.Length == 0) { return null; } var candidate = FindCandidate(nums); return IsMajority(nums, candidate) ? candidate : null; } private static int FindCandidate(int[] nums) { int candidate = nums[0]; int count = 1; for (int i = 1; i < nums.Length; i++) { if (count == 0) { candidate = nums[i]; } count += nums[i] == candidate ? 1 : -1; } return candidate; } private static bool IsMajority(int[] nums, int candidate) => nums.Count(n => n == candidate) > nums.Length / 2; } ================================================ FILE: Algorithms/Other/DecisionsConvolutions.cs ================================================ namespace Algorithms.Other; /// /// Almost all real complex decision-making task is described by more than one criterion. /// There are different methods to select the best decisions from the defined set of decisions. /// This class contains implementations of the popular convolution methods: linear and maxmin. /// public static class DecisionsConvolutions { /// /// This method implements the linear method of decision selection. It is based on /// the calculation of the "value" for each decision and the selection of the most /// valuable one. /// /// Contains a collection of the criteria sets. /// Contains a set of priorities for each criterion. /// The most effective decision that is represented by a set of criterias. public static List Linear(List> matrix, List priorities) { var decisionValues = new List(); foreach (var decision in matrix) { decimal sum = 0; for (int i = 0; i < decision.Count; i++) { sum += decision[i] * priorities[i]; } decisionValues.Add(sum); } decimal bestDecisionValue = decisionValues.Max(); int bestDecisionIndex = decisionValues.IndexOf(bestDecisionValue); return matrix[bestDecisionIndex]; } /// /// This method implements maxmin method of the decision selection. It is based on /// the calculation of the least criteria value and comparison of decisions based /// on the calculated results. /// /// Contains a collection of the criteria sets. /// Contains a set of priorities for each criterion. /// The most effective decision that is represented by a set of criterias. public static List MaxMin(List> matrix, List priorities) { var decisionValues = new List(); foreach (var decision in matrix) { decimal minValue = decimal.MaxValue; for (int i = 0; i < decision.Count; i++) { decimal result = decision[i] * priorities[i]; if (result < minValue) { minValue = result; } } decisionValues.Add(minValue); } decimal bestDecisionValue = decisionValues.Max(); int bestDecisionIndex = decisionValues.IndexOf(bestDecisionValue); return matrix[bestDecisionIndex]; } } ================================================ FILE: Algorithms/Other/FermatPrimeChecker.cs ================================================ namespace Algorithms.Other; /// /// Fermat's prime tester https://en.wikipedia.org/wiki/Fermat_primality_test. /// public static class FermatPrimeChecker { /// /// Checks if input number is a probable prime. /// /// Input number. /// Number of times to check. /// True if is a prime; False otherwise. public static bool IsPrime(int numberToTest, int timesToCheck) { // You have to use BigInteger for two reasons: // 1. The pow operation between two int numbers usually overflows an int // 2. The pow and modular operation is very optimized var numberToTestBigInteger = new BigInteger(numberToTest); var exponentBigInteger = new BigInteger(numberToTest - 1); // Create a random number generator using the current time as seed var r = new Random(default(DateTime).Millisecond); var iterator = 1; var prime = true; while (iterator < timesToCheck && prime) { var randomNumber = r.Next(1, numberToTest); var randomNumberBigInteger = new BigInteger(randomNumber); if (BigInteger.ModPow(randomNumberBigInteger, exponentBigInteger, numberToTestBigInteger) != 1) { prime = false; } iterator++; } return prime; } } ================================================ FILE: Algorithms/Other/FloodFill.cs ================================================ using SkiaSharp; namespace Algorithms.Other; /// /// Flood fill, also called seed fill, is an algorithm that determines and /// alters the area connected to a given node in a multi-dimensional array with /// some matching attribute. It is used in the "bucket" fill tool of paint /// programs to fill connected, similarly-colored areas with a different color. /// (description adapted from https://en.wikipedia.org/wiki/Flood_fill) /// (see also: https://www.techiedelight.com/flood-fill-algorithm/). /// public static class FloodFill { private static readonly List<(int XOffset, int YOffset)> Neighbors = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]; /// /// Implements the flood fill algorithm through a breadth-first approach using a queue. /// /// The bitmap to which the algorithm is applied. /// The start location on the bitmap. /// The old color to be replaced. /// The new color to replace the old one. public static void BreadthFirstSearch(SKBitmap bitmap, (int X, int Y) location, SKColor targetColor, SKColor replacementColor) { if (location.X < 0 || location.X >= bitmap.Width || location.Y < 0 || location.Y >= bitmap.Height) { throw new ArgumentOutOfRangeException(nameof(location), $"{nameof(location)} should point to a pixel within the bitmap"); } var queue = new List<(int X, int Y)> { location, }; while (queue.Count > 0) { BreadthFirstFill(bitmap, location, targetColor, replacementColor, queue); } } /// /// Implements the flood fill algorithm through a depth-first approach through recursion. /// /// The bitmap to which the algorithm is applied. /// The start location on the bitmap. /// The old color to be replaced. /// The new color to replace the old one. public static void DepthFirstSearch(SKBitmap bitmap, (int X, int Y) location, SKColor targetColor, SKColor replacementColor) { if (location.X < 0 || location.X >= bitmap.Width || location.Y < 0 || location.Y >= bitmap.Height) { throw new ArgumentOutOfRangeException(nameof(location), $"{nameof(location)} should point to a pixel within the bitmap"); } DepthFirstFill(bitmap, location, targetColor, replacementColor); } private static void BreadthFirstFill(SKBitmap bitmap, (int X, int Y) location, SKColor targetColor, SKColor replacementColor, List<(int X, int Y)> queue) { (int X, int Y) currentLocation = queue[0]; queue.RemoveAt(0); if (bitmap.GetPixel(currentLocation.X, currentLocation.Y) == targetColor) { bitmap.SetPixel(currentLocation.X, currentLocation.Y, replacementColor); for (int i = 0; i < Neighbors.Count; i++) { int x = currentLocation.X + Neighbors[i].XOffset; int y = currentLocation.Y + Neighbors[i].YOffset; if (x >= 0 && x < bitmap.Width && y >= 0 && y < bitmap.Height) { queue.Add((x, y)); } } } } private static void DepthFirstFill(SKBitmap bitmap, (int X, int Y) location, SKColor targetColor, SKColor replacementColor) { if (bitmap.GetPixel(location.X, location.Y) == targetColor) { bitmap.SetPixel(location.X, location.Y, replacementColor); for (int i = 0; i < Neighbors.Count; i++) { int x = location.X + Neighbors[i].XOffset; int y = location.Y + Neighbors[i].YOffset; if (x >= 0 && x < bitmap.Width && y >= 0 && y < bitmap.Height) { DepthFirstFill(bitmap, (x, y), targetColor, replacementColor); } } } } } ================================================ FILE: Algorithms/Other/GaussOptimization.cs ================================================ namespace Algorithms.Other; /// /// The Gaussian method (coordinate descent method) refers to zero-order methods in which only the value /// of the function Q(X) at different points in the space of variables is used to organize the search /// for the extremum. This reduces the overall computational cost of finding the extremum. Also in /// the Gaussian method, the procedures for finding and moving the operating point are simplified as /// much as possible. /// public class GaussOptimization { /// /// Implementation of function extremum search by the Gauss optimization algorithm. /// /// Function for which extremum has to be found. /// This parameter identifies how much step size will be decreased each iteration. /// The initial shift step. /// This value is used to control the accuracy of the optimization. In case if the error is less than eps, /// optimization will be stopped. /// The first function parameter. /// The second function parameter. /// A tuple of coordinates of function extremum. public (double X1, double X2) Optimize( Func func, double n, double step, double eps, double x1, double x2) { // The initial value of the error double error = 1; while (Math.Abs(error) > eps) { // Calculation of the function with coordinates that are calculated with shift double bottom = func(x1, x2 - step); double top = func(x1, x2 + step); double left = func(x1 - step, x2); double right = func(x1 + step, x2); // Determination of the best option. var possibleFunctionValues = new List { bottom, top, left, right }; double maxValue = possibleFunctionValues.Max(); double maxValueIndex = possibleFunctionValues.IndexOf(maxValue); // Error evaluation error = maxValue - func(x1, x2); // Coordinates update for the best option switch (maxValueIndex) { case 0: x2 -= step; break; case 1: x2 += step; break; case 2: x1 -= step; break; default: x1 += step; break; } // Step reduction step /= n; } return (x1, x2); } } ================================================ FILE: Algorithms/Other/GeoLocation.cs ================================================ namespace Algorithms.Other; public static class GeoLocation { private const double EarthRadiusKm = 6371.01d; /// /// Calculates spherical distance between 2 points given their latitude, longitude coordinates. /// https://www.movable-type.co.uk/scripts/latlong.html. /// /// Latitude of point A. /// Longitude of point A. /// Latitude of point B. /// Longitude of point B. /// Spherical distance between A and B. public static double CalculateDistanceFromLatLng(double lat1, double lng1, double lat2, double lng2) { var pi180 = Math.PI / 180d; var lat1Radian = lat1 * pi180; var lng1Radian = lng1 * pi180; var lat2Radian = lat2 * pi180; var lng2Radian = lng2 * pi180; var diffLat = lat2Radian - lat1Radian; var diffLng = lng2Radian - lng1Radian; var haversine = Math.Sin(diffLat / 2) * Math.Sin(diffLat / 2) + Math.Cos(lat1Radian) * Math.Cos(lat2Radian) * Math.Sin(diffLng / 2) * Math.Sin(diffLng / 2); var distance = EarthRadiusKm * (2d * Math.Atan2(Math.Sqrt(haversine), Math.Sqrt(1 - haversine))); return distance * 1000; // Convert from km -> m } } ================================================ FILE: Algorithms/Other/Geofence.cs ================================================ namespace Algorithms.Other; public class Geofence(double latitude, double longitude, double radiusInMeters) { public double Latitude { get; set; } = latitude; public double Longitude { get; set; } = longitude; public double RadiusInMeters { get; set; } = radiusInMeters; /// /// Checks whether the provided user location (latitude and longitude) is within the geofence boundary. /// The geofence is defined by a center point (latitude, longitude) and a radius in meters. /// /// The latitude of the user's current location. /// The longitude of the user's current location. /// Returns true if the user is inside the geofence, otherwise returns false. public bool IsInside(double userLatitude, double userLongitude) { double distance = GeoLocation.CalculateDistanceFromLatLng(Latitude, Longitude, userLatitude, userLongitude); return distance <= RadiusInMeters; } } ================================================ FILE: Algorithms/Other/Geohash.cs ================================================ namespace Algorithms.Other; public static class Geohash { private const string Base32Characters = "0123456789bcdefghjkmnpqrstuvwxyz"; // Convert latitude and longitude coordinates into a concise string private const int GeohashLength = 12; // ± 1.86 cm /// /// Encodes the provided latitude and longitude coordinates into a Geohash string. /// Geohashing is a method to encode geographic coordinates (latitude, longitude). /// into a short string of letters and digits. Each character in the resulting Geohash . /// string adds more precision to the location. The longer the Geohash, the smaller the area. /// /// The latitude of the location to encode. It must be a value between -90 and 90. /// The longitude of the location to encode. It must be a value between -180 and 180. /// /// A Geohash string of length 12 representing the location with high precision. /// A longer Geohash provides higher precision in terms of geographic area. /// and a 12-character Geohash can be accurate down to around 1.86 cm. /// public static string Encode(double latitude, double longitude) { double[] latitudeRange = [-90.0, 90.0]; double[] longitudeRange = [-180.0, 180.0]; bool isEncodingLongitude = true; int currentBit = 0; int base32Index = 0; StringBuilder geohashResult = new StringBuilder(); while (geohashResult.Length < GeohashLength) { double midpoint; if (isEncodingLongitude) { midpoint = (longitudeRange[0] + longitudeRange[1]) / 2; if (longitude > midpoint) { base32Index |= 1 << (4 - currentBit); longitudeRange[0] = midpoint; } else { longitudeRange[1] = midpoint; } } else { midpoint = (latitudeRange[0] + latitudeRange[1]) / 2; if (latitude > midpoint) { base32Index |= 1 << (4 - currentBit); latitudeRange[0] = midpoint; } else { latitudeRange[1] = midpoint; } } isEncodingLongitude = !isEncodingLongitude; if (currentBit < 4) { currentBit++; } else { geohashResult.Append(Base32Characters[base32Index]); currentBit = 0; base32Index = 0; } } return geohashResult.ToString(); } } ================================================ FILE: Algorithms/Other/Int2Binary.cs ================================================ namespace Algorithms.Other; /// /// Manually converts an integer of certain size to a string of the binary representation. /// public static class Int2Binary { /// /// Returns string of the binary representation of given Int. /// /// Number to be converted. /// Binary representation of input. public static string Int2Bin(ushort input) { ushort msb = ushort.MaxValue / 2 + 1; var output = new StringBuilder(); for (var i = 0; i < 16; i++) { if (input >= msb) { output.Append("1"); input -= msb; msb /= 2; } else { output.Append("0"); msb /= 2; } } return output.ToString(); } /// /// Returns string of the binary representation of given Int. /// /// Number to be converted. /// Binary representation of input. public static string Int2Bin(uint input) { var msb = uint.MaxValue / 2 + 1; var output = new StringBuilder(); for (var i = 0; i < 32; i++) { if (input >= msb) { output.Append("1"); input -= msb; msb /= 2; } else { output.Append("0"); msb /= 2; } } return output.ToString(); } /// /// Returns string of the binary representation of given Int. /// /// Number to be converted. /// Binary representation of input. public static string Int2Bin(ulong input) { var msb = ulong.MaxValue / 2 + 1; var output = new StringBuilder(); for (var i = 0; i < 64; i++) { if (input >= msb) { output.Append("1"); input -= msb; msb /= 2; } else { output.Append("0"); msb /= 2; } } return output.ToString(); } } ================================================ FILE: Algorithms/Other/JulianEaster.cs ================================================ namespace Algorithms.Other; /// /// Date of Easter calculated with Meeus's Julian algorithm. /// The algorithm is described in Jean Meeus' Astronomical Algorithms (1991, p. 69). /// public static class JulianEaster { /// /// Calculates the date of Easter. /// /// Year to calculate the date of Easter. /// Date of Easter as a DateTime. public static DateTime Calculate(int year) { var a = year % 4; var b = year % 7; var c = year % 19; var d = (19 * c + 15) % 30; var e = (2 * a + 4 * b - d + 34) % 7; var month = (int)Math.Floor((d + e + 114) / 31M); var day = ((d + e + 114) % 31) + 1; DateTime easter = new(year, month, day, 00, 00, 00, DateTimeKind.Utc); return easter; } } ================================================ FILE: Algorithms/Other/KadanesAlgorithm.cs ================================================ namespace Algorithms.Other; /// /// Kadane's Algorithm is used to find the maximum sum of a contiguous subarray /// within a one-dimensional array of numbers. It has a time complexity of O(n). /// This algorithm is a classic example of dynamic programming. /// Reference: "Introduction to Algorithms" by Cormen, Leiserson, Rivest, and Stein (CLRS). /// public static class KadanesAlgorithm { /// /// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm. /// The algorithm works by maintaining two variables: /// - maxSoFar: The maximum sum found so far (global maximum) /// - maxEndingHere: The maximum sum of subarray ending at current position (local maximum) /// At each position, we decide whether to extend the existing subarray or start a new one. /// /// The input array of integers. /// The maximum sum of a contiguous subarray. /// Thrown when the input array is null or empty. /// /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]. /// Output: 6 (subarray [4, -1, 2, 1]). /// public static int FindMaximumSubarraySum(int[] array) { // Validate input to ensure array is not null or empty if (array == null || array.Length == 0) { throw new ArgumentException("Array cannot be null or empty.", nameof(array)); } // Initialize both variables with the first element // maxSoFar tracks the best sum we've seen across all subarrays int maxSoFar = array[0]; // maxEndingHere tracks the best sum ending at the current position int maxEndingHere = array[0]; // Iterate through the array starting from the second element for (int i = 1; i < array.Length; i++) { // Key decision: Either extend the current subarray or start fresh // If adding current element to existing sum is worse than the element alone, // it's better to start a new subarray from current element maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]); // Update the global maximum if current subarray sum is better maxSoFar = Math.Max(maxSoFar, maxEndingHere); } return maxSoFar; } /// /// Finds the maximum sum of a contiguous subarray and returns the start and end indices. /// This variant tracks the indices of the maximum subarray in addition to the sum. /// Useful when you need to know which elements form the maximum subarray. /// /// The input array of integers. /// A tuple containing the maximum sum, start index, and end index. /// Thrown when the input array is null or empty. /// /// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]. /// Output: (MaxSum: 6, StartIndex: 3, EndIndex: 6). /// The subarray is [4, -1, 2, 1]. /// public static (int MaxSum, int StartIndex, int EndIndex) FindMaximumSubarrayWithIndices(int[] array) { // Validate input if (array == null || array.Length == 0) { throw new ArgumentException("Array cannot be null or empty.", nameof(array)); } // Initialize tracking variables int maxSoFar = array[0]; // Global maximum sum int maxEndingHere = array[0]; // Local maximum sum ending at current position int start = 0; // Start index of the maximum subarray int end = 0; // End index of the maximum subarray int tempStart = 0; // Temporary start index for current subarray // Process each element starting from index 1 for (int i = 1; i < array.Length; i++) { // Decide whether to extend current subarray or start a new one if (array[i] > maxEndingHere + array[i]) { // Starting fresh from current element is better maxEndingHere = array[i]; tempStart = i; // Mark this as potential start of new subarray } else { // Extending the current subarray is better maxEndingHere = maxEndingHere + array[i]; } // Update global maximum and indices if we found a better subarray if (maxEndingHere > maxSoFar) { maxSoFar = maxEndingHere; start = tempStart; // Commit the start index end = i; // Current position is the end } } return (maxSoFar, start, end); } /// /// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm for long integers. /// This overload handles larger numbers that exceed int range (up to 2^63 - 1). /// The algorithm logic is identical to the int version but uses long arithmetic. /// /// The input array of long integers. /// The maximum sum of a contiguous subarray. /// Thrown when the input array is null or empty. /// /// Input: [1000000000L, -500000000L, 1000000000L]. /// Output: 1500000000L (entire array). /// public static long FindMaximumSubarraySum(long[] array) { // Validate input if (array == null || array.Length == 0) { throw new ArgumentException("Array cannot be null or empty.", nameof(array)); } // Initialize with first element (using long arithmetic) long maxSoFar = array[0]; long maxEndingHere = array[0]; // Apply Kadane's algorithm with long values for (int i = 1; i < array.Length; i++) { // Decide: extend current subarray or start new one maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]); // Update global maximum maxSoFar = Math.Max(maxSoFar, maxEndingHere); } return maxSoFar; } } ================================================ FILE: Algorithms/Other/KochSnowflake.cs ================================================ using SkiaSharp; namespace Algorithms.Other; /// /// The Koch snowflake is a fractal curve and one of the earliest fractals to /// have been described. The Koch snowflake can be built up iteratively, in a /// sequence of stages. The first stage is an equilateral triangle, and each /// successive stage is formed by adding outward bends to each side of the /// previous stage, making smaller equilateral triangles. /// This can be achieved through the following steps for each line: /// 1. divide the line segment into three segments of equal length. /// 2. draw an equilateral triangle that has the middle segment from step 1 /// as its base and points outward. /// 3. remove the line segment that is the base of the triangle from step 2. /// (description adapted from https://en.wikipedia.org/wiki/Koch_snowflake ) /// (for a more detailed explanation and an implementation in the /// Processing language, see https://natureofcode.com/book/chapter-8-fractals/ /// #84-the-koch-curve-and-the-arraylist-technique ). /// public static class KochSnowflake { /// /// Go through the number of iterations determined by the argument "steps". /// Be careful with high values (above 5) since the time to calculate increases /// exponentially. /// /// /// The vectors composing the shape to which /// the algorithm is applied. /// /// The number of iterations. /// The transformed vectors after the iteration-steps. public static List Iterate(List initialVectors, int steps = 5) { List vectors = initialVectors; for (var i = 0; i < steps; i++) { vectors = IterationStep(vectors); } return vectors; } /// /// Method to render the Koch snowflake to a bitmap. To save the /// bitmap the command 'GetKochSnowflake().Save("KochSnowflake.png")' can be used. /// /// The width of the rendered bitmap. /// The number of iterations. /// The bitmap of the rendered Koch snowflake. public static SKBitmap GetKochSnowflake( int bitmapWidth = 600, int steps = 5) { if (bitmapWidth <= 0) { throw new ArgumentOutOfRangeException( nameof(bitmapWidth), $"{nameof(bitmapWidth)} should be greater than zero"); } var offsetX = bitmapWidth / 10f; var offsetY = bitmapWidth / 3.7f; var vector1 = new Vector2(offsetX, offsetY); var vector2 = new Vector2(bitmapWidth / 2, (float)Math.Sin(Math.PI / 3) * bitmapWidth * 0.8f + offsetY); var vector3 = new Vector2(bitmapWidth - offsetX, offsetY); List initialVectors = [vector1, vector2, vector3, vector1]; List vectors = Iterate(initialVectors, steps); return GetBitmap(vectors, bitmapWidth, bitmapWidth); } /// /// Loops through each pair of adjacent vectors. Each line between two adjacent /// vectors is divided into 4 segments by adding 3 additional vectors in-between /// the original two vectors. The vector in the middle is constructed through a /// 60 degree rotation so it is bent outwards. /// /// /// The vectors composing the shape to which /// the algorithm is applied. /// /// The transformed vectors after the iteration-step. private static List IterationStep(List vectors) { List newVectors = []; for (var i = 0; i < vectors.Count - 1; i++) { var startVector = vectors[i]; var endVector = vectors[i + 1]; newVectors.Add(startVector); var differenceVector = endVector - startVector; newVectors.Add(startVector + differenceVector / 3); newVectors.Add(startVector + differenceVector / 3 + Rotate(differenceVector / 3, 60)); newVectors.Add(startVector + differenceVector * 2 / 3); } newVectors.Add(vectors[^1]); return newVectors; } /// /// Standard rotation of a 2D vector with a rotation matrix /// (see https://en.wikipedia.org/wiki/Rotation_matrix ). /// /// The vector to be rotated. /// The angle by which to rotate the vector. /// The rotated vector. private static Vector2 Rotate(Vector2 vector, float angleInDegrees) { var radians = angleInDegrees * (float)Math.PI / 180; var ca = (float)Math.Cos(radians); var sa = (float)Math.Sin(radians); return new Vector2(ca * vector.X - sa * vector.Y, sa * vector.X + ca * vector.Y); } /// /// Utility-method to render the Koch snowflake to a bitmap. /// /// The vectors defining the edges to be rendered. /// The width of the rendered bitmap. /// The height of the rendered bitmap. /// The bitmap of the rendered edges. private static SKBitmap GetBitmap( List vectors, int bitmapWidth, int bitmapHeight) { SKBitmap bitmap = new(bitmapWidth, bitmapHeight); var canvas = new SKCanvas(bitmap); // Set the background white var rect = SKRect.Create(0, 0, bitmapWidth, bitmapHeight); var paint = new SKPaint { Style = SKPaintStyle.Fill, Color = SKColors.White, }; canvas.DrawRect(rect, paint); paint.Color = SKColors.Black; // Draw the edges for (var i = 0; i < vectors.Count - 1; i++) { var x1 = vectors[i].X; var y1 = vectors[i].Y; var x2 = vectors[i + 1].X; var y2 = vectors[i + 1].Y; canvas.DrawLine(new SKPoint(x1, y1), new SKPoint(x2, y2), paint); } return bitmap; } } ================================================ FILE: Algorithms/Other/Luhn.cs ================================================ namespace Algorithms.Other; /// /// Luhn algorithm is a simple /// checksum formula used to validate /// a variety of identification numbers, /// such as credit card numbers. /// More information on the link: /// https://en.wikipedia.org/wiki/Luhn_algorithm. /// public static class Luhn { /// /// Checking the validity of a sequence of numbers. /// /// The number that will be checked for validity. /// /// True: Number is valid. /// False: Number isn`t valid. /// public static bool Validate(string number) => GetSum(number) % 10 == 0; /// /// This algorithm finds one missing digit. /// In place of the unknown digit, put "x". /// /// The number in which to find the missing digit. /// Missing digit. public static int GetLostNum(string number) { var missingDigitIndex = number.Length - 1 - number.LastIndexOf("x", StringComparison.CurrentCultureIgnoreCase); var checkDigit = GetSum(number.Replace("x", "0", StringComparison.CurrentCultureIgnoreCase)) * 9 % 10; return missingDigitIndex % 2 == 0 ? checkDigit : Validate(number.Replace("x", (checkDigit / 2).ToString())) ? checkDigit / 2 : (checkDigit + 9) / 2; } /// /// Computes the sum found by the Luhn algorithm. /// /// The number for which the sum will be calculated. /// Sum. private static int GetSum(string number) { var sum = 0; var span = number.AsSpan(); for (var i = 0; i < span.Length; i++) { var c = span[i]; if (c is < '0' or > '9') { continue; } var digit = c - '0'; digit = (i + span.Length) % 2 == 0 ? 2 * digit : digit; if (digit > 9) { digit -= 9; } sum += digit; } return sum; } } ================================================ FILE: Algorithms/Other/Mandelbrot.cs ================================================ using SkiaSharp; namespace Algorithms.Other; /// /// The Mandelbrot set is the set of complex numbers "c" for which the series /// "z_(n+1) = z_n * z_n + c" does not diverge, i.e. remains bounded. Thus, a /// complex number "c" is a member of the Mandelbrot set if, when starting with /// "z_0 = 0" and applying the iteration repeatedly, the absolute value of /// "z_n" remains bounded for all "n > 0". Complex numbers can be written as /// "a + b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" /// is the imaginary component, usually drawn on the y-axis. Most visualizations /// of the Mandelbrot set use a color-coding to indicate after how many steps in /// the series the numbers outside the set cross the divergence threshold. /// Images of the Mandelbrot set exhibit an elaborate and infinitely /// complicated boundary that reveals progressively ever-finer recursive detail /// at increasing magnifications, making the boundary of the Mandelbrot set a /// fractal curve. /// (description adapted from https://en.wikipedia.org/wiki/Mandelbrot_set) /// (see also https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set). /// public static class Mandelbrot { private const byte Alpha = 255; /// /// Method to generate the bitmap of the Mandelbrot set. Two types of coordinates /// are used: bitmap-coordinates that refer to the pixels and figure-coordinates /// that refer to the complex numbers inside and outside the Mandelbrot set. The /// figure-coordinates in the arguments of this method determine which section /// of the Mandelbrot set is viewed. The main area of the Mandelbrot set is /// roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. /// To save the bitmap the command 'GetBitmap().Save("Mandelbrot.png")' can be used. /// /// The width of the rendered bitmap. /// The height of the rendered bitmap. /// The x-coordinate of the center of the figure. /// The y-coordinate of the center of the figure. /// The width of the figure. /// Maximum number of steps to check for divergent behavior. /// Render in color or black and white. /// The bitmap of the rendered Mandelbrot set. public static SKBitmap GetBitmap( int bitmapWidth = 800, int bitmapHeight = 600, double figureCenterX = -0.6, double figureCenterY = 0, double figureWidth = 3.2, int maxStep = 50, bool useDistanceColorCoding = true) { if (bitmapWidth <= 0) { throw new ArgumentOutOfRangeException( nameof(bitmapWidth), $"{nameof(bitmapWidth)} should be greater than zero"); } if (bitmapHeight <= 0) { throw new ArgumentOutOfRangeException( nameof(bitmapHeight), $"{nameof(bitmapHeight)} should be greater than zero"); } if (maxStep <= 0) { throw new ArgumentOutOfRangeException( nameof(maxStep), $"{nameof(maxStep)} should be greater than zero"); } var bitmap = new SKBitmap(bitmapWidth, bitmapHeight); var figureHeight = figureWidth / bitmapWidth * bitmapHeight; // loop through the bitmap-coordinates for (var bitmapX = 0; bitmapX < bitmapWidth; bitmapX++) { for (var bitmapY = 0; bitmapY < bitmapHeight; bitmapY++) { // determine the figure-coordinates based on the bitmap-coordinates var figureX = figureCenterX + ((double)bitmapX / bitmapWidth - 0.5) * figureWidth; var figureY = figureCenterY + ((double)bitmapY / bitmapHeight - 0.5) * figureHeight; var distance = GetDistance(figureX, figureY, maxStep); // color the corresponding pixel based on the selected coloring-function bitmap.SetPixel( bitmapX, bitmapY, useDistanceColorCoding ? ColorCodedColorMap(distance) : BlackAndWhiteColorMap(distance)); } } return bitmap; } /// /// Black and white color-coding that ignores the relative distance. The Mandelbrot /// set is black, everything else is white. /// /// Distance until divergence threshold. /// The color corresponding to the distance. private static SKColor BlackAndWhiteColorMap(double distance) => distance >= 1 ? new SKColor(0, 0, 0, Alpha) : new SKColor(255, 255, 255, Alpha); /// /// Color-coding taking the relative distance into account. The Mandelbrot set /// is black. /// /// Distance until divergence threshold. /// The color corresponding to the distance. private static SKColor ColorCodedColorMap(double distance) { if (distance >= 1) { return new SKColor(0, 0, 0, Alpha); } // simplified transformation of HSV to RGB // distance determines hue var hue = 360 * distance; double saturation = 1; double val = 255; var hi = (int)Math.Floor(hue / 60) % 6; var f = hue / 60 - Math.Floor(hue / 60); var v = (byte)val; const byte p = 0; var q = (byte)(val * (1 - f * saturation)); var t = (byte)(val * (1 - (1 - f) * saturation)); switch (hi) { case 0: return new SKColor(v, t, p, Alpha); case 1: return new SKColor(q, v, p, Alpha); case 2: return new SKColor(p, v, t, Alpha); case 3: return new SKColor(p, q, v, Alpha); case 4: return new SKColor(t, p, v, Alpha); default: return new SKColor(v, p, q, Alpha); } } /// /// Return the relative distance (ratio of steps taken to maxStep) after which the complex number /// constituted by this x-y-pair diverges. Members of the Mandelbrot set do not /// diverge so their distance is 1. /// /// The x-coordinate within the figure. /// The y-coordinate within the figure. /// Maximum number of steps to check for divergent behavior. /// The relative distance as the ratio of steps taken to maxStep. private static double GetDistance(double figureX, double figureY, int maxStep) { var a = figureX; var b = figureY; var currentStep = 0; for (var step = 0; step < maxStep; step++) { currentStep = step; var aNew = a * a - b * b + figureX; b = 2 * a * b + figureY; a = aNew; // divergence happens for all complex number with an absolute value // greater than 4 (= divergence threshold) if (a * a + b * b > 4) { break; } } return (double)currentStep / (maxStep - 1); } } ================================================ FILE: Algorithms/Other/ParetoOptimization.cs ================================================ namespace Algorithms.Other; /// /// Almost all real complex decision-making task is described by more than one criterion. /// Therefore, the methods of multicriteria optimization are important. For a wide range /// of tasks multicriteria optimization, described by some axioms of "reasonable" /// behavior in the process of choosing from a set of possible solutions X, each set of /// selected solutions Sel X should be contained in a set optimal for Pareto. /// public class ParetoOptimization { /// /// Performs decision optimizations by using Paretor's optimization algorithm. /// /// Contains a collection of the criterias sets. /// An optimized collection of the criterias sets. public List> Optimize(List> matrix) { var optimizedMatrix = new List>(matrix.Select(i => i)); int i = 0; while (i < optimizedMatrix.Count) { for (int j = i + 1; j < optimizedMatrix.Count; j++) { decimal directParwiseDifference = GetMinimalPairwiseDifference(optimizedMatrix[i], optimizedMatrix[j]); decimal indirectParwiseDifference = GetMinimalPairwiseDifference(optimizedMatrix[j], optimizedMatrix[i]); /* * in case all criteria of one set are larger that the criteria of another, this * decision is not optimal and it has to be removed */ if (directParwiseDifference >= 0 || indirectParwiseDifference >= 0) { optimizedMatrix.RemoveAt(directParwiseDifference >= 0 ? j : i); i--; break; } } i++; } return optimizedMatrix; } /// /// Calculates the smallest difference between criteria of input decisions. /// /// Criterias of the first decision. /// Criterias of the second decision. /// Values that represent the smallest difference between criteria of input decisions. private decimal GetMinimalPairwiseDifference(List arr1, List arr2) { decimal min = decimal.MaxValue; if (arr1.Count == arr2.Count) { for (int i = 0; i < arr1.Count; i++) { decimal difference = arr1[i] - arr2[i]; if (min > difference) { min = difference; } } } return min; } } ================================================ FILE: Algorithms/Other/PollardsRhoFactorizing.cs ================================================ using Algorithms.Numeric.GreatestCommonDivisor; namespace Algorithms.Other; /// Implementation of the Pollard's rho algorithm. /// Algorithm for integer factorization. /// Wiki: https://en.wikipedia.org/wiki/Pollard's_rho_algorithm. /// public static class PollardsRhoFactorizing { public static int Calculate(int number) { var x = 2; var y = 2; var d = 1; var p = number; var i = 0; var gcd = new BinaryGreatestCommonDivisorFinder(); while (d == 1) { x = Fun_g(x, p); y = Fun_g(Fun_g(y, p), p); d = gcd.FindGcd(Math.Abs(x - y), p); i++; } return d; } private static int Fun_g(int x, int p) { return (x * x + 1) % p; } } ================================================ FILE: Algorithms/Other/RGBHSVConversion.cs ================================================ namespace Algorithms.Other; /// /// The RGB color model is an additive color model in which red, green, and /// blue light are added together in various ways to reproduce a broad array of /// colors. The name of the model comes from the initials of the three additive /// primary colors, red, green, and blue. Meanwhile, the HSV representation /// models how colors appear under light. In it, colors are represented using /// three components: hue, saturation and (brightness-)value. This class /// provides methods for converting colors from one representation to the other. /// (description adapted from https://en.wikipedia.org/wiki/RGB_color_model and /// https://en.wikipedia.org/wiki/HSL_and_HSV). /// public static class RgbHsvConversion { /// /// Conversion from the HSV-representation to the RGB-representation. /// /// Hue of the color. /// Saturation of the color. /// Brightness-value of the color. /// The tuple of RGB-components. public static (byte Red, byte Green, byte Blue) HsvToRgb( double hue, double saturation, double value) { if (hue < 0 || hue > 360) { throw new ArgumentOutOfRangeException(nameof(hue), $"{nameof(hue)} should be between 0 and 360"); } if (saturation < 0 || saturation > 1) { throw new ArgumentOutOfRangeException( nameof(saturation), $"{nameof(saturation)} should be between 0 and 1"); } if (value < 0 || value > 1) { throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} should be between 0 and 1"); } var chroma = value * saturation; var hueSection = hue / 60; var secondLargestComponent = chroma * (1 - Math.Abs(hueSection % 2 - 1)); var matchValue = value - chroma; return GetRgbBySection(hueSection, chroma, matchValue, secondLargestComponent); } /// /// Conversion from the RGB-representation to the HSV-representation. /// /// Red-component of the color. /// Green-component of the color. /// Blue-component of the color. /// The tuple of HSV-components. public static (double Hue, double Saturation, double Value) RgbToHsv( byte red, byte green, byte blue) { var dRed = (double)red / 255; var dGreen = (double)green / 255; var dBlue = (double)blue / 255; var value = Math.Max(Math.Max(dRed, dGreen), dBlue); var chroma = value - Math.Min(Math.Min(dRed, dGreen), dBlue); var saturation = value.Equals(0) ? 0 : chroma / value; double hue; if (chroma.Equals(0)) { hue = 0; } else if (value.Equals(dRed)) { hue = 60 * (0 + (dGreen - dBlue) / chroma); } else if (value.Equals(dGreen)) { hue = 60 * (2 + (dBlue - dRed) / chroma); } else { hue = 60 * (4 + (dRed - dGreen) / chroma); } hue = (hue + 360) % 360; return (hue, saturation, value); } private static (byte Red, byte Green, byte Blue) GetRgbBySection( double hueSection, double chroma, double matchValue, double secondLargestComponent) { byte red; byte green; byte blue; if (hueSection >= 0 && hueSection <= 1) { red = ConvertToByte(chroma + matchValue); green = ConvertToByte(secondLargestComponent + matchValue); blue = ConvertToByte(matchValue); } else if (hueSection > 1 && hueSection <= 2) { red = ConvertToByte(secondLargestComponent + matchValue); green = ConvertToByte(chroma + matchValue); blue = ConvertToByte(matchValue); } else if (hueSection > 2 && hueSection <= 3) { red = ConvertToByte(matchValue); green = ConvertToByte(chroma + matchValue); blue = ConvertToByte(secondLargestComponent + matchValue); } else if (hueSection > 3 && hueSection <= 4) { red = ConvertToByte(matchValue); green = ConvertToByte(secondLargestComponent + matchValue); blue = ConvertToByte(chroma + matchValue); } else if (hueSection > 4 && hueSection <= 5) { red = ConvertToByte(secondLargestComponent + matchValue); green = ConvertToByte(matchValue); blue = ConvertToByte(chroma + matchValue); } else { red = ConvertToByte(chroma + matchValue); green = ConvertToByte(matchValue); blue = ConvertToByte(secondLargestComponent + matchValue); } return (red, green, blue); } private static byte ConvertToByte(double input) => (byte)Math.Round(255 * input); } ================================================ FILE: Algorithms/Other/SieveOfEratosthenes.cs ================================================ namespace Algorithms.Other; /// /// Implements the Sieve of Eratosthenes. /// public class SieveOfEratosthenes { private readonly bool[] primes; /// /// Initializes a new instance of the class. /// Uses the Sieve of Eratosthenes to precalculate the primes from 0 up to maximumNumberToCheck. /// Requires enough memory to allocate maximumNumberToCheck bytes. /// https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes . /// /// long which specifies the largest number you wish to know if it is prime. public SieveOfEratosthenes(long maximumNumberToCheck) { primes = new bool[maximumNumberToCheck + 1]; // initialize primes array Array.Fill(this.primes, true, 2, primes.Length - 2); for (long i = 2; i * i <= maximumNumberToCheck; i++) { if (!primes[i]) { continue; } for (long composite = i * i; composite <= maximumNumberToCheck; composite += i) { primes[composite] = false; } } } /// /// Gets the maximumNumberToCheck the class was instantiated with. /// public long MaximumNumber => primes.Length - 1; /// /// Returns a boolean indicating whether the number is prime. /// /// The number you desire to know if it is prime or not. /// A boolean indicating whether the number is prime or not. public bool IsPrime(long numberToCheck) => primes[numberToCheck]; /// /// Returns an IEnumerable of long primes in asending order. /// /// Primes in ascending order. public IEnumerable GetPrimes() { for (long i = 2; i < primes.Length; i++) { if (primes[i]) { yield return i; } } } } ================================================ FILE: Algorithms/Other/Triangulator.cs ================================================ namespace Algorithms.Other; public class Triangulator { public (double Latitude, double Longitude) CalculatePosition(List<(double Latitude, double Longitude)> baseLocations, List distances) { if (baseLocations.Count < 3 || distances.Count < 3) { throw new ArgumentException("At least three points and corresponding distances are required."); } // Get the coordinates of the three base stations double lat1 = baseLocations[0].Latitude; double lon1 = baseLocations[0].Longitude; double lat2 = baseLocations[1].Latitude; double lon2 = baseLocations[1].Longitude; double lat3 = baseLocations[2].Latitude; double lon3 = baseLocations[2].Longitude; // Convert coordinates to radians lat1 = ToRadians(lat1); lon1 = ToRadians(lon1); lat2 = ToRadians(lat2); lon2 = ToRadians(lon2); lat3 = ToRadians(lat3); lon3 = ToRadians(lon3); // Calculate the center point double centerLat = (lat1 + lat2 + lat3) / 3; double centerLon = (lon1 + lon2 + lon3) / 3; // Convert back to degrees centerLat = ToDegrees(centerLat); centerLon = ToDegrees(centerLon); return (centerLat, centerLon); } private double ToRadians(double degrees) { return degrees * Math.PI / 180; } private double ToDegrees(double radians) { return radians * 180 / Math.PI; } } ================================================ FILE: Algorithms/Other/WelfordsVariance.cs ================================================ namespace Algorithms.Other; /// Implementation of Welford's variance algorithm. /// public class WelfordsVariance { /// /// Mean accumulates the mean of the entire dataset, /// m2 aggregates the squared distance from the mean, /// count aggregates the number of samples seen so far. /// private int count; public double Count => count; private double mean; public double Mean => count > 1 ? mean : double.NaN; private double m2; public double Variance => count > 1 ? m2 / count : double.NaN; public double SampleVariance => count > 1 ? m2 / (count - 1) : double.NaN; public WelfordsVariance() { count = 0; mean = 0; } public WelfordsVariance(double[] values) { count = 0; mean = 0; AddRange(values); } public void AddValue(double newValue) { count++; AddValueToDataset(newValue); } public void AddRange(double[] values) { var length = values.Length; for (var i = 1; i <= length; i++) { count++; AddValueToDataset(values[i - 1]); } } private void AddValueToDataset(double newValue) { var delta1 = newValue - mean; var newMean = mean + delta1 / count; var delta2 = newValue - newMean; m2 += delta1 * delta2; mean = newMean; } } ================================================ FILE: Algorithms/Problems/DynamicProgramming/CoinChange/DynamicCoinChangeSolver.cs ================================================ namespace Algorithms.Problems.DynamicProgramming.CoinChange; public static class DynamicCoinChangeSolver { /// /// Generates an array of changes for current coin. /// For instance, having coin C = 6 and array A = [1,3,4] it returns an array R = [2,3,5]. /// Because, 6 - 4 = 2, 6 - 3 = 3, 6 - 1 = 5. /// /// The value of the coin to be exchanged. /// An array of available coins. /// Array of changes of current coins by available coins. public static int[] GenerateSingleCoinChanges(int coin, int[] coins) { ValidateCoin(coin); ValidateCoinsArray(coins); var coinsArrayCopy = new int[coins.Length]; Array.Copy(coins, coinsArrayCopy, coins.Length); Array.Sort(coinsArrayCopy); Array.Reverse(coinsArrayCopy); List list = []; foreach (var item in coinsArrayCopy) { if (item > coin) { continue; } var difference = coin - item; list.Add(difference); } var result = list.ToArray(); return result; } /// /// Given a positive integer N, such as coin. /// Generates a change dictionary for all values [1,N]. /// Used in so-called backward induction in search of the minimum exchange. /// /// The value of coin. /// Array of available coins. /// Change dictionary for all values [1,N], where N is the coin. public static Dictionary GenerateChangesDictionary(int coin, int[] coins) { Dictionary dict = []; var currentCoin = 1; while (currentCoin <= coin) { var changeArray = GenerateSingleCoinChanges(currentCoin, coins); dict[currentCoin] = changeArray; currentCoin++; } return dict; } /// /// Gets a next coin value, such that changes array contains the minimal change overall possible changes. /// For example, having coin N = 6 and A = [1,3,4] coins array. /// The minimum next coin for 6 will be 3, because changes of 3 by A = [1,3,4] contains 0, the minimal change. /// /// Coin to be exchanged. /// Dictionary of exchanges for [1, coin]. /// Index of the next coin with minimal exchange. public static int GetMinimalNextCoin(int coin, Dictionary exchanges) { var nextCoin = int.MaxValue; var minChange = int.MaxValue; var coinChanges = exchanges[coin]; foreach (var change in coinChanges) { if (change == 0) { return 0; } var currentChange = exchanges[change]; var min = currentChange.Min(); var minIsLesser = min < minChange; if (minIsLesser) { nextCoin = change; minChange = min; } } return nextCoin; } /// /// Performs a coin change such that an amount of coins is minimal. /// /// The value of coin to be exchanged. /// An array of available coins. /// Array of exchanges. public static int[] MakeCoinChangeDynamic(int coin, int[] coins) { var changesTable = GenerateChangesDictionary(coin, coins); var list = new List(); var currentCoin = coin; var nextCoin = int.MaxValue; while (nextCoin != 0) { nextCoin = GetMinimalNextCoin(currentCoin, changesTable); var difference = currentCoin - nextCoin; list.Add(difference); currentCoin = nextCoin; } var result = list.ToArray(); return result; } private static void ValidateCoin(int coin) { if (coin <= 0) { throw new InvalidOperationException($"The coin cannot be lesser or equal to zero {nameof(coin)}."); } } private static void ValidateCoinsArray(int[] coinsArray) { var coinsAsArray = coinsArray.ToArray(); if (coinsAsArray.Length == 0) { throw new InvalidOperationException($"Coins array cannot be empty {nameof(coinsAsArray)}."); } var coinsContainOne = coinsAsArray.Any(x => x == 1); if (!coinsContainOne) { throw new InvalidOperationException($"Coins array must contain coin 1 {nameof(coinsAsArray)}."); } var containsNonPositive = coinsAsArray.Any(x => x <= 0); if (containsNonPositive) { throw new InvalidOperationException( $"{nameof(coinsAsArray)} cannot contain numbers less than or equal to zero"); } var containsDuplicates = coinsAsArray.GroupBy(x => x).Any(g => g.Count() > 1); if (containsDuplicates) { throw new InvalidOperationException($"Coins array cannot contain duplicates {nameof(coinsAsArray)}."); } } } ================================================ FILE: Algorithms/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistance.cs ================================================ namespace Algorithms.Problems.DynamicProgramming; /// /// /// Levenshtein distance between two words is the minimum number of single-character edits (insertions, deletions or substitutions) required to change one word into the other. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Levenshtein_distance. /// /// public static class LevenshteinDistance { /// /// Calculates Levenshtein distance. /// Time and space complexity is O(ab) where a and b are the lengths of the source and target strings. /// /// Source string. /// Target string. /// Levenshtein distance between source and target strings. public static int Calculate(string source, string target) { var distances = new int[source.Length + 1, target.Length + 1]; for (var i = 0; i <= source.Length; i++) { distances[i, 0] = i; } for (var i = 0; i <= target.Length; i++) { distances[0, i] = i; } for (var i = 1; i <= source.Length; i++) { for (var j = 1; j <= target.Length; j++) { var substitionCost = source[i - 1] == target[j - 1] ? 0 : 1; distances[i, j] = Math.Min(distances[i - 1, j] + 1, Math.Min(distances[i, j - 1] + 1, distances[i - 1, j - 1] + substitionCost)); } } return distances[source.Length, target.Length]; } } ================================================ FILE: Algorithms/Problems/GraphColoring/GraphColoringSolver.cs ================================================ namespace Algorithms.Problems.GraphColoring; /// /// Solves the Graph Coloring Problem using backtracking to assign colors to graph vertices /// such that no two adjacent vertices share the same color. /// /// /// /// The Graph Coloring Problem is an NP-complete problem that aims to color the vertices /// of a graph with the minimum number of colors such that no two adjacent vertices have /// the same color. This implementation uses a backtracking algorithm to find a valid coloring /// given a specific number of colors. /// /// /// The algorithm attempts to assign colors to vertices one by one. If a color assignment /// leads to a conflict, it backtracks and tries a different color. If no valid coloring /// exists with the given number of colors, an exception is thrown. /// /// /// Complexity: The worst-case time complexity is O(k^n) where n is the number of /// vertices and k is the number of colors. This is an exponential algorithm suitable for /// small to medium-sized graphs or graphs with special structures. /// /// /// Applications: Graph coloring has numerous practical applications including: /// register allocation in compilers, scheduling problems, frequency assignment in mobile networks, /// and solving Sudoku puzzles. /// /// /// For more information, see: /// Graph Coloring on Wikipedia. /// /// public sealed class GraphColoringSolver { /// /// Attempts to color a graph with the specified number of colors. /// /// /// A square boolean matrix representing the graph where adjacencyMatrix[i, j] /// is true if there is an edge between vertex i and vertex j. /// The matrix must be symmetric for undirected graphs. /// /// The number of colors to use for coloring. Must be positive. /// /// An array where each element represents the color assigned to the corresponding vertex /// (0-indexed colors from 0 to - 1). /// /// /// Thrown when is null. /// /// /// Thrown when the adjacency matrix is not square, when is non-positive, /// or when no valid coloring exists with the given number of colors. /// /// /// /// This method finds the first valid coloring it encounters. Multiple valid colorings /// may exist for a given graph, but only one is returned. /// /// /// Example: For a triangle graph (3 vertices, all connected), at least 3 colors /// are required. Calling this method with numColors = 2 will throw an exception, /// while numColors = 3 will return a valid coloring such as [0, 1, 2]. /// /// public int[] ColorGraph(bool[,] adjacencyMatrix, int numColors) { if (adjacencyMatrix is null) { throw new ArgumentNullException(nameof(adjacencyMatrix)); } var numVertices = adjacencyMatrix.GetLength(0); if (numVertices != adjacencyMatrix.GetLength(1)) { throw new ArgumentException("Adjacency matrix must be square.", nameof(adjacencyMatrix)); } if (numColors <= 0) { throw new ArgumentException("Number of colors must be positive.", nameof(numColors)); } // Handle empty graph if (numVertices == 0) { return Array.Empty(); } var colors = new int[numVertices]; // Initialize all vertices as uncolored (-1) Array.Fill(colors, -1); if (!ColorVertex(adjacencyMatrix, colors, 0, numColors)) { throw new ArgumentException( $"Graph cannot be colored with {numColors} color(s). " + $"A larger number of colors may be required."); } return colors; } /// /// Recursively attempts to color vertices using backtracking. /// /// The graph adjacency matrix. /// Current color assignment for each vertex. /// The current vertex to color. /// The number of available colors. /// true if a valid coloring is found; otherwise, false. /// /// This method tries each available color for the current vertex. If a color is valid /// (doesn't conflict with adjacent vertices), it proceeds to color the next vertex. /// If no valid color is found, it backtracks. /// private bool ColorVertex(bool[,] adjacencyMatrix, int[] colors, int vertex, int numColors) { var numVertices = adjacencyMatrix.GetLength(0); // Base case: all vertices are colored if (vertex == numVertices) { return true; } // Try each color for the current vertex for (var color = 0; color < numColors; color++) { if (IsSafeToColor(adjacencyMatrix, colors, vertex, color)) { colors[vertex] = color; // Recursively color the next vertex if (ColorVertex(adjacencyMatrix, colors, vertex + 1, numColors)) { return true; } // Backtrack if the current color assignment doesn't lead to a solution colors[vertex] = -1; } } return false; } /// /// Checks whether it is safe to assign a color to a vertex. /// /// The graph adjacency matrix. /// Current color assignment for each vertex. /// The vertex to check. /// The color to assign. /// /// true if the color can be safely assigned (no adjacent vertex has this color); /// otherwise, false. /// /// /// A color is safe to assign if none of the already-colored adjacent vertices /// have the same color. /// private bool IsSafeToColor(bool[,] adjacencyMatrix, int[] colors, int vertex, int color) { var numVertices = adjacencyMatrix.GetLength(0); for (var i = 0; i < numVertices; i++) { // Check if vertex i is adjacent to the current vertex and has the same color if (adjacencyMatrix[vertex, i] && colors[i] == color) { return false; } } return true; } } ================================================ FILE: Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Algorithms.Problems.JobScheduling; /// /// Implements the greedy algorithm for Interval Scheduling. /// Finds the maximum set of non-overlapping jobs. /// public static class IntervalSchedulingSolver { /// /// Returns the maximal set of non-overlapping jobs. /// /// List of jobs to schedule. /// List of selected jobs (maximal set). public static List Schedule(IEnumerable jobs) { if (jobs == null) { throw new ArgumentNullException(nameof(jobs)); } // Sort jobs by their end time (earliest finish first) var sortedJobs = jobs.OrderBy(j => j.End).ToList(); var result = new List(); int lastEnd = int.MinValue; foreach (var job in sortedJobs) { // If the job starts after the last selected job ends, select it if (job.Start >= lastEnd) { result.Add(job); lastEnd = job.End; } } return result; } } ================================================ FILE: Algorithms/Problems/JobScheduling/Job.cs ================================================ namespace Algorithms.Problems.JobScheduling; /// /// Represents a single job with a start and end time. /// public record Job(int Start, int End); ================================================ FILE: Algorithms/Problems/KnightTour/OpenKnightTour.cs ================================================ namespace Algorithms.Problems.KnightTour; /// /// Computes a (single) Knight's Tour on an n × n chessboard using /// depth-first search (DFS) with backtracking. /// /// /// /// A Knight's Tour is a sequence of knight moves that visits every square exactly once. /// This implementation returns the first tour it finds (if any), starting from whichever /// starting cell leads to a solution first. It explores every board square as a potential /// starting position in row-major order. /// /// /// The algorithm is a plain backtracking search—no heuristics (e.g., Warnsdorff’s rule) /// are applied. As a result, runtime can grow exponentially with n and become /// impractical on larger boards. /// /// /// Solvability (square boards): /// A (non-closed) tour exists for n = 1 and for all n ≥ 5. /// There is no tour for n ∈ {2, 3, 4}. This implementation throws an /// if no tour is found. /// /// /// Coordinate convention: The board is indexed as [row, column], /// zero-based, with (0,0) in the top-left corner. /// /// public sealed class OpenKnightTour { /// /// Attempts to find a Knight's Tour on an n × n board. /// /// Board size (number of rows/columns). Must be positive. /// /// A 2D array of size n × n where each cell contains the /// 1-based visit order (from 1 to n*n) of the knight. /// /// /// Thrown when ≤ 0, or when no tour exists / is found for the given . /// /// /// /// This routine tries every square as a starting point. As soon as a complete tour is found, /// the filled board is returned. If no tour is found, an exception is thrown. /// /// /// Performance: Exponential in the worst case. For larger boards, consider adding /// Warnsdorff’s heuristic (choose next moves with the fewest onward moves) or a hybrid approach. /// /// public int[,] Tour(int n) { if (n <= 0) { throw new ArgumentException("Board size must be positive.", nameof(n)); } var board = new int[n, n]; // Try every square as a starting point. for (var r = 0; r < n; r++) { for (var c = 0; c < n; c++) { board[r, c] = 1; // first step if (KnightTourHelper(board, (r, c), 1)) { return board; } board[r, c] = 0; // backtrack and try next start } } throw new ArgumentException($"Knight Tour cannot be performed on a board of size {n}."); } /// /// Recursively extends the current partial tour from after placing /// move number in that position. /// /// The board with placed move numbers; 0 means unvisited. /// Current knight position (Row, Col). /// The move number just placed at . /// true if a full tour is completed; false otherwise. /// /// Tries each legal next move in a fixed order (no heuristics). If a move leads to a dead end, /// it backtracks by resetting the target cell to 0 and tries the next candidate. /// private bool KnightTourHelper(int[,] board, (int Row, int Col) pos, int current) { if (IsComplete(board)) { return true; } foreach (var (nr, nc) in GetValidMoves(pos, board.GetLength(0))) { if (board[nr, nc] == 0) { board[nr, nc] = current + 1; if (KnightTourHelper(board, (nr, nc), current + 1)) { return true; } board[nr, nc] = 0; // backtrack } } return false; } /// /// Computes all legal knight moves from on an n × n board. /// /// Current position (R, C). /// Board dimension (rows = columns = ). /// /// An enumeration of on-board destination coordinates. Order is fixed and unoptimized: /// (+1,+2), (-1,+2), (+1,-2), (-1,-2), (+2,+1), (+2,-1), (-2,+1), (-2,-1). /// /// /// Keeping a deterministic order makes the search reproducible, but it’s not necessarily fast. /// To accelerate, pre-sort by onward-degree (Warnsdorff) or by a custom heuristic. /// private IEnumerable<(int R, int C)> GetValidMoves((int R, int C) position, int n) { var r = position.R; var c = position.C; var candidates = new (int Dr, int Dc)[] { (1, 2), (-1, 2), (1, -2), (-1, -2), (2, 1), (2, -1), (-2, 1), (-2, -1), }; foreach (var (dr, dc) in candidates) { var nr = r + dr; var nc = c + dc; if (nr >= 0 && nr < n && nc >= 0 && nc < n) { yield return (nr, nc); } } } /// /// Checks whether the tour is complete; i.e., every cell is non-zero. /// /// The board to check. /// true if all cells have been visited; otherwise, false. /// /// A complete board means the knight has visited exactly n × n distinct cells. /// private bool IsComplete(int[,] board) { var n = board.GetLength(0); for (var row = 0; row < n; row++) { for (var col = 0; col < n; col++) { if (board[row, col] == 0) { return false; } } } return true; } } ================================================ FILE: Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs ================================================ namespace Algorithms.Problems.NQueens; public class BacktrackingNQueensSolver { /// /// Solves N-Queen Problem given a n dimension chessboard and using backtracking with recursion algorithm. /// If we find a dead-end within or current solution we go back and try another position for queen. /// /// Number of rows. /// All solutions. public IEnumerable BacktrackSolve(int n) { if (n < 0) { throw new ArgumentException(nameof(n)); } return BacktrackSolve(new bool[n, n], 0); } private static IEnumerable BacktrackSolve(bool[,] board, int col) { var solutions = col < board.GetLength(0) - 1 ? HandleIntermediateColumn(board, col) : HandleLastColumn(board); return solutions; } private static IEnumerable HandleIntermediateColumn(bool[,] board, int col) { // To start placing queens on possible spaces within the board. for (var i = 0; i < board.GetLength(0); i++) { if (CanPlace(board, i, col)) { board[i, col] = true; foreach (var solution in BacktrackSolve(board, col + 1)) { yield return solution; } board[i, col] = false; } } } private static IEnumerable HandleLastColumn(bool[,] board) { var n = board.GetLength(0); for (var i = 0; i < n; i++) { if (CanPlace(board, i, n - 1)) { board[i, n - 1] = true; yield return (bool[,])board.Clone(); board[i, n - 1] = false; } } } /// /// Checks whether current queen can be placed in current position, /// outside attacking range of another queen. /// /// Source board. /// Row coordinate. /// Col coordinate. /// true if queen can be placed in given chessboard coordinates; false otherwise. private static bool CanPlace(bool[,] board, int row, int col) { // To check whether there are any queens on current row. for (var i = 0; i < col; i++) { if (board[row, i]) { return false; } } // To check diagonal attack top-left range. for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { if (board[i, j]) { return false; } } // To check diagonal attack bottom-left range. for (int i = row + 1, j = col - 1; j >= 0 && i < board.GetLength(0); i++, j--) { if (board[i, j]) { return false; } } // Return true if it can use position. return true; } } ================================================ FILE: Algorithms/Problems/StableMarriage/Accepter.cs ================================================ namespace Algorithms.Problems.StableMarriage; public class Accepter { public Proposer? EngagedTo { get; set; } public List PreferenceOrder { get; set; } = []; public bool PrefersOverCurrent(Proposer newProposer) => EngagedTo is null || PreferenceOrder.IndexOf(newProposer) < PreferenceOrder.IndexOf(EngagedTo); } ================================================ FILE: Algorithms/Problems/StableMarriage/GaleShapley.cs ================================================ namespace Algorithms.Problems.StableMarriage; public static class GaleShapley { /// /// Finds a stable matching between two equal sets of elements (fills EngagedTo properties). /// time complexity: O(n^2), where n - array size. /// Guarantees: /// - Everyone is matched /// - Matches are stable (there is no better accepter, for any given proposer, which would accept a new match). /// Presented and proven by David Gale and Lloyd Shapley in 1962. /// public static void Match(Proposer[] proposers, Accepter[] accepters) { if (proposers.Length != accepters.Length) { throw new ArgumentException("Collections must have equal count"); } while (proposers.Any(m => !IsEngaged(m))) { DoSingleMatchingRound(proposers.Where(m => !IsEngaged(m))); } } private static bool IsEngaged(Proposer proposer) => proposer.EngagedTo is not null; private static void DoSingleMatchingRound(IEnumerable proposers) { foreach (var newProposer in proposers) { var accepter = newProposer.PreferenceOrder.First!.Value; if (accepter.EngagedTo is null) { Engage(newProposer, accepter); } else { if (accepter.PrefersOverCurrent(newProposer)) { Free(accepter.EngagedTo); Engage(newProposer, accepter); } } newProposer.PreferenceOrder.RemoveFirst(); } } private static void Free(Proposer proposer) { proposer.EngagedTo = null; } private static void Engage(Proposer proposer, Accepter accepter) { proposer.EngagedTo = accepter; accepter.EngagedTo = proposer; } } ================================================ FILE: Algorithms/Problems/StableMarriage/Proposer.cs ================================================ namespace Algorithms.Problems.StableMarriage; public class Proposer { public Accepter? EngagedTo { get; set; } public LinkedList PreferenceOrder { get; set; } = new(); } ================================================ FILE: Algorithms/Problems/TravelingSalesman/TravelingSalesmanSolver.cs ================================================ namespace Algorithms.Problems.TravelingSalesman; /// /// Provides methods to solve the Traveling Salesman Problem (TSP) using brute-force and nearest neighbor heuristics. /// The TSP is a classic optimization problem in which a salesman must visit each city exactly once and return to the starting city, minimizing the total travel distance. /// public static class TravelingSalesmanSolver { /// /// Solves the TSP using brute-force search. This method checks all possible permutations of cities to find the shortest possible route. /// WARNING: This approach is only feasible for small numbers of cities due to factorial time complexity. /// /// A square matrix where element [i, j] represents the distance from city i to city j. /// A tuple containing the minimal route (as an array of city indices) and the minimal total distance. public static (int[] Route, double Distance) SolveBruteForce(double[,] distanceMatrix) { int n = distanceMatrix.GetLength(0); if (n != distanceMatrix.GetLength(1)) { throw new ArgumentException("Distance matrix must be square."); } if (n < 2) { throw new ArgumentException("At least two cities are required."); } var cities = Enumerable.Range(0, n).ToArray(); double minDistance = double.MaxValue; int[]? bestRoute = null; foreach (var perm in Permute(cities.Skip(1).ToArray())) { var route = new int[n + 1]; route[0] = 0; for (int i = 0; i < perm.Length; i++) { route[i + 1] = perm[i]; } // Ensure route ends at city 0 route[n] = 0; double dist = 0; for (int i = 0; i < n; i++) { dist += distanceMatrix[route[i], route[i + 1]]; } if (dist < minDistance) { minDistance = dist; bestRoute = (int[])route.Clone(); } } return (bestRoute ?? Array.Empty(), minDistance); } /// /// Solves the TSP using the nearest neighbor heuristic. This method builds a route by always visiting the nearest unvisited city next. /// This approach is much faster but may not find the optimal solution. /// /// A square matrix where element [i, j] represents the distance from city i to city j. /// The starting city index. /// A tuple containing the route (as an array of city indices) and the total distance. public static (int[] Route, double Distance) SolveNearestNeighbor(double[,] distanceMatrix, int start = 0) { int n = distanceMatrix.GetLength(0); if (n != distanceMatrix.GetLength(1)) { throw new ArgumentException("Distance matrix must be square."); } if (start < 0 || start >= n) { throw new ArgumentOutOfRangeException(nameof(start)); } var visited = new bool[n]; List route = [start]; visited[start] = true; double totalDistance = 0; int current = start; for (int step = 1; step < n; step++) { double minDist = double.MaxValue; int next = -1; for (int j = 0; j < n; j++) { if (!visited[j] && distanceMatrix[current, j] < minDist) { minDist = distanceMatrix[current, j]; next = j; } } if (next == -1) { throw new InvalidOperationException("No unvisited cities remain."); } route.Add(next); visited[next] = true; totalDistance += minDist; current = next; } totalDistance += distanceMatrix[current, start]; route.Add(start); return (route.ToArray(), totalDistance); } /// /// Generates all permutations of the input array. /// Used for brute-force TSP solution. /// private static IEnumerable Permute(int[] arr) { if (arr.Length == 1) { yield return arr; yield break; } for (int i = 0; i < arr.Length; i++) { var rest = arr.Where((_, idx) => idx != i).ToArray(); foreach (var perm in Permute(rest)) { yield return [arr[i], ..perm]; } } } } ================================================ FILE: Algorithms/RecommenderSystem/CollaborativeFiltering.cs ================================================ namespace Algorithms.RecommenderSystem; public class CollaborativeFiltering(ISimilarityCalculator similarityCalculator) { private readonly ISimilarityCalculator similarityCalculator = similarityCalculator; /// /// Method to calculate similarity between two users using Pearson correlation. /// /// Rating of User 1. /// Rating of User 2. /// double value to reflect the index of similarity between two users. public double CalculateSimilarity(Dictionary user1Ratings, Dictionary user2Ratings) { var commonItems = user1Ratings.Keys.Intersect(user2Ratings.Keys).ToList(); if (commonItems.Count == 0) { return 0; } var user1Scores = commonItems.Select(item => user1Ratings[item]).ToArray(); var user2Scores = commonItems.Select(item => user2Ratings[item]).ToArray(); var avgUser1 = user1Scores.Average(); var avgUser2 = user2Scores.Average(); double numerator = 0; double sumSquare1 = 0; double sumSquare2 = 0; double epsilon = 1e-10; for (var i = 0; i < commonItems.Count; i++) { var diff1 = user1Scores[i] - avgUser1; var diff2 = user2Scores[i] - avgUser2; numerator += diff1 * diff2; sumSquare1 += diff1 * diff1; sumSquare2 += diff2 * diff2; } var denominator = Math.Sqrt(sumSquare1 * sumSquare2); return Math.Abs(denominator) < epsilon ? 0 : numerator / denominator; } /// /// Predict a rating for a specific item by a target user. /// /// The item for which the rating needs to be predicted. /// The user for whom the rating is being predicted. /// /// A dictionary containing user ratings where: /// - The key is the user's identifier (string). /// - The value is another dictionary where the key is the item identifier (string), and the value is the rating given by the user (double). /// /// The predicted rating for the target item by the target user. /// If there is insufficient data to predict a rating, the method returns 0. /// public double PredictRating(string targetItem, string targetUser, Dictionary> ratings) { var targetUserRatings = ratings[targetUser]; double totalSimilarity = 0; double weightedSum = 0; double epsilon = 1e-10; foreach (var otherUser in ratings.Keys.Where(u => u != targetUser)) { var otherUserRatings = ratings[otherUser]; if (otherUserRatings.ContainsKey(targetItem)) { var similarity = similarityCalculator.CalculateSimilarity(targetUserRatings, otherUserRatings); totalSimilarity += Math.Abs(similarity); weightedSum += similarity * otherUserRatings[targetItem]; } } return Math.Abs(totalSimilarity) < epsilon ? 0 : weightedSum / totalSimilarity; } } ================================================ FILE: Algorithms/RecommenderSystem/ISimilarityCalculator.cs ================================================ namespace Algorithms.RecommenderSystem { public interface ISimilarityCalculator { double CalculateSimilarity(Dictionary user1Ratings, Dictionary user2Ratings); } } ================================================ FILE: Algorithms/Search/AStar/AStar.cs ================================================ namespace Algorithms.Search.AStar; /// /// Contains the code for A* Pathfinding. /// public static class AStar { /// /// Resets the Nodes in the list. /// /// Resets the nodes to be used again. public static void ResetNodes(List nodes) { foreach (var node in nodes) { node.CurrentCost = 0; node.EstimatedCost = 0; node.Parent = null; node.State = NodeState.Unconsidered; } } /// /// Generates the Path from an (solved) node graph, before it gets reset. /// /// The node where we want to go. /// The Path to the target node. public static List GeneratePath(Node target) { var ret = new List(); var current = target; while (!(current is null)) { ret.Add(current); current = current.Parent; } ret.Reverse(); return ret; } /// /// Computes the path from => to. /// /// Start node. /// end node. /// Path from start to end. public static List Compute(Node from, Node to) { var done = new List(); // A priority queue that will sort our nodes based on the total cost estimate var open = new PriorityQueue(); foreach (var node in from.ConnectedNodes) { // Add connecting nodes if traversable if (node.Traversable) { // Calculate the Costs node.CurrentCost = from.CurrentCost + from.DistanceTo(node) * node.TraversalCostMultiplier; node.EstimatedCost = from.CurrentCost + node.DistanceTo(to); // Enqueue open.Enqueue(node); } } while (true) { // End Condition( Path not found ) if (open.Count == 0) { ResetNodes(done); ResetNodes(open.GetData()); return []; } // Selecting next Element from queue var current = open.Dequeue(); // Add it to the done list done.Add(current); current.State = NodeState.Closed; // EndCondition( Path was found ) if (current == to) { var ret = GeneratePath(to); // Create the Path // Reset all Nodes that were used. ResetNodes(done); ResetNodes(open.GetData()); return ret; } AddOrUpdateConnected(current, to, open); } } private static void AddOrUpdateConnected(Node current, Node to, PriorityQueue queue) { foreach (var connected in current.ConnectedNodes) { if (!connected.Traversable || connected.State == NodeState.Closed) { continue; // Do ignore already checked and not traversable nodes. } // Adds a previously not "seen" node into the Queue if (connected.State == NodeState.Unconsidered) { connected.Parent = current; connected.CurrentCost = current.CurrentCost + current.DistanceTo(connected) * connected.TraversalCostMultiplier; connected.EstimatedCost = connected.CurrentCost + connected.DistanceTo(to); connected.State = NodeState.Open; queue.Enqueue(connected); } else if (current != connected) { // Updating the cost of the node if the current way is cheaper than the previous var newCCost = current.CurrentCost + current.DistanceTo(connected); var newTCost = newCCost + current.EstimatedCost; if (newTCost < connected.TotalCost) { connected.Parent = current; connected.CurrentCost = newCCost; } } else { // Codacy made me do it. throw new PathfindingException( "Detected the same node twice. Confusion how this could ever happen"); } } } } ================================================ FILE: Algorithms/Search/AStar/Node.cs ================================================ namespace Algorithms.Search.AStar; /// /// Contains Positional and other information about a single node. /// public class Node(VecN position, bool traversable, double traverseMultiplier) : IComparable, IEquatable { /// /// Gets the Total cost of the Node. /// The Current Costs + the estimated costs. /// public double TotalCost => EstimatedCost + CurrentCost; /// /// Gets or sets the Distance between this node and the target node. /// public double EstimatedCost { get; set; } /// /// Gets a value indicating whether how costly it is to traverse over this node. /// public double TraversalCostMultiplier { get; } = traverseMultiplier; /// /// Gets or sets a value indicating whether to go from the start node to this node. /// public double CurrentCost { get; set; } /// /// Gets or sets the state of the Node /// Can be Unconsidered(Default), Open and Closed. /// public NodeState State { get; set; } /// /// Gets a value indicating whether the node is traversable. /// public bool Traversable { get; } = traversable; /// /// Gets or sets a list of all connected nodes. /// public Node[] ConnectedNodes { get; set; } = []; /// /// Gets or sets he "previous" node that was processed before this node. /// public Node? Parent { get; set; } /// /// Gets the positional information of the node. /// public VecN Position { get; } = position; /// /// Compares the Nodes based on their total costs. /// Total Costs: A* Pathfinding. /// Current: Djikstra Pathfinding. /// Estimated: Greedy Pathfinding. /// /// The other node. /// A comparison between the costs. public int CompareTo(Node? other) => TotalCost.CompareTo(other?.TotalCost ?? 0); public bool Equals(Node? other) => CompareTo(other) == 0; public static bool operator ==(Node left, Node right) => left?.Equals(right) != false; public static bool operator >(Node left, Node right) => left.CompareTo(right) > 0; public static bool operator <(Node left, Node right) => left.CompareTo(right) < 0; public static bool operator !=(Node left, Node right) => !(left == right); public static bool operator <=(Node left, Node right) => left.CompareTo(right) <= 0; public static bool operator >=(Node left, Node right) => left.CompareTo(right) >= 0; public override bool Equals(object? obj) => obj is Node other && Equals(other); public override int GetHashCode() => Position.GetHashCode() + Traversable.GetHashCode() + TraversalCostMultiplier.GetHashCode(); /// /// Returns the distance to the other node. /// /// The other node. /// Distance between this and other. public double DistanceTo(Node other) => Math.Sqrt(Position.SqrDistance(other.Position)); } ================================================ FILE: Algorithms/Search/AStar/NodeState.cs ================================================ namespace Algorithms.Search.AStar; /// /// The states the nodes can have. /// public enum NodeState { /// /// TODO. /// Unconsidered = 0, /// /// TODO. /// Open = 1, /// /// TODO. /// Closed = 2, } ================================================ FILE: Algorithms/Search/AStar/PathfindingException.cs ================================================ namespace Algorithms.Search.AStar; /// /// A pathfinding exception is thrown when the Pathfinder encounters a critical error and can not continue. /// public class PathfindingException(string message) : Exception(message) { } ================================================ FILE: Algorithms/Search/AStar/PriorityQueue.cs ================================================ // todo: extract to data structures namespace Algorithms.Search.AStar; /// /// Generic Priority Queue. /// List based. /// /// /// The type that will be stored. /// Has to be IComparable of T. /// public class PriorityQueue where T : IComparable { private readonly bool isDescending; // The underlying structure. private readonly List list; public PriorityQueue(bool isDescending = false) { this.isDescending = isDescending; list = []; } /// /// Initializes a new instance of the class. /// /// Initial capacity. /// Should Reverse Sort order? Default: false. public PriorityQueue(int capacity, bool isDescending = false) { list = new List(capacity); this.isDescending = isDescending; } /// /// Initializes a new instance of the class. /// /// Internal data. /// Should Reverse Sort order? Default: false. public PriorityQueue(IEnumerable collection, bool isDescending = false) : this() { this.isDescending = isDescending; foreach (var item in collection) { Enqueue(item); } } /// /// Gets Number of enqueued items. /// public int Count => list.Count; /// /// Enqueues an item into the Queue. /// /// The item to Enqueue. public void Enqueue(T x) { list.Add(x); var i = Count - 1; // Position of x while (i > 0) { var p = (i - 1) / 2; // Start at half of i if ((isDescending ? -1 : 1) * list[p].CompareTo(x) <= 0) { break; } list[i] = list[p]; // Put P to position of i i = p; // I = (I-1)/2 } if (Count > 0) { list[i] = x; // If while loop way executed at least once(X got replaced by some p), add it to the list } } /// /// Dequeues the item at the end of the queue. /// /// The dequeued item. public T Dequeue() { var target = Peek(); // Get first in list var root = list[Count - 1]; // Hold last of the list list.RemoveAt(Count - 1); // But remove it from the list var i = 0; while (i * 2 + 1 < Count) { var a = i * 2 + 1; // Every second entry starting by 1 var b = i * 2 + 2; // Every second entries neighbour var c = b < Count && (isDescending ? -1 : 1) * list[b].CompareTo(list[a]) < 0 ? b : a; // Whether B(B is in range && B is smaller than A) or A if ((isDescending ? -1 : 1) * list[c].CompareTo(root) >= 0) { break; } list[i] = list[c]; i = c; } if (Count > 0) { list[i] = root; } return target; } /// /// Returns the next element in the queue without dequeuing. /// /// The next element of the queue. public T Peek() { if (Count == 0) { throw new InvalidOperationException("Queue is empty."); } return list[0]; } /// /// Clears the Queue. /// public void Clear() => list.Clear(); /// /// Returns the Internal Data. /// /// The internal data structure. public List GetData() => list; } ================================================ FILE: Algorithms/Search/AStar/VecN.cs ================================================ namespace Algorithms.Search.AStar; /// /// Vector Struct with N Dimensions. /// /// /// Initializes a new instance of the struct. /// /// Vector components as array. public struct VecN(params double[] vals) : IEquatable { private readonly double[] data = vals; /// /// Gets the dimension count of this vector. /// public int N => data.Length; /// /// Returns the Length squared. /// /// The squared length of the vector. public double SqrLength() { double ret = 0; for (var i = 0; i < data.Length; i++) { ret += data[i] * data[i]; } return ret; } /// /// Returns the Length of the vector. /// /// Length of the Vector. public double Length() => Math.Sqrt(SqrLength()); /// /// Returns the Distance between this and other. /// /// Other vector. /// The distance between this and other. public double Distance(VecN other) { var delta = Subtract(other); return delta.Length(); } /// /// Returns the squared Distance between this and other. /// /// Other vector. /// The squared distance between this and other. public double SqrDistance(VecN other) { var delta = Subtract(other); return delta.SqrLength(); } /// /// Substracts other from this vector. /// /// Other vector. /// The new vector. public VecN Subtract(VecN other) { var dd = new double[Math.Max(data.Length, other.data.Length)]; for (var i = 0; i < dd.Length; i++) { double val = 0; if (data.Length > i) { val = data[i]; } if (other.data.Length > i) { val -= other.data[i]; } dd[i] = val; } return new VecN(dd); } /// /// Is used to compare Vectors with each other. /// /// The vector to be compared. /// A value indicating if other has the same values as this. public bool Equals(VecN other) { if (other.N != N) { return false; } for (var i = 0; i < other.data.Length; i++) { if (Math.Abs(data[i] - other.data[i]) > 0.000001) { return false; } } return true; } } ================================================ FILE: Algorithms/Search/BinarySearcher.cs ================================================ namespace Algorithms.Search; /// /// Binary Searcher checks an array for element specified by checking /// if element is greater or less than the half being checked. /// time complexity: O(log(n)), /// space complexity: O(1). /// Note: Array must be sorted beforehand. /// /// Type of element stored inside array. 2. public class BinarySearcher where T : IComparable { /// /// Finds index of an array by using binary search. /// /// Sorted array to search in. /// Item to search for. /// Index of item that equals to item searched for or -1 if none found. public int FindIndex(T[] sortedData, T item) { var leftIndex = 0; var rightIndex = sortedData.Length - 1; while (leftIndex <= rightIndex) { var middleIndex = leftIndex + (rightIndex - leftIndex) / 2; if (item.CompareTo(sortedData[middleIndex]) > 0) { leftIndex = middleIndex + 1; continue; } if (item.CompareTo(sortedData[middleIndex]) < 0) { rightIndex = middleIndex - 1; continue; } return middleIndex; } return -1; } } ================================================ FILE: Algorithms/Search/BoyerMoore.cs ================================================ namespace Algorithms.Search; /// /// A Boyer-Moore majority finder algorithm implementation. /// /// Type of element stored inside array. public static class BoyerMoore where T : IComparable { public static T? FindMajority(IEnumerable input) { var candidate = FindMajorityCandidate(input, input.Count()); if (VerifyMajority(input, input.Count(), candidate)) { return candidate; } return default(T?); } // Find majority candidate private static T FindMajorityCandidate(IEnumerable input, int length) { int count = 1; T candidate = input.First(); foreach (var element in input.Skip(1)) { if (candidate.Equals(element)) { count++; } else { count--; } if (count == 0) { candidate = element; count = 1; } } return candidate; } // Verify that candidate is indeed the majority private static bool VerifyMajority(IEnumerable input, int size, T candidate) { return input.Count(x => x.Equals(candidate)) > size / 2; } } ================================================ FILE: Algorithms/Search/FastSearcher.cs ================================================ using Utilities.Exceptions; namespace Algorithms.Search; /// /// The idea: you could combine the advantages from both binary-search and interpolation search algorithm. /// Time complexity: /// worst case: Item couldn't be found: O(log n), /// average case: O(log log n), /// best case: O(1). /// Note: This algorithm is recursive and the array has to be sorted beforehand. /// public class FastSearcher { /// /// Finds index of first item in array that satisfies specified term /// throws ItemNotFoundException if the item couldn't be found. /// /// Span of sorted numbers which will be used to find the item. /// Term to check against. /// Index of first item that satisfies term. /// Gets thrown when the given item couldn't be found in the array. public int FindIndex(Span array, int item) { if (array.Length == 0) { throw new ItemNotFoundException(); } if (item < array[0] || item > array[^1]) { throw new ItemNotFoundException(); } if (array[0] == array[^1]) { return item == array[0] ? 0 : throw new ItemNotFoundException(); } var (left, right) = ComputeIndices(array, item); var (from, to) = SelectSegment(array, left, right, item); return from + FindIndex(array.Slice(from, to - from + 1), item); } private (int Left, int Right) ComputeIndices(Span array, int item) { var indexBinary = array.Length / 2; int[] section = [ array.Length - 1, item - array[0], array[^1] - array[0], ]; var indexInterpolation = section[0] * section[1] / section[2]; // Left is min and right is max of the indices return indexInterpolation > indexBinary ? (indexBinary, indexInterpolation) : (indexInterpolation, indexBinary); } private (int From, int To) SelectSegment(Span array, int left, int right, int item) { if (item < array[left]) { return (0, left - 1); } if (item < array[right]) { return (left, right - 1); } return (right, array.Length - 1); } } ================================================ FILE: Algorithms/Search/FibonacciSearcher.cs ================================================ namespace Algorithms.Search; /// /// Class that implements Fibonacci search algorithm. /// /// Type of array element. public class FibonacciSearcher where T : IComparable { /// /// Finds the index of the item searched for in the array. /// Time complexity: /// worst-case: O(log n), /// average-case: O(log n), /// best-case: O(1). /// /// Sorted array to be searched in. Cannot be null. /// Item to be searched for. Cannot be null. /// If an item is found, return index. If the array is empty or an item is not found, return -1. /// Gets thrown when the given array or item is null. public int FindIndex(T[] array, T item) { if (array is null) { throw new ArgumentNullException("array"); } if (item is null) { throw new ArgumentNullException("item"); } var arrayLength = array.Length; if (arrayLength > 0) { // find the smallest Fibonacci number that equals or is greater than the array length var fibonacciNumberBeyondPrevious = 0; var fibonacciNumPrevious = 1; var fibonacciNum = fibonacciNumPrevious; while (fibonacciNum <= arrayLength) { fibonacciNumberBeyondPrevious = fibonacciNumPrevious; fibonacciNumPrevious = fibonacciNum; fibonacciNum = fibonacciNumberBeyondPrevious + fibonacciNumPrevious; } // offset to drop the left part of the array var offset = -1; while (fibonacciNum > 1) { var index = Math.Min(offset + fibonacciNumberBeyondPrevious, arrayLength - 1); switch (item.CompareTo(array[index])) { // reject approximately 1/3 of the existing array in front // by moving Fibonacci numbers case > 0: fibonacciNum = fibonacciNumPrevious; fibonacciNumPrevious = fibonacciNumberBeyondPrevious; fibonacciNumberBeyondPrevious = fibonacciNum - fibonacciNumPrevious; offset = index; break; // reject approximately 2/3 of the existing array behind // by moving Fibonacci numbers case < 0: fibonacciNum = fibonacciNumberBeyondPrevious; fibonacciNumPrevious = fibonacciNumPrevious - fibonacciNumberBeyondPrevious; fibonacciNumberBeyondPrevious = fibonacciNum - fibonacciNumPrevious; break; default: return index; } } // check the last element if (fibonacciNumPrevious == 1 && item.Equals(array[^1])) { return arrayLength - 1; } } return -1; } } ================================================ FILE: Algorithms/Search/InterpolationSearch.cs ================================================ namespace Algorithms.Search; /// /// Class that implements interpolation search algorithm. /// public static class InterpolationSearch { /// /// Finds the index of the item searched for in the array. /// Algorithm performance: /// worst-case: O(n), /// average-case: O(log(log(n))), /// best-case: O(1). /// /// Array with sorted elements to be searched in. Cannot be null. /// Value to be searched for. Cannot be null. /// If an item is found, return index, else return -1. public static int FindIndex(int[] sortedArray, int val) { var start = 0; var end = sortedArray.Length - 1; while (start <= end && val >= sortedArray[start] && val <= sortedArray[end]) { var denominator = (sortedArray[end] - sortedArray[start]) * (val - sortedArray[start]); if (denominator == 0) { denominator = 1; } var pos = start + (end - start) / denominator; if (sortedArray[pos] == val) { return pos; } if (sortedArray[pos] < val) { start = pos + 1; } else { end = pos - 1; } } return -1; } } ================================================ FILE: Algorithms/Search/JumpSearcher.cs ================================================ namespace Algorithms.Search; /// /// Jump Search checks fewer elements by jumping ahead by fixed steps. /// The optimal steps to jump is √n, where n refers to the number of elements in the array. /// Time Complexity: O(√n) /// Note: The array has to be sorted beforehand. /// /// Type of the array element. public class JumpSearcher where T : IComparable { /// /// Find the index of the item searched for in the array. /// /// Sorted array to be search in. Cannot be null. /// Item to be search for. Cannot be null. /// If item is found, return index. If array is empty or item not found, return -1. public int FindIndex(T[] sortedArray, T searchItem) { if (sortedArray is null) { throw new ArgumentNullException("sortedArray"); } if (searchItem is null) { throw new ArgumentNullException("searchItem"); } int jumpStep = (int)Math.Floor(Math.Sqrt(sortedArray.Length)); int currentIndex = 0; int nextIndex = jumpStep; if (sortedArray.Length != 0) { while (sortedArray[nextIndex - 1].CompareTo(searchItem) < 0) { currentIndex = nextIndex; nextIndex += jumpStep; if (nextIndex >= sortedArray.Length) { nextIndex = sortedArray.Length - 1; break; } } for (int i = currentIndex; i <= nextIndex; i++) { if (sortedArray[i].CompareTo(searchItem) == 0) { return i; } } } return -1; } } ================================================ FILE: Algorithms/Search/LinearSearcher.cs ================================================ using Utilities.Exceptions; namespace Algorithms.Search; /// /// Class that implements linear search algorithm. /// /// Type of array element. public class LinearSearcher { /// /// Finds first item in array that satisfies specified term /// Time complexity: O(n) /// Space complexity: O(1). /// /// Array to search in. /// Term to check against. /// First item that satisfies term. public T Find(T[] data, Func term) { for (var i = 0; i < data.Length; i++) { if (term(data[i])) { return data[i]; } } throw new ItemNotFoundException(); } /// /// Finds index of first item in array that satisfies specified term /// Time complexity: O(n) /// Space complexity: O(1). /// /// Array to search in. /// Term to check against. /// Index of first item that satisfies term or -1 if none found. public int FindIndex(T[] data, Func term) { for (var i = 0; i < data.Length; i++) { if (term(data[i])) { return i; } } return -1; } } ================================================ FILE: Algorithms/Search/RecursiveBinarySearcher.cs ================================================ namespace Algorithms.Search; /// /// RecursiveBinarySearcher. /// /// Type of searcher target. public class RecursiveBinarySearcher where T : IComparable { /// /// Finds index of item in collection that equals to item searched for, /// time complexity: O(log(n)), /// space complexity: O(1), /// where n - collection size. /// /// Sorted collection to search in. /// Item to search for. /// Thrown if input collection is null. /// Index of item that equals to item searched for or -1 if none found. public int FindIndex(IList? collection, T item) { if (collection is null) { throw new ArgumentNullException(nameof(collection)); } var leftIndex = 0; var rightIndex = collection.Count - 1; return FindIndex(collection, item, leftIndex, rightIndex); } /// /// Finds index of item in array that equals to item searched for, /// time complexity: O(log(n)), /// space complexity: O(1), /// where n - array size. /// /// Sorted array to search in. /// Item to search for. /// Minimum search range. /// Maximum search range. /// Index of item that equals to item searched for or -1 if none found. private int FindIndex(IList collection, T item, int leftIndex, int rightIndex) { if (leftIndex > rightIndex) { return -1; } var middleIndex = leftIndex + (rightIndex - leftIndex) / 2; var result = item.CompareTo(collection[middleIndex]); return result switch { var r when r == 0 => middleIndex, var r when r > 0 => FindIndex(collection, item, middleIndex + 1, rightIndex), var r when r < 0 => FindIndex(collection, item, leftIndex, middleIndex - 1), _ => -1, }; } } ================================================ FILE: Algorithms/Sequences/AllOnesSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// The all ones sequence. /// /// /// OEIS: https://oeis.org/A000012. /// /// public class AllOnesSequence : ISequence { /// /// Gets all ones sequence. /// public IEnumerable Sequence { get { while (true) { yield return 1; } } } } ================================================ FILE: Algorithms/Sequences/AllThreesSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// The all threes sequence. /// /// /// OEIS: https://oeis.org/A010701. /// /// public class AllThreesSequence : ISequence { public IEnumerable Sequence { get { while (true) { yield return 3; } } } } ================================================ FILE: Algorithms/Sequences/AllTwosSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// The all twos sequence. /// /// /// OEIS: https://oeis.org/A007395. /// /// public class AllTwosSequence : ISequence { public IEnumerable Sequence { get { while (true) { yield return 2; } } } } ================================================ FILE: Algorithms/Sequences/BinaryPrimeConstantSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of binary prime constant /// (Characteristic function of primes: 1 if n is prime, else 0). /// /// /// Wikipedia: https://wikipedia.org/wiki/Prime_constant. /// /// /// OEIS: https://oeis.org/A010051. /// /// public class BinaryPrimeConstantSequence : ISequence { /// /// Gets sequence of binary prime constant. /// public IEnumerable Sequence { get { ISequence primes = new PrimesSequence(); var n = new BigInteger(0); foreach (var p in primes.Sequence) { for (n++; n < p; n++) { yield return 0; } yield return 1; } } } } ================================================ FILE: Algorithms/Sequences/BinomialSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of binomial coefficients. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Binomial_coefficient. /// /// /// OEIS: http://oeis.org/A007318. /// /// public class BinomialSequence : ISequence { /// /// Gets sequence of binomial coefficients. /// public IEnumerable Sequence { get { var i = 0; while (true) { var row = GenerateRow(i); foreach (var coefficient in row) { yield return coefficient; } i++; } } } private static BigInteger BinomialCoefficient(long n, long k) { if (k == 0 || k == n) { return new BigInteger(1); } if (n < 0) { return new BigInteger(0); } return BinomialCoefficient(n - 1, k) + BinomialCoefficient(n - 1, k - 1); } private static IEnumerable GenerateRow(long n) { long k = 0; while (k <= n) { yield return BinomialCoefficient(n, k); k++; } } } ================================================ FILE: Algorithms/Sequences/CakeNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Cake numbers: maximal number of pieces resulting from n planar cuts through a cube /// (or cake): C(n+1,3) + n + 1. /// /// /// OEIS: https://oeis.org/A000125. /// /// public class CakeNumbersSequence : ISequence { public IEnumerable Sequence { get { var n = new BigInteger(0); while (true) { var next = (BigInteger.Pow(n, 3) + 5 * n + 6) / 6; n++; yield return next; } } } } ================================================ FILE: Algorithms/Sequences/CatalanSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Catalan numbers: C[n+1] = (2*(2*n+1)*C[n])/(n+2). /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Catalan_number. /// /// /// OEIS:http://oeis.org/A000108. /// /// public class CatalanSequence : ISequence { /// /// Gets sequence of Catalan numbers. /// public IEnumerable Sequence { get { // initialize the first element (1) and define it's enumerator (0) var catalan = new BigInteger(1); var n = 0; while (true) { yield return catalan; catalan = (2 * (2 * n + 1) * catalan) / (n + 2); n++; } } } } ================================================ FILE: Algorithms/Sequences/CentralPolygonalNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Central polygonal numbers (the Lazy Caterer's sequence): n(n+1)/2 + 1; or, maximal number of pieces /// formed when slicing a pancake with n cuts. /// /// /// OEIS: https://oeis.org/A000124. /// /// public class CentralPolygonalNumbersSequence : ISequence { public IEnumerable Sequence { get { var n = new BigInteger(0); while (true) { var next = n * (n + 1) / 2 + 1; n++; yield return next; } } } } ================================================ FILE: Algorithms/Sequences/CubesSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of cube numbers. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Cube_(algebra). /// /// /// OEIS: https://oeis.org/A000578. /// /// public class CubesSequence : ISequence { /// /// Gets sequence of cube numbers. /// public IEnumerable Sequence { get { var n = BigInteger.Zero; while (true) { yield return n * n * n; n++; } } } } ================================================ FILE: Algorithms/Sequences/DivisorsCountSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of the number of divisors of n, starting with 1. /// /// /// OEIS: https://oeis.org/A000005. /// /// public class DivisorsCountSequence : ISequence { /// /// Gets sequence of number of divisors for n, starting at 1. /// public IEnumerable Sequence { get { yield return BigInteger.One; for (var n = new BigInteger(2); ; n++) { var count = 2; for (var k = 2; k < n; k++) { BigInteger.DivRem(n, k, out var remainder); if (remainder == 0) { count++; } } yield return count; } } } } ================================================ FILE: Algorithms/Sequences/EuclidNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of Euclid numbers: 1 + product of the first n primes. /// /// /// Wikipedia: https://wikipedia.org/wiki/Euclid_number. /// /// /// OEIS: https://oeis.org/A006862. /// /// public class EuclidNumbersSequence : ISequence { /// /// Gets sequence of Euclid numbers. /// public IEnumerable Sequence { get { var primorialNumbers = new PrimorialNumbersSequence().Sequence; foreach (var n in primorialNumbers) { yield return n + 1; } } } } ================================================ FILE: Algorithms/Sequences/EulerTotientSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of Euler totient function phi(n). /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Euler%27s_totient_function. /// /// /// OEIS: https://oeis.org/A000010. /// /// public class EulerTotientSequence : ISequence { /// /// /// Gets sequence of Euler totient function phi(n). /// /// /// 'n' is copied from value of the loop of i that's being enumerated over. /// 1) Initialize result as n /// 2) Consider every number 'factor' (where 'factor' is a prime divisor of n). /// If factor divides n, then do following /// a) Subtract all multiples of factor from 1 to n [all multiples of factor /// will have gcd more than 1 (at least factor) with n] /// b) Update n by repeatedly dividing it by factor. /// 3) If the reduced n is more than 1, then remove all multiples /// of n from result. /// /// /// Base code was from https://www.geeksforgeeks.org/eulers-totient-function/. /// /// /// Implementation avoiding floating point operations was used for base /// and replacement of loop going from 1 to sqrt(n) was replaced with /// List of prime factors. /// /// public IEnumerable Sequence { get { yield return BigInteger.One; for (BigInteger i = 2; ; i++) { var n = i; var result = n; var factors = PrimeFactors(i); foreach (var factor in factors) { while (n % factor == 0) { n /= factor; } result -= result / factor; } if (n > 1) { result -= result / n; } yield return result; } } } /// /// /// Uses the prime sequence to find all prime factors of the /// number we're looking at. /// /// /// The prime sequence is examined until its value squared is /// less than or equal to target, and checked to make sure it /// evenly divides the target. If it evenly divides, it's added /// to the result which is returned as a List. /// /// /// Number that is being factored. /// List of prime factors of target. private static IEnumerable PrimeFactors(BigInteger target) { return new PrimesSequence() .Sequence.TakeWhile(prime => prime * prime <= target) .Where(prime => target % prime == 0) .ToList(); } } ================================================ FILE: Algorithms/Sequences/FactorialSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of factorial numbers. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Factorial. /// /// /// OEIS: https://oeis.org/A000142. /// /// public class FactorialSequence : ISequence { /// /// Gets sequence of factorial numbers. /// public IEnumerable Sequence { get { var n = 0; var factorial = new BigInteger(1); while (true) { yield return factorial; n++; factorial *= n; } } } } ================================================ FILE: Algorithms/Sequences/FermatNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of Fermat numbers: a(n) = 2^(2^n) + 1. /// /// /// Wikipedia: https://wikipedia.org/wiki/Fermat_number. /// /// /// OEIS: https://oeis.org/A000215. /// /// public class FermatNumbersSequence : ISequence { /// /// Gets sequence of Fermat numbers. /// public IEnumerable Sequence { get { var n = new BigInteger(2); while (true) { yield return n + 1; n *= n; } } } } ================================================ FILE: Algorithms/Sequences/FermatPrimesSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of Fermat primes: primes of the form 2^(2^k) + 1, for some k >= 0. /// /// /// Wikipedia: https://wikipedia.org/wiki/Fermat_number. /// /// /// OEIS: https://oeis.org/A019434. /// /// public class FermatPrimesSequence : ISequence { /// /// Gets sequence of Fermat primes. /// public IEnumerable Sequence { get { var fermatNumbers = new FermatNumbersSequence().Sequence.Take(5); foreach (var n in fermatNumbers) { yield return n; } } } } ================================================ FILE: Algorithms/Sequences/FibonacciSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Fibonacci sequence. /// /// /// Wikipedia: https://wikipedia.org/wiki/Fibonacci_number. /// /// /// OEIS: https://oeis.org/A000045. /// /// public class FibonacciSequence : ISequence { /// /// Gets Fibonacci sequence. /// public IEnumerable Sequence { get { yield return 0; yield return 1; BigInteger previous = 0; BigInteger current = 1; while (true) { var next = previous + current; previous = current; current = next; yield return next; } } } } ================================================ FILE: Algorithms/Sequences/GolombsSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Golomb's sequence. a(n) is the number of times n occurs in the sequence, starting with a(1) = 1. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Golomb_sequence. /// /// /// OEIS: https://oeis.org/A001462. /// /// public class GolombsSequence : ISequence { /// /// Gets Golomb's sequence. /// public IEnumerable Sequence { get { yield return 1; yield return 2; yield return 2; var queue = new Queue(); queue.Enqueue(2); for (var i = 3; ; i++) { var repetitions = queue.Dequeue(); for (var j = 0; j < repetitions; j++) { queue.Enqueue(i); yield return i; } } } } } ================================================ FILE: Algorithms/Sequences/ISequence.cs ================================================ namespace Algorithms.Sequences; /// /// Common interface for all integer sequences. /// public interface ISequence { /// /// Gets sequence as enumerable. /// IEnumerable Sequence { get; } } ================================================ FILE: Algorithms/Sequences/KolakoskiSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Kolakoski sequence; n-th element is the length of the n-th run in the sequence itself. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Kolakoski_sequence. /// /// /// OEIS: https://oeis.org/A000002. /// /// public class KolakoskiSequence : ISequence { /// /// Gets Kolakoski sequence. /// public IEnumerable Sequence { get { yield return 1; yield return 2; yield return 2; var queue = new Queue(); queue.Enqueue(2); var nextElement = 1; while (true) { var nextRun = queue.Dequeue(); for (var i = 0; i < nextRun; i++) { queue.Enqueue(nextElement); yield return nextElement; } nextElement = 1 + nextElement % 2; } } } } ================================================ FILE: Algorithms/Sequences/KolakoskiSequence2.cs ================================================ namespace Algorithms.Sequences; /// /// /// Kolakoski sequence; n-th element is the length of the n-th run in the sequence itself. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Kolakoski_sequence. /// /// /// OEIS: https://oeis.org/A000002. /// /// public class KolakoskiSequence2 : ISequence { /// /// Gets Kolakoski sequence. /// public IEnumerable Sequence { get { yield return 1; yield return 2; yield return 2; var inner = new KolakoskiSequence2().Sequence.Skip(2); var nextElement = 1; foreach (var runLength in inner) { yield return nextElement; if (runLength > 1) { yield return nextElement; } nextElement = 1 + nextElement % 2; } } } } ================================================ FILE: Algorithms/Sequences/KummerNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of Kummer numbers (also called Euclid numbers of the second kind): /// -1 + product of first n consecutive primes. /// /// /// Wikipedia: https://wikipedia.org/wiki/Euclid_number. /// /// /// OEIS: https://oeis.org/A057588. /// /// public class KummerNumbersSequence : ISequence { /// /// Gets sequence of Kummer numbers. /// public IEnumerable Sequence { get { var primorialNumbers = new PrimorialNumbersSequence().Sequence.Skip(1); foreach (var n in primorialNumbers) { yield return n - 1; } } } } ================================================ FILE: Algorithms/Sequences/LucasNumbersBeginningAt2Sequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of Lucas number values. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Lucas_number. /// /// /// OEIS: http://oeis.org/A000032. /// /// public class LucasNumbersBeginningAt2Sequence : ISequence { /// /// /// Gets Lucas number sequence. /// /// /// Lucas numbers follow the same type of operation that the Fibonacci (A000045) /// sequence performs with starting values of 2, 1 versus 0,1. As Fibonacci does, /// the ratio between two consecutive Lucas numbers converges to phi. /// /// /// This implementation is similar to A000204, but starts with the index of 0, thus having the /// initial values being (2,1) instead of starting at index 1 with initial values of (1,3). /// /// /// A simple relationship to Fibonacci can be shown with L(n) = F(n-1) + F(n+1), n>= 1. /// /// n | L(n) | F(n-1) | F(n+1) /// --|-------|--------+--------+ /// 0 | 2 | | | /// 1 | 1 | 0 | 1 | /// 2 | 3 | 1 | 2 | /// 3 | 4 | 1 | 3 | /// 4 | 7 | 2 | 5 | /// 5 | 11 | 3 | 8 | /// --|-------|--------+--------+. /// /// public IEnumerable Sequence { get { yield return 2; yield return 1; BigInteger previous = 2; BigInteger current = 1; while (true) { var next = previous + current; previous = current; current = next; yield return next; } } } } ================================================ FILE: Algorithms/Sequences/MakeChangeSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Number of ways of making change for n cents using coins of 1, 2, 5, 10 cents. /// /// /// OEIS: https://oeis.org/A000008. /// /// public class MakeChangeSequence : ISequence { /// /// /// Gets sequence of number of ways of making change for n cents /// using coins of 1, 2, 5, 10 cents. /// /// /// Uses formula from OEIS page by Michael Somos /// along with first 17 values to prevent index issues. /// /// /// Formula: /// a(n) = a(n-2) +a(n-5) - a(n-7) + a(n-10) - a(n-12) - a(n-15) + a(n-17) + 1. /// /// public IEnumerable Sequence { get { var seed = new List { 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 16, 19, 22, 25, }; foreach (var value in seed) { yield return value; } for (var index = 17; ; index++) { BigInteger newValue = seed[index - 2] + seed[index - 5] - seed[index - 7] + seed[index - 10] - seed[index - 12] - seed[index - 15] + seed[index - 17] + 1; seed.Add(newValue); yield return newValue; } } } } ================================================ FILE: Algorithms/Sequences/MatchstickTriangleSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of number of triangles in triangular matchstick arrangement of side n for n>=0. /// /// /// M. E. Larsen, The eternal triangle – a history of a counting problem, College Math. J., 20 (1989), 370-392. /// https://web.math.ku.dk/~mel/mel.pdf. /// /// /// OEIS: http://oeis.org/A002717. /// /// public class MatchstickTriangleSequence : ISequence { /// /// /// Gets number of triangles contained in an triangular arrangement of matchsticks of side length n. /// /// /// This also counts the subset of smaller triangles contained within the arrangement. /// /// /// Based on the PDF referenced above, the sequence is derived from step 8, using the resulting equation /// of f(n) = (n(n+2)(2n+1) -(delta)(n)) / 8. Using BigInteger values, we can effectively remove /// (delta)(n) from the previous by using integer division instead. /// /// /// Examples follow. ///
    ///   .
    ///  / \   This contains 1 triangle of size 1.
    /// .---.
    ///
    ///     .
    ///    / \     This contains 4 triangles of size 1.
    ///   .---.    This contains 1 triangle of size 2.
    ///  / \ / \   This contains 5 triangles total.
    /// .---.---.
    ///
    ///       .
    ///      / \      This contains 9 triangles of size 1.
    ///     .---.     This contains 3 triangles of size 2.
    ///    / \ / \    This contains 1 triangles of size 3.
    ///   .---.---.
    ///  / \ / \ / \  This contains 13 triangles total.
    /// .---.---.---.
    /// 
///
///
public IEnumerable Sequence { get { var index = BigInteger.Zero; var eight = new BigInteger(8); while (true) { var temp = index * (index + 2) * (index * 2 + 1); var result = BigInteger.Divide(temp, eight); yield return result; index++; } } } } ================================================ FILE: Algorithms/Sequences/NaturalSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of natural numbers. /// /// /// Wikipedia: https://wikipedia.org/wiki/Natural_number. /// /// /// OEIS: https://oeis.org/A000027. /// /// public class NaturalSequence : ISequence { /// /// Gets sequence of natural numbers. /// public IEnumerable Sequence { get { var n = new BigInteger(1); while (true) { yield return n++; } } } } ================================================ FILE: Algorithms/Sequences/NegativeIntegersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of negative integers. /// /// /// Wikipedia: https://wikipedia.org/wiki/Negative_number. /// /// /// OEIS: http://oeis.org/A001478. /// /// public class NegativeIntegersSequence : ISequence { /// /// Gets sequence of negative integers. /// public IEnumerable Sequence { get { var n = new BigInteger(-1); while (true) { yield return n--; } } } } ================================================ FILE: Algorithms/Sequences/NumberOfBooleanFunctionsSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of number of truth tables generated by Boolean expressions of n variables /// (Double exponentials of 2: a(n) = 2^(2^n)). /// /// /// Wikipedia: https://wikipedia.org/wiki/Truth_table. /// /// /// OEIS: https://oeis.org/A001146. /// /// public class NumberOfBooleanFunctionsSequence : ISequence { /// /// Gets sequence of number Of Boolean functions. /// public IEnumerable Sequence { get { var n = new BigInteger(2); while (true) { yield return n; n *= n; } } } } ================================================ FILE: Algorithms/Sequences/NumberOfPrimesByNumberOfDigitsSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Number of primes with n digits /// (The number of primes between 10^(n-1) and 10^n). /// /// /// Wikipedia: https://wikipedia.org/wiki/Prime-counting_function. /// /// /// OEIS: https://oeis.org/A006879. /// /// public class NumberOfPrimesByNumberOfDigitsSequence : ISequence { /// /// Gets sequence of number of primes. /// public IEnumerable Sequence { get { ISequence primes = new PrimesSequence(); var powerOf10 = new BigInteger(1); var counter = new BigInteger(0); foreach (var p in primes.Sequence) { if (p > powerOf10) { yield return counter; counter = 0; powerOf10 *= 10; } counter++; } } } } ================================================ FILE: Algorithms/Sequences/NumberOfPrimesByPowersOf10Sequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of number of primes less than 10^n (with at most n digits). /// /// /// Wikipedia: https://wikipedia.org/wiki/Prime-counting_function. /// /// /// OEIS: https://oeis.org/A006880. /// /// public class NumberOfPrimesByPowersOf10Sequence : ISequence { /// /// Gets sequence of numbers of primes. /// public IEnumerable Sequence { get { ISequence primes = new PrimesSequence(); var powerOf10 = new BigInteger(1); var counter = new BigInteger(0); foreach (var p in primes.Sequence) { if (p > powerOf10) { yield return counter; powerOf10 *= 10; } counter++; } } } } ================================================ FILE: Algorithms/Sequences/OnesCountingSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// 1's-counting sequence: number of 1's in binary expression of n. /// /// /// OEIS: https://oeis.org/A000120. /// /// public class OnesCountingSequence : ISequence { /// /// /// Gets the generated sequence of the 1's contained in the binary representation of n. /// /// /// The sequence is generated as follows. /// 1. The initial 0 value is provided. /// 2. A recursively generated sequence is iterated, starting with a length of 1 (i.e., 2^0), /// followed by increasing 2^x length values. /// 3. Each sequence starts with the value 1, and a targeted value of depths that it will recurse /// for the specific iteration. /// 4. If the call depth to the recursive function is met, it returns the value argument received. /// 5. If the call depth has not been met, it recurses to create 2 sequences, one starting with the /// value argument, and the following with the value argument + 1. /// 6. Using ':' as a visual separator for each sequence, this results in the following sequences /// that are returned sequentially after the initial 0. /// 1 : 1, 2 : 1, 2, 2, 3 : 1, 2, 2, 3, 2, 3, 3, 4. /// /// /// /// This one comes from thinking over information contained within the COMMENTS section of the OEIS page. /// /// /// Using the comments provided by Benoit Cloitre, Robert G. Wilson v, and Daniel Forgues, the above /// algorithm was coded. /// /// /// public IEnumerable Sequence { get { yield return 0; var depth = 0; while (true) { foreach (var count in GenerateFractalCount(BigInteger.One, depth)) { yield return count; } depth++; } } } /// /// /// Recursive function to generate sequences. /// /// /// The value that will start off the current IEnumerable sequence. /// The remaining depth of recursion. Value of 0 is the stop condition. /// An IEnumerable sequence of BigInteger values that can be iterated over. private static IEnumerable GenerateFractalCount(BigInteger i, int depth) { // Terminal condition if (depth == 0) { yield return i; yield break; } foreach (var firstHalf in GenerateFractalCount(i, depth - 1)) { yield return firstHalf; } foreach (var secondHalf in GenerateFractalCount(i + 1, depth - 1)) { yield return secondHalf; } } } ================================================ FILE: Algorithms/Sequences/PowersOf10Sequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of powers of 10: a(n) = 10^n. /// /// /// Wikipedia: https://wikipedia.org/wiki/Power_of_10. /// /// /// OEIS: https://oeis.org/A011557. /// /// public class PowersOf10Sequence : ISequence { /// /// Gets sequence of powers of 10. /// public IEnumerable Sequence { get { var n = new BigInteger(1); while (true) { yield return n; n *= 10; } } } } ================================================ FILE: Algorithms/Sequences/PowersOf2Sequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of powers of 2: a(n) = 2^n. /// /// /// Wikipedia: https://wikipedia.org/wiki/Power_of_two. /// /// /// OEIS: https://oeis.org/A000079. /// /// public class PowersOf2Sequence : ISequence { /// /// Gets sequence of powers of 2. /// public IEnumerable Sequence { get { var n = new BigInteger(1); while (true) { yield return n; n *= 2; } } } } ================================================ FILE: Algorithms/Sequences/PrimePiSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of number of primes less than or equal to n (PrimePi(n)). /// /// /// Wikipedia: https://wikipedia.org/wiki/Prime-counting_function. /// /// /// OEIS: https://oeis.org/A000720. /// /// public class PrimePiSequence : ISequence { /// /// Gets sequence of number of primes. /// public IEnumerable Sequence { get { ISequence primes = new PrimesSequence(); var n = new BigInteger(0); var counter = new BigInteger(0); foreach (var p in primes.Sequence) { for (n++; n < p; n++) { yield return counter; } yield return ++counter; } } } } ================================================ FILE: Algorithms/Sequences/PrimesSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of prime numbers. /// /// /// Wikipedia: https://wikipedia.org/wiki/Prime_number. /// /// /// OEIS: https://oeis.org/A000040. /// /// public class PrimesSequence : ISequence { /// /// Gets sequence of prime numbers. /// public IEnumerable Sequence { get { yield return 2; var primes = new List { 2, }; var n = new BigInteger(3); while (true) { if (primes.All(p => n % p != 0)) { yield return n; primes.Add(n); } n += 2; } } } } ================================================ FILE: Algorithms/Sequences/PrimorialNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of primorial numbers: product of first n primes. /// /// /// Wikipedia: https://wikipedia.org/wiki/Primorial. /// /// /// OEIS: https://oeis.org/A002110. /// /// public class PrimorialNumbersSequence : ISequence { /// /// Gets sequence of primorial numbers. /// public IEnumerable Sequence { get { var primes = new PrimesSequence().Sequence; var n = new BigInteger(1); foreach (var p in primes) { yield return n; n *= p; } } } } ================================================ FILE: Algorithms/Sequences/RecamansSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Recaman's sequence. a(0) = 0; for n > 0, a(n) = a(n-1) - n if nonnegative and not already in the sequence, otherwise a(n) = a(n-1) + n. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Recam%C3%A1n%27s_sequence. /// /// /// OEIS: http://oeis.org/A005132. /// /// public class RecamansSequence : ISequence { /// /// Gets Recaman's sequence. /// public IEnumerable Sequence { get { yield return 0; var elements = new HashSet { 0 }; var previous = 0; var i = 1; while (true) { var current = previous - i; if (current < 0 || elements.Contains(current)) { current = previous + i; } yield return current; previous = current; elements.Add(current); i++; } } } } ================================================ FILE: Algorithms/Sequences/SquaresSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of square numbers. /// /// /// Wikipedia: https://wikipedia.org/wiki/Square_number. /// /// /// OEIS: http://oeis.org/A000290. /// /// public class SquaresSequence : ISequence { /// /// Gets sequence of square numbers. /// public IEnumerable Sequence { get { var n = new BigInteger(0); while (true) { yield return n * n; n++; } } } } ================================================ FILE: Algorithms/Sequences/TetrahedralSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Sequence of tetrahedral (triangular pyramids) counts for n >= 0. /// /// /// OEIS: http://oeis.org/A000292. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Tetrahedral_number. /// /// public class TetrahedralSequence : ISequence { /// /// /// Gets the value of packing spheres in a regular tetrahedron /// with increasing by 1 triangular numbers under each layer. /// /// /// It can be reviewed by starting at the 4th row of Pascal's Triangle /// following the diagonal values going into the triangle. /// /// public IEnumerable Sequence { get { var index = BigInteger.Zero; var six = new BigInteger(6); while (true) { yield return BigInteger.Divide(index * (index + 1) * (index + 2), six); index++; } } } } ================================================ FILE: Algorithms/Sequences/TetranacciNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Tetranacci numbers: a(n) = a(n-1) + a(n-2) + a(n-3) + a(n-4) with a(0) = a(1) = a(2) = a(3) = 1. /// /// /// OEIS: https://oeis.org/A000288. /// /// public class TetranacciNumbersSequence : ISequence { public IEnumerable Sequence { get { var buffer = Enumerable.Repeat(BigInteger.One, 4).ToArray(); while (true) { yield return buffer[0]; var next = buffer[0] + buffer[1] + buffer[2] + buffer[3]; buffer[0] = buffer[1]; buffer[1] = buffer[2]; buffer[2] = buffer[3]; buffer[3] = next; } } } } ================================================ FILE: Algorithms/Sequences/ThreeNPlusOneStepsSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Number of halving and tripling steps to reach 1 in the '3n+1' problem. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Collatz_conjecture. /// /// /// OEIS: https://oeis.org/A006577. /// /// public class ThreeNPlusOneStepsSequence : ISequence { /// /// Gets sequence of number of halving and tripling steps to reach 1 in the '3n+1' problem. /// public IEnumerable Sequence { get { BigInteger startingValue = 1; while (true) { BigInteger counter = 0; BigInteger currentValue = startingValue; while (currentValue != 1) { if (currentValue.IsEven) { currentValue /= 2; } else { currentValue = 3 * currentValue + 1; } counter++; } yield return counter; startingValue++; } } } } ================================================ FILE: Algorithms/Sequences/TribonacciNumbersSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Tribonacci numbers: a(n) = a(n-1) + a(n-2) + a(n-3) with a(0)=a(1)=a(2)=1. /// /// /// OEIS: https://oeis.org/A000213. /// /// public class TribonacciNumbersSequence : ISequence { public IEnumerable Sequence { get { var buffer = Enumerable.Repeat(BigInteger.One, 4).ToArray(); while (true) { yield return buffer[0]; var next = buffer[0] + buffer[1] + buffer[2]; buffer[0] = buffer[1]; buffer[1] = buffer[2]; buffer[2] = next; } } } } ================================================ FILE: Algorithms/Sequences/VanEcksSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// Van Eck's sequence. For n >= 1, if there exists an m < n such that a(m) = a(n), take the largest such m and set a(n+1) = n-m; otherwise a(n+1) = 0. Start with a(1)=0. /// /// /// OEIS: http://oeis.org/A181391. /// /// public class VanEcksSequence : ISequence { /// /// Gets Van Eck's sequence. /// public IEnumerable Sequence { get { yield return 0; var dictionary = new Dictionary(); BigInteger previous = 0; BigInteger currentIndex = 2; // 1-based index while (true) { BigInteger element = 0; if (dictionary.TryGetValue(previous, out var previousIndex)) { element = currentIndex - previousIndex; } yield return element; dictionary[previous] = currentIndex; previous = element; currentIndex++; } } } } ================================================ FILE: Algorithms/Sequences/ZeroSequence.cs ================================================ namespace Algorithms.Sequences; /// /// /// The zero sequence. /// /// /// OEIS: https://oeis.org/A000004. /// /// public class ZeroSequence : ISequence { public IEnumerable Sequence { get { while (true) { yield return 0; } } } } ================================================ FILE: Algorithms/Shufflers/FisherYatesShuffler.cs ================================================ namespace Algorithms.Shufflers; /// /// Fisher-Yates shuffle is a simple shuffling algorithm, /// which is usually used to shuffle a deck of cards. /// /// Type array input. public class FisherYatesShuffler : IShuffler { /// /// Shuffles input array using Fisher-Yates algorithm. /// The algorithm starts shuffling from the last element /// and swap elements one by one. We use random index to /// choose element we use in swap operation. /// /// Array to shuffle. /// Random generator seed. Used to repeat the shuffle. public void Shuffle(T[] array, int? seed = null) { var random = seed is null ? new Random() : new Random(seed.Value); for (var i = array.Length - 1; i > 0; i--) { var j = random.Next(0, i + 1); (array[i], array[j]) = (array[j], array[i]); } } } ================================================ FILE: Algorithms/Shufflers/IShuffler.cs ================================================ namespace Algorithms.Shufflers; /// /// Shuffles array. /// /// Type of array item. public interface IShuffler { /// /// Shuffles array. /// /// Array to Shuffle. void Shuffle(T[] array, int? seed = null); } ================================================ FILE: Algorithms/Shufflers/LINQShuffler.cs ================================================ namespace Algorithms.Shufflers; /// /// LINQ Shuffle is a simple shuffling algorithm, /// where the elements within a collection are shuffled using /// LINQ queries and lambda expressions in C#. /// /// Type array input. public class LinqShuffler { /// /// First, it will generate a random value for each element. /// Next, it will sort the elements based on these generated /// random numbers using OrderBy. /// /// Array to shuffle. /// Random generator seed. Used to repeat the shuffle. public T[] Shuffle(T[] array, int? seed = null) { var random = seed is null ? new Random() : new Random(seed.Value); return array.OrderBy(x => random.Next()).ToArray(); } } ================================================ FILE: Algorithms/Shufflers/NaiveShuffler.cs ================================================ namespace Algorithms.Shufflers; /// /// Naive Shuffle is a simple and incorrect shuffling algorithm /// that randomly swaps every element with any other element in the array. /// /// Type array input. public class NaiveShuffler : IShuffler { /// /// First, it loop from 0 to n - 1. /// Next, it will randomly pick any j in the array. /// Lastly, it will swap array[i] with array[j]. /// /// Array to shuffle. /// Random generator seed. Used to repeat the shuffle. public void Shuffle(T[] array, int? seed = null) { var random = seed is null ? new Random() : new Random(seed.Value); for (int i = 0; i < array.Length; i++) { int j = random.Next(array.Length); T temp = array[i]; array[i] = array[j]; array[j] = temp; } } } ================================================ FILE: Algorithms/Shufflers/RecursiveShuffler.cs ================================================ namespace Algorithms.Shufflers; /// /// Recursive Shuffler is a recursive version of /// Fisher-Yates shuffle algorithm. This can only be used /// for educational purposes due to stack depth limits. /// /// Type array input. public class RecursiveShuffler : IShuffler { /// /// This is the public overload method that calls the private overload method. /// /// Array to shuffle. /// Random generator seed. Used to repeat the shuffle. public void Shuffle(T[] array, int? seed = null) { Shuffle(array, array.Length - 1, seed); } /// /// First, it will check the length of the array on the base case. /// Next, if there's still items left, it will shuffle the sub-array. /// Lastly, it will randomly select index from 0 to number of items of the array /// then swap the elements array[items] and array[index]. /// /// Array to shuffle. /// Number of items in the array. /// Random generator seed. Used to repeat the shuffle. private void Shuffle(T[] array, int items, int? seed) { if (items <= 0) { return; } Shuffle(array, items - 1, seed); var random = seed is null ? new Random() : new Random(seed.Value); int index = random.Next(items + 1); (array[items], array[index]) = (array[index], array[items]); (array[items], array[index]) = (array[index], array[items]); } } ================================================ FILE: Algorithms/Sorters/Comparison/BasicTimSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// A basic implementation of the TimSort algorithm for sorting arrays. /// /// The type of elements in the array. /// /// Initializes a new instance of the class. /// /// The comparer to use for comparing elements. public class BasicTimSorter(IComparer comparer) { private readonly int minRuns = 32; private readonly IComparer comparer = comparer ?? Comparer.Default; /// /// Sorts the specified array using the TimSort algorithm. /// /// The array to sort. public void Sort(T[] array) { var n = array.Length; // Step 1: Sort small pieces of the array using Insertion Sort for (var i = 0; i < n; i += minRuns) { InsertionSort(array, i, Math.Min(i + minRuns - 1, n - 1)); } // Step 2: Merge sorted runs using Merge Sort for (var size = minRuns; size < n; size *= 2) { for (var left = 0; left < n; left += 2 * size) { var mid = left + size - 1; var right = Math.Min(left + 2 * size - 1, n - 1); if (mid < right) { Merge(array, left, mid, right); } } } } /// /// Sorts a portion of the array using the Insertion Sort algorithm. /// /// The array to sort. /// The starting index of the portion to sort. /// The ending index of the portion to sort. private void InsertionSort(T[] array, int left, int right) { for (var i = left + 1; i <= right; i++) { var key = array[i]; var j = i - 1; // Move elements of array[0..i-1], that are greater than key, // to one position ahead of their current position while (j >= left && comparer.Compare(array[j], key) > 0) { array[j + 1] = array[j]; j--; } array[j + 1] = key; } } /// /// Merges two sorted subarrays into a single sorted subarray. /// /// The array containing the subarrays to merge. /// The starting index of the first subarray. /// The ending index of the first subarray. /// The ending index of the second subarray. private void Merge(T[] array, int left, int mid, int right) { // Create segments for left and right subarrays var leftSegment = new ArraySegment(array, left, mid - left + 1); var rightSegment = new ArraySegment(array, mid + 1, right - mid); // Convert segments to arrays var leftArray = leftSegment.ToArray(); var rightArray = rightSegment.ToArray(); var i = 0; var j = 0; var k = left; // Merge the two subarrays back into the main array while (i < leftArray.Length && j < rightArray.Length) { array[k++] = comparer.Compare(leftArray[i], rightArray[j]) <= 0 ? leftArray[i++] : rightArray[j++]; } // Copy remaining elements from leftArray, if any while (i < leftArray.Length) { array[k++] = leftArray[i++]; } // Copy remaining elements from rightArray, if any while (j < rightArray.Length) { array[k++] = rightArray[j++]; } } } ================================================ FILE: Algorithms/Sorters/Comparison/BinaryInsertionSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements binary insertion sort algorithm. /// /// Type of array element. public class BinaryInsertionSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// variant of insertion sort where binary search is used to find place for next element /// internal, in-place, unstable, /// time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { for (var i = 1; i < array.Length; i++) { var target = array[i]; var moveIndex = i - 1; var targetInsertLocation = BinarySearch(array, 0, moveIndex, target, comparer); Array.Copy(array, targetInsertLocation, array, targetInsertLocation + 1, i - targetInsertLocation); array[targetInsertLocation] = target; } } /// Implementation of Binary Search using an iterative approach. /// /// An array of values sorted in ascending order between the index values left and right to search /// through. /// /// Left index to search from (inclusive). /// Right index to search to (inclusive). /// The value to find placefor in the provided array. /// Compares elements. /// The index where to insert target value. private static int BinarySearch(T[] array, int from, int to, T target, IComparer comparer) { var left = from; var right = to; while (right > left) { var middle = (left + right) / 2; var comparisonResult = comparer.Compare(target, array[middle]); if (comparisonResult == 0) { return middle + 1; } if (comparisonResult > 0) { left = middle + 1; } else { right = middle - 1; } } return comparer.Compare(target, array[left]) < 0 ? left : left + 1; } } ================================================ FILE: Algorithms/Sorters/Comparison/BogoSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements bogo sort algorithm. /// /// Type of array element. public class BogoSorter : IComparisonSorter { private readonly Random random = new(); /// /// Sorts array using specified comparer, /// randomly shuffles elements until array is sorted, /// internal, in-place, unstable, /// worst-case time complexity: unbounded (infinite), /// average time complexity: O((n+1)!), /// space complexity: O(n), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { while (!IsSorted(array, comparer)) { Shuffle(array); } } private bool IsSorted(T[] array, IComparer comparer) { for (var i = 0; i < array.Length - 1; i++) { if (comparer.Compare(array[i], array[i + 1]) > 0) { return false; } } return true; } private void Shuffle(T[] array) { var taken = new bool[array.Length]; var newArray = new T[array.Length]; for (var i = 0; i < array.Length; i++) { int nextPos; do { nextPos = random.Next(0, int.MaxValue) % array.Length; } while (taken[nextPos]); taken[nextPos] = true; newArray[nextPos] = array[i]; } for (var i = 0; i < array.Length; i++) { array[i] = newArray[i]; } } } ================================================ FILE: Algorithms/Sorters/Comparison/BubbleSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements bubble sort algorithm. /// /// Type of array element. public class BubbleSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// internal, in-place, stable, /// time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { for (var i = 0; i < array.Length - 1; i++) { var wasChanged = false; for (var j = 0; j < array.Length - i - 1; j++) { if (comparer.Compare(array[j], array[j + 1]) > 0) { var temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; wasChanged = true; } } if (!wasChanged) { break; } } } } ================================================ FILE: Algorithms/Sorters/Comparison/CocktailSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Cocktail Sort is a variation of Bubble sort, where Cocktail /// Sort traverses through a given array in both directions alternatively. /// /// Array input type. public class CocktailSorter : IComparisonSorter { /// /// Sorts array using Cocktail sort algorithm. /// /// Input array. /// Type of comparer for array elements. public void Sort(T[] array, IComparer comparer) => CocktailSort(array, comparer); private static void CocktailSort(IList array, IComparer comparer) { var swapped = true; var startIndex = 0; var endIndex = array.Count - 1; while (swapped) { for (var i = startIndex; i < endIndex; i++) { if (comparer.Compare(array[i], array[i + 1]) != 1) { continue; } var highValue = array[i]; array[i] = array[i + 1]; array[i + 1] = highValue; } endIndex--; swapped = false; for (var i = endIndex; i > startIndex; i--) { if (comparer.Compare(array[i], array[i - 1]) != -1) { continue; } var highValue = array[i]; array[i] = array[i - 1]; array[i - 1] = highValue; swapped = true; } startIndex++; } } } ================================================ FILE: Algorithms/Sorters/Comparison/CombSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Comb sort is a relatively simple sorting algorithm that improves on bubble sort. /// /// Type of array element. public class CombSorter(double shrinkFactor = 1.3) : IComparisonSorter { private double ShrinkFactor { get; } = shrinkFactor; /// /// Sorts array using specified comparer, /// internal, in-place, unstable, /// worst case performance: O(n^2), /// best case performance: O(n log(n)), /// average performance: O(n^2 / 2^p), /// space complexity: O(1), /// where n - array length and p - number of increments. /// See here for more info. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { var gap = array.Length; var sorted = false; while (!sorted) { gap = (int)Math.Floor(gap / ShrinkFactor); if (gap <= 1) { gap = 1; sorted = true; } for (var i = 0; i < array.Length - gap; i++) { if (comparer.Compare(array[i], array[i + gap]) > 0) { (array[i], array[i + gap]) = (array[i + gap], array[i]); sorted = false; } } } } } ================================================ FILE: Algorithms/Sorters/Comparison/CycleSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Cycle sort is an in-place, unstable sorting algorithm, /// a comparison sort that is theoretically optimal in terms of the total /// number of writes to the original array. /// It is based on the idea that the permutation to be sorted can be factored /// into cycles, which can individually be rotated to give a sorted result. /// /// Type array input. public class CycleSorter : IComparisonSorter { /// /// Sorts input array using Cycle sort. /// /// Input array. /// Integer comparer. public void Sort(T[] array, IComparer comparer) { for (var i = 0; i < array.Length - 1; i++) { MoveCycle(array, i, comparer); } } private static void MoveCycle(T[] array, int startingIndex, IComparer comparer) { var item = array[startingIndex]; var pos = startingIndex + CountSmallerElements(array, startingIndex + 1, item, comparer); if (pos == startingIndex) { return; } pos = SkipSameElements(array, pos, item, comparer); var temp = array[pos]; array[pos] = item; item = temp; while (pos != startingIndex) { pos = startingIndex + CountSmallerElements(array, startingIndex + 1, item, comparer); pos = SkipSameElements(array, pos, item, comparer); temp = array[pos]; array[pos] = item; item = temp; } } private static int SkipSameElements(T[] array, int nextIndex, T item, IComparer comparer) { while (comparer.Compare(array[nextIndex], item) == 0) { nextIndex++; } return nextIndex; } private static int CountSmallerElements(T[] array, int startingIndex, T element, IComparer comparer) { var smallerElements = 0; for (var i = startingIndex; i < array.Length; i++) { if (comparer.Compare(array[i], element) < 0) { smallerElements++; } } return smallerElements; } } ================================================ FILE: Algorithms/Sorters/Comparison/ExchangeSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements exchange sort algorithm. /// /// Type of array element. public class ExchangeSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// internal, in-place, stable, /// time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { for (var i = 0; i < array.Length - 1; i++) { for (var j = i + 1; j < array.Length; j++) { if (comparer.Compare(array[i], array[j]) > 0) { (array[j], array[i]) = (array[i], array[j]); } } } } } ================================================ FILE: Algorithms/Sorters/Comparison/GnomeSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements gnome sort algorithm. /// /// Type of array element. public class GnomeSorter : IComparisonSorter { /// /// Moves forward through the array until it founds two elements out of order, /// then swaps them and move back one position, /// internal, in-place, stable, /// time complexity: O(n2), /// space complexity: O(1). /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { int index = 0; while (index < array.Length) { if (index == 0 || comparer.Compare(array[index], array[index - 1]) >= 0) { index++; } else { Swap(array, index, index - 1); index--; } } } public void Swap(T[] array, int index1, int index2) { (array[index1], array[index2]) = (array[index2], array[index1]); } } ================================================ FILE: Algorithms/Sorters/Comparison/HeapSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Heap sort is a comparison based sorting technique /// based on Binary Heap data structure. /// /// Input array type. public class HeapSorter : IComparisonSorter { /// /// Sorts input array using heap sort algorithm. /// /// Input array. /// Comparer type for elements. public void Sort(T[] array, IComparer comparer) => HeapSort(array, comparer); private static void HeapSort(IList data, IComparer comparer) { var heapSize = data.Count; for (var p = (heapSize - 1) / 2; p >= 0; p--) { MakeHeap(data, heapSize, p, comparer); } for (var i = data.Count - 1; i > 0; i--) { var temp = data[i]; data[i] = data[0]; data[0] = temp; heapSize--; MakeHeap(data, heapSize, 0, comparer); } } private static void MakeHeap(IList input, int heapSize, int index, IComparer comparer) { var rIndex = index; while (true) { var left = (rIndex + 1) * 2 - 1; var right = (rIndex + 1) * 2; var largest = left < heapSize && comparer.Compare(input[left], input[rIndex]) == 1 ? left : rIndex; // finds the index of the largest if (right < heapSize && comparer.Compare(input[right], input[largest]) == 1) { largest = right; } if (largest == rIndex) { return; } // process of reheaping / swapping var temp = input[rIndex]; input[rIndex] = input[largest]; input[largest] = temp; rIndex = largest; } } } ================================================ FILE: Algorithms/Sorters/Comparison/IComparisonSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Sorts array in ascending order using comparison sort. /// /// Type of array item. public interface IComparisonSorter { /// /// Sorts array in ascending order. /// /// Array to sort. /// Comparer to compare items of . void Sort(T[] array, IComparer comparer); } ================================================ FILE: Algorithms/Sorters/Comparison/InsertionSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements insertion sort algorithm. /// /// Type of array element. public class InsertionSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// internal, in-place, stable, /// time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { for (var i = 1; i < array.Length; i++) { for (var j = i; j > 0 && comparer.Compare(array[j], array[j - 1]) < 0; j--) { var temp = array[j - 1]; array[j - 1] = array[j]; array[j] = temp; } } } } ================================================ FILE: Algorithms/Sorters/Comparison/MedianOfThreeQuickSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Sorts arrays using quicksort (selecting median of three as a pivot). /// /// Type of array element. public sealed class MedianOfThreeQuickSorter : QuickSorter { protected override T SelectPivot(T[] array, IComparer comparer, int left, int right) { var leftPoint = array[left]; var middlePoint = array[left + (right - left) / 2]; var rightPoint = array[right]; return FindMedian(comparer, leftPoint, middlePoint, rightPoint); } private static T FindMedian(IComparer comparer, T a, T b, T c) { if (comparer.Compare(a, b) <= 0) { // a <= b <= c if (comparer.Compare(b, c) <= 0) { return b; } // a <= c < b if (comparer.Compare(a, c) <= 0) { return c; } // c < a <= b return a; } // a > b >= c if (comparer.Compare(b, c) >= 0) { return b; } // a >= c > b if (comparer.Compare(a, c) >= 0) { return c; } // c > a > b return a; } } ================================================ FILE: Algorithms/Sorters/Comparison/MergeSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Divide and Conquer algorithm, which splits /// array in two halves, calls itself for the two /// halves and then merges the two sorted halves. /// /// Type of array elements. public class MergeSorter : IComparisonSorter { /// /// Sorts array using merge sort algorithm, /// originally designed as external sorting algorithm, /// internal, stable, /// time complexity: O(n log(n)), /// space complexity: O(n), /// where n - array length. /// /// Array to sort. /// Comparer to compare elements of . public void Sort(T[] array, IComparer comparer) { if (array.Length <= 1) { return; } var (left, right) = Split(array); Sort(left, comparer); Sort(right, comparer); Merge(array, left, right, comparer); } private static void Merge(T[] array, T[] left, T[] right, IComparer comparer) { var mainIndex = 0; var leftIndex = 0; var rightIndex = 0; while (leftIndex < left.Length && rightIndex < right.Length) { var compResult = comparer.Compare(left[leftIndex], right[rightIndex]); array[mainIndex++] = compResult <= 0 ? left[leftIndex++] : right[rightIndex++]; } while (leftIndex < left.Length) { array[mainIndex++] = left[leftIndex++]; } while (rightIndex < right.Length) { array[mainIndex++] = right[rightIndex++]; } } private static (T[] Left, T[] Right) Split(T[] array) { var mid = array.Length / 2; return (array.Take(mid).ToArray(), array.Skip(mid).ToArray()); } } ================================================ FILE: Algorithms/Sorters/Comparison/MiddlePointQuickSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Sorts arrays using quicksort (selecting middle point as a pivot). /// /// Type of array element. public sealed class MiddlePointQuickSorter : QuickSorter { protected override T SelectPivot(T[] array, IComparer comparer, int left, int right) => array[left + (right - left) / 2]; } ================================================ FILE: Algorithms/Sorters/Comparison/PancakeSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements pancake sort algorithm. /// /// Type of array element. public class PancakeSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// internal, in-place, stable, /// time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { var n = array.Length; // Start from the complete array and one by one // reduce current size by one for (var currSize = n; currSize > 1; --currSize) { // Find index of the maximum element in // array[0..curr_size-1] var mi = FindMax(array, currSize, comparer); // Move the maximum element to end of current array // if it's not already at the end if (mi != currSize - 1) { // To move to the end, first move maximum // number to beginning Flip(array, mi); // Now move the maximum number to end by // reversing current array Flip(array, currSize - 1); } } } // Reverses array[0..i] private void Flip(T[] array, int i) { T temp; var start = 0; while (start < i) { temp = array[start]; array[start] = array[i]; array[i] = temp; start++; i--; } } // Returns index of the maximum element // in array[0..n-1] private int FindMax(T[] array, int n, IComparer comparer) { var mi = 0; for (var i = 0; i < n; i++) { if (comparer.Compare(array[i], array[mi]) == 1) { mi = i; } } return mi; } } ================================================ FILE: Algorithms/Sorters/Comparison/QuickSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Sorts arrays using quicksort. /// /// Type of array element. public abstract class QuickSorter : IComparisonSorter { /// /// Sorts array using Hoare partition scheme, /// internal, in-place, /// time complexity average: O(n log(n)), /// time complexity worst: O(n^2), /// space complexity: O(log(n)), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) => Sort(array, comparer, 0, array.Length - 1); protected abstract T SelectPivot(T[] array, IComparer comparer, int left, int right); private void Sort(T[] array, IComparer comparer, int left, int right) { if (left >= right) { return; } var p = Partition(array, comparer, left, right); Sort(array, comparer, left, p); Sort(array, comparer, p + 1, right); } private int Partition(T[] array, IComparer comparer, int left, int right) { var pivot = SelectPivot(array, comparer, left, right); var nleft = left; var nright = right; while (true) { while (comparer.Compare(array[nleft], pivot) < 0) { nleft++; } while (comparer.Compare(array[nright], pivot) > 0) { nright--; } if (nleft >= nright) { return nright; } var t = array[nleft]; array[nleft] = array[nright]; array[nright] = t; nleft++; nright--; } } } ================================================ FILE: Algorithms/Sorters/Comparison/RandomPivotQuickSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Sorts arrays using quicksort (selecting random point as a pivot). /// /// Type of array element. public sealed class RandomPivotQuickSorter : QuickSorter { private readonly Random random = new(); protected override T SelectPivot(T[] array, IComparer comparer, int left, int right) => array[random.Next(left, right + 1)]; } ================================================ FILE: Algorithms/Sorters/Comparison/SelectionSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements selection sort algorithm. /// /// Type of array element. public class SelectionSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// internal, in-place, stable, /// time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { for (var i = 0; i < array.Length - 1; i++) { var jmin = i; for (var j = i + 1; j < array.Length; j++) { if (comparer.Compare(array[jmin], array[j]) > 0) { jmin = j; } } var t = array[i]; array[i] = array[jmin]; array[jmin] = t; } } } ================================================ FILE: Algorithms/Sorters/Comparison/ShellSorter.cs ================================================ namespace Algorithms.Sorters.Comparison; /// /// Class that implements Shell sort algorithm. /// /// Type of array element. public class ShellSorter : IComparisonSorter { /// /// Sorts array using specified comparer, /// based on bubble sort, /// internal, in-place, unstable, /// worst-case time complexity: O(n^2), /// space complexity: O(1), /// where n - array length. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { for (var step = array.Length / 2; step > 0; step /= 2) { for (var i = 0; i < step; i++) { GappedBubbleSort(array, comparer, i, step); } } } private static void GappedBubbleSort(T[] array, IComparer comparer, int start, int step) { for (var j = start; j < array.Length - step; j += step) { var wasChanged = false; for (var k = start; k < array.Length - j - step; k += step) { if (comparer.Compare(array[k], array[k + step]) > 0) { var temp = array[k]; array[k] = array[k + step]; array[k + step] = temp; wasChanged = true; } } if (!wasChanged) { break; } } } } ================================================ FILE: Algorithms/Sorters/Comparison/TimSorter.cs ================================================ using Algorithms.Sorters.Utils; namespace Algorithms.Sorters.Comparison; /// /// Timsort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort, designed to perform well on many kinds of real-world data. /// It was originally implemented by Tim Peters in 2002 for use in the Python programming language. /// /// This class is based on a Java interpretation of Tim Peter's original work. /// Java class is viewable here: /// https://web.archive.org/web/20190119032242/http://cr.openjdk.java.net:80/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java /// /// Tim Peters's list sort for Python, is described in detail here: /// http://svn.python.org/projects/python/trunk/Objects/listsort.txt /// /// Tim's C code may be found here: http://svn.python.org/projects/python/trunk/Objects/listobject.c /// /// The underlying techniques are described in this paper (and may have even earlier origins): /// "Optimistic Sorting and Information Theoretic Complexity" /// Peter McIlroy /// SODA (Fourth Annual ACM-SIAM Symposium on Discrete Algorithms), /// pp 467-474, Austin, Texas, 25-27 January 1993. /// /// Type of array element. public class TimSorter : IComparisonSorter { private readonly int minMerge; private readonly int initMinGallop; private readonly int[] runBase; private readonly int[] runLengths; private int minGallop; private int stackSize; private IComparer comparer = default!; /// /// Private class for handling gallop merges, allows for tracking array indexes and wins. /// /// Type of array element. private class TimChunk { public Tc[] Array { get; set; } = default!; public int Index { get; set; } public int Remaining { get; set; } public int Wins { get; set; } } public TimSorter(TimSorterSettings settings, IComparer comparer) { initMinGallop = minGallop; // Using the worst case stack size from the C implementation. // Based on the findings in the original listsort.txt: // ... the stack can never grow larger than about log_base_phi(N) entries, where phi = (1 + sqrt(5)) / 2 ~= 1.618. // Thus a small # of stack slots suffice for very large arrays ... runBase = new int[85]; runLengths = new int[85]; stackSize = 0; minGallop = settings.MinGallop; minMerge = settings.MinMerge; this.comparer = comparer ?? Comparer.Default; } /// /// Sorts array using specified comparer /// worst case performance: O(n log(n)), /// best case performance: O(n), /// See here for more info. /// /// Array to sort. /// Compares elements. public void Sort(T[] array, IComparer comparer) { ArgumentNullException.ThrowIfNull(array); var start = 0; var remaining = array.Length; if (remaining < minMerge) { if (remaining < 2) { // Arrays of size 0 or 1 are always sorted. return; } // Don't need to merge, just binary sort BinarySort(array, start, remaining, start); return; } var minRun = MinRunLength(remaining, minMerge); do { // Identify next run var runLen = CountRunAndMakeAscending(array, start); // If the run is too short extend to Min(MIN_RUN, remaining) if (runLen < minRun) { var force = Math.Min(minRun, remaining); BinarySort(array, start, start + force, start + runLen); runLen = force; } runBase[stackSize] = start; runLengths[stackSize] = runLen; stackSize++; MergeCollapse(array); start += runLen; remaining -= runLen; } while (remaining != 0); MergeForceCollapse(array); } /// /// Returns the minimum acceptable run length for an array of the specified /// length.Natural runs shorter than this will be extended. /// /// Computation is: /// If total less than minRun, return n (it's too small to bother with fancy stuff). /// Else if total is an exact power of 2, return minRun/2. /// Else return an int k, where , such that total/k /// is close to, but strictly less than, an exact power of 2. /// /// Total length remaining to sort. /// Minimum run length to be merged. private static int MinRunLength(int total, int minRun) { var r = 0; while (total >= minRun) { r |= total & 1; total >>= 1; } return total + r; } /// /// Reverse the specified range of the specified array. /// /// the array in which a range is to be reversed. /// the index of the first element in the range to be reversed. /// the index after the last element in the range to be reversed. private static void ReverseRange(T[] array, int start, int end) { end--; while (start < end) { var t = array[start]; array[start++] = array[end]; array[end--] = t; } } /// /// Check the chunks before getting in to a merge to make sure there's something to actually do. /// /// TimChunk of the left hand side. /// TimChunk of the right hand side. /// The current target point for the remaining values. /// If a merge is required. private static bool NeedsMerge(TimChunk left, TimChunk right, ref int dest) { right.Array[dest++] = right.Array[right.Index++]; if (--right.Remaining == 0) { Array.Copy(left.Array, left.Index, right.Array, dest, left.Remaining); return false; } if (left.Remaining == 1) { Array.Copy(right.Array, right.Index, right.Array, dest, right.Remaining); right.Array[dest + right.Remaining] = left.Array[left.Index]; return false; } return true; } /// /// Moves over the last parts of the chunks. /// /// TimChunk of the left hand side. /// TimChunk of the right hand side. /// The current target point for the remaining values. private static void FinalizeMerge(TimChunk left, TimChunk right, int dest) { if (left.Remaining == 1) { Array.Copy(right.Array, right.Index, right.Array, dest, right.Remaining); right.Array[dest + right.Remaining] = left.Array[left.Index]; } else if (left.Remaining == 0) { throw new ArgumentException("Comparison method violates its general contract!"); } else { Array.Copy(left.Array, left.Index, right.Array, dest, left.Remaining); } } /// /// Returns the length of the run beginning at the specified position in /// the specified array and reverses the run if it is descending (ensuring /// that the run will always be ascending when the method returns). /// /// A run is the longest ascending sequence with: /// /// /// /// or the longest descending sequence with: /// /// a[lo + 1] > a[lo + 2] > ...]]> /// /// For its intended use in a stable mergesort, the strictness of the /// definition of "descending" is needed so that the call can safely /// reverse a descending sequence without violating stability. /// /// the array in which a run is to be counted and possibly reversed. /// index of the first element in the run. /// the length of the run beginning at the specified position in the specified array. private int CountRunAndMakeAscending(T[] array, int start) { var runHi = start + 1; if (runHi == array.Length) { return 1; } // Find end of run, and reverse range if descending if (comparer.Compare(array[runHi++], array[start]) < 0) { // Descending while (runHi < array.Length && comparer.Compare(array[runHi], array[runHi - 1]) < 0) { runHi++; } ReverseRange(array, start, runHi); } else { // Ascending while (runHi < array.Length && comparer.Compare(array[runHi], array[runHi - 1]) >= 0) { runHi++; } } return runHi - start; } /// /// Sorts the specified portion of the specified array using a binary /// insertion sort. It requires O(n log n) compares, but O(n^2) data movement. /// /// Array to sort. /// The index of the first element in the range to be sorted. /// The index after the last element in the range to be sorted. /// The index of the first element in the range that is not already known to be sorted, must be between start and end. private void BinarySort(T[] array, int start, int end, int first) { if (first >= end || first <= start) { first = start + 1; } for (; first < end; first++) { var target = array[first]; var targetInsertLocation = BinarySearch(array, start, first - 1, target); Array.Copy(array, targetInsertLocation, array, targetInsertLocation + 1, first - targetInsertLocation); array[targetInsertLocation] = target; } } private int BinarySearch(T[] array, int left, int right, T target) { while (left < right) { var mid = (left + right) >> 1; if (comparer.Compare(target, array[mid]) < 0) { right = mid; } else { left = mid + 1; } } return comparer.Compare(target, array[left]) < 0 ? left : left + 1; } private void MergeCollapse(T[] array) { while (stackSize > 1) { var n = stackSize - 2; if (n > 0 && runLengths[n - 1] <= runLengths[n] + runLengths[n + 1]) { if (runLengths[n - 1] < runLengths[n + 1]) { n--; } MergeAt(array, n); } else if (runLengths[n] <= runLengths[n + 1]) { MergeAt(array, n); } else { break; } } } private void MergeForceCollapse(T[] array) { while (stackSize > 1) { var n = stackSize - 2; if (n > 0 && runLengths[n - 1] < runLengths[n + 1]) { n--; } MergeAt(array, n); } } private void MergeAt(T[] array, int index) { var baseA = runBase[index]; var lenA = runLengths[index]; var baseB = runBase[index + 1]; var lenB = runLengths[index + 1]; runLengths[index] = lenA + lenB; if (index == stackSize - 3) { runBase[index + 1] = runBase[index + 2]; runLengths[index + 1] = runLengths[index + 2]; } stackSize--; var k = GallopingStrategy.GallopRight(array, array[baseB], baseA, lenA, comparer); baseA += k; lenA -= k; if (lenA <= 0) { return; } lenB = GallopingStrategy.GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, comparer); if (lenB <= 0) { return; } Merge(array, baseA, lenA, baseB, lenB); } private void Merge(T[] array, int baseA, int lenA, int baseB, int lenB) { var endA = baseA + lenA; var dest = baseA; TimChunk left = new() { Array = array[baseA..endA], Remaining = lenA, }; TimChunk right = new() { Array = array, Index = baseB, Remaining = lenB, }; // Move first element of the right chunk and deal with degenerate cases. if (!TimSorter.NeedsMerge(left, right, ref dest)) { // One of the chunks had 0-1 items in it, so no need to merge anything. return; } var gallop = minGallop; while (RunMerge(left, right, ref dest, ref gallop)) { // Penalize for leaving gallop mode gallop = gallop > 0 ? gallop + 2 : 2; } minGallop = gallop >= 1 ? gallop : 1; FinalizeMerge(left, right, dest); } private bool RunMerge(TimChunk left, TimChunk right, ref int dest, ref int gallop) { // Reset the number of times in row a run wins. left.Wins = 0; right.Wins = 0; // Run a stable merge sort until (if ever) one run starts winning consistently. if (StableMerge(left, right, ref dest, gallop)) { // Stable merge sort completed with no viable gallops, time to exit. return false; } // One run is winning so consistently that galloping may be a huge win. // So try that, and continue galloping until (if ever) neither run appears to be winning consistently anymore. do { if (GallopMerge(left, right, ref dest)) { // Galloped all the way to the end, merge is complete. return false; } // We had a bit of a run, so make it easier to get started again. gallop--; } while (left.Wins >= initMinGallop || right.Wins >= initMinGallop); return true; } private bool StableMerge(TimChunk left, TimChunk right, ref int dest, int gallop) { do { if (comparer.Compare(right.Array[right.Index], left.Array[left.Index]) < 0) { right.Array[dest++] = right.Array[right.Index++]; right.Wins++; left.Wins = 0; if (--right.Remaining == 0) { return true; } } else { right.Array[dest++] = left.Array[left.Index++]; left.Wins++; right.Wins = 0; if (--left.Remaining == 1) { return true; } } } while ((left.Wins | right.Wins) < gallop); return false; } private bool GallopMerge(TimChunk left, TimChunk right, ref int dest) { left.Wins = GallopingStrategy.GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, comparer); if (left.Wins != 0) { Array.Copy(left.Array, left.Index, right.Array, dest, left.Wins); dest += left.Wins; left.Index += left.Wins; left.Remaining -= left.Wins; if (left.Remaining <= 1) { return true; } } right.Array[dest++] = right.Array[right.Index++]; if (--right.Remaining == 0) { return true; } right.Wins = GallopingStrategy.GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, comparer); if (right.Wins != 0) { Array.Copy(right.Array, right.Index, right.Array, dest, right.Wins); dest += right.Wins; right.Index += right.Wins; right.Remaining -= right.Wins; if (right.Remaining == 0) { return true; } } right.Array[dest++] = left.Array[left.Index++]; if (--left.Remaining == 1) { return true; } return false; } } ================================================ FILE: Algorithms/Sorters/Comparison/TimSorterSettings.cs ================================================ namespace Algorithms.Sorters.Comparison; public class TimSorterSettings(int minMerge = 32, int minGallop = 7) { public int MinMerge { get; } = minMerge; public int MinGallop { get; } = minGallop; } ================================================ FILE: Algorithms/Sorters/External/ExternalMergeSorter.cs ================================================ namespace Algorithms.Sorters.External; public class ExternalMergeSorter : IExternalSorter { public void Sort( ISequentialStorage mainMemory, ISequentialStorage temporaryMemory, IComparer comparer) { var originalSource = mainMemory; var source = mainMemory; var temp = temporaryMemory; var totalLength = mainMemory.Length; for (var stripLength = 1L; stripLength < totalLength; stripLength *= 2) { using var left = source.GetReader(); using var right = source.GetReader(); using var output = temp.GetWriter(); for (var i = 0L; i < stripLength; i++) { right.Read(); } Merge(left, right, output, stripLength, Math.Min(stripLength, totalLength - stripLength), comparer); var step = 2 * stripLength; long rightStripStart; for (rightStripStart = stripLength + step; rightStripStart < mainMemory.Length; rightStripStart += step) { for (var i = 0L; i < stripLength; i++) { left.Read(); right.Read(); } Merge( left, right, output, stripLength, Math.Min(stripLength, totalLength - rightStripStart), comparer); } for (var i = 0L; i < totalLength + stripLength - rightStripStart; i++) { output.Write(right.Read()); } (source, temp) = (temp, source); } if (source == originalSource) { return; } using var sorted = source.GetReader(); using var dest = originalSource.GetWriter(); for (var i = 0; i < totalLength; i++) { dest.Write(sorted.Read()); } } private static void Merge( ISequentialStorageReader left, ISequentialStorageReader right, ISequentialStorageWriter output, long leftLength, long rightLength, IComparer comparer) { var leftIndex = 0L; var rightIndex = 0L; var l = left.Read(); var r = right.Read(); while (true) { if (comparer.Compare(l, r) < 0) { output.Write(l); leftIndex++; if (leftIndex == leftLength) { break; } l = left.Read(); } else { output.Write(r); rightIndex++; if (rightIndex == rightLength) { break; } r = right.Read(); } } if (leftIndex < leftLength) { output.Write(l); Copy(left, output, leftLength - leftIndex - 1); } if (rightIndex < rightLength) { output.Write(r); Copy(right, output, rightLength - rightIndex - 1); } } private static void Copy(ISequentialStorageReader from, ISequentialStorageWriter to, long count) { for (var i = 0; i < count; i++) { to.Write(from.Read()); } } } ================================================ FILE: Algorithms/Sorters/External/IExternalSorter.cs ================================================ namespace Algorithms.Sorters.External; public interface IExternalSorter { /// /// Sorts elements in sequential storage in ascending order. /// /// Memory that contains array to sort and will contain the result. /// Temporary memory for working purposes. void Sort(ISequentialStorage mainMemory, ISequentialStorage temporaryMemory, IComparer comparer); } ================================================ FILE: Algorithms/Sorters/External/ISequentialStorage.cs ================================================ namespace Algorithms.Sorters.External; public interface ISequentialStorage { public int Length { get; } ISequentialStorageReader GetReader(); ISequentialStorageWriter GetWriter(); } ================================================ FILE: Algorithms/Sorters/External/ISequentialStorageReader.cs ================================================ namespace Algorithms.Sorters.External; public interface ISequentialStorageReader : IDisposable { T Read(); } ================================================ FILE: Algorithms/Sorters/External/ISequentialStorageWriter.cs ================================================ namespace Algorithms.Sorters.External; public interface ISequentialStorageWriter : IDisposable { void Write(T value); } ================================================ FILE: Algorithms/Sorters/External/Storages/IntFileStorage.cs ================================================ using System.IO; namespace Algorithms.Sorters.External.Storages; public class IntFileStorage(string filename, int length) : ISequentialStorage { private readonly string filename = filename; public int Length { get; } = length; public ISequentialStorageReader GetReader() => new FileReader(filename); public ISequentialStorageWriter GetWriter() => new FileWriter(filename); private class FileReader(string filename) : ISequentialStorageReader { private readonly BinaryReader reader = new BinaryReader(File.OpenRead(filename)); public void Dispose() => reader.Dispose(); public int Read() => reader.ReadInt32(); } private class FileWriter(string filename) : ISequentialStorageWriter { private readonly BinaryWriter writer = new BinaryWriter(File.OpenWrite(filename)); public void Write(int value) => writer.Write(value); public void Dispose() => writer.Dispose(); } } ================================================ FILE: Algorithms/Sorters/External/Storages/IntInMemoryStorage.cs ================================================ namespace Algorithms.Sorters.External.Storages; public class IntInMemoryStorage(int[] array) : ISequentialStorage { private readonly int[] storage = array; public int Length => storage.Length; public ISequentialStorageReader GetReader() => new InMemoryReader(storage); public ISequentialStorageWriter GetWriter() => new InMemoryWriter(storage); private class InMemoryReader(int[] storage) : ISequentialStorageReader { private readonly int[] storage = storage; private int offset; public void Dispose() { // Nothing to dispose here } public int Read() => storage[offset++]; } private class InMemoryWriter(int[] storage) : ISequentialStorageWriter { private readonly int[] storage = storage; private int offset; public void Write(int value) => storage[offset++] = value; public void Dispose() { // Nothing to dispose here } } } ================================================ FILE: Algorithms/Sorters/Integer/BucketSorter.cs ================================================ namespace Algorithms.Sorters.Integer; /// /// Class that implements bucket sort algorithm. /// public class BucketSorter : IIntegerSorter { private const int NumOfDigitsInBase10 = 10; /// /// Sorts array elements using BucketSort Algorithm. /// /// Array to sort. public void Sort(int[] array) { if (array.Length <= 1) { return; } // store maximum number of digits in numbers to sort var totalDigits = NumberOfDigits(array); // bucket array where numbers will be placed var buckets = new int[NumOfDigitsInBase10, array.Length + 1]; // go through all digit places and sort each number // according to digit place value for (var pass = 1; pass <= totalDigits; pass++) { DistributeElements(array, buckets, pass); // distribution pass CollectElements(array, buckets); // gathering pass if (pass != totalDigits) { EmptyBucket(buckets); // set size of buckets to 0 } } } /// /// Determines the number of digits in the largest number. /// /// Input array. /// Number of digits. private static int NumberOfDigits(IEnumerable array) => (int)Math.Floor(Math.Log10(array.Max()) + 1); /// /// To distribute elements into buckets based on specified digit. /// /// Input array. /// Array of buckets. /// Digit. private static void DistributeElements(IEnumerable data, int[,] buckets, int digit) { // determine the divisor used to get specific digit var divisor = (int)Math.Pow(10, digit); foreach (var element in data) { // bucketNumber example for hundreds digit: // ( 1234 % 1000 ) / 100 --> 2 var bucketNumber = NumOfDigitsInBase10 * (element % divisor) / divisor; // retrieve value in pail[ bucketNumber , 0 ] to // determine the location in row to store element var elementNumber = ++buckets[bucketNumber, 0]; // location in bucket to place element buckets[bucketNumber, elementNumber] = element; } } /// /// Return elements to original array. /// /// Input array. /// Array of buckets. private static void CollectElements(IList data, int[,] buckets) { var subscript = 0; // initialize location in data // loop over buckets for (var i = 0; i < NumOfDigitsInBase10; i++) { // loop over elements in each bucket for (var j = 1; j <= buckets[i, 0]; j++) { data[subscript++] = buckets[i, j]; // add element to array } } } /// /// Sets size of all buckets to zero. /// /// Array of buckets. private static void EmptyBucket(int[,] buckets) { for (var i = 0; i < NumOfDigitsInBase10; i++) { buckets[i, 0] = 0; // set size of bucket to 0 } } } ================================================ FILE: Algorithms/Sorters/Integer/CountingSorter.cs ================================================ namespace Algorithms.Sorters.Integer; /// /// Counting sort is an algorithm for sorting a collection of objects according to keys that are small integers; that /// is, it is an integer sorting algorithm. It operates by counting the number of objects that have each distinct key /// value, and using arithmetic on those counts to determine the positions of each key value in the output sequence. /// Its running time is linear in the number of items and the difference between the maximum and minimum key values, so /// it is only suitable for direct use in situations where the variation in keys is not significantly greater than the /// number of items. However, it is often used as a subroutine in another sorting algorithm, radix sort, that can /// handle larger keys more efficiently. /// public class CountingSorter : IIntegerSorter { /// /// /// Sorts input array using counting sort algorithm. /// /// /// Time complexity: O(n+k), where k is the range of the non-negative key values. /// /// /// Space complexity: O(n+k), where k is the range of the non-negative key values. /// /// /// Input array. public void Sort(int[] array) { if (array.Length == 0) { return; } var max = array.Max(); var min = array.Min(); var count = new int[max - min + 1]; var output = new int[array.Length]; for (var i = 0; i < array.Length; i++) { count[array[i] - min]++; } for (var i = 1; i < count.Length; i++) { count[i] += count[i - 1]; } for (var i = array.Length - 1; i >= 0; i--) { output[count[array[i] - min] - 1] = array[i]; count[array[i] - min]--; } Array.Copy(output, array, array.Length); } } ================================================ FILE: Algorithms/Sorters/Integer/IIntegerSorter.cs ================================================ namespace Algorithms.Sorters.Integer; /// /// Sorts array of integers without comparing them. /// public interface IIntegerSorter { /// /// Sorts array in ascending order. /// /// Array to sort. void Sort(int[] array); } ================================================ FILE: Algorithms/Sorters/Integer/RadixSorter.cs ================================================ namespace Algorithms.Sorters.Integer; /// /// Radix sort is a non-comparative integer sorting algorithm that sorts data with integer keys by grouping keys by the /// individual /// digits which share the same significant position and value. A positional notation is required, but because integers /// can represent /// strings of characters (e.g., names or dates) and specially formatted floating point numbers, radix sort is not /// limited to integers. /// public class RadixSorter : IIntegerSorter { /// /// Sorts array in ascending order. /// /// Array to sort. public void Sort(int[] array) { var bits = 4; var b = new int[array.Length]; var rshift = 0; for (var mask = ~(-1 << bits); mask != 0; mask <<= bits, rshift += bits) { var cntarray = new int[1 << bits]; foreach (var t in array) { var key = (t & mask) >> rshift; ++cntarray[key]; } for (var i = 1; i < cntarray.Length; ++i) { cntarray[i] += cntarray[i - 1]; } for (var p = array.Length - 1; p >= 0; --p) { var key = (array[p] & mask) >> rshift; --cntarray[key]; b[cntarray[key]] = array[p]; } var temp = b; b = array; array = temp; } } } ================================================ FILE: Algorithms/Sorters/String/IStringSorter.cs ================================================ namespace Algorithms.Sorters.String; /// /// Sorts array of strings without comparing them. /// public interface IStringSorter { /// /// Sorts array in ascending order. /// /// Array to sort. void Sort(string[] array); } ================================================ FILE: Algorithms/Sorters/String/MsdRadixStringSorter.cs ================================================ namespace Algorithms.Sorters.String; /// /// Radix sort is a non-comparative sorting algorithm. It avoids comparison by creating /// and distributing elements into buckets according to their radix. /// Radix sorts can be implemented to start at either the most significant digit (MSD) /// or least significant digit (LSD). /// MSD radix sorts are most suitable for sorting array of strings with variable length /// in lexicographical order. /// public class MsdRadixStringSorter : IStringSorter { /// /// Sort array of strings using MSD radix sort algorithm. /// /// Array to sort. public void Sort(string[] array) => Sort(array, 0, array.Length - 1, 0, new string[array.Length]); private static void Sort(string[] array, int l, int r, int d, string[] temp) { if (l >= r) { return; } const int k = 256; var count = new int[k + 2]; for (var i = l; i <= r; i++) { var j = Key(array[i]); count[j + 2]++; } for (var i = 1; i < count.Length; i++) { count[i] += count[i - 1]; } for (var i = l; i <= r; i++) { var j = Key(array[i]); temp[count[j + 1]++] = array[i]; } for (var i = l; i <= r; i++) { array[i] = temp[i - l]; } for (var i = 0; i < k; i++) { Sort(array, l + count[i], l + count[i + 1] - 1, d + 1, temp); } int Key(string s) => d >= s.Length ? -1 : s[d]; } } ================================================ FILE: Algorithms/Sorters/Utils/GallopingStrategy.cs ================================================ namespace Algorithms.Sorters.Utils; public static class GallopingStrategy { public static int GallopLeft(T[] array, T key, int baseIndex, int length, IComparer comparer) { if (array.Length == 0) { return 0; } var (offset, lastOfs) = comparer.Compare(key, array[baseIndex]) > 0 ? RightRun(array, key, baseIndex, length, 0, comparer) : LeftRun(array, key, baseIndex, 0, comparer); return FinalOffset(array, key, baseIndex, offset, lastOfs, 1, comparer); } public static int GallopRight(T[] array, T key, int baseIndex, int length, IComparer comparer) { if (array.Length == 0) { return 0; } var (offset, lastOfs) = comparer.Compare(key, array[baseIndex]) < 0 ? LeftRun(array, key, baseIndex, length, comparer) : RightRun(array, key, baseIndex, length, 0, comparer); return FinalOffset(array, key, baseIndex, offset, lastOfs, 0, comparer); } public static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0 ? (shiftable << 1) + 1 : int.MaxValue; private static (int Offset, int LastOfs) LeftRun(T[] array, T key, int baseIndex, int hint, IComparer comparer) { var maxOfs = hint + 1; var (offset, tmp) = (1, 0); while (offset < maxOfs && comparer.Compare(key, array[baseIndex + hint - offset]) < 0) { tmp = offset; offset = BoundLeftShift(offset); } if (offset > maxOfs) { offset = maxOfs; } var lastOfs = hint - offset; offset = hint - tmp; return (offset, lastOfs); } private static (int Offset, int LastOfs) RightRun(T[] array, T key, int baseIndex, int len, int hint, IComparer comparer) { var (offset, lastOfs) = (1, 0); var maxOfs = len - hint; while (offset < maxOfs && comparer.Compare(key, array[baseIndex + hint + offset]) > 0) { lastOfs = offset; offset = BoundLeftShift(offset); } if (offset > maxOfs) { offset = maxOfs; } offset += hint; lastOfs += hint; return (offset, lastOfs); } private static int FinalOffset(T[] array, T key, int baseIndex, int offset, int lastOfs, int lt, IComparer comparer) { lastOfs++; while (lastOfs < offset) { var m = lastOfs + (int)((uint)(offset - lastOfs) >> 1); if (comparer.Compare(key, array[baseIndex + m]) < lt) { offset = m; } else { lastOfs = m + 1; } } return offset; } } ================================================ FILE: Algorithms/Stack/BalancedParenthesesChecker.cs ================================================ namespace Algorithms.Stack; /// /// It checks if an expression has matching and balanced parentheses. /// @author Mohit Singh. mohit-gogitter /// public class BalancedParenthesesChecker { private static readonly Dictionary ParenthesesMap = new Dictionary { { '(', ')' }, { '{', '}' }, { '[', ']' }, }; /// /// Determines if a given string expression containing brackets is balanced. /// A string is considered balanced if all opening brackets have corresponding closing brackets /// in the correct order. The supported brackets are '()', '{}', and '[]'. /// /// /// The input string expression containing the brackets to check for balance. /// /// /// true if the brackets in the expression are balanced; otherwise, false. /// /// /// Thrown when the input expression contains invalid characters or is null/empty. /// Only '(', ')', '{', '}', '[', ']' characters are allowed. /// public bool IsBalanced(string expression) { if (string.IsNullOrEmpty(expression)) { throw new ArgumentException("The input expression cannot be null or empty."); } Stack stack = new Stack(); foreach (char c in expression) { if (IsOpeningParenthesis(c)) { stack.Push(c); } else if (IsClosingParenthesis(c)) { if (!IsBalancedClosing(stack, c)) { return false; } } else { throw new ArgumentException($"Invalid character '{c}' found in the expression."); } } return stack.Count == 0; } private static bool IsOpeningParenthesis(char c) { return c == '(' || c == '{' || c == '['; } private static bool IsClosingParenthesis(char c) { return c == ')' || c == '}' || c == ']'; } private static bool IsBalancedClosing(Stack stack, char close) { if (stack.Count == 0) { return false; } char open = stack.Pop(); return IsMatchingPair(open, close); } private static bool IsMatchingPair(char open, char close) { return ParenthesesMap.ContainsKey(open) && ParenthesesMap[open] == close; } } ================================================ FILE: Algorithms/Stack/InfixToPostfix.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Algorithms.Stack { /// /// The Code focuses on Converting an Infix Expression to a Postfix Expression and Evaluates the value for the expression. /// @author Aalok Choudhari. kaloa2025 /// public static class InfixToPostfix { /// /// Infix Expression String to Convert. /// Postfix Expression. /// /// Thrown when the input expression contains invalid characters or is null/empty. /// Only following characters are allowed : Parentheses['(',')'], Operands[a-z,A-Z,0-9], Operators['+','-','*','/','^']. /// /// public static string InfixToPostfixConversion(string initialInfixExpression) { ValidateInfix(initialInfixExpression); Stack stack = new Stack(); StringBuilder postfixExpression = new StringBuilder(); foreach (char c in initialInfixExpression) { if (char.IsWhiteSpace(c)) { continue; } if (!IsValidCharacter(c)) { throw new ArgumentException($"Invalid character {c}."); } ProcessInfixCharacter(c, stack, postfixExpression); } EmptyRemainingStack(stack, postfixExpression); return postfixExpression.ToString(); } /// /// Postfix Expression String to Evaluate. /// Postfix Expression's Calculated value. /// /// Thrown when the input expression contains invalid characters or is null/empty. /// /// /// Validates expression to have sufficient operands for performing operation. /// /// public static int PostfixExpressionEvaluation(string postfixExpression) { ValidatePostfix(postfixExpression); Stack stack = new Stack(); foreach (char ch in postfixExpression) { if(char.IsWhiteSpace(ch)) { continue; } if(char.IsDigit(ch)) { stack.Push(ch - '0'); continue; } if (IsOperator(ch)) { EvaluateOperator(stack, ch); continue; } throw new InvalidOperationException($"Invalid character in expression: {ch}"); } if (stack.Count != 1) { throw new InvalidOperationException("Invalid postfix expression: Leftover operands."); } return stack.Pop(); } private static void ProcessInfixCharacter(char c, Stack stack, StringBuilder postfixExpression) { if (IsOperand(c)) { postfixExpression.Append(c); return; } if (c == '(') { stack.Push(c); return; } if (c == ')') { ProcessClosingParenthesis(stack, postfixExpression); return; } ProcessOperator(c, stack, postfixExpression); } private static void ProcessClosingParenthesis(Stack stack, StringBuilder postfixExpression) { while (stack.Count > 0 && stack.Peek() != '(') { postfixExpression.Append(stack.Pop()); } if (stack.Count == 0) { throw new InvalidOperationException("Mismatched parentheses in expression."); } stack.Pop(); } private static void ProcessOperator(char c, Stack stack, StringBuilder postfixExpression) { while (stack.Count > 0 && stack.Peek() != '(' && Precedence(stack.Peek()) >= Precedence(c)) { postfixExpression.Append(stack.Pop()); } stack.Push(c); } private static void EmptyRemainingStack(Stack stack, StringBuilder postfix) { while (stack.Count > 0) { if (stack.Peek() is '(' or ')') { throw new InvalidOperationException("Mismatched parentheses."); } postfix.Append(stack.Pop()); } } private static void EvaluateOperator(Stack stack, char op) { if (stack.Count < 2) { throw new InvalidOperationException("Insufficient operands"); } int b = stack.Pop(); int a = stack.Pop(); if (op == '/' && b == 0) { throw new DivideByZeroException("Cannot divide by zero"); } int result = op switch { '+' => a + b, '-' => a - b, '*' => a * b, '/' => a / b, '^' => (int)Math.Pow(a, b), _ => throw new InvalidOperationException($"Unknown operator {op}"), }; stack.Push(result); } private static void ValidateInfix(string expr) { if (string.IsNullOrEmpty(expr) || string.IsNullOrWhiteSpace(expr)) { throw new ArgumentException("Infix cannot be null or empty."); } } private static void ValidatePostfix(string expr) { if (string.IsNullOrEmpty(expr) || string.IsNullOrWhiteSpace(expr)) { throw new ArgumentException("Postfix cannot be null or empty."); } } /// /// Decided Operator Precedence. /// Operator character whose precedence is asked. /// Precedence rank of parameter operator character. /// [ExcludeFromCodeCoverage] private static int Precedence(char operatorChar) { if (operatorChar == '^') { return 3; } if (operatorChar == '*' || operatorChar == '/') { return 2; } if (operatorChar == '+' || operatorChar == '-') { return 1; } return 0; } /// /// Checks for character if its an Operand. /// Character asked to verify whether its an operand. /// True if its a digit or a Letter. /// private static bool IsOperand(char ch) => char.IsLetterOrDigit(ch); private static readonly HashSet Operators = new() { '+', '-', '*', '/', '^' }; /// /// Checks Operator. /// Character asked to verify whether its an operator. /// True if its allowded operator character. /// private static bool IsOperator(char ch) => Operators.Contains(ch); /// /// Checks Valid Character. /// Character asked to verify whether its an valid Character for expression. /// True if its allowded character. /// private static bool IsValidCharacter(char c) => IsOperand(c) || IsOperator(c) || IsParenthesis(c); private static bool IsParenthesis(char c) => c == '(' || c == ')'; } } ================================================ FILE: Algorithms/Stack/NextGreaterElement.cs ================================================ namespace Algorithms.Stack; /// /// For each element in an array, the utility finds the next greater element on the right side using a stack. /// @author Mohit Singh. mohit-gogitter /// public class NextGreaterElement { /// /// Finds the next greater element for each element in the input array. /// The next greater element for an element x is the first element greater than x to its right. /// If there is no greater element, -1 is returned for that element. /// /// The array of integers to find the next greater elements for. /// An array where each index contains the next greater element of the corresponding element in the input array, or -1 if no such element exists. /// Thrown when the input array is null. public int[] FindNextGreaterElement(int[] nums) { int[] result = new int[nums.Length]; Stack stack = new Stack(); // Initialize all elements in the result array to -1 for (int i = 0; i < nums.Length; i++) { result[i] = -1; } for (int i = 0; i < nums.Length; i++) { // While the stack is not empty and the current element is greater than the element // corresponding to the index stored at the top of the stack while (stack.Count > 0 && nums[i] > nums[stack.Peek()]) { int index = stack.Pop(); result[index] = nums[i]; // Set the next greater element } stack.Push(i); // Push current index to stack } return result; } } ================================================ FILE: Algorithms/Stack/ReverseStack.cs ================================================ namespace Algorithms.Stack; /// /// Reverses the elements in a stack using recursion. /// @author Mohit Singh. mohit-gogitter /// public class ReverseStack { /// /// Recursively reverses the elements of the specified stack. /// /// The type of elements in the stack. /// The stack to be reversed. This parameter cannot be null. /// Thrown when the stack parameter is null. public void Reverse(Stack stack) { if (stack.Count == 0) { return; } T temp = stack.Pop(); Reverse(stack); InsertAtBottom(stack, temp); } private void InsertAtBottom(Stack stack, T value) { if (stack.Count == 0) { stack.Push(value); } else { T temp = stack.Pop(); InsertAtBottom(stack, value); stack.Push(temp); } } } ================================================ FILE: Algorithms/Strings/GeneralStringAlgorithms.cs ================================================ namespace Algorithms.Strings; /// /// Implements simple algorithms on strings. /// public static class GeneralStringAlgorithms { /// /// Finds character that creates longest consecutive substring with single character. /// /// String to find in. /// Tuple containing char and number of times it appeared in a row. public static Tuple FindLongestConsecutiveCharacters(string input) { var maxChar = input[0]; var max = 1; var current = 1; for (var i = 1; i < input.Length; i++) { if (input[i] == input[i - 1]) { current++; if (current > max) { max = current; maxChar = input[i]; } } else { current = 1; } } return new Tuple(maxChar, max); } } ================================================ FILE: Algorithms/Strings/ManachersAlgorithm.cs ================================================ namespace Algorithms.Strings; /// /// Manacher's Algorithm is used to find the longest palindromic substring in linear time O(n). /// This algorithm is significantly more efficient than the naive O(n²) approach. /// /// KEY CONCEPTS: /// 1. String Transformation: Inserts special characters to handle even/odd palindromes uniformly. /// 2. Palindrome Radius: For each position, stores how far the palindrome extends. /// 3. Center Expansion: Expands around each potential center to find palindromes. /// 4. Symmetry Property: Uses previously computed palindrome information to skip redundant checks. /// 5. Right Boundary Tracking: Maintains the rightmost boundary of any palindrome found. /// /// WHY IT'S FAST: /// The algorithm achieves O(n) time by ensuring each character is examined at most twice: /// - Once when it's inside a known palindrome (using mirror property). /// - Once when expanding beyond the known boundary. /// /// Reference: "A New Linear-Time On-Line Algorithm for Finding the Smallest Initial Palindrome of a String" /// by Glenn Manacher (1975), Journal of the ACM. /// public static class ManachersAlgorithm { /// /// Finds the longest palindromic substring using Manacher's Algorithm. /// /// ALGORITHM STEPS: /// 1. PREPROCESSING: Transform "abc" to "^#a#b#c#$" to handle even/odd palindromes uniformly. /// - ^ and $ are sentinels that prevent boundary checks. /// - # characters create uniform spacing. /// /// 2. INITIALIZATION: Set up tracking variables. /// - palindromeRadii[i]: How far palindrome extends from position i. /// - center: Center of rightmost palindrome found. /// - rightBoundary: Right edge of rightmost palindrome. /// /// 3. MAIN LOOP: For each position i in transformed string. /// a) If i is within rightBoundary, use mirror property: /// - mirror = 2 * center - i (symmetric position). /// - Start with min(palindromeRadii[mirror], rightBoundary - i). /// b) Expand palindrome by comparing characters on both sides. /// c) Update center and rightBoundary if palindrome extends further right. /// d) Track the longest palindrome found. /// /// 4. EXTRACTION: Convert transformed coordinates back to original string. /// /// Time Complexity: O(n) - Each character examined at most twice. /// Space Complexity: O(n) - For transformed string and radius array. /// /// The input string to search for palindromes. /// The longest palindromic substring found in the input. /// Thrown when the input string is null. /// /// Input: "babad". /// Output: "bab" or "aba" (both are valid longest palindromes with length 3). /// /// Detailed Example: /// Input: "abaxyz". /// Transformed: "^#a#b#a#x#y#z#$". /// Process finds "aba" at indices 1-3 with radius 3 in transformed string. /// Maps back to indices 0-2 in original string. /// Output: "aba". /// public static string FindLongestPalindrome(string input) { // Validate input if (input == null) { throw new ArgumentException("Input string cannot be null.", nameof(input)); } // Handle edge cases if (input.Length == 0) { return string.Empty; } if (input.Length == 1) { return input; } // STEP 1: Transform the string to handle even-length palindromes uniformly // Example: "abc" becomes "^#a#b#c#$" // // WHY THIS WORKS: // - Original "aba" (odd): Center is 'b' at index 1. // - Original "abba" (even): No single center character. // - Transformed "#a#b#b#a#": Both have clear centers (the middle #). // // SENTINELS (^ and $): // - Prevent index out of bounds during expansion. // - Never match any character, so expansion stops naturally. string transformed = PreprocessString(input); int n = transformed.Length; // STEP 2: Initialize data structures // palindromeRadii[i] = radius of palindrome centered at position i. // Example: If transformed[i] is center of "#a#b#a#", radius = 3. // (3 characters on each side match). int[] palindromeRadii = new int[n]; // Track the rightmost palindrome boundary for optimization. // center: Position of the palindrome that extends furthest right. // rightBoundary: The rightmost index covered by that palindrome. // These help us use symmetry to avoid redundant comparisons. int center = 0; int rightBoundary = 0; // Track the longest palindrome found during the scan. // maxLength: Radius of the longest palindrome. // maxCenter: Position where the longest palindrome is centered. int maxLength = 0; int maxCenter = 0; // STEP 3: Process each position in the transformed string. // Skip first and last positions (sentinels ^ and $). for (int i = 1; i < n - 1; i++) { // OPTIMIZATION: Use mirror property for positions within known palindrome // // If we have a palindrome centered at 'center' extending to 'rightBoundary': // center - radius ... center ... center + radius // mirror i // // The mirror position reflects i across the center: // mirror = center - (i - center) = 2 * center - i. int mirror = 2 * center - i; // KEY INSIGHT: If i is within rightBoundary, we can use symmetry. // The palindrome at position i might be similar to the one at mirror position. if (i < rightBoundary) { // We can safely copy the radius from the mirror position, BUT: // 1. palindromeRadii[mirror]: What we know from the mirror side. // 2. rightBoundary - i: We can't assume anything beyond rightBoundary. // // Take the minimum because: // - If mirror's palindrome fits within bounds, we can use it. // - If it extends beyond, we only know up to rightBoundary. palindromeRadii[i] = Math.Min(rightBoundary - i, palindromeRadii[mirror]); } // EXPANSION PHASE: Try to extend the palindrome further. // We start from palindromeRadii[i] (not 0) to avoid redundant checks. // This is why the algorithm is O(n) - we never re-check characters. // // Example: If palindromeRadii[i] = 2, we already know: // transformed[i-2] == transformed[i+2] and // transformed[i-1] == transformed[i+1]. // So we start checking at distance 3. // // The sentinels (^ and $) guarantee we never go out of bounds. // Expansion stops naturally when characters don't match. while (transformed[i + palindromeRadii[i] + 1] == transformed[i - palindromeRadii[i] - 1]) { palindromeRadii[i]++; } // UPDATE TRACKING: If this palindrome extends further right than any before. // // WHY THIS MATTERS: // By tracking the rightmost boundary, we can use the mirror property // for future positions, avoiding redundant character comparisons. // // Example: If we find a large palindrome early, all positions within it // can benefit from the symmetry property. if (i + palindromeRadii[i] > rightBoundary) { center = i; // This position is now our reference center. rightBoundary = i + palindromeRadii[i]; // Update the rightmost boundary. } // TRACK MAXIMUM: Remember the longest palindrome found so far. // We need both the length and position to extract it later. if (palindromeRadii[i] > maxLength) { maxLength = palindromeRadii[i]; // Radius of longest palindrome. maxCenter = i; // Where it's centered in transformed string. } } // STEP 4: Extract the longest palindrome from the original string. // // COORDINATE MAPPING: // Transformed string has format: ^#a#b#c#$. // Position in transformed -> Position in original: (pos - 1) / 2. // // Example: "aba" -> "^#a#b#a#$". // - maxCenter = 4 (the middle 'b' in transformed). // - maxLength = 3 (radius). // - Original center = (4 - 1) / 2 = 1 (index of 'b' in "aba"). // - Start = (maxCenter - maxLength) / 2 = (4 - 3) / 2 = 0. // - Length = maxLength = 3. // - Result: input.Substring(0, 3) = "aba". int start = (maxCenter - maxLength) / 2; return input.Substring(start, maxLength); } /// /// Finds the longest palindromic substring and returns detailed information. /// This method provides more detailed information than FindLongestPalindrome, /// including the exact starting position and length in the original string. /// /// USE CASE: /// When you need to know WHERE the palindrome is located, not just what it is. /// Useful for highlighting, replacing, or further processing the palindrome. /// /// The input string to search for palindromes. /// /// A tuple containing: /// - The longest palindromic substring /// - The starting index of the longest palindrome in the original string /// - The length of the longest palindrome. /// /// Thrown when the input string is null. /// /// Input: "babad". /// Output: (Palindrome: "bab", StartIndex: 0, Length: 3). /// public static (string Palindrome, int StartIndex, int Length) FindLongestPalindromeWithDetails(string input) { // Validate input if (input == null) { throw new ArgumentException("Input string cannot be null.", nameof(input)); } // Handle edge cases if (input.Length == 0) { return (string.Empty, 0, 0); } if (input.Length == 1) { return (input, 0, 1); } // Apply the same algorithm as FindLongestPalindrome. // (See detailed comments in that method for step-by-step explanation). string transformed = PreprocessString(input); int n = transformed.Length; int[] palindromeRadii = new int[n]; int center = 0; int rightBoundary = 0; int maxLength = 0; int maxCenter = 0; // Main algorithm loop for (int i = 1; i < n - 1; i++) { // Use mirror property if within known palindrome. int mirror = 2 * center - i; if (i < rightBoundary) { palindromeRadii[i] = Math.Min(rightBoundary - i, palindromeRadii[mirror]); } // Expand palindrome. // Sentinels guarantee no out-of-bounds access. while (transformed[i + palindromeRadii[i] + 1] == transformed[i - palindromeRadii[i] - 1]) { palindromeRadii[i]++; } // Update rightmost boundary. if (i + palindromeRadii[i] > rightBoundary) { center = i; rightBoundary = i + palindromeRadii[i]; } // Track maximum. if (palindromeRadii[i] > maxLength) { maxLength = palindromeRadii[i]; maxCenter = i; } } // Calculate the start index and extract the palindrome. // Map from transformed coordinates to original string coordinates. int startIndex = (maxCenter - maxLength) / 2; string palindrome = input.Substring(startIndex, maxLength); // Return all three pieces of information. return (palindrome, startIndex, maxLength); } /// /// Checks if the entire string is a palindrome using Manacher's Algorithm. /// /// EFFICIENCY: /// - This approach: O(n) time using Manacher's algorithm. /// - Naive approach: O(n) time for reversing + O(n) for comparison. /// - Both are O(n), but this avoids creating a reversed copy. /// /// LOGIC: /// If the longest palindrome in the string equals the string length, /// then the entire string must be a palindrome. /// /// The string to check. /// True if the entire string is a palindrome, false otherwise. /// Thrown when the input string is null. /// /// Input: "racecar". /// Output: true. /// public static bool IsPalindrome(string input) { if (input == null) { throw new ArgumentException("Input string cannot be null.", nameof(input)); } // Strings of length 0 or 1 are always palindromes. if (input.Length <= 1) { return true; } // Find the longest palindrome in the string. // If it spans the entire string, then the string is a palindrome. var (_, _, length) = FindLongestPalindromeWithDetails(input); return length == input.Length; } /// /// Preprocesses the input string by inserting special characters. /// This transformation is the KEY to making Manacher's algorithm work efficiently. /// /// PROBLEM IT SOLVES: /// - Odd-length palindromes ("aba") have a center character. /// - Even-length palindromes ("abba") have a center between characters. /// - Without transformation, we'd need separate logic for each case. /// /// SOLUTION: /// Insert '#' between every character, making all palindromes odd-length. /// /// EXAMPLES: /// - "aba" (odd) -> "^#a#b#a#$" (center is 'b'). /// - "abba" (even) -> "^#a#b#b#a#$" (center is '#' between the two 'b's). /// /// SENTINELS (^ and $): /// - Placed at start and end. /// - Never match any character (including each other). /// - Automatically stop expansion without explicit boundary checks. /// - Prevent IndexOutOfBoundsException. /// /// COORDINATE MAPPING: /// - Original index i maps to transformed index (2*i + 2). /// - Transformed index j maps to original index (j - 1) / 2. /// /// The original input string. /// The transformed string with inserted special characters. private static string PreprocessString(string input) { // Calculate the size of transformed string. // Original: n characters. // Transformed: ^ + # + (n chars with # between each) + # + $. // Total: 1 + 1 + n + (n-1) + 1 + 1 = 2n + 3. int n = input.Length; char[] transformed = new char[n * 2 + 3]; // Place sentinels at boundaries. transformed[0] = '^'; // Start sentinel (never matches anything). transformed[n * 2 + 2] = '$'; // End sentinel (never matches anything). // Build the transformed string: #a#b#c#. // For input "abc": // Position 0: ^ (sentinel). // Position 1: # (separator). // Position 2: a (input[0]). // Position 3: # (separator). // Position 4: b (input[1]). // Position 5: # (separator). // Position 6: c (input[2]). // Position 7: # (separator). // Position 8: $ (sentinel). for (int i = 0; i < n; i++) { transformed[2 * i + 1] = '#'; // Separator before character. transformed[2 * i + 2] = input[i]; // Original character. } transformed[n * 2 + 1] = '#'; // Final separator. return new string(transformed); } } ================================================ FILE: Algorithms/Strings/Palindrome.cs ================================================ namespace Algorithms.Strings; /// /// Palindrome a series of characters or a string that when reversed, /// equals the original string. /// public static class Palindrome { /// /// Function to check if a string is a palindrome. /// /// String being checked. public static bool IsStringPalindrome(string word) => TypifyString(word).Equals(TypifyString(ReverseString(word))); /// /// Typify string to lower and remove white spaces. /// /// String to remove spaces. /// Returns original string without spaces. private static string TypifyString(string word) => Regex.Replace(word.ToLowerInvariant(), @"\s+", string.Empty); /// /// Helper function that returns a reversed string inputed. /// /// String to be reversed. /// Returns s reversed. private static string ReverseString(string s) { var arr = s.ToCharArray(); Array.Reverse(arr); return new string(arr); } } ================================================ FILE: Algorithms/Strings/PatternMatching/Bitap.cs ================================================ namespace Algorithms.Strings.PatternMatching; /// /// The Bitap algorithm is a fuzzy string matching technique. It ains to find approximate matches of a pattern within a /// text, allowing for a certain degree of mismatch (e.g., mistypes, minor variations etc.). It's knowd for its efficiency, /// using bitwise operations for fast comparisons. /// /// /// How it works: /// /// /// Initialization /// /// Bitmasks are created for each character in the pattern. These bitmasks are essentially binary numbers where each bit /// represents a specific character's position within the pattern. An initial state variable R is set to all 1s, /// indicating that all characters in the pattern are initially unmatched. /// /// /// /// Iteration /// /// The algorithm iterates through each character in the text. For each character, the state R is updated using /// bitwise operations (shifts and logical ORs). This update reflects whether the current character in the text matches /// the corresponding character in the pattern. /// /// /// /// Matching /// /// After each iteration, the algorithm checks if the least significant bit of R is set to 1. /// If it is, it means there's a potential match at that position, with a mismatch distance that's within the allowed /// threshold. /// /// /// /// /// /// Finding Matches /// /// /// If the least significant bit of R is 1, it means a potential match is found. /// The number of leading zeros in R indicates the mismatch distance. /// If this distance is within the allowed threshold, it's considered a valid match. /// /// public static class Bitap { /// /// /// This function implements the Bitap algorithm for finding exact matches of a pattern within a text. /// It aims to find the first occurrence of the pattern in the text, allowing for no mismatches. /// /// /// The algorithm iterates through each character in the text. For each character, the state R is updated using /// bitwise operations (shifts and logical ORs). This update reflects whether the current character in the text matches /// the corresponding character in the pattern. /// /// /// After each iteration, the algorithm checks if the least significant bit of R is set to 1. /// If it is, it means there's a potential match at that position, with a mismatch distance of 0. /// The function returns the index of the first occurrence of the pattern in the text, or -1 if not found. /// /// /// The function throws an if the pattern is longer than 31 characters. /// This is because the maximum length of the pattern is 31, because if it's longer than that, /// we won't be able to represent the pattern mask in an int. /// /// /// The text to search in. /// The pattern to search for. /// The index of the first occurrence of the pattern in the text, or -1 if not found. /// The pattern is longer than 31 characters. public static int FindExactPattern(string text, string pattern) { // The length of the pattern. var len = pattern.Length; // An array of integers that will be used to mask the pattern. // The pattern mask is a bitmask that we will use to search for the pattern characters // in the text. We'll set the bit corresponding to the character in the pattern // to 0, and then use bitwise operations to check for the pattern. var patternMask = new int[128]; int index; // Check if the pattern is empty. if (string.IsNullOrEmpty(pattern)) { return 0; } // Check if the pattern is longer than 31 characters. if (len > 31) { throw new ArgumentException("The pattern is longer than 31 characters."); } // Initialize the register R to all 1s. var r = ~1; // Initialize the pattern mask to all 1s. for (index = 0; index <= 127; ++index) { patternMask[index] = ~0; } // Set the bits corresponding to the characters in the pattern to 0 in the pattern mask. for (index = 0; index < len; ++index) { patternMask[pattern[index]] &= ~(1 << index); } // Iterate through each character in the text. for (index = 0; index < text.Length; ++index) { // Update the state R by ORing the pattern mask with the character in the text, // and then shift it to the left by 1. r |= patternMask[text[index]]; r <<= 1; // Check if the least significant bit of R is set to 1. // If there's a potential match at that position, with a mismatch distance of 0, // return the index of the first occurrence of the pattern in the text. if ((r & 1 << len) == 0) { return index - len + 1; } } // If no match is found, return -1. return -1; } /// /// Finds the first occurrence of a pattern in a given text with a given threshold for mismatches. /// /// The text to search in. /// The pattern to search for. /// The maximum number of mismatches allowed. /// The index of the first occurrence of the pattern in the text, or -1 if not found. public static int FindFuzzyPattern(string text, string pattern, int threshold) { // Create a pattern mask for each character in the pattern. // The pattern mask is a bitmask that we will use to search for the pattern characters // in the text. We'll set the bit corresponding to the character in the pattern // to 0, and then use bitwise operations to check for the pattern. var patternMask = new int[128]; // Create a register array. // The register array is used to keep track of the pattern mask as we search for the pattern. // We'll start with a register that has all bits set to 1, because all bits in the pattern mask // will be set to 1 initially. var r = new int[(threshold + 1) * sizeof(int)]; var len = pattern.Length; // Check for empty strings. // If the text is empty, return 0. // If the pattern is empty, return 0. if (string.IsNullOrEmpty(text)) { return 0; } if (string.IsNullOrEmpty(pattern)) { return 0; } // Check for a pattern that is too long. // If the pattern is longer than 31 characters, return -1. // The maximum length of the pattern is 31, because if it's longer than that, // we won't be able to represent the pattern mask in an int. if (len > 31) { return -1; } // Initialize the register. // Set the least significant bit in the register to 0 or 1 // depending on whether the current character in the text matches the pattern. // This will make it easier to check for the pattern later. for (var i = 0; i <= threshold; ++i) { r[i] = ~1; } // Initialize the pattern mask. // Set the bit corresponding to each character in the pattern to 0 in the pattern mask. // This will make it easier to check for the pattern later. for (var i = 0; i <= 127; i++) { patternMask[i] = ~0; } // Set the pattern mask for each character in the pattern. // Use bitwise AND to clear the bit corresponding to the current character. for (var i = 0; i < len; ++i) { patternMask[pattern[i]] &= ~(1 << i); } // Search for the pattern in the text. // Loop through each character in the text. for (var i = 0; i < text.Length; ++i) { // Update the register. // Set the least significant bit in the register to 0 or 1 // depending on whether the current character in the text matches the pattern. // This will make it easier to check for the pattern later. var oldR = r[0]; r[0] |= patternMask[text[i]]; r[0] <<= 1; // Update the other registers. // Set the least significant bit in each register to 0 or 1 // depending on whether the current character in the text matches the pattern. // This will make it easier to check for the pattern later. for (var j = 1; j <= threshold; ++j) { var tmp = r[j]; r[j] = (oldR & (r[j] | patternMask[text[i]])) << 1; oldR = tmp; } // If the pattern has been found, return the index. // Check the most significant bit in the register. // If it's 0, then the pattern has been found. if ((r[threshold] & 1 << len) == 0) { // The pattern has been found. // Return the index of the first character in the pattern. return i - len + 1; } } // The pattern has not been found. return -1; } } ================================================ FILE: Algorithms/Strings/PatternMatching/BoyerMoore.cs ================================================ namespace Algorithms.Strings.PatternMatching; /// /// The idea: You compare the pattern with the text from right to left. /// If the text symbol that is compared with the rightmost pattern symbol /// does not occur in the pattern at all, then the pattern can be shifted /// by m positions behind this text symbol. /// Complexity: /// Time: Preprocessing: O(m²) /// Comparison: O(mn) /// Space: O(m + a) /// where m - pattern length /// n - text length /// a - alphabet length. /// Source: https://www.inf.hs-flensburg.de/lang/algorithmen/pattern/bmen.htm /// https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm. /// public static class BoyerMoore { /// /// Finds the index of the first occurrence of the pattern p in t. /// /// Input text. /// Search pattern. /// Index of the pattern in text or -1 if the pattern was not found. public static int FindFirstOccurrence(string t, string p) { // Pattern length var m = p.Length; // Text length var n = t.Length; // For each symbol of the alphabet, the position of its rightmost occurrence in the pattern, // or -1 if the symbol does not occur in the pattern. int[] badChar = BadCharacterRule(p, m); // Each entry goodSuffix[i] contains the shift distance of the pattern // if a mismatch at position i – 1 occurs, i.e. if the suffix of the pattern starting at position i has matched. int[] goodSuffix = GoodSuffixRule(p, m); // Index in text var i = 0; // Index in pattern int j; while (i <= n - m) { // Starting at end of pattern j = m - 1; // While matching while (j >= 0 && p[j] == t[i + j]) { j--; } // Pattern found if (j < 0) { return i; } // Pattern is shifted by the maximum of the values // given by the good-suffix and the bad-character heuristics i += Math.Max(goodSuffix[j + 1], j - badChar[t[i + j]]); } // Pattern not found return -1; } /// /// Finds out the position of its rightmost occurrence in the pattern for each symbol of the alphabet, /// or -1 if the symbol does not occur in the pattern. /// /// Search pattern. /// Length of the pattern. /// Array of the named postition for each symbol of the alphabet. private static int[] BadCharacterRule(string p, int m) { // For each character (note that there are more than 256 characters) int[] badChar = new int[256]; Array.Fill(badChar, -1); // Iterate from left to right over the pattern for (var j = 0; j < m; j++) { badChar[p[j]] = j; } return badChar; } /// /// Finds out the shift distance of the pattern if a mismatch at position i – 1 occurs /// for each character of the pattern, i.e. if the suffix of the pattern starting at position i has matched. /// /// Search pattern. /// Length of the pattern. /// Array of the named shift distance for each character of the pattern. private static int[] GoodSuffixRule(string p, int m) { // CASE 1 // The matching suffix occurs somewhere else in the pattern // --> matching suffix is a border of a suffix of the pattern // f[i] contains starting position of the widest border of the suffix of the pattern beginning at position i int[] f = new int[p.Length + 1]; // Suffix of p[m] has no border --> f[m] = m+1 f[m] = m + 1; // Corresponding shift distance int[] s = new int[p.Length + 1]; // Start of suffix including border of the pattern // (hint: https://www.inf.hs-flensburg.de/lang/algorithmen/pattern/kmpen.htm#section2) var i = m; // Start of suffix of the pattern var j = m + 1; while (i > 0) { // checking if a shorter border that is already known can be extended to the left by the same symbol while (j <= m && p[i - 1] != p[j - 1]) { if (s[j] == 0) { s[j] = j - i; } j = f[j]; } --i; --j; f[i] = j; } // CASE 2 // Only a part of the matching suffix occurs at the beginning of the pattern // (filling remaining entries) j = f[0]; for (i = 0; i <= m; i++) { // Starting postition of the greates border if (s[i] == 0) { s[i] = j; } // From position i = j, it switches to the next narrower border f[j] if (i == j) { j = f[j]; } } return s; } } ================================================ FILE: Algorithms/Strings/PatternMatching/KnuthMorrisPrattSearcher.cs ================================================ namespace Algorithms.Strings.PatternMatching; public class KnuthMorrisPrattSearcher { /// /// An implementation of Knuth–Morris–Pratt Algorithm. /// Worst case time complexity: O(n + k) /// where n - text length, k - pattern length. /// /// The string to look in. /// The pattern to look for. /// /// The zero-based positions of all occurrences of in . /// public IEnumerable FindIndexes(string str, string pat) { var lps = FindLongestPrefixSuffixValues(pat); for (int i = 0, j = 0; i < str.Length;) { if (pat[j] == str[i]) { j++; i++; } if (j == pat.Length) { yield return i - j; j = lps[j - 1]; continue; } if (i < str.Length && pat[j] != str[i]) { if (j != 0) { j = lps[j - 1]; } else { i += 1; } } } } /// /// Return the longest prefix suffix values for pattern. /// /// pattern to seek. /// The longest prefix suffix values for . public int[] FindLongestPrefixSuffixValues(string pat) { var lps = new int[pat.Length]; for (int i = 1, len = 0; i < pat.Length;) { if (pat[i] == pat[len]) { len++; lps[i] = len; i++; continue; } if (len != 0) { len = lps[len - 1]; } else { lps[i] = 0; i++; } } return lps; } } ================================================ FILE: Algorithms/Strings/PatternMatching/NaiveStringSearch.cs ================================================ // Implements the traditional naive string matching algorithm in C# for TheAlgorithms/C-Sharp. namespace Algorithms.Strings.PatternMatching; /// /// Implements the traditional naive string matching algorithm in C#. /// public static class NaiveStringSearch { /// /// NaiveSearch(Content, Pattern) will return an array containing each index of Content in which Pattern appears. /// Cost: O(n*m). /// /// The text body across which to search for a given pattern. /// The pattern against which to check the given text body. /// Array containing each index of Content in which Pattern appears. public static int[] NaiveSearch(string content, string pattern) { var m = pattern.Length; var n = content.Length; List indices = []; for (var e = 0; e <= n - m; e++) { int j; for (j = 0; j < m; j++) { if (content[e + j] != pattern[j]) { break; } } if (j == m) { indices.Add(e); } } return indices.ToArray(); } } ================================================ FILE: Algorithms/Strings/PatternMatching/RabinKarp.cs ================================================ namespace Algorithms.Strings.PatternMatching; /// /// The idea: You calculate the hash for the pattern p and the hash values for all the prefixes of the text /// t. /// Now, you can compare a substring in constant time using the calculated hashes. /// time complexity: O(p + t), /// space complexity: O(t), /// where t - text length /// p - pattern length. /// public static class RabinKarp { /// /// Finds the index of all occurrences of the pattern p int t. /// /// List of starting indices of the pattern in the text. public static List FindAllOccurrences(string text, string pattern) { // Prime number const ulong p = 65537; // Modulo coefficient const ulong m = (ulong)1e9 + 7; // p_pow[i] = P^i mod M ulong[] pPow = new ulong[Math.Max(pattern.Length, text.Length)]; pPow[0] = 1; for (var i = 1; i < pPow.Length; i++) { pPow[i] = pPow[i - 1] * p % m; } // hash_t[i] is the sum of the previous hash values of the letters (t[0], t[1], ..., t[i-1]) and the hash value of t[i] itself (mod M). // The hash value of a letter t[i] is equal to the product of t[i] and p_pow[i] (mod M). ulong[] hashT = new ulong[text.Length + 1]; for (var i = 0; i < text.Length; i++) { hashT[i + 1] = (hashT[i] + text[i] * pPow[i]) % m; } // hash_s is equal to sum of the hash values of the pattern (mod M). ulong hashS = 0; for (var i = 0; i < pattern.Length; i++) { hashS = (hashS + pattern[i] * pPow[i]) % m; } // In the next step you iterate over the text with the pattern. List occurrences = []; for (var i = 0; i + pattern.Length - 1 < text.Length; i++) { // In each step you calculate the hash value of the substring to be tested. // By storing the hash values of the letters as a prefixes you can do this in constant time. var currentHash = (hashT[i + pattern.Length] + m - hashT[i]) % m; // Now you can compare the hash value of the substring with the product of the hash value of the pattern and p_pow[i]. if (currentHash == hashS * pPow[i] % m) { // If the hash values are identical, do a double-check in case a hash collision occurs. var j = 0; while (j < pattern.Length && text[i + j] == pattern[j]) { ++j; } if (j == pattern.Length) { // If the hash values are identical and the double-check passes, a substring was found that matches the pattern. // In this case you add the index i to the list of occurences. occurrences.Add(i); } } } return occurrences; } } ================================================ FILE: Algorithms/Strings/PatternMatching/WildCardMatcher.cs ================================================ namespace Algorithms.Strings.PatternMatching; /// /// Implentation of regular expression matching with support for '.' and '*'. /// '.' Matches any single character. /// '*' Matches zero or more of the preceding element. /// The matching should cover the entire input string (not partial). /// public static class WildCardMatcher { /// /// Using bottom-up dynamic programming for matching the input string with the pattern. /// /// Time complexity: O(n*m), where n is the length of the input string and m is the length of the pattern. /// /// Constrain: The pattern cannot start with '*'. /// /// The input string to match. /// The pattern to match. /// True if the input string matches the pattern, false otherwise. /// Thrown when the pattern starts with '*'. public static bool MatchPattern(string inputString, string pattern) { if (pattern.Length > 0 && pattern[0] == '*') { throw new ArgumentException("Pattern cannot start with *"); } var inputLength = inputString.Length + 1; var patternLength = pattern.Length + 1; // DP 2d matrix, where dp[i, j] is true if the first i characters in the input string match the first j characters in the pattern // This DP is initialized to all falses, as it is the default value for a boolean. var dp = new bool[inputLength, patternLength]; // Empty string and empty pattern are a match dp[0, 0] = true; // Since the empty string can only match a pattern that has a * in it, we need to initialize the first row of the DP matrix for (var j = 1; j < patternLength; j++) { if (pattern[j - 1] == '*') { dp[0, j] = dp[0, j - 2]; } } // Now using bottom-up approach to find for all remaining lenghts of input and pattern for (var i = 1; i < inputLength; i++) { for (var j = 1; j < patternLength; j++) { MatchRemainingLenghts(inputString, pattern, dp, i, j); } } return dp[inputLength - 1, patternLength - 1]; } // Helper method to match the remaining lengths of the input string and the pattern // This method is called for all i and j where i > 0 and j > 0 private static void MatchRemainingLenghts(string inputString, string pattern, bool[,] dp, int i, int j) { // If the characters match or the pattern has a ., then the result is the same as the previous positions. if (inputString[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') { dp[i, j] = dp[i - 1, j - 1]; } else if (pattern[j - 1] == '*') { MatchForZeroOrMore(inputString, pattern, dp, i, j); } else { // If the characters do not match, then the result is false, which is the default value. } } // Helper method to match for the "*" pattern. private static void MatchForZeroOrMore(string inputString, string pattern, bool[,] dp, int i, int j) { if (dp[i, j - 2]) { dp[i, j] = true; } else if (inputString[i - 1] == pattern[j - 2] || pattern[j - 2] == '.') { dp[i, j] = dp[i - 1, j]; } else { // Leave the default value of false } } } ================================================ FILE: Algorithms/Strings/PatternMatching/ZblockSubstringSearch.cs ================================================ namespace Algorithms.Strings.PatternMatching; /// Implementation Z-block substring search. /// public static class ZblockSubstringSearch { /// /// This algorithm finds all occurrences of a pattern in a text in linear time - O(m+n). /// public static int FindSubstring(string pattern, string text) { var concatStr = $"{pattern}${text}"; var patternLength = pattern.Length; var n = concatStr.Length; var zArray = new int[n]; var left = 0; var right = 0; for(var i = 1; i < n; i++) { if(i > right) { left = i; right = ComputeNewRightValue(concatStr, n, left, i); zArray[i] = right - left; right--; } else { var k = i - left; if (zArray[k] < (right - i + 1)) { zArray[i] = zArray[k]; } else { left = i; right = ComputeNewRightValue(concatStr, n, left, right); zArray[i] = right - left; right--; } } } var found = 0; foreach(var z_value in zArray) { if(z_value == patternLength) { found++; } } return found; } private static int ComputeNewRightValue(string concatStr, int n, int left, int right) { while (right < n && concatStr[right - left].Equals(concatStr[right])) { right++; } return right; } } ================================================ FILE: Algorithms/Strings/Permutation.cs ================================================ namespace Algorithms.Strings; public static class Permutation { /// /// Returns every anagram of a given word. /// /// List of anagrams. public static List GetEveryUniquePermutation(string word) { if (word.Length < 2) { return [ word, ]; } var result = new HashSet(); for (var i = 0; i < word.Length; i++) { var temp = GetEveryUniquePermutation(word.Remove(i, 1)); result.UnionWith(temp.Select(subPerm => word[i] + subPerm)); } return result.ToList(); } } ================================================ FILE: Algorithms/Strings/Similarity/CosineSimilarity.cs ================================================ namespace Algorithms.Strings.Similarity; public static class CosineSimilarity { /// /// Calculates the Cosine Similarity between two strings. /// Cosine Similarity is a measure of similarity between two non-zero vectors of an inner product space. /// It measures the cosine of the angle between the two vectors. /// /// The first string. /// The second string. /// /// A double value between 0 and 1 that represents the similarity /// of the two strings. /// public static double Calculate(string left, string right) { // Step 1: Get the vectors for the two strings // Each vector represents the frequency of each character in the string. var vectors = GetVectors(left.ToLowerInvariant(), right.ToLowerInvariant()); var leftVector = vectors.LeftVector; var rightVector = vectors.RightVector; // Step 2: Calculate the intersection of the two vectors // The intersection is the set of characters that appear in both strings. var intersection = GetIntersection(leftVector, rightVector); // Step 3: Calculate the dot product of the two vectors // The dot product is the sum of the products of the corresponding values of the characters in the intersection. var dotProduct = DotProduct(leftVector, rightVector, intersection); // Step 4: Calculate the square magnitude of each vector // The magnitude is the square root of the sum of the squares of the values in the vector. var mLeft = 0.0; foreach (var value in leftVector.Values) { mLeft += value * value; } var mRight = 0.0; foreach (var value in rightVector.Values) { mRight += value * value; } // Step 5: Check if either vector is zero // If either vector is zero (i.e., all characters are unique), the Cosine Similarity is 0. if (mLeft <= 0 || mRight <= 0) { return 0.0; } // Step 6: Calculate and return the Cosine Similarity // The Cosine Similarity is the dot product divided by the product of the magnitudes. return dotProduct / (Math.Sqrt(mLeft) * Math.Sqrt(mRight)); } /// /// Calculates the vectors for the given strings. /// /// The first string. /// The second string. /// A tuple containing the vectors for the two strings. private static (Dictionary LeftVector, Dictionary RightVector) GetVectors(string left, string right) { var leftVector = new Dictionary(); var rightVector = new Dictionary(); // Calculate the frequency of each character in the left string foreach (var character in left) { leftVector.TryGetValue(character, out var frequency); leftVector[character] = ++frequency; } // Calculate the frequency of each character in the right string foreach (var character in right) { rightVector.TryGetValue(character, out var frequency); rightVector[character] = ++frequency; } return (leftVector, rightVector); } /// /// Calculates the dot product between two vectors represented as dictionaries of character frequencies. /// The dot product is the sum of the products of the corresponding values of the characters in the intersection of the two vectors. /// /// The vector of the left string. /// The vector of the right string. /// The intersection of the two vectors, represented as a set of characters. /// The dot product of the two vectors. private static double DotProduct(Dictionary leftVector, Dictionary rightVector, HashSet intersection) { // Initialize the dot product to 0 double dotProduct = 0; // Iterate over each character in the intersection of the two vectors foreach (var character in intersection) { // Calculate the product of the corresponding values of the characters in the left and right vectors dotProduct += leftVector[character] * rightVector[character]; } // Return the dot product return dotProduct; } /// /// Calculates the intersection of two vectors, represented as dictionaries of character frequencies. /// /// The vector of the left string. /// The vector of the right string. /// A HashSet containing the characters that appear in both vectors. private static HashSet GetIntersection(Dictionary leftVector, Dictionary rightVector) { // Initialize a HashSet to store the intersection of the two vectors. var intersection = new HashSet(); // Iterate over each key-value pair in the left vector. foreach (var kvp in leftVector) { // If the right vector contains the same key, add it to the intersection. if (rightVector.ContainsKey(kvp.Key)) { intersection.Add(kvp.Key); } } return intersection; } } ================================================ FILE: Algorithms/Strings/Similarity/DamerauLevenshteinDistance.cs ================================================ namespace Algorithms.Strings.Similarity; public static class DamerauLevenshteinDistance { /// /// Calculates the Damerau-Levenshtein distance between two strings. /// The Damerau-Levenshtein distance is a string metric for measuring the difference between two sequences. /// It is calculated as the minimum number of operations needed to transform one sequence into the other. /// The possible operations are insertion, deletion, substitution, and transposition. /// /// The first string. /// The second string. /// The Damerau-Levenshtein distance between the two strings. public static int Calculate(string left, string right) { // Get the lengths of the input strings. var leftSize = left.Length; var rightSize = right.Length; // Initialize a matrix of distances between the two strings. var distances = InitializeDistanceArray(leftSize, rightSize); // Iterate over each character in the left string. for (var i = 1; i < leftSize + 1; i++) { // Iterate over each character in the right string. for (var j = 1; j < rightSize + 1; j++) { // Calculate the cost of the current operation. // If the characters at the current positions are the same, the cost is 0. // Otherwise, the cost is 1. var cost = left[i - 1] == right[j - 1] ? 0 : 1; // Calculate the minimum distance by considering three possible operations: // deletion, insertion, and substitution. distances[i, j] = Math.Min( Math.Min(// deletion distances[i - 1, j] + 1, // delete the character from the left string distances[i, j - 1] + 1), // insert the character into the right string distances[i - 1, j - 1] + cost); // substitute the character in the left string with the character in the right string // If the current character in the left string is the same as the character // two positions to the left in the right string and the current character // in the right string is the same as the character one position to the right // in the left string, then we can also consider a transposition operation. if (i > 1 && j > 1 && left[i - 1] == right[j - 2] && left[i - 2] == right[j - 1]) { distances[i, j] = Math.Min( distances[i, j], // current minimum distance distances[i - 2, j - 2] + cost); // transpose the last two characters } } } // Return the distance between the two strings. return distances[leftSize, rightSize]; } /// /// Initializes a matrix of distances between two string representations. /// /// This method creates a matrix of distances where the dimensions are one larger /// than the input strings. The first row of the matrix represents the distances /// when the left string is empty, and the first column represents the distances /// when the right string is empty. The values in the first row and first column /// are the lengths of the corresponding strings. /// /// The matrix is used by the Damerau-Levenshtein algorithm to calculate the /// minimum number of single-character edits (insertions, deletions, or substitutions) /// required to change one word into the other. /// The matrix is initialized with dimensions one larger than the input strings. /// The first row of the matrix represents the distances when the left string is empty. /// The first column of the matrix represents the distances when the right string is empty. /// The values in the first row and first column are the lengths of the corresponding strings. /// Initializes a matrix of distances between two strings representations. /// /// The size of the left string. /// The size of the right string. /// A matrix of distances. private static int[,] InitializeDistanceArray(int leftSize, int rightSize) { // Initialize a matrix of distances with dimensions one larger than the input strings. var matrix = new int[leftSize + 1, rightSize + 1]; // Set the values in the first row to the lengths of the left string. // This represents the distance when the left string is empty. for (var i = 1; i < leftSize + 1; i++) { matrix[i, 0] = i; } // Set the values in the first column to the lengths of the right string. // This represents the distance when the right string is empty. for (var i = 1; i < rightSize + 1; i++) { matrix[0, i] = i; } // Return the initialized matrix of distances. return matrix; } } ================================================ FILE: Algorithms/Strings/Similarity/HammingDistance.cs ================================================ namespace Algorithms.Strings.Similarity; /// /// /// Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. /// Time complexity is O(n) where n is the length of the string. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Hamming_distance. /// /// public static class HammingDistance { /// /// Calculates Hamming distance between two strings of equal length. /// /// First string. /// Second string. /// Levenshtein distance between source and target strings. public static int Calculate(string s1, string s2) { if (s1.Length != s2.Length) { throw new ArgumentException("Strings must be equal length."); } var distance = 0; for (var i = 0; i < s1.Length; i++) { distance += s1[i] != s2[i] ? 1 : 0; } return distance; } } ================================================ FILE: Algorithms/Strings/Similarity/JaccardDistance.cs ================================================ namespace Algorithms.Strings.Similarity; /// /// /// Jaccard distance is a measure of two sets of data are. It is calculated by subtracting the Jaccard similarity /// coefficient from 1, or, equivalently by dividing the difference of the sizes of the union and intersection of two sets /// by the size of the union. /// /// /// For example, suppose we have two sets of words: /// /// /// A = {apple, banana, cherry, date} /// /// /// B = {banana, cherry, elderberry, fig} /// /// /// /// /// The number of common elements in both sets is 2 (banana and cherry). The number of elements in either set is 6 /// (apple, banana, cherry, date, elderberry, fig). /// /// /// The Jaccard similarity coefficient is 2 / 6 = 0.333333 or 33.333% similarity. /// /// /// The Jaccard distance is 1 - 0.33333 = 0.66667. This means that the two sets are about 67% different. /// /// /// Jaccard distance is commonly used to calculate a matrix of clustering and multidimensional scaling of sample tests. /// /// public class JaccardDistance { private readonly JaccardSimilarity jaccardSimilarity = new(); /// /// Calculate the Jaccard distance between to strings. /// /// The first string. /// The second string. /// The Jaccard distance. public double Calculate(string left, string right) { return 1.0 - jaccardSimilarity.Calculate(left, right); } } ================================================ FILE: Algorithms/Strings/Similarity/JaccardSimilarity.cs ================================================ namespace Algorithms.Strings.Similarity; /// /// /// Jaccard similarity is a statistic that measures how similar two sets of data are. It is calculated by dividing /// the number of common elements in both sets by the number of elements in either set. More formally, it is the /// quotient of the division of the size of the size of the intersection divided by the size of the union of two sets. /// /// /// The result is a value between 0 and 1, where 0 means no similarity and 1 means perfect similarity. /// /// /// For example, suppose we have two sets of words: /// /// /// A = {apple, banana, cherry, date} /// /// /// B = {banana, cherry, elderberry, fig} /// /// /// /// /// The number of common elements in both sets is 2 (banana and cherry). The number of elements in either set is 6 /// (apple, banana, cherry, date, elderberry, fig). /// /// /// The Jaccard similarity coefficient is 2 / 6 = 0.333333 or 33.333% similarity. /// /// public class JaccardSimilarity { /// /// Calculates the Jaccard similarity coefficient between two strings. /// /// The first string to compare. /// The second string to compare. /// A double value between 0 and 1 that represents the similarity of the two strings. /// Thrown when either the input is null. /// /// This method uses a HashSet to represent the sets of characters in the input strings. /// public double Calculate(string left, string right) { // Validate the input strings before proceeding. ValidateInput(left, right); // Get the lengths of the input strings. var leftLength = left.Length; var rightLength = right.Length; // If both strings are empty, return 1.0 as the similarity coefficient. if (leftLength == 0 && rightLength == 0) { return 1.0d; } // If either string is empty, return 0.0 as the similarity coefficient. if (leftLength == 0 || rightLength == 0) { return 0.0d; } // Get the unique characters in each string. var leftSet = new HashSet(left); var rightSet = new HashSet(right); // Get the union of the two strings. var unionSet = new HashSet(leftSet); foreach (var c in rightSet) { unionSet.Add(c); } // Calculate the intersection size of the two strings. var intersectionSize = leftSet.Count + rightSet.Count - unionSet.Count; // Return the Jaccard similarity coefficient as the ratio of intersection to union. return 1.0d * intersectionSize / unionSet.Count; } /// /// Validates the input strings and throws an exception if either is null. /// /// The first string to validate. /// The second string to validate. private void ValidateInput(string left, string right) { if (left == null || right == null) { var paramName = left == null ? nameof(left) : nameof(right); throw new ArgumentNullException(paramName, "Input cannot be null"); } } } ================================================ FILE: Algorithms/Strings/Similarity/JaroSimilarity.cs ================================================ namespace Algorithms.Strings.Similarity; /// /// /// Jaro Similarity measures how similar two strings are. /// Result is between 0 and 1 where 0 represnts that there is no similarity between strings and 1 represents equal strings. /// Time complexity is O(a*b) where a is the length of the first string and b is the length of the second string. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance#Jaro_similarity. /// /// public static class JaroSimilarity { /// /// Calculates Jaro Similarity between two strings. /// /// First string. /// Second string. public static double Calculate(string s1, string s2) { if (s1 == s2) { return 1; } var longerString = s1.Length > s2.Length ? s1 : s2; var shorterString = s1.Length < s2.Length ? s1 : s2; // will look for matching characters in this range var matchingCharacterRange = Math.Max((longerString.Length / 2) - 1, 0); var matches = 0d; // true if i-th index of s1 was matched in s2 var s1MatchedIndeces = new bool[s1.Length]; // true if i-th index of s2 was matched in s1 var s2MatchedIndeces = new bool[s2.Length]; for (var i = 0; i < longerString.Length; i++) { var startIndex = Math.Max(i - matchingCharacterRange, 0); var endIndex = Math.Min(i + matchingCharacterRange, shorterString.Length - 1); for (var j = startIndex; j <= endIndex; j++) { if (s1[i] == s2[j] && !s2MatchedIndeces[j]) { matches++; s1MatchedIndeces[i] = true; s2MatchedIndeces[j] = true; break; } } } if (matches == 0) { return 0; } var transpositions = CalculateTranspositions(s1, s2, s1MatchedIndeces, s2MatchedIndeces); return ((matches / s1.Length) + (matches / s2.Length) + ((matches - transpositions) / matches)) / 3; } /// /// Calculates number of matched characters that are not in the right order. /// private static int CalculateTranspositions(string s1, string s2, bool[] s1MatchedIndeces, bool[] s2MatchedIndeces) { var transpositions = 0; var s2Index = 0; for (var s1Index = 0; s1Index < s1.Length; s1Index++) { if (s1MatchedIndeces[s1Index]) { while (!s2MatchedIndeces[s2Index]) { s2Index++; } if (s1[s1Index] != s2[s2Index]) { transpositions++; } s2Index++; } } transpositions /= 2; return transpositions; } } ================================================ FILE: Algorithms/Strings/Similarity/JaroWinklerDistance.cs ================================================ namespace Algorithms.Strings.Similarity; /// /// /// Jaro–Winkler distance is a string metric measuring an edit distance between two sequences. /// The score is normalized such that 1 means an exact match and 0 means there is no similarity. /// Time complexity is O(a*b) where a is the length of the first string and b is the length of the second string. /// /// /// Wikipedia: https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance. /// /// public static class JaroWinklerDistance { /// /// Calculates Jaro–Winkler distance. /// /// First string. /// Second string. /// Scaling factor for how much the score is adjusted upwards for having common prefixes. Default is 0.1. /// Distance between two strings. public static double Calculate(string s1, string s2, double scalingFactor = 0.1) { var jaroSimilarity = JaroSimilarity.Calculate(s1, s2); var commonPrefixLength = s1.Zip(s2).Take(4).TakeWhile(x => x.First == x.Second).Count(); var jaroWinklerSimilarity = jaroSimilarity + commonPrefixLength * scalingFactor * (1 - jaroSimilarity); return 1 - jaroWinklerSimilarity; } } ================================================ FILE: Algorithms/Strings/Similarity/OptimalStringAlignment.cs ================================================ namespace Algorithms.Strings.Similarity { /// /// Provides methods to calculate the Optimal String Alignment distance between two strings. /// /// The Optimal String Alignment distance, also known as the restricted Damerau-Levenshtein distance, /// is a string metric used to measure the difference between two sequences. It is similar to the /// Levenshtein distance, but it also considers transpositions (swapping of two adjacent characters) /// as a single operation. This metric is particularly useful when adjacent characters are commonly /// transposed, such as in typographical errors. /// /// The OSA distance between two strings is defined as the minimum number of operations required to /// transform one string into the other, where the operations include: /// /// 1. Insertion: Adding a single character. /// 2. Deletion: Removing a single character. /// 3. Substitution: Replacing one character with another. /// 4. Transposition: Swapping two adjacent characters (this is what distinguishes OSA from the /// traditional Levenshtein distance). /// /// The OSA distance algorithm ensures that no operation is applied more than once to the same /// character in the same position. This is the main difference between the OSA and the more general /// Damerau-Levenshtein distance, which does not have this restriction. /// /// /// Example Usage: /// /// int distance = OptimalStringAlignmentDistance("example", "exmaple"); /// Console.WriteLine(distance); // Output: 1 /// /// In this example, the strings "example" and "exmaple" differ by one transposition of adjacent characters ('a' and 'm'), /// so the OSA distance is 1. /// /// /// int distance = OptimalStringAlignmentDistance("kitten", "sitting"); /// Console.WriteLine(distance); // Output: 3 /// /// Here, the strings "kitten" and "sitting" have three differences (substitutions 'k' to 's', 'e' to 'i', and insertion of 'g'), /// resulting in an OSA distance of 3. /// /// /// /// This algorithm has a time complexity of O(n * m), where n and m are the lengths of the two input strings. /// It is efficient for moderate-sized strings but may become computationally expensive for very long strings. /// public static class OptimalStringAlignment { /// /// Calculates the Optimal String Alignment distance between two strings. /// /// The first string. /// The second string. /// The Optimal String Alignment distance between the two strings. /// Thrown when either of the input strings is null. public static double Calculate(string firstString, string secondString) { ArgumentNullException.ThrowIfNull(nameof(firstString)); ArgumentNullException.ThrowIfNull(nameof(secondString)); if (firstString == secondString) { return 0.0; } if (firstString.Length == 0) { return secondString.Length; } if (secondString.Length == 0) { return firstString.Length; } var distanceMatrix = GenerateDistanceMatrix(firstString.Length, secondString.Length); distanceMatrix = CalculateDistance(firstString, secondString, distanceMatrix); return distanceMatrix[firstString.Length, secondString.Length]; } /// /// Generates the initial distance matrix for the given lengths of the two strings. /// /// The length of the first string. /// The length of the second string. /// The initialized distance matrix. private static int[,] GenerateDistanceMatrix(int firstLength, int secondLength) { var distanceMatrix = new int[firstLength + 2, secondLength + 2]; for (var i = 0; i <= firstLength; i++) { distanceMatrix[i, 0] = i; } for (var j = 0; j <= secondLength; j++) { distanceMatrix[0, j] = j; } return distanceMatrix; } /// /// Calculates the distance matrix for the given strings using the Optimal String Alignment algorithm. /// /// The first string. /// The second string. /// The initial distance matrix. /// The calculated distance matrix. private static int[,] CalculateDistance(string firstString, string secondString, int[,] distanceMatrix) { for (var i = 1; i <= firstString.Length; i++) { for (var j = 1; j <= secondString.Length; j++) { var cost = 1; if (firstString[i - 1] == secondString[j - 1]) { cost = 0; } distanceMatrix[i, j] = Minimum( distanceMatrix[i - 1, j - 1] + cost, // substitution distanceMatrix[i, j - 1] + 1, // insertion distanceMatrix[i - 1, j] + 1); // deletion if (i > 1 && j > 1 && firstString[i - 1] == secondString[j - 2] && firstString[i - 2] == secondString[j - 1]) { distanceMatrix[i, j] = Math.Min( distanceMatrix[i, j], distanceMatrix[i - 2, j - 2] + cost); // transposition } } } return distanceMatrix; } /// /// Returns the minimum of three integers. /// /// The first integer. /// The second integer. /// The third integer. /// The minimum of the three integers. private static int Minimum(int a, int b, int c) { return Math.Min(a, Math.Min(b, c)); } } } ================================================ FILE: Algorithms.Tests/Algorithms.Tests.csproj ================================================ net8.0 false ..\stylecop.ruleset true enable runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: Algorithms.Tests/AssemblyInfo.cs ================================================ using NUnit.Framework; [assembly: Parallelizable(ParallelScope.Children)] ================================================ FILE: Algorithms.Tests/Compressors/BurrowsWheelerTransformTests.cs ================================================ using Algorithms.DataCompression; namespace Algorithms.Tests.Compressors; public class BurrowsWheelerTransformTests { [TestCase("banana", "nnbaaa", 3)] [TestCase("SIX.MIXED.PIXIES.SIFT.SIXTY.PIXIE.DUST.BOXES", "TEXYDST.E.IXIXIXXSSMPPS.B..E.S.EUSFXDIIOIIIT", 29)] [TestCase("", "", 0)] public void Encode(string input, string expectedString, int expectedIndex) { var bwt = new BurrowsWheelerTransform(); var (encoded, index) = bwt.Encode(input); Assert.That(encoded, Is.EqualTo(expectedString)); Assert.That(index, Is.EqualTo(expectedIndex)); } [TestCase("nnbaaa", 3, "banana")] [TestCase("TEXYDST.E.IXIXIXXSSMPPS.B..E.S.EUSFXDIIOIIIT", 29, "SIX.MIXED.PIXIES.SIFT.SIXTY.PIXIE.DUST.BOXES")] [TestCase("", 0, "")] public void Decode(string encoded, int index, string expected) { var bwt = new BurrowsWheelerTransform(); var result = bwt.Decode(encoded, index); Assert.That(result, Is.EqualTo(expected)); } [Test] [Repeat(100)] public void RandomEncodeDecode() { var bwt = new BurrowsWheelerTransform(); var random = new Randomizer(); var inputString = random.GetString(); var (encoded, index) = bwt.Encode(inputString); var result = bwt.Decode(encoded, index); Assert.That(result, Is.EqualTo(inputString)); } } ================================================ FILE: Algorithms.Tests/Compressors/HuffmanCompressorTests.cs ================================================ using Algorithms.DataCompression; using Algorithms.Sorters.Comparison; namespace Algorithms.Tests.Compressors; public static class HuffmanCompressorTests { [TestCase("This is a string", "101010110111011101110111100011111010010010010011000")] [TestCase("Hello", "1101110010")] [TestCase("dddddddddd", "1111111111")] [TestCase("a", "1")] [TestCase("", "")] public static void CompressingPhrase(string uncompressedText, string expectedCompressedText) { //Arrange var sorter = new BubbleSorter(); var translator = new Translator(); var huffman = new HuffmanCompressor(sorter, translator); //Act var (compressedText, decompressionKeys) = huffman.Compress(uncompressedText); var decompressedText = translator.Translate(compressedText, decompressionKeys); //Assert Assert.That(compressedText, Is.EqualTo(expectedCompressedText)); Assert.That(decompressedText, Is.EqualTo(uncompressedText)); } [Test] public static void DecompressedTextTheSameAsOriginal( [Random(0, 1000, 100, Distinct = true)] int length) { //Arrange var sorter = new BubbleSorter(); var translator = new Translator(); var huffman = new HuffmanCompressor(sorter, translator); var text = Randomizer.CreateRandomizer().GetString(length); //Act var (compressedText, decompressionKeys) = huffman.Compress(text); var decompressedText = translator.Translate(compressedText, decompressionKeys); //Assert Assert.That(decompressedText, Is.EqualTo(text)); } [Test] public static void ListNodeComparer_NullIsUnordered() { var comparer = new HuffmanCompressor.ListNodeComparer(); var node = new HuffmanCompressor.ListNode('a', 0.1); comparer.Compare(node, null).Should().Be(0); comparer.Compare(null, node).Should().Be(0); comparer.Compare(null, null).Should().Be(0); } } ================================================ FILE: Algorithms.Tests/Compressors/ShannonFanoCompressorTests.cs ================================================ using Algorithms.DataCompression; using Algorithms.Knapsack; namespace Algorithms.Tests.Compressors; public static class ShannonFanoCompressorTests { [TestCase("dddddddddd", "1111111111")] [TestCase("a", "1")] [TestCase("", "")] public static void CompressingPhrase(string uncompressedText, string expectedCompressedText) { //Arrange var solver = new NaiveKnapsackSolver<(char, double)>(); var translator = new Translator(); var shannonFanoCompressor = new ShannonFanoCompressor(solver, translator); //Act var (compressedText, decompressionKeys) = shannonFanoCompressor.Compress(uncompressedText); var decompressedText = translator.Translate(compressedText, decompressionKeys); //Assert Assert.That(compressedText, Is.EqualTo(expectedCompressedText)); Assert.That(decompressedText, Is.EqualTo(uncompressedText)); } [Test] public static void DecompressedTextTheSameAsOriginal([Random(0, 1000, 100)] int length) { //Arrange var solver = new NaiveKnapsackSolver<(char, double)>(); var translator = new Translator(); var shannonFanoCompressor = new ShannonFanoCompressor(solver, translator); var text = Randomizer.CreateRandomizer().GetString(length); //Act var (compressedText, decompressionKeys) = shannonFanoCompressor.Compress(text); var decompressedText = translator.Translate(compressedText, decompressionKeys); //Assert Assert.That(decompressedText, Is.EqualTo(text)); } } ================================================ FILE: Algorithms.Tests/Compressors/TranslatorTests.cs ================================================ using Algorithms.DataCompression; namespace Algorithms.Tests.Compressors; public static class TranslatorTests { [Test] public static void TranslateCorrectly() { // Arrange var translator = new Translator(); var dict = new Dictionary { { "Hey", "Good day" }, { " ", " " }, { "man", "sir" }, { "!", "." }, }; // Act var translatedText = translator.Translate("Hey man!", dict); // Assert Assert.That(translatedText, Is.EqualTo("Good day sir.")); } } ================================================ FILE: Algorithms.Tests/Crypto/Digests/AsconDigestTests.cs ================================================ using Algorithms.Crypto.Digests; using Algorithms.Crypto.Exceptions; namespace Algorithms.Tests.Crypto.Digests; [NonParallelizable] public class AsconDigestTests { private readonly AsconDigest asconHash = new AsconDigest(AsconDigest.AsconParameters.AsconHash); private readonly AsconDigest asconHashA = new AsconDigest(AsconDigest.AsconParameters.AsconHashA); [TestCase("a", "02a9d471afab12914197af7090f00d16c41b6e30be0a63bbfd00bc13064de548")] [TestCase("abc", "d37fe9f1d10dbcfad8408a6804dbe91124a8912693322bb23ec1701e19e3fd51")] [TestCase("Hello", "d80f38d94ad72bd18718879f753a44870e8446925ff64bd7441db5fe020b6c0c")] [TestCase("message digest", "e8848979c5adfd21bfcf29e54be1dd085ee523d251e8e6876f2654d6368da0ca")] [TestCase("abcdefghijklmnopqrstuvwxyz", "c62368674e1b2301f19f46c50bb7f87a988a3e41205d68ab9d7882d2a15e917b")] [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4ff71928d740524735b5ab12bb1598463054f88089f3c5f9760b6bdcd23f897b")] [TestCase("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "2dae8b553b93841120e88ee77b9ccb8b512a32318db6012025f3f1c482b1def8")] public void AsconHash_ReturnsCorrectValue(string input, string expected) { var inputBytes = Encoding.ASCII.GetBytes(input); var result = asconHash.Digest(inputBytes); result.Should().Be(expected); } [TestCase("a", "062bb0346671da00da4f460308b4d2c4d9877c3e2827d6229ff5361332d36527")] [TestCase("abc", "836a5ddba0142b011ce3425ea9789fd6a21628d619195a48c1540f847667a84e")] [TestCase("Hello", "15f245df8af697dc540e86083822809ab7299575d8ad6c2e17ecc603a7ab79dd")] [TestCase("message digest", "3f18a1f398a40a77e0e9477aa6cb50e9e1abecff651c1874f9717c02c8a165ba")] [TestCase("abcdefghijklmnopqrstuvwxyz", "406b809260f361e12dcf0bf924bfe1ffd2f987fc18d90b94fc544ff80dc2946b")] [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "5c6c69ff3ee83361391b7236c8eb6718f52df43de5a61a4f4d2819d40430dc19")] [TestCase("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "d8e38fc50d682550cd176decda61adb7fd1c793cdafa825f17f3a002d65847be")] public void AsconHashA_ReturnsCorrectValue(string input, string expected) { var inputBytes = Encoding.ASCII.GetBytes(input); var result = asconHashA.Digest(inputBytes); result.Should().Be(expected); } [Test] public void BlockUpdate_WithValidOffsetAndLength_ShouldProcessCorrectly() { // Arrange var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 }; var offset = 2; var length = 6; // Picking 6 bytes starting from offset 2 // Act var act = () => asconHash.BlockUpdate(input, offset, length); // Assert act.Should().NotThrow(); // Ensure no exceptions are thrown during processing // Finalize the hash and check the output size var output = new byte[asconHash.GetDigestSize()]; asconHash.DoFinal(output, 0); output.Should().HaveCount(32); // Ascon hash size is 32 bytes } [Test] public void BlockUpdate_WithInvalidOffset_ShouldThrowDataLengthException() { // Arrange var input = new byte[] { 0x00, 0x11, 0x22, 0x33 }; var offset = 3; // Offset goes too close to the end var length = 3; // Length would exceed buffer size // Act var act = () => asconHash.BlockUpdate(input, offset, length); // Assert act.Should().Throw() .WithMessage("input buffer too short"); } [Test] public void BlockUpdate_WithInvalidLength_ShouldThrowDataLengthException() { // Arrange var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 }; var offset = 1; // Valid offset var length = 10; // Invalid length (exceeds buffer) // Act var act = () => asconHash.BlockUpdate(input, offset, length); // Assert act.Should().Throw() .WithMessage("input buffer too short"); } [Test] public void BlockUpdate_WithPartialBlock_ShouldProcessCorrectly() { // Arrange var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44 }; var offset = 0; var length = 5; // Less than 8 bytes, partial block // Act asconHash.BlockUpdate(input, offset, length); // Assert var output = new byte[asconHash.GetDigestSize()]; asconHash.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } [Test] public void BlockUpdate_WithFullBlock_ShouldProcessCorrectly() { // Arrange var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; var offset = 0; var length = 8; // Full block // Act asconHash.BlockUpdate(input, offset, length); // Assert var output = new byte[asconHash.GetDigestSize()]; asconHash.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } [Test] public void BlockUpdate_MultipleCalls_ShouldProcessCorrectly() { // Arrange var input1 = new byte[] { 0x00, 0x11, 0x22 }; var input2 = new byte[] { 0x33, 0x44, 0x55, 0x66, 0x77 }; // Act asconHash.BlockUpdate(input1, 0, input1.Length); asconHash.BlockUpdate(input2, 0, input2.Length); // Assert var output = new byte[asconHash.GetDigestSize()]; asconHash.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } [Test] public void AsconHash_WhenGetNameIsCalled_ReturnsCorrectValue() { asconHash.AlgorithmName.Should().Be("Ascon-Hash"); asconHashA.AlgorithmName.Should().Be("Ascon-HashA"); } [Test] public void AsconHash_WhenGetByteLengthIsCalled_ReturnsCorrectValue() { asconHash.GetByteLength().Should().Be(8); } [Test] public void Update_ShouldProcessByte_WhenBufferIsFull() { // Arrange byte[] inputBytes = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]; // 8 bytes to fill the buffer // Act foreach (var input in inputBytes) { asconHashA.Update(input); } // Assert // Since the buffer is full after 8 updates, we expect the state to have been processed. var output = new byte[asconHashA.GetDigestSize()]; asconHashA.DoFinal(output, 0); output.Should().HaveCount(32); // Ascon hash size is 32 bytes } [Test] public void Update_ShouldNotProcess_WhenBufferIsNotFull() { // Arrange byte[] inputBytes = [0x00, 0x11, 0x22, 0x33]; // Only 4 bytes (buffer is not full) // Act foreach (var input in inputBytes) { asconHashA.Update(input); } // Assert // Even though the buffer has received input, it should not process until it is full (8 bytes). // We can check that DoFinal still completes, but the buffer has not been processed yet. var output = new byte[asconHashA.GetDigestSize()]; asconHashA.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } [Test] public void Update_ShouldProcessMultipleBlocks() { // Arrange var inputBytes = new byte[16]; // Enough to fill two full blocks (16 bytes) // Act foreach (var input in inputBytes) { asconHashA.Update(input); } // Assert // Ensure that the state is processed twice since 16 bytes were passed (2 blocks of 8 bytes). var output = new byte[asconHashA.GetDigestSize()]; asconHashA.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } [Test] public void Update_ShouldHandleSingleByteCorrectly() { // Arrange byte input = 0xFF; // Single byte input // Act asconHashA.Update(input); // Assert // Even though one byte is provided, it should not process the state (waiting for 8 bytes). var output = new byte[asconHashA.GetDigestSize()]; asconHashA.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } [Test] public void Update_ShouldAccumulateStateWithMultipleUpdates() { // Arrange byte[] inputBytes = [0x00, 0x11, 0x22]; // Partial input // Act foreach (var input in inputBytes) { asconHashA.Update(input); } // Add more data to fill the buffer. byte[] additionalBytes = [0x33, 0x44, 0x55, 0x66, 0x77]; foreach (var input in additionalBytes) { asconHashA.Update(input); } // Assert // Ensure that the state is correctly updated after multiple partial updates. var output = new byte[asconHashA.GetDigestSize()]; asconHashA.DoFinal(output, 0); output.Should().HaveCount(32); // Ensure valid hash output } private static string ToHexString(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); } } ================================================ FILE: Algorithms.Tests/Crypto/Digests/Md2DigestTests.cs ================================================ using Algorithms.Crypto.Digests; namespace Algorithms.Tests.Crypto.Digesters; [NonParallelizable] public class Md2DigestTests { private readonly Md2Digest digest = new Md2Digest(); [TestCase("", "8350E5A3E24C153DF2275C9F80692773")] [TestCase("a", "32EC01EC4A6DAC72C0AB96FB34C0B5D1")] [TestCase("abc", "DA853B0D3F88D99B30283A69E6DED6BB")] [TestCase("message digest", "AB4F496BFB2A530B219FF33031FE06B0")] [TestCase("abcdefghijklmnopqrstuvwxyz", "4E8DDFF3650292AB5A4108C3AA47940B")] [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "DA33DEF2A42DF13975352846C30338CD")] [TestCase("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "D5976F79D83D3A0DC9806C3C66F3EFD8")] [TestCase("123456789012345678901234567890123456789012345678901234567890123456789012345678901", "6FAD0685C4A3D03E3D352D12BBAD6BE3")] public void Digest_ReturnsCorrectValue(string input, string expected) { var inputBytes = Encoding.ASCII.GetBytes(input); var result = digest.Digest(inputBytes); var output = Convert.ToHexString(result); output.Should().Be(expected); } } ================================================ FILE: Algorithms.Tests/Crypto/Exceptions/CryptoExceptionTests.cs ================================================ using Algorithms.Crypto.Exceptions; namespace Algorithms.Tests.Crypto.Exceptions { [TestFixture] public class CryptoExceptionTests { [Test] public void CryptoException_ShouldBeCreatedWithoutMessageOrInnerException() { // Act var exception = new CryptoException(); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().NotBeNullOrEmpty(); exception.InnerException.Should().BeNull(); } [Test] public void CryptoException_ShouldSetMessage() { // Arrange var expectedMessage = "This is a custom cryptographic error."; // Act var exception = new CryptoException(expectedMessage); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().Be(expectedMessage); exception.InnerException.Should().BeNull(); } [Test] public void CryptoException_ShouldSetMessageAndInnerException() { // Arrange var expectedMessage = "An error occurred during encryption."; var innerException = new InvalidOperationException("Invalid operation"); // Act var exception = new CryptoException(expectedMessage, innerException); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().Be(expectedMessage); exception.InnerException.Should().Be(innerException); } [Test] public void CryptoException_MessageShouldNotBeNullWhenUsingDefaultConstructor() { // Act var exception = new CryptoException(); // Assert exception.Message.Should().NotBeNullOrEmpty(); // Even the default Exception message is not null or empty. } } } ================================================ FILE: Algorithms.Tests/Crypto/Exceptions/DataLengthExceptionTests.cs ================================================ using Algorithms.Crypto.Exceptions; namespace Algorithms.Tests.Crypto.Exceptions { [TestFixture] public class DataLengthExceptionTests { [Test] public void DataLengthException_ShouldBeCreatedWithoutMessageOrInnerException() { // Act var exception = new DataLengthException(); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().NotBeNullOrEmpty(); exception.InnerException.Should().BeNull(); } [Test] public void DataLengthException_ShouldSetMessage() { // Arrange var expectedMessage = "Data length is invalid."; // Act var exception = new DataLengthException(expectedMessage); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().Be(expectedMessage); exception.InnerException.Should().BeNull(); } [Test] public void DataLengthException_ShouldSetMessageAndInnerException() { // Arrange var expectedMessage = "An error occurred due to incorrect data length."; var innerException = new ArgumentException("Invalid argument"); // Act var exception = new DataLengthException(expectedMessage, innerException); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().Be(expectedMessage); exception.InnerException.Should().Be(innerException); } [Test] public void DataLengthException_MessageShouldNotBeNullWhenUsingDefaultConstructor() { // Act var exception = new DataLengthException(); // Assert exception.Message.Should().NotBeNullOrEmpty(); // Even the default Exception message is not null or empty. } } } ================================================ FILE: Algorithms.Tests/Crypto/Exceptions/OutputLengthExceptionTests.cs ================================================ using Algorithms.Crypto.Exceptions; namespace Algorithms.Tests.Crypto.Exceptions { [TestFixture] public class OutputLengthExceptionTests { [Test] public void OutputLengthException_ShouldBeCreatedWithoutMessageOrInnerException() { // Act var exception = new OutputLengthException(); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().NotBeNullOrEmpty(); exception.InnerException.Should().BeNull(); } [Test] public void OutputLengthException_ShouldSetMessage() { // Arrange var expectedMessage = "Output buffer is too short."; // Act var exception = new OutputLengthException(expectedMessage); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().Be(expectedMessage); exception.InnerException.Should().BeNull(); } [Test] public void OutputLengthException_ShouldSetMessageAndInnerException() { // Arrange var expectedMessage = "Output length error."; var innerException = new ArgumentException("Invalid argument"); // Act var exception = new OutputLengthException(expectedMessage, innerException); // Assert exception.Should().BeOfType() .And.Subject.As() .Message.Should().Be(expectedMessage); exception.InnerException.Should().Be(innerException); } [Test] public void OutputLengthException_MessageShouldNotBeNullWhenUsingDefaultConstructor() { // Act var exception = new OutputLengthException(); // Assert exception.Message.Should().NotBeNullOrEmpty(); // Even the default Exception message is not null or empty. } } } ================================================ FILE: Algorithms.Tests/Crypto/Paddings/Iso10126D2PaddingTests.cs ================================================ using Algorithms.Crypto.Paddings; namespace Algorithms.Tests.Crypto.Paddings; public class Iso10126D2PaddingTests { private readonly Iso10126D2Padding padding = new Iso10126D2Padding(); [Test] public void AddPadding_WhenInputOffsetIsLessThanInputDataLength_ShouldNotThrowException() { var inputData = new byte[10]; const int inputOffset = 5; Action act = () => padding.AddPadding(inputData, inputOffset); act.Should().NotThrow(); } [Test] public void AddPadding_WhenInputOffsetIsEqualToInputDataLength_ShouldThrowException() { var inputData = new byte[10]; const int inputOffset = 10; Action act = () => padding.AddPadding(inputData, inputOffset); act.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenInputOffsetIsGreaterThanInputDataLength_ShouldThrowException() { var inputData = new byte[10]; const int inputOffset = 128; Action act = () => padding.AddPadding(inputData, inputOffset); act.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenInputArrayIsValid_ShouldReturnCorrectPaddingSize() { var inputData = new byte[10]; const int inputOffset = 5; var result = padding.AddPadding(inputData, inputOffset); result.Should().Be(5); } [Test] public void RemovePadding_WhenLengthIsLessThanOne_ShouldThrowATantrum() { var inputData = new byte[] { 0 }; Action act = () => padding.RemovePadding(inputData); act.Should().Throw() .WithMessage("Invalid padding length"); } [Test] public void RemovePadding_WhenPaddingLengthIsGreaterThanInputDataLength_ShouldThrowAnException() { var inputData = new byte[] { 2 }; Action act = () => padding.RemovePadding(inputData); act.Should().Throw() .WithMessage("Invalid padding length"); } [Test] public void RemovePadding_WhenInputDataIsValid_ShouldReturnCorrectData() { var inputData = new byte[] { 1, 2, 3, 1 }; var expected = new byte[] { 1, 2, 3 }; var result = padding.RemovePadding(inputData); result.Should().Equal(expected); } [Test] public void GetPaddingCount_WhenInputIsNull_ShouldThrowAnException() { byte[]? input = null; Action act = () => padding.GetPaddingCount(input!); act.Should().Throw() .WithMessage("Input cannot be null (Parameter 'input')"); } [Test] public void GetPaddingCount_WhenPaddingBlockIsCorrupted_ShouldThrowAnException() { var input = new byte[] { 1, 2, 3, 4, 5, 7 }; Action act = () => padding.GetPaddingCount(input); act.Should().Throw() .WithMessage("Padding block is corrupted"); } [Test] public void GetPaddingCount_WhenInputDataIsValid_ShouldReturnCorrectPaddingCount() { var input = new byte[] { 1, 2, 3, 4, 1 }; var result = padding.GetPaddingCount(input); result.Should().Be(1); } } ================================================ FILE: Algorithms.Tests/Crypto/Paddings/Iso7816D4PaddingTests.cs ================================================ using Algorithms.Crypto.Paddings; namespace Algorithms.Tests.Crypto.Paddings; public class Iso7816D4PaddingTests { private readonly Iso7816D4Padding padding = new Iso7816D4Padding(); [Test] public void AddPadding_WhenCalledWithValidInput_ShouldReturnCorrectPadding() { var inputData = new byte[10]; var inputOffset = 5; var result = padding.AddPadding(inputData, inputOffset); result.Should().Be(5); inputData[5].Should().Be(80); inputData.Skip(6).Should().AllBeEquivalentTo(0); } [Test] public void AddPadding_WhenCalledWithInvalidInput_ShouldThrowArgumentException() { var inputData = new byte[10]; var inputOffset = 11; Action act = () => padding.AddPadding(inputData, inputOffset); act.Should().Throw() .WithMessage("not enough space in input array for padding"); } [Test] public void AddPadding_WhenCalledWithZeroOffset_ShouldReturnCorrectPadding() { var inputData = new byte[10]; var inputOffset = 0; var result = padding.AddPadding(inputData, inputOffset); result.Should().Be(10); inputData[0].Should().Be(80); inputData.Skip(1).Should().AllBeEquivalentTo(0); } [Test] public void AddPadding_WhenCalledWithOffsetEqualToLength_ShouldThrowArgumentException() { var inputData = new byte[10]; var inputOffset = 10; Action act = () => padding.AddPadding(inputData, inputOffset); act.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenCalledWithEmptyArray_ShouldThrowArgumentException() { var inputData = new byte[0]; var inputOffset = 0; Action act = () => padding.AddPadding(inputData, inputOffset); act.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void RemovePadding_WhenCalledWithValidInput_shouldReturnCorrectData() { var inputData = new byte[] { 1, 2, 3, 4, 5, 0x80, 0, 0, 0 }; var result = padding.RemovePadding(inputData); result.Should().Equal([1, 2, 3, 4, 5]); } [Test] public void RemovePadding_WhenCalledWithInvalidInput_ShouldThrowArgumentException() { var inputData = new byte[] { 1, 2, 3, 4, 5 }; Action act = () => padding.RemovePadding(inputData); act.Should().Throw() .WithMessage("Invalid padding"); } [Test] public void RemovePadding_WhenCalledWithArrayContainingOnlyPadding_ShouldReturnEmptyArray() { var inputData = new byte[] { 0x80, 0, 0, 0, 0 }; var result = padding.RemovePadding(inputData); result.Should().BeEmpty(); } [Test] public void RemovePadding_WhenCalledWithArrayNotContainingStartOfPadding_ShouldThrowArgumentException() { var input = new byte[] { 1, 2, 3, 4, 5, 0, 0, 0, 0 }; Action act = () => padding.RemovePadding(input); act.Should().Throw() .WithMessage("Invalid padding"); } [Test] public void GetPaddingCount_WhenCalledWithValidInput_ShouldReturnCorrectCount() { var inputData = new byte[] { 1, 2, 3, 4, 5, 0x80, 0, 0, 0 }; var result = padding.GetPaddingCount(inputData); result.Should().Be(4); } [Test] public void GetPaddingCount_WhenCalledWithInvalidInput_ShouldThrowArgumentException() { var inputData = new byte[] { 1, 2, 3, 4, 5 }; Action act = () => padding.GetPaddingCount(inputData); act.Should().Throw() .WithMessage("Pad block corrupted"); } [Test] public void GetPaddingCount_WhenCalledWithEmptyArray_ShouldThrowArgumentException() { var inputData = Array.Empty(); Action act = () => padding.GetPaddingCount(inputData); act.Should().Throw() .WithMessage("Pad block corrupted"); } [Test] public void GetPaddingCount_WhenCalledWithArrayContainingOnlyPadding_ShouldReturnCorrectCount() { var inputData = new byte[] { 0x80, 0x00, 0x00 }; var result = padding.GetPaddingCount(inputData); result.Should().Be(3); } [Test] public void GetPaddingCount_WhenCalledWithArrayNotContainingStartOfPadding_ShouldThrowAnException() { var inputData = new byte[] { 1, 2, 3, 4, 5, 0, 0, 0, 0 }; Action act = () => padding.GetPaddingCount(inputData); act.Should().Throw() .WithMessage("Pad block corrupted"); } } ================================================ FILE: Algorithms.Tests/Crypto/Paddings/Pkcs7PaddingTests.cs ================================================ using Algorithms.Crypto.Paddings; namespace Algorithms.Tests.Crypto.Paddings; public class Pkcs7PaddingTests { private const int DefaultBlockSize = 16; [Test] public void Constructor_WhenBlockSizeIsLessThanOne_ShouldThrowArgumentOutOfRangeException() { const int blockSize = 0; Action act = () => new Pkcs7Padding(blockSize); act.Should().Throw() .WithMessage("Invalid block size: 0 (Parameter 'blockSize')"); } [Test] public void Constructor_WhenBlockSizeIsMoreThan255_ShouldThrowArgumentOutOfRangeException() { const int blockSize = 256; Action act = () => new Pkcs7Padding(blockSize); act.Should().Throw() .WithMessage("Invalid block size: 256 (Parameter 'blockSize')"); } [Test] public void Constructor_WhenBlockSizeIsWithinValidRange_ShouldNotThrowAFit() { const int blockSize = 128; Action act = () => new Pkcs7Padding(blockSize); act.Should().NotThrow(); } [Test] public void AddPadding_WhenNotEnoughSpaceInInputArrayForPadding_ShouldThrowArgumentException() { var padding = new Pkcs7Padding(DefaultBlockSize); const int inputOffset = 1; var size16Input = new byte[16]; Action act = () => padding.AddPadding(size16Input, inputOffset); act.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenInputArrayHasEnoughSpace_ShouldReturnCorrectPaddingSize() { var padding = new Pkcs7Padding(DefaultBlockSize); const int inputOffset = DefaultBlockSize; var size32Input = new byte[32]; var result = padding.AddPadding(size32Input, inputOffset); result.Should().Be(DefaultBlockSize); } [Test] public void AddPadding_WhenAppliedToAnInputArray_ShouldAddCorrectPKCS7Padding() { var padding = new Pkcs7Padding(DefaultBlockSize); const int inputOffset = DefaultBlockSize; var size32Input = new byte[32]; padding.AddPadding(size32Input, inputOffset); for (var i = 0; i < DefaultBlockSize - 1; i++) { size32Input[inputOffset + i].Should().Be(DefaultBlockSize); } } [Test] public void RemovePadding_WhenAppliedToAValidInputArray_ShouldRemovePKCS7PaddingCorrectly() { var paddingSize = 5; var size32Input = new byte[32]; for (var i = 0; i < paddingSize; i++) { size32Input[size32Input.Length - 1 - i] = (byte)paddingSize; } var padding = new Pkcs7Padding(DefaultBlockSize); var output = padding.RemovePadding(size32Input); output.Length.Should().Be(size32Input.Length - paddingSize); } [Test] public void RemovePadding_WhenInputLengthNotMultipleOfBlockSize_ShouldThrowArgumentException() { var input = new byte[DefaultBlockSize + 1]; // Length is not a multiple of blockSize var padding = new Pkcs7Padding(DefaultBlockSize); Action act = () => padding.RemovePadding(input); act.Should().Throw() .WithMessage("Input length must be a multiple of block size"); } [Test] public void RemovePadding_WhenInvalidPaddingLength_ShouldThrowArgumentException() { var size32Input = new byte[32]; size32Input[^1] = (byte)(DefaultBlockSize + 1); // Set invalid padding length var padding = new Pkcs7Padding(DefaultBlockSize); Action act = () => padding.RemovePadding(size32Input); act.Should().Throw().WithMessage("Invalid padding length"); } [Test] public void RemovePadding_WhenInvalidPadding_ShouldThrowArgumentException() { var size32Input = new byte[32]; size32Input[^1] = (byte)(DefaultBlockSize); // Set valid padding length size32Input[^2] = (byte)(DefaultBlockSize - 1); // Set invalid padding byte var padding = new Pkcs7Padding(DefaultBlockSize); Action act = () => padding.RemovePadding(size32Input); act.Should().Throw() .WithMessage("Invalid padding"); } [Test] public void GetPaddingCount_WhenArrayIsNull_ShouldThrowArgumentNullException() { var padding = new Pkcs7Padding(DefaultBlockSize); Action act = () => padding.GetPaddingCount(null!); act.Should().Throw(); } [Test] public void GetPaddingCount_WhenInputArrayIsValid_ShouldReturnCorrectPaddingCount() { const int paddingSize = 5; var size32Input = new byte[32]; for (var i = 0; i < paddingSize; i++) { size32Input[size32Input.Length - 1 - i] = (byte)paddingSize; // Add padding bytes at the end of the array } var padding = new Pkcs7Padding(DefaultBlockSize); var output = padding.GetPaddingCount(size32Input); output.Should().Be(paddingSize); } [Test] public void GetPaddingCount_WhenInvalidPadding_ShouldThrowArgumentException() { var size32Input = new byte[32]; size32Input[^1] = DefaultBlockSize; size32Input[^2] = DefaultBlockSize - 1; var padding = new Pkcs7Padding(DefaultBlockSize); Action act = () => padding.GetPaddingCount(size32Input); act.Should().Throw() .WithMessage("Padding block is corrupted"); } } ================================================ FILE: Algorithms.Tests/Crypto/Paddings/TbcPaddingTests.cs ================================================ using Algorithms.Crypto.Paddings; namespace Algorithms.Tests.Crypto.Paddings; public class TbcPaddingTests { private readonly TbcPadding padding = new TbcPadding(); [Test] public void AddPadding_WhenInputOffsetIsZero_ShouldPadWithLastBit() { var input = new byte[] { 0x01, 0x02, 0x03, 0x04 }; var inputOffset = 0; var result = padding.AddPadding(input, inputOffset); result.Should().Be(4); input.Should().BeEquivalentTo(new byte[] { 0xff, 0xff, 0xff, 0xff }); } [Test] public void AddPadding_WhenInputOffsetIsPositive_ShouldPadWithPreviousBit() { var input = new byte[] { 0x01, 0x02, 0x03, 0x04 }; var inputOffset = 2; var result = padding.AddPadding(input, inputOffset); result.Should().Be(2); input.Should().BeEquivalentTo(new byte[] { 0x01, 0x02, 0xff, 0xff }); } [Test] public void AddPadding_WhenInputOffsetIsGreaterThanLength_ShouldThrowArgumentException() { var input = new byte[] { 0x01, 0x02, 0x03, 0x04 }; var inputOffset = 5; Action act = () => padding.AddPadding(input, inputOffset); act.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenLastBitIsZero_ShouldPadWith0xFF() { var input = new byte[] { 0x02 }; const int inputOffset = 0; var result = padding.AddPadding(input, inputOffset); result.Should().Be(1); input.Should().BeEquivalentTo(new byte[] { 0xFF }); } [Test] public void AddPadding_WhenLastBitIsOne_ShouldPadWith0x00() { var input = new byte[] { 0x03 }; const int inputOffset = 0; var result = padding.AddPadding(input, inputOffset); result.Should().Be(1); input.Should().BeEquivalentTo(new byte[] { 0x00 }); } [Test] public void RemovePadding_WhenCalledWithPaddedData_ShouldReturnUnpaddedData() { var paddedData = new byte[] { 0x01, 0x02, 0x03, 0xff, 0xff }; var expectedData = new byte[] { 0x01, 0x02, 0x03 }; var result = padding.RemovePadding(paddedData); result.Should().BeEquivalentTo(expectedData); } [Test] public void RemovePadding_WhenCalledWithUnpaddedData_ShouldReturnsSameData() { var unpaddedData = new byte[] { 0x01, 0x02, 0x03 }; var result = padding.RemovePadding(unpaddedData); result.Should().BeEquivalentTo(unpaddedData); } [Test] public void RemovePadding_WhenCalledWithEmptyArray_ShouldReturnEmptyArray() { var emptyData = Array.Empty(); var result = padding.RemovePadding(emptyData); result.Should().BeEquivalentTo(emptyData); } [Test] public void RemovePadding_WhenCalledWithSingleBytePaddedData_ShouldReturnEmptyArray() { var singleBytePaddedData = new byte[] { 0xff }; var result = padding.RemovePadding(singleBytePaddedData); result.Should().BeEmpty(); } [Test] public void RemovePadding_WhenCalledWitAllBytesPadded_ShouldReturnEmptyArray() { var allBytesPaddedData = new byte[] { 0xff, 0xff, 0xff }; var emptyData = Array.Empty(); var result = padding.RemovePadding(allBytesPaddedData); result.Should().BeEquivalentTo(emptyData); } [Test] public void GetPaddingBytes_WhenCalledWithPaddedData_ShouldReturnCorrectPaddingCount() { var paddedData = new byte[] { 0x01, 0x02, 0x03, 0xff, 0xff }; const int expectedPaddingCount = 2; var result = padding.GetPaddingCount(paddedData); result.Should().Be(expectedPaddingCount); } [Test] public void GetPaddingBytes_WhenCalledWithUnpaddedData_ShouldReturnZero() { var unpaddedData = new byte[] { 0x01, 0x02, 0x03 }; Action action = () => padding.GetPaddingCount(unpaddedData); action.Should().Throw() .WithMessage("No padding found"); } [Test] public void GetPaddingBytes_WhenCalledWithEmptyArray_ShouldReturnZero() { var emptyData = Array.Empty(); Action action = () => padding.GetPaddingCount(emptyData); action.Should().Throw() .WithMessage("No padding found."); } } ================================================ FILE: Algorithms.Tests/Crypto/Paddings/X932PaddingTests.cs ================================================ using Algorithms.Crypto.Paddings; namespace Algorithms.Tests.Crypto.Paddings; public class X932PaddingTests { private readonly X932Padding zeroPadding = new X932Padding(false); private readonly X932Padding randomPadding = new X932Padding(true); [Test] public void AddPadding_WhenCalledWithZeroPadding_ShouldReturnCorrectCode() { var inputData = new byte[10]; const int inputOffset = 5; var result = zeroPadding.AddPadding(inputData, inputOffset); result.Should().Be(5); } [Test] public void AddPadding_WhenCalledWithZeroPaddingAndOffsetIsEqualToDataLength_ShouldThrowArgumentException() { var inputData = new byte[10]; const int inputOffset = 10; Action action = () => zeroPadding.AddPadding(inputData, inputOffset); action.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenCalledWithZeroPaddingAndOffsetIsGreaterThanDataLength_ShouldThrowArgumentException() { var inputData = new byte[10]; const int inputOffset = 11; Action action = () => zeroPadding.AddPadding(inputData, inputOffset); action.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenCalledWithRandomPadding_ShouldReturnCorrectCode() { var inputData = new byte[10]; const int inputOffset = 5; var result = randomPadding.AddPadding(inputData, inputOffset); result.Should().Be(5); } [Test] public void AddPadding_WhenCalledWithRandomPaddingAndOffsetIsEqualToDataLength_ShouldThrowArgumentException() { var inputData = new byte[10]; const int inputOffset = 10; Action action = () => randomPadding.AddPadding(inputData, inputOffset); action.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenCalledWithRandomPaddingAndOffsetIsGreaterThanDataLength_ShouldThrowArgumentException() { var inputData = new byte[10]; const int inputOffset = 11; Action action = () => randomPadding.AddPadding(inputData, inputOffset); action.Should().Throw() .WithMessage("Not enough space in input array for padding"); } [Test] public void AddPadding_WhenCalledWithZeroPaddingAndOffsetIsZero_ShouldReturnLengthOfInputData() { var inputData = new byte[10]; const int inputOffset = 0; var result = zeroPadding.AddPadding(inputData, inputOffset); result.Should().Be(10); } [Test] public void AddPadding_WhenCalledWithRandomPaddingAndOffsetIsZero_ShouldReturnLengthOfInputData() { var inputData = new byte[10]; const int inputOffset = 0; var result = randomPadding.AddPadding(inputData, inputOffset); result.Should().Be(10); } [Test] public void AddPadding_WhenCalledWithRandomPadding_ShouldFillThePaddingWithRandomValues() { var inputData = new byte[10]; const int inputOffset = 5; var result = randomPadding.AddPadding(inputData, inputOffset); for (var i = inputOffset; i < inputData.Length - 1; i++) { inputData[i].Should().BeInRange(0, 255); } inputData[^1].Should().Be((byte)result); } [Test] public void RemovePadding_WhenCalledInEmptyArray_ShouldReturnAnEmptyArray() { var result = zeroPadding.RemovePadding(Array.Empty()); result.Should().AllBeEquivalentTo(Array.Empty()); } [Test] public void RemovePadding_WhenCalledOnArrayWithValidPadding_ShouldRemovePadding() { var inputData = new byte[] { 1, 2, 3, 2 }; var expectedOutput = new byte[] { 1, 2 }; var result = zeroPadding.RemovePadding(inputData); result.Should().BeEquivalentTo(expectedOutput); } [Test] public void RemovePadding_WithInvalidPadding_ThrowsArgumentException() { var inputData = new byte[] { 1, 2, 3, 5 }; Action act = () => zeroPadding.RemovePadding(inputData); act.Should().Throw() .WithMessage("Invalid padding length"); } [Test] public void GetPaddingCount_WithValidPadding_ReturnsCorrectCount() { var inputData = new byte[] { 1, 2, 3, 2 }; var result = zeroPadding.GetPaddingCount(inputData); result.Should().Be(2); } [Test] public void GetPaddingCount_WithInvalidPadding_ThrowsArgumentException() { var inputData = new byte[] { 1, 2, 3, 5 }; Action action = () => zeroPadding.GetPaddingCount(inputData); action.Should().Throw() .WithMessage("Pad block corrupted"); } } ================================================ FILE: Algorithms.Tests/Crypto/Utils/ByteEncodingUtils.cs ================================================ using Algorithms.Crypto.Utils; namespace Algorithms.Tests.Crypto.Utils { [TestFixture] public class ByteEncodingUtilsTests { [Test] public void BigEndianToUint64_ByteArray_ShouldConvertCorrectly() { // Arrange byte[] input = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; var expected = 0x0123456789ABCDEFUL; // Act var result = ByteEncodingUtils.BigEndianToUint64(input, 0); // Assert result.Should().Be(expected); } [Test] public void BigEndianToUint64_ByteArray_WithOffset_ShouldConvertCorrectly() { // Arrange byte[] input = [0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; var expected = 0x0123456789ABCDEFUL; // Act var result = ByteEncodingUtils.BigEndianToUint64(input, 2); // Assert result.Should().Be(expected); } [Test] public void BigEndianToUint64_Span_ShouldConvertCorrectly() { // Arrange Span input = stackalloc byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; var expected = 0x0123456789ABCDEFUL; // Act var result = ByteEncodingUtils.BigEndianToUint64(input); // Assert result.Should().Be(expected); } [Test] public void UInt64ToBigEndian_ShouldWriteCorrectly() { // Arrange var value = 0x0123456789ABCDEFUL; Span output = stackalloc byte[8]; byte[] expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; // Act ByteEncodingUtils.UInt64ToBigEndian(value, output); // Assert output.ToArray().Should().Equal(expected); } [Test] public void BigEndianToUint64_InvalidOffset_ShouldThrowException() { // Arrange byte[] input = [0x01, 0x23]; // Act Action act = () => ByteEncodingUtils.BigEndianToUint64(input, 1); // Assert act.Should().Throw(); } } } ================================================ FILE: Algorithms.Tests/Crypto/Utils/LongUtilsTests.cs ================================================ using Algorithms.Crypto.Utils; namespace Algorithms.Tests.Crypto.Utils { [TestFixture] public class LongUtilsTests { [Test] public void RotateLeft_Long_ShouldRotateCorrectly() { // Arrange var input = 0x0123456789ABCDEF; var distance = 8; var expected = 0x23456789ABCDEF01L; // The expected result is a signed long value. // Act var result = LongUtils.RotateLeft(input, distance); // Assert result.Should().Be(expected); } [Test] public void RotateLeft_Ulong_ShouldRotateCorrectly() { // Arrange var input = 0x0123456789ABCDEFUL; var distance = 8; var expected = 0x23456789ABCDEF01UL; // The expected result is an unsigned ulong value. // Act var result = LongUtils.RotateLeft(input, distance); // Assert result.Should().Be(expected); } [Test] public void RotateRight_Long_ShouldRotateCorrectly() { // Arrange var input = 0x0123456789ABCDEF; var distance = 8; var expected = unchecked((long)0xEF0123456789ABCD); // Using unchecked to correctly represent signed long. // Act var result = LongUtils.RotateRight(input, distance); // Assert result.Should().Be(expected); } [Test] public void RotateRight_Ulong_ShouldRotateCorrectly() { // Arrange var input = 0x0123456789ABCDEFUL; var distance = 8; var expected = 0xEF0123456789ABCDUL; // The expected result is an unsigned ulong value. // Act var result = LongUtils.RotateRight(input, distance); // Assert result.Should().Be(expected); } [Test] public void RotateLeft_Long_ShouldHandleZeroRotation() { // Arrange var input = 0x0123456789ABCDEF; var distance = 0; // Act var result = LongUtils.RotateLeft(input, distance); // Assert result.Should().Be(input); // No rotation, result should be the same as input. } [Test] public void RotateRight_Ulong_ShouldHandleFullRotation() { // Arrange var input = 0x0123456789ABCDEFUL; var distance = 64; // Act var result = LongUtils.RotateRight(input, distance); // Assert result.Should().Be(input); // Full 64-bit rotation should result in the same value. } } } ================================================ FILE: Algorithms.Tests/Crypto/Utils/ValidationUtilsTests.cs ================================================ using Algorithms.Crypto.Utils; using Algorithms.Crypto.Exceptions; namespace Algorithms.Tests.Crypto.Utils { [TestFixture] public class ValidationUtilsTests { [Test] public void CheckDataLength_WithBufferOutOfBounds_ShouldThrowDataLengthException() { // Arrange var buffer = new byte[5]; // A byte array of length 5 var offset = 3; // Starting at index 3 var length = 4; // Expecting to read 4 bytes (which will exceed the buffer size) var errorMessage = "Buffer is too short"; // Act var act = () => ValidationUtils.CheckDataLength(buffer, offset, length, errorMessage); // Assert act.Should().Throw() .WithMessage(errorMessage); } [Test] public void CheckOutputLength_WithCondition_ShouldThrowOutputLengthException() { // Arrange var condition = true; var errorMessage = "Output length is invalid"; // Act var act = () => ValidationUtils.CheckOutputLength(condition, errorMessage); // Assert act.Should().Throw() .WithMessage(errorMessage); } [Test] public void CheckOutputLength_WithCondition_ShouldNotThrowOutputLengthException() { // Arrange var condition = false; var errorMessage = "Output length is invalid"; // Act var act = () => ValidationUtils.CheckOutputLength(condition, errorMessage); // Assert act.Should().NotThrow(); } [Test] public void CheckOutputLength_WithBufferOutOfBounds_ShouldThrowOutputLengthException() { // Arrange var buffer = new byte[5]; var offset = 3; var length = 4; var errorMessage = "Output buffer is too short"; // Act var act = () => ValidationUtils.CheckOutputLength(buffer, offset, length, errorMessage); // Assert act.Should().Throw() .WithMessage(errorMessage); } [Test] public void CheckOutputLength_WithBProperBufferSize_ShouldThrowOutputLengthException() { // Arrange var buffer = new byte[5]; var offset = 0; var length = 4; var errorMessage = "Output buffer is too short"; // Act var act = () => ValidationUtils.CheckOutputLength(buffer, offset, length, errorMessage); // Assert act.Should().NotThrow(); } [Test] public void CheckOutputLength_SpanExceedsLimit_ShouldThrowOutputLengthException() { // Arrange Span output = new byte[10]; var outputLength = output.Length; var maxLength = 5; var errorMessage = "Output exceeds maximum length"; // Act var act = () => ValidationUtils.CheckOutputLength(outputLength > maxLength, errorMessage); // Capture the length // Assert act.Should().Throw() .WithMessage(errorMessage); } [Test] public void CheckOutputLength_SpanDoesNotExceedLimit_ShouldThrowOutputLengthException() { // Arrange Span output = new byte[10]; var outputLength = output.Length; var maxLength = 15; var errorMessage = "Output exceeds maximum length"; // Act var act = () => ValidationUtils.CheckOutputLength(outputLength > maxLength, errorMessage); // Capture the length // Assert act.Should().NotThrow(); } } } ================================================ FILE: Algorithms.Tests/Encoders/AutokeyEncoderTests.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders { public static class AutokeyEncoderTests { [Test] public static void DecodedStringIsTheSame() { // Arrange var plainText = "PLAINTEXT"; var keyword = "KEYWORD"; var encoder = new AutokeyEncorder(); // Act var encoded = encoder.Encode(plainText, keyword); var decoded = encoder.Decode(encoded, keyword); // Assert Assert.That(decoded, Is.EqualTo(plainText)); } } } ================================================ FILE: Algorithms.Tests/Encoders/BlowfishEncoderTests.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; // Tests ported from the Java Algorithms repository public class BlowfishEncoderTests { private const string Key = "aabb09182736ccdd"; [Test] public void BlowfishEncoder_Encryption_ShouldWorkCorrectly() { // Arrange var encoder = new BlowfishEncoder(); encoder.GenerateKey(Key); const string plainText = "123456abcd132536"; const string cipherText = "d748ec383d3405f7"; // Act var result = encoder.Encrypt(plainText); // Assert result.Should().Be(cipherText); } [Test] public void BlowfishEncoder_Decryption_ShouldWorkCorrectly() { // Arrange var encoder = new BlowfishEncoder(); encoder.GenerateKey(Key); const string cipherText = "d748ec383d3405f7"; const string plainText = "123456abcd132536"; // Act var result = encoder.Decrypt(cipherText); // Assert result.Should().Be(plainText); } } ================================================ FILE: Algorithms.Tests/Encoders/CaesarEncoderTests.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; public static class CaesarEncoderTests { [Test] public static void DecodedStringIsTheSame([Random(100)] int key) { // Arrange var encoder = new CaesarEncoder(); var random = new Randomizer(); var message = random.GetString(); // Act var encoded = encoder.Encode(message, key); var decoded = encoder.Decode(encoded, key); // Assert Assert.That(decoded, Is.EqualTo(message)); } } ================================================ FILE: Algorithms.Tests/Encoders/FeistelCipherTest.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; public static class FeistelCipherTests { [Test] public static void DecodedStringIsTheSame([Random(100)] uint key) { // Arrange var encoder = new FeistelCipher(); var random = new Randomizer(); int lenOfString = random.Next(1000); string message = random.GetString(lenOfString); // Act var encoded = encoder.Encode(message, key); var decoded = encoder.Decode(encoded, key); // Assert Assert.That(decoded, Is.EqualTo(message)); } [TestCase("00001111", (uint)0x12345678)] [TestCase("00001111222233334444555566667", (uint)0x12345678)] [TestCase("000011112222333344445555666677", (uint)0x12345678)] [TestCase("0000111122223333444455556666777", (uint)0x12345678)] // The plain text will be padded to fill the size of block (16 bytes), so the encoded message should be aligned with the rule // (text.Length % 16 == 0) public static void TestEncodedMessageSize(string testCase, uint key) { // Arrange var encoder = new FeistelCipher(); // Assert Assert.Throws(() => encoder.Decode(testCase, key)); } } ================================================ FILE: Algorithms.Tests/Encoders/HillEnconderTests.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; public static class HillEnconderTests { [Test] [Repeat(100)] public static void DecodedStringIsTheSame() { // Arrange var encoder = new HillEncoder(); var random = new Randomizer(); var message = random.GetString(); var key = new double[,] { { 0, 4, 5 }, { 9, 2, -1 }, { 3, 17, 7 } }; // Act var encodedText = encoder.Encode(message, key); var decodeText = encoder.Decode(encodedText, key); // Assert Assert.That(decodeText, Is.EqualTo(message)); } } ================================================ FILE: Algorithms.Tests/Encoders/NysiisEncoderTests.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; public class NysiisEncoderTests { private static readonly string[] Names = [ "Jay", "John", "Jane", "Zayne", "Guerra", "Iga", "Cowan", "Louisa", "Arnie", "Olsen", "Corban", "Nava", "Cynthia Malone", "Amiee MacKee", "MacGyver", "Yasmin Edge", ]; private static readonly string[] Expected = [ "JY", "JAN", "JAN", "ZAYN", "GAR", "IG", "CAN", "LAS", "ARNY", "OLSAN", "CARBAN", "NAV", "CYNTANALAN", "ANANACY", "MCGYVAR", "YASNANADG", ]; private static IEnumerable TestData => Names.Zip(Expected, (l, r) => new[] { l, r }); [TestCaseSource(nameof(TestData))] public void AttemptNysiis(string source, string expected) { var enc = new NysiisEncoder(); var nysiis = enc.Encode(source); Assert.That(nysiis, Is.EqualTo(expected)); } } ================================================ FILE: Algorithms.Tests/Encoders/SoundexEncoderTest.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; public static class SoundexEncoderTest { private static readonly string[] Names = [ "Robert", "Rupert", "Rubin", "Ashcraft", "Ashcroft", "Tymczak", "Pfister", "Honeyman", ]; private static readonly string[] Expected = ["R163", "R163", "R150", "A261", "A261", "T522", "P236", "H555"]; private static IEnumerable TestData => Names.Zip(Expected, (l, r) => new[] { l, r }); [TestCaseSource(nameof(TestData))] public static void AttemptSoundex(string source, string encoded) { SoundexEncoder enc = new(); var nysiis = enc.Encode(source); Assert.That(encoded, Is.EqualTo(nysiis)); } } ================================================ FILE: Algorithms.Tests/Encoders/VigenereEncoderTests.cs ================================================ using Algorithms.Encoders; namespace Algorithms.Tests.Encoders; public static class VigenereEncoderTests { [Test] [Repeat(100)] public static void DecodedStringIsTheSame() { // Arrange var random = new Randomizer(); var encoder = new VigenereEncoder(); var message = random.GetString(); var key = random.GetString(random.Next(1, 1000)); // Act var encoded = encoder.Encode(message, key); var decoded = encoder.Decode(encoded, key); // Assert Assert.That(decoded, Is.EqualTo(message)); } [Test] public static void Encode_KeyIsTooShort_KeyIsAppended() { // Arrange var encoder = new VigenereEncoder(); var message = new string('a', 2); var key = new string('a', 1); // Act var encoded = encoder.Encode(message, key); var decoded = encoder.Decode(encoded, key); // Assert Assert.That(decoded, Is.EqualTo(message)); } [Test] public static void EmptyKeyThrowsException() { var random = new Randomizer(); var encoder = new VigenereEncoder(); var message = random.GetString(); var key = string.Empty; _ = Assert.Throws(() => encoder.Encode(message, key)); _ = Assert.Throws(() => encoder.Decode(message, key)); } } ================================================ FILE: Algorithms.Tests/Financial/PresentValueTests.cs ================================================ using Algorithms.Financial; namespace Algorithms.Tests.Financial; public static class PresentValueTests { [TestCase(0.13, new[] { 10.0, 20.70, -293.0, 297.0 }, 4.69)] [TestCase(0.07, new[] { -109129.39, 30923.23, 15098.93, 29734.0, 39.0 }, -42739.63)] [TestCase(0.07, new[] { 109129.39, 30923.23, 15098.93, 29734.0, 39.0 }, 175519.15)] [TestCase(0.0, new[] { 109129.39, 30923.23, 15098.93, 29734.0, 39.0 }, 184924.55)] public static void Present_Value_General_Tests(double discountRate, double[] cashFlow, double expected) => PresentValue.Calculate(discountRate, cashFlow.ToList()) .Should() .Be(expected); [TestCase(-1.0, new[] { 10.0, 20.70, -293.0, 297.0 })] [TestCase(1.0, new double[] { })] public static void Present_Value_Exception_Tests(double discountRate, double[] cashFlow) => Assert.Throws(() => PresentValue.Calculate(discountRate, cashFlow.ToList())); } ================================================ FILE: Algorithms.Tests/GlobalUsings.cs ================================================ // ----------------------------------------------------------------------------- // Global using directives for the C-Sharp solution. // These namespaces are imported globally so they don’t need to be repeatedly declared // in individual files, improving readability and reducing boilerplate. // // Guidelines: // - Keep only the most commonly used namespaces here. // - Add project-specific namespaces (e.g., Utilities.Extensions) only if they are // required across the majority of files in the project. // - Avoid placing rarely used namespaces here to maintain clarity. // ----------------------------------------------------------------------------- global using System; // Core base classes and fundamental types global using System.Collections.Generic; // Generic collection types (List, Dictionary, etc.) global using System.Linq; // LINQ query operators for collections global using System.Numerics; // Numeric types such as BigInteger and Complex global using System.Text; // Text encoding, StringBuilder, etc. global using Utilities.Extensions; // Common extension methods used across the solution global using NUnit.Framework; // Testing framework providing attributes and assertions for test cases global using NUnit.Framework.Internal; // Internal NUnit infrastructure (test context, utilities) — generally used for advanced or framework-level test control global using FluentAssertions; // Assertion library for more readable and expressive unit tests ================================================ FILE: Algorithms.Tests/Graph/ArticulationPointsTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Algorithms.Graph; using FluentAssertions; using NUnit.Framework; namespace Algorithms.Tests.Graph; public class ArticulationPointsTests { [Test] public void Find_SimpleChain_ReturnsMiddleVertex() { // Arrange: A - B - C (B is articulation point) var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().ContainSingle(); result.Should().Contain("B"); } [Test] public void Find_Triangle_ReturnsEmpty() { // Arrange: A - B - C - A (no articulation points) var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_StarGraph_ReturnsCenterVertex() { // Arrange: Star with center A var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C", "D" }, "B" => new[] { "A" }, "C" => new[] { "A" }, "D" => new[] { "A" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().ContainSingle(); result.Should().Contain("A"); } [Test] public void Find_BridgeGraph_ReturnsMultiplePoints() { // Arrange: (A-B-C) - D - (E-F-G) var vertices = new[] { "A", "B", "C", "D", "E", "F", "G" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C", "D" }, "C" => new[] { "B" }, "D" => new[] { "B", "E" }, "E" => new[] { "D", "F" }, "F" => new[] { "E", "G" }, "G" => new[] { "F" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(4); result.Should().Contain(new[] { "B", "D", "E", "F" }); } [Test] public void Find_DisconnectedGraph_FindsPointsInEachComponent() { // Arrange: (A-B-C) and (D-E-F) var vertices = new[] { "A", "B", "C", "D", "E", "F" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, "D" => new[] { "E" }, "E" => new[] { "D", "F" }, "F" => new[] { "E" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(2); result.Should().Contain(new[] { "B", "E" }); } [Test] public void Find_SingleVertex_ReturnsEmpty() { // Arrange var vertices = new[] { "A" }; IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_TwoVertices_ReturnsEmpty() { // Arrange: A - B var vertices = new[] { "A", "B" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_ComplexGraph_ReturnsCorrectPoints() { // Arrange: Complex graph with multiple articulation points var vertices = new[] { 1, 2, 3, 4, 5, 6, 7 }; IEnumerable GetNeighbors(int v) => v switch { 1 => new[] { 2, 3 }, 2 => new[] { 1, 3 }, 3 => new[] { 1, 2, 4 }, 4 => new[] { 3, 5, 6 }, 5 => new[] { 4, 6 }, 6 => new[] { 4, 5, 7 }, 7 => new[] { 6 }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().Contain(new[] { 3, 4, 6 }); } [Test] public void Find_EmptyGraph_ReturnsEmpty() { // Arrange var vertices = Array.Empty(); IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_NullVertices_ThrowsArgumentNullException() { // Act Action act = () => ArticulationPoints.Find(null!, v => Array.Empty()); // Assert act.Should().Throw().WithParameterName("vertices"); } [Test] public void Find_NullGetNeighbors_ThrowsArgumentNullException() { // Arrange var vertices = new[] { "A" }; // Act Action act = () => ArticulationPoints.Find(vertices, null!); // Assert act.Should().Throw().WithParameterName("getNeighbors"); } [Test] public void IsArticulationPoint_ValidPoint_ReturnsTrue() { // Arrange: A - B - C var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.IsArticulationPoint("B", vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsArticulationPoint_NotArticulationPoint_ReturnsFalse() { // Arrange: A - B - C var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.IsArticulationPoint("A", vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void Count_SimpleChain_ReturnsOne() { // Arrange: A - B - C var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Count(vertices, GetNeighbors); // Assert result.Should().Be(1); } [Test] public void Count_Triangle_ReturnsZero() { // Arrange: A - B - C - A var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = ArticulationPoints.Count(vertices, GetNeighbors); // Assert result.Should().Be(0); } [Test] public void Find_LargeGraph_FindsAllPoints() { // Arrange: Large chain var vertices = Enumerable.Range(1, 10).ToArray(); IEnumerable GetNeighbors(int v) { var neighbors = new List(); if (v > 1) { neighbors.Add(v - 1); } if (v < 10) { neighbors.Add(v + 1); } return neighbors; } // Act var result = ArticulationPoints.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(8); // All except endpoints result.Should().NotContain(new[] { 1, 10 }); } } ================================================ FILE: Algorithms.Tests/Graph/BellmanFordTests.cs ================================================ using Algorithms.Graph; using DataStructures.Graph; namespace Algorithms.Tests.Graph; public class BellmanFordTests { [Test] public void CorrectDistancesTest() { var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); var vertex4 = graph.AddVertex(4); var vertex5 = graph.AddVertex(5); graph.AddEdge(vertex1, vertex2, 3); graph.AddEdge(vertex1, vertex5, -4); graph.AddEdge(vertex1, vertex3, 8); graph.AddEdge(vertex2, vertex5, 7); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex3, vertex2, 4); graph.AddEdge(vertex4, vertex3, -5); graph.AddEdge(vertex4, vertex1, 2); graph.AddEdge(vertex5, vertex4, 6); var expectedDistances = new Dictionary, double> { { vertex1, 0 }, { vertex2, 1 }, { vertex3, -3 }, { vertex4, 2 }, { vertex5, -4 } }; var bellmanFord = new BellmanFord(graph, [], []); var calculatedDistances = bellmanFord.Run(vertex1); foreach (var vertex in graph.Vertices) { if (vertex != null) { calculatedDistances[vertex].Should().BeApproximately(expectedDistances[vertex], 0.001); } } } [Test] public void NegativeWeightCycleTest() { var graph = new DirectedWeightedGraph(3); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); graph.AddEdge(vertex1, vertex2, -1); graph.AddEdge(vertex2, vertex3, -2); graph.AddEdge(vertex3, vertex1, -3); var bellmanFord = new BellmanFord(graph, [], []); Action action = () => bellmanFord.Run(vertex1); action.Should().Throw().WithMessage("Graph contains a negative weight cycle."); } } ================================================ FILE: Algorithms.Tests/Graph/BipartiteGraphTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Algorithms.Graph; using FluentAssertions; using NUnit.Framework; namespace Algorithms.Tests.Graph; public class BipartiteGraphTests { [Test] public void IsBipartite_EmptyGraph_ReturnsTrue() { // Arrange var vertices = Array.Empty(); IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_SingleVertex_ReturnsTrue() { // Arrange var vertices = new[] { "A" }; IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_TwoVerticesConnected_ReturnsTrue() { // Arrange: A - B var vertices = new[] { "A", "B" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_Triangle_ReturnsFalse() { // Arrange: A - B - C - A (odd cycle) var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void IsBipartite_Square_ReturnsTrue() { // Arrange: A - B - C - D - A (even cycle) var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "D" }, "B" => new[] { "A", "C" }, "C" => new[] { "B", "D" }, "D" => new[] { "A", "C" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_CompleteBipartiteK23_ReturnsTrue() { // Arrange: Complete bipartite K(2,3) var vertices = new[] { "A", "B", "X", "Y", "Z" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "X", "Y", "Z" }, "B" => new[] { "X", "Y", "Z" }, "X" => new[] { "A", "B" }, "Y" => new[] { "A", "B" }, "Z" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_DisconnectedBipartiteComponents_ReturnsTrue() { // Arrange: (A-B) and (C-D) var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A" }, "C" => new[] { "D" }, "D" => new[] { "C" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_DisconnectedWithOddCycle_ReturnsFalse() { // Arrange: (A-B) and (C-D-E-C triangle) var vertices = new[] { "A", "B", "C", "D", "E" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A" }, "C" => new[] { "D", "E" }, "D" => new[] { "C", "E" }, "E" => new[] { "C", "D" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void IsBipartite_StarGraph_ReturnsTrue() { // Arrange: Star with center A var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C", "D" }, "B" => new[] { "A" }, "C" => new[] { "A" }, "D" => new[] { "A" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_Pentagon_ReturnsFalse() { // Arrange: Pentagon (5-cycle) var vertices = new[] { 1, 2, 3, 4, 5 }; IEnumerable GetNeighbors(int v) => v switch { 1 => new[] { 2, 5 }, 2 => new[] { 1, 3 }, 3 => new[] { 2, 4 }, 4 => new[] { 3, 5 }, 5 => new[] { 1, 4 }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void IsBipartite_NullVertices_ThrowsArgumentNullException() { // Act Action act = () => BipartiteGraph.IsBipartite(null!, v => Array.Empty()); // Assert act.Should().Throw().WithParameterName("vertices"); } [Test] public void IsBipartite_NullGetNeighbors_ThrowsArgumentNullException() { // Arrange var vertices = new[] { "A" }; // Act Action act = () => BipartiteGraph.IsBipartite(vertices, null!); // Assert act.Should().Throw().WithParameterName("getNeighbors"); } [Test] public void GetPartitions_BipartiteGraph_ReturnsCorrectSets() { // Arrange: A - B - C - D (chain) var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B", "D" }, "D" => new[] { "C" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); // Assert result.Should().NotBeNull(); result!.Value.SetA.Should().Contain(new[] { "A", "C" }); result.Value.SetB.Should().Contain(new[] { "B", "D" }); } [Test] public void GetPartitions_NonBipartiteGraph_ReturnsNull() { // Arrange: Triangle var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); // Assert result.Should().BeNull(); } [Test] public void GetPartitions_EmptyGraph_ReturnsEmptySets() { // Arrange var vertices = Array.Empty(); IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); // Assert result.Should().NotBeNull(); result!.Value.SetA.Should().BeEmpty(); result.Value.SetB.Should().BeEmpty(); } [Test] public void GetPartitions_NullVertices_ThrowsArgumentNullException() { // Act Action act = () => BipartiteGraph.GetPartitions(null!, v => Array.Empty()); // Assert act.Should().Throw().WithParameterName("vertices"); } [Test] public void GetPartitions_NullGetNeighbors_ThrowsArgumentNullException() { // Arrange var vertices = new[] { "A" }; // Act Action act = () => BipartiteGraph.GetPartitions(vertices, null!); // Assert act.Should().Throw().WithParameterName("getNeighbors"); } [Test] public void IsBipartiteDfs_BipartiteGraph_ReturnsTrue() { // Arrange: Square var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "D" }, "B" => new[] { "A", "C" }, "C" => new[] { "B", "D" }, "D" => new[] { "A", "C" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartiteDfs(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartiteDfs_NonBipartiteGraph_ReturnsFalse() { // Arrange: Triangle var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.IsBipartiteDfs(vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void IsBipartiteDfs_NullVertices_ThrowsArgumentNullException() { // Act Action act = () => BipartiteGraph.IsBipartiteDfs(null!, v => Array.Empty()); // Assert act.Should().Throw().WithParameterName("vertices"); } [Test] public void IsBipartiteDfs_NullGetNeighbors_ThrowsArgumentNullException() { // Arrange var vertices = new[] { "A" }; // Act Action act = () => BipartiteGraph.IsBipartiteDfs(vertices, null!); // Assert act.Should().Throw().WithParameterName("getNeighbors"); } [Test] public void IsBipartite_LargeEvenCycle_ReturnsTrue() { // Arrange: Large even cycle (100 vertices) var vertices = Enumerable.Range(0, 100).ToArray(); IEnumerable GetNeighbors(int v) { var neighbors = new List { v > 0 ? v - 1 : 99, // Previous or close cycle v < 99 ? v + 1 : 0, // Next or close cycle }; return neighbors; } // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBipartite_LargeOddCycle_ReturnsFalse() { // Arrange: Large odd cycle (101 vertices) var vertices = Enumerable.Range(0, 101).ToArray(); IEnumerable GetNeighbors(int v) { var neighbors = new List { v > 0 ? v - 1 : 100, // Previous or close cycle v < 100 ? v + 1 : 0, // Next or close cycle }; return neighbors; } // Act var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void GetPartitions_CompleteBipartite_ReturnsCorrectSets() { // Arrange: K(2,2) var vertices = new[] { "A", "B", "X", "Y" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "X", "Y" }, "B" => new[] { "X", "Y" }, "X" => new[] { "A", "B" }, "Y" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); // Assert result.Should().NotBeNull(); result!.Value.SetA.Should().HaveCount(2); result.Value.SetB.Should().HaveCount(2); } } ================================================ FILE: Algorithms.Tests/Graph/BreadthFirstSearchTests.cs ================================================ using Algorithms.Graph; using DataStructures.Graph; namespace Algorithms.Tests.Graph; public class BreadthFirstSearchTests { [Test] public void VisitAll_ShouldCountNumberOfVisitedVertix_ResultShouldBeTheSameAsNumberOfVerticesInGraph() { //Arrange var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(20); var vertex3 = graph.AddVertex(40); var vertex4 = graph.AddVertex(40); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex4, vertex1, 1); var dfsSearcher = new BreadthFirstSearch(); long countOfVisitedVertices = 0; //Act dfsSearcher.VisitAll(graph, vertex1, _ => countOfVisitedVertices++); //Assert Assert.That(graph.Count, Is.EqualTo(countOfVisitedVertices)); } [Test] public void VisitAll_ShouldCountNumberOfVisitedVerices_TwoSeparatedGraphInOne() { //Arrange var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(20); var vertex3 = graph.AddVertex(40); var vertex4 = graph.AddVertex(40); var vertex5 = graph.AddVertex(40); var vertex6 = graph.AddVertex(40); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex4, vertex5, 1); graph.AddEdge(vertex5, vertex6, 1); var dfsSearcher = new BreadthFirstSearch(); long countOfVisitedVerticesPerFirstGraph = 0; long countOfVisitedVerticesPerSecondGraph = 0; //Act dfsSearcher.VisitAll(graph, vertex1, _ => countOfVisitedVerticesPerFirstGraph++); dfsSearcher.VisitAll(graph, vertex4, _ => countOfVisitedVerticesPerSecondGraph++); //Assert Assert.That(countOfVisitedVerticesPerFirstGraph, Is.EqualTo(3)); Assert.That(countOfVisitedVerticesPerSecondGraph, Is.EqualTo(3)); } [Test] public void VisitAll_ReturnTheSuqenceOfVertices_ShouldBeTheSameAsExpected() { //Arrange var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(20); var vertex3 = graph.AddVertex(40); var vertex4 = graph.AddVertex(40); var vertex5 = graph.AddVertex(40); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex1, vertex5, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex2, vertex5, 1); graph.AddEdge(vertex2, vertex4, 1); var dfsSearcher = new BreadthFirstSearch(); var expectedSequenceOfVisitedVertices = new List> { vertex1, vertex2, vertex5, vertex3, vertex4, }; var sequenceOfVisitedVertices = new List>(); //Act dfsSearcher.VisitAll(graph, vertex1, vertex => sequenceOfVisitedVertices.Add(vertex)); //Assert Assert.That(sequenceOfVisitedVertices, Is.EqualTo(expectedSequenceOfVisitedVertices)); } } ================================================ FILE: Algorithms.Tests/Graph/BreadthFirstTreeTraversalTests.cs ================================================ using Algorithms.Graph; using DataStructures.BinarySearchTree; namespace Algorithms.Tests.Graph; public static class BreadthFirstTreeTraversalTests { [Test] public static void CorrectLevelOrderTraversal() { // Arrange int[] correctPath = [7, 4, 13, 2, 5, 11, 15, 14, 16]; int[] insertionOrder = [7, 13, 11, 15, 14, 4, 5, 16, 2]; BinarySearchTree testTree = new BinarySearchTree(); foreach (int data in insertionOrder) { testTree.Add(data); } // Act int[] levelOrder = BreadthFirstTreeTraversal.LevelOrderTraversal(testTree); // Assert Assert.That(correctPath, Is.EqualTo(levelOrder)); } [Test] public static void EmptyArrayForNullRoot() { // Arrange BinarySearchTree testTree = new BinarySearchTree(); // Act int[] levelOrder = BreadthFirstTreeTraversal.LevelOrderTraversal(testTree); // Assert Assert.That(levelOrder, Is.Empty); } [TestCase(new[] { 7, 9, 5 })] [TestCase(new[] { 7, 13, 11, 15, 14, 4, 5, 16, 2 })] public static void IncorrectLevelOrderTraversal(int[] insertion) { // Arrange BinarySearchTree testTree = new BinarySearchTree(); foreach (int data in insertion) { testTree.Add(data); } // Act int[] levelOrder = BreadthFirstTreeTraversal.LevelOrderTraversal(testTree); // Assert Assert.That(insertion, Is.Not.EqualTo(levelOrder)); } [Test] public static void DeepestNodeInTree() { // Arrange BinarySearchTree testTree = new BinarySearchTree(); int[] insertion = [7, 13, 11, 15, 4, 5, 12, 2, 9]; foreach (int data in insertion) { testTree.Add(data); } // Act int deepest = BreadthFirstTreeTraversal.DeepestNode(testTree); // Assert Assert.That(deepest, Is.EqualTo(12)); } [Test] public static void DeepestNodeOfEmptyTree() { // Arrange BinarySearchTree testTree = new BinarySearchTree(); // Act int? deepest = BreadthFirstTreeTraversal.DeepestNode(testTree); // Assert Assert.That(deepest, Is.Null); } } ================================================ FILE: Algorithms.Tests/Graph/BridgesTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Algorithms.Graph; using FluentAssertions; using NUnit.Framework; namespace Algorithms.Tests.Graph; public class BridgesTests { [Test] public void Find_SimpleChain_ReturnsAllEdges() { // Arrange: A - B - C (both edges are bridges) var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(2); result.Should().Contain(new[] { ("A", "B"), ("B", "C") }); } [Test] public void Find_Triangle_ReturnsEmpty() { // Arrange: A - B - C - A (no bridges in cycle) var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_TwoComponentsConnectedByBridge_ReturnsBridge() { // Arrange: (A-B-C) - D - (E-F-G) var vertices = new[] { "A", "B", "C", "D", "E", "F", "G" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C", "D" }, "C" => new[] { "A", "B" }, "D" => new[] { "B", "E" }, "E" => new[] { "D", "F", "G" }, "F" => new[] { "E", "G" }, "G" => new[] { "E", "F" }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(2); result.Should().Contain(new[] { ("B", "D"), ("D", "E") }); } [Test] public void Find_StarGraph_ReturnsAllEdges() { // Arrange: Star with center A var vertices = new[] { "A", "B", "C", "D" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C", "D" }, "B" => new[] { "A" }, "C" => new[] { "A" }, "D" => new[] { "A" }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(3); result.Should().Contain(new[] { ("A", "B"), ("A", "C"), ("A", "D") }); } [Test] public void Find_DisconnectedGraph_FindsBridgesInEachComponent() { // Arrange: (A-B-C) and (D-E-F) var vertices = new[] { "A", "B", "C", "D", "E", "F" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, "D" => new[] { "E" }, "E" => new[] { "D", "F" }, "F" => new[] { "E" }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(4); result.Should().Contain(new[] { ("A", "B"), ("B", "C"), ("D", "E"), ("E", "F") }); } [Test] public void Find_SingleVertex_ReturnsEmpty() { // Arrange var vertices = new[] { "A" }; IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_TwoVertices_ReturnsBridge() { // Arrange: A - B var vertices = new[] { "A", "B" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A" }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().ContainSingle(); result.Should().Contain(("A", "B")); } [Test] public void Find_ComplexGraph_ReturnsCorrectBridges() { // Arrange: Complex graph with cycles and bridges var vertices = new[] { 1, 2, 3, 4, 5, 6, 7 }; IEnumerable GetNeighbors(int v) => v switch { 1 => new[] { 2, 3 }, 2 => new[] { 1, 3 }, 3 => new[] { 1, 2, 4 }, 4 => new[] { 3, 5, 6 }, 5 => new[] { 4, 6 }, 6 => new[] { 4, 5, 7 }, 7 => new[] { 6 }, _ => Array.Empty(), }; // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().Contain(new[] { (3, 4), (6, 7) }); } [Test] public void Find_EmptyGraph_ReturnsEmpty() { // Arrange var vertices = Array.Empty(); IEnumerable GetNeighbors(string v) => Array.Empty(); // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().BeEmpty(); } [Test] public void Find_NullVertices_ThrowsArgumentNullException() { // Act Action act = () => Bridges.Find(null!, v => Array.Empty()); // Assert act.Should().Throw().WithParameterName("vertices"); } [Test] public void Find_NullGetNeighbors_ThrowsArgumentNullException() { // Arrange var vertices = new[] { "A" }; // Act Action act = () => Bridges.Find(vertices, null!); // Assert act.Should().Throw().WithParameterName("getNeighbors"); } [Test] public void IsBridge_ValidBridge_ReturnsTrue() { // Arrange: A - B - C var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = Bridges.IsBridge("A", "B", vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBridge_ReverseEdge_ReturnsTrue() { // Arrange: A - B - C var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = Bridges.IsBridge("B", "A", vertices, GetNeighbors); // Assert result.Should().BeTrue(); } [Test] public void IsBridge_NotBridge_ReturnsFalse() { // Arrange: A - B - C - A (triangle) var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = Bridges.IsBridge("A", "B", vertices, GetNeighbors); // Assert result.Should().BeFalse(); } [Test] public void Count_SimpleChain_ReturnsTwo() { // Arrange: A - B - C var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B" }, "B" => new[] { "A", "C" }, "C" => new[] { "B" }, _ => Array.Empty(), }; // Act var result = Bridges.Count(vertices, GetNeighbors); // Assert result.Should().Be(2); } [Test] public void Count_Triangle_ReturnsZero() { // Arrange: A - B - C - A var vertices = new[] { "A", "B", "C" }; IEnumerable GetNeighbors(string v) => v switch { "A" => new[] { "B", "C" }, "B" => new[] { "A", "C" }, "C" => new[] { "A", "B" }, _ => Array.Empty(), }; // Act var result = Bridges.Count(vertices, GetNeighbors); // Assert result.Should().Be(0); } [Test] public void Find_LargeChain_FindsAllBridges() { // Arrange: Large chain var vertices = Enumerable.Range(1, 10).ToArray(); IEnumerable GetNeighbors(int v) { var neighbors = new List(); if (v > 1) { neighbors.Add(v - 1); } if (v < 10) { neighbors.Add(v + 1); } return neighbors; } // Act var result = Bridges.Find(vertices, GetNeighbors); // Assert result.Should().HaveCount(9); // All edges in chain are bridges } } ================================================ FILE: Algorithms.Tests/Graph/DepthFirstSearchTests.cs ================================================ using Algorithms.Graph; using DataStructures.Graph; namespace Algorithms.Tests.Graph; public class DepthFirstSearchTests { [Test] public void VisitAll_ShouldCountNumberOfVisitedVertix_ResultShouldBeTheSameAsNumberOfVerticesInGraph() { //Arrange var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(20); var vertex3 = graph.AddVertex(40); var vertex4 = graph.AddVertex(40); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex4, vertex1, 1); var dfsSearcher = new DepthFirstSearch(); long countOfVisitedVertices = 0; //Act dfsSearcher.VisitAll(graph, vertex1, _ => countOfVisitedVertices++); //Assert Assert.That(graph.Count, Is.EqualTo(countOfVisitedVertices)); } [Test] public void VisitAll_ShouldCountNumberOfVisitedVertices_TwoSeparatedGraphInOne() { //Arrange var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(20); var vertex3 = graph.AddVertex(40); var vertex4 = graph.AddVertex(40); var vertex5 = graph.AddVertex(40); var vertex6 = graph.AddVertex(40); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex4, vertex5, 1); graph.AddEdge(vertex5, vertex6, 1); var dfsSearcher = new DepthFirstSearch(); long countOfVisitedVerticesPerFirstGraph = 0; long countOfVisitedVerticesPerSecondGraph = 0; //Act dfsSearcher.VisitAll(graph, vertex1, _ => countOfVisitedVerticesPerFirstGraph++); dfsSearcher.VisitAll(graph, vertex4, _ => countOfVisitedVerticesPerSecondGraph++); //Assert Assert.That(3, Is.EqualTo(countOfVisitedVerticesPerFirstGraph)); Assert.That(3, Is.EqualTo(countOfVisitedVerticesPerSecondGraph)); } [Test] public void VisitAll_ReturnTheSuqenceOfVertices_ShouldBeTheSameAsExpected() { //Arrange var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(20); var vertex3 = graph.AddVertex(40); var vertex4 = graph.AddVertex(40); var vertex5 = graph.AddVertex(40); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex3, vertex5, 1); var dfsSearcher = new DepthFirstSearch(); var expectedSequenceOfVisitedVertices = new List> { vertex1, vertex2, vertex3, vertex5, vertex4, }; var sequenceOfVisitedVertices = new List>(); //Act dfsSearcher.VisitAll(graph, vertex1, vertex => sequenceOfVisitedVertices.Add(vertex)); //Assert Assert.That(sequenceOfVisitedVertices, Is.EqualTo(expectedSequenceOfVisitedVertices)); } } ================================================ FILE: Algorithms.Tests/Graph/Dijkstra/DijkstraTests.cs ================================================ using Algorithms.Graph.Dijkstra; using DataStructures.Graph; namespace Algorithms.Tests.Graph.Dijkstra; [TestFixture] public class DijkstraTests { [Test] public void DijkstraTest1_Success() { // here test case is from https://www.youtube.com/watch?v=pVfj6mxhdMw var graph = new DirectedWeightedGraph(5); var a = graph.AddVertex('A'); var b = graph.AddVertex('B'); var c = graph.AddVertex('C'); var d = graph.AddVertex('D'); var e = graph.AddVertex('E'); graph.AddEdge(a, b, 6); graph.AddEdge(b, a, 6); graph.AddEdge(a, d, 1); graph.AddEdge(d, a, 1); graph.AddEdge(d, e, 1); graph.AddEdge(e, d, 1); graph.AddEdge(d, b, 2); graph.AddEdge(b, d, 2); graph.AddEdge(e, b, 2); graph.AddEdge(b, e, 2); graph.AddEdge(e, c, 5); graph.AddEdge(c, e, 5); graph.AddEdge(c, b, 5); graph.AddEdge(b, c, 5); var shortestPathList = DijkstraAlgorithm.GenerateShortestPath(graph, a); shortestPathList.Length.Should().Be(5); shortestPathList[0].Vertex.Should().Be(a); shortestPathList[0].Distance.Should().Be(0); shortestPathList[0].PreviousVertex.Should().Be(a); shortestPathList[0].ToString().Should() .Be($"Vertex: {a} - Distance: {0} - Previous: {a}"); shortestPathList[1].Vertex.Should().Be(b); shortestPathList[1].Distance.Should().Be(3); shortestPathList[1].PreviousVertex.Should().Be(d); shortestPathList[1].ToString().Should() .Be($"Vertex: {b} - Distance: {3} - Previous: {d}"); shortestPathList[2].Vertex.Should().Be(c); shortestPathList[2].Distance.Should().Be(7); shortestPathList[2].PreviousVertex.Should().Be(e); shortestPathList[2].ToString().Should() .Be($"Vertex: {c} - Distance: {7} - Previous: {e}"); shortestPathList[3].Vertex.Should().Be(d); shortestPathList[3].Distance.Should().Be(1); shortestPathList[3].PreviousVertex.Should().Be(a); shortestPathList[3].ToString().Should() .Be($"Vertex: {d} - Distance: {1} - Previous: {a}"); shortestPathList[4].Vertex.Should().Be(e); shortestPathList[4].Distance.Should().Be(2); shortestPathList[4].PreviousVertex.Should().Be(d); shortestPathList[4].ToString().Should() .Be($"Vertex: {e} - Distance: {2} - Previous: {d}"); } [Test] public void DijkstraTest2_Success() { var graph = new DirectedWeightedGraph(5); var a = graph.AddVertex('A'); var b = graph.AddVertex('B'); var c = graph.AddVertex('C'); graph.AddEdge(a, b, 1); graph.AddEdge(b, a, 1); graph.AddEdge(b, c, 1); graph.AddEdge(c, b, 1); graph.AddEdge(a, c, 3); graph.AddEdge(c, a, 3); var shortestPathList = DijkstraAlgorithm.GenerateShortestPath(graph, a); shortestPathList.Length.Should().Be(3); shortestPathList[0].Vertex.Should().Be(a); shortestPathList[0].Distance.Should().Be(0); shortestPathList[0].PreviousVertex.Should().Be(a); shortestPathList[0].ToString().Should() .Be($"Vertex: {a} - Distance: {0} - Previous: {a}"); shortestPathList[1].Vertex.Should().Be(b); shortestPathList[1].Distance.Should().Be(1); shortestPathList[1].PreviousVertex.Should().Be(a); shortestPathList[1].ToString().Should() .Be($"Vertex: {b} - Distance: {1} - Previous: {a}"); shortestPathList[2].Vertex.Should().Be(c); shortestPathList[2].Distance.Should().Be(2); shortestPathList[2].PreviousVertex.Should().Be(b); shortestPathList[2].ToString().Should() .Be($"Vertex: {c} - Distance: {2} - Previous: {b}"); } [Test] public void DijkstraTest3_Success() { var graph = new DirectedWeightedGraph(5); var a = graph.AddVertex('A'); var b = graph.AddVertex('B'); var c = graph.AddVertex('C'); graph.AddEdge(a, b, 1); graph.AddEdge(b, a, 1); graph.AddEdge(a, c, 3); graph.AddEdge(c, a, 3); var shortestPathList = DijkstraAlgorithm.GenerateShortestPath(graph, a); shortestPathList.Length.Should().Be(3); shortestPathList[0].Vertex.Should().Be(a); shortestPathList[0].Distance.Should().Be(0); shortestPathList[0].PreviousVertex.Should().Be(a); shortestPathList[0].ToString().Should() .Be($"Vertex: {a} - Distance: {0} - Previous: {a}"); shortestPathList[1].Vertex.Should().Be(b); shortestPathList[1].Distance.Should().Be(1); shortestPathList[1].PreviousVertex.Should().Be(a); shortestPathList[1].ToString().Should() .Be($"Vertex: {b} - Distance: {1} - Previous: {a}"); shortestPathList[2].Vertex.Should().Be(c); shortestPathList[2].Distance.Should().Be(3); shortestPathList[2].PreviousVertex.Should().Be(a); shortestPathList[2].ToString().Should() .Be($"Vertex: {c} - Distance: {3} - Previous: {a}"); } [Test] public void DijkstraTest4_Success() { var graph = new DirectedWeightedGraph(5); var a = graph.AddVertex('A'); var b = graph.AddVertex('B'); var c = graph.AddVertex('C'); var d = graph.AddVertex('D'); graph.AddEdge(a, b, 1); graph.AddEdge(b, a, 1); graph.AddEdge(a, c, 3); graph.AddEdge(c, a, 3); graph.AddEdge(c, d, 5); graph.AddEdge(d, c, 5); var shortestPathList = DijkstraAlgorithm.GenerateShortestPath(graph, a); shortestPathList.Length.Should().Be(4); shortestPathList[0].Vertex.Should().Be(a); shortestPathList[0].Distance.Should().Be(0); shortestPathList[0].PreviousVertex.Should().Be(a); shortestPathList[0].ToString().Should() .Be($"Vertex: {a} - Distance: {0} - Previous: {a}"); shortestPathList[1].Vertex.Should().Be(b); shortestPathList[1].Distance.Should().Be(1); shortestPathList[1].PreviousVertex.Should().Be(a); shortestPathList[1].ToString().Should() .Be($"Vertex: {b} - Distance: {1} - Previous: {a}"); shortestPathList[2].Vertex.Should().Be(c); shortestPathList[2].Distance.Should().Be(3); shortestPathList[2].PreviousVertex.Should().Be(a); shortestPathList[2].ToString().Should() .Be($"Vertex: {c} - Distance: {3} - Previous: {a}"); shortestPathList[3].Vertex.Should().Be(d); shortestPathList[3].Distance.Should().Be(8); shortestPathList[3].PreviousVertex.Should().Be(c); shortestPathList[3].ToString().Should() .Be($"Vertex: {d} - Distance: {8} - Previous: {c}"); } [Test] public void DijkstraTest5_Success() { // here test case is from https://www.youtube.com/watch?v=pVfj6mxhdMw var graph = new DirectedWeightedGraph(7); var a = graph.AddVertex('A'); var b = graph.AddVertex('B'); var c = graph.AddVertex('C'); var d = graph.AddVertex('D'); var e = graph.AddVertex('E'); var w = graph.AddVertex('W'); var z = graph.AddVertex('Z'); graph.AddEdge(a, b, 6); graph.AddEdge(b, a, 6); graph.AddEdge(a, d, 1); graph.AddEdge(d, a, 1); graph.AddEdge(d, e, 1); graph.AddEdge(e, d, 1); graph.AddEdge(d, b, 2); graph.AddEdge(b, d, 2); graph.AddEdge(e, b, 2); graph.AddEdge(b, e, 2); graph.AddEdge(e, c, 5); graph.AddEdge(c, e, 5); graph.AddEdge(c, b, 5); graph.AddEdge(b, c, 5); graph.AddEdge(a, w, 50); graph.AddEdge(w, a, 50); graph.AddEdge(w, z, 1); graph.AddEdge(z, w, 1); var shortestPathList = DijkstraAlgorithm.GenerateShortestPath(graph, a); shortestPathList.Length.Should().Be(7); shortestPathList[0].Vertex.Should().Be(a); shortestPathList[0].Distance.Should().Be(0); shortestPathList[0].PreviousVertex.Should().Be(a); shortestPathList[0].ToString().Should() .Be($"Vertex: {a} - Distance: {0} - Previous: {a}"); shortestPathList[1].Vertex.Should().Be(b); shortestPathList[1].Distance.Should().Be(3); shortestPathList[1].PreviousVertex.Should().Be(d); shortestPathList[1].ToString().Should() .Be($"Vertex: {b} - Distance: {3} - Previous: {d}"); shortestPathList[2].Vertex.Should().Be(c); shortestPathList[2].Distance.Should().Be(7); shortestPathList[2].PreviousVertex.Should().Be(e); shortestPathList[2].ToString().Should() .Be($"Vertex: {c} - Distance: {7} - Previous: {e}"); shortestPathList[3].Vertex.Should().Be(d); shortestPathList[3].Distance.Should().Be(1); shortestPathList[3].PreviousVertex.Should().Be(a); shortestPathList[3].ToString().Should() .Be($"Vertex: {d} - Distance: {1} - Previous: {a}"); shortestPathList[4].Vertex.Should().Be(e); shortestPathList[4].Distance.Should().Be(2); shortestPathList[4].PreviousVertex.Should().Be(d); shortestPathList[4].ToString().Should() .Be($"Vertex: {e} - Distance: {2} - Previous: {d}"); shortestPathList[5].Vertex.Should().Be(w); shortestPathList[5].Distance.Should().Be(50); shortestPathList[5].PreviousVertex.Should().Be(a); shortestPathList[5].ToString().Should() .Be($"Vertex: {w} - Distance: {50} - Previous: {a}"); shortestPathList[6].Vertex.Should().Be(z); shortestPathList[6].Distance.Should().Be(51); shortestPathList[6].PreviousVertex.Should().Be(w); shortestPathList[6].ToString().Should() .Be($"Vertex: {z} - Distance: {51} - Previous: {w}"); } [Test] public void DijkstraTest6_Success() { var graph = new DirectedWeightedGraph(5); var a = graph.AddVertex('A'); var b = graph.AddVertex('B'); var c = graph.AddVertex('C'); var d = graph.AddVertex('D'); graph.AddEdge(a, b, 1); graph.AddEdge(b, a, 1); graph.AddEdge(c, d, 5); graph.AddEdge(d, c, 5); var shortestPathList = DijkstraAlgorithm.GenerateShortestPath(graph, a); shortestPathList.Length.Should().Be(4); shortestPathList[0].Vertex.Should().Be(a); shortestPathList[0].Distance.Should().Be(0); shortestPathList[0].PreviousVertex.Should().Be(a); shortestPathList[0].ToString().Should() .Be($"Vertex: {a} - Distance: {0} - Previous: {a}"); shortestPathList[1].Vertex.Should().Be(b); shortestPathList[1].Distance.Should().Be(1); shortestPathList[1].PreviousVertex.Should().Be(a); shortestPathList[1].ToString().Should() .Be($"Vertex: {b} - Distance: {1} - Previous: {a}"); shortestPathList[2].Vertex.Should().Be(c); shortestPathList[2].Distance.Should().Be(double.MaxValue); shortestPathList[2].PreviousVertex.Should().BeNull(); shortestPathList[2].ToString().Should() .Be($"Vertex: {c} - Distance: {double.MaxValue} - Previous: {null}"); shortestPathList[3].Vertex.Should().Be(d); shortestPathList[3].Distance.Should().Be(double.MaxValue); shortestPathList[3].PreviousVertex.Should().BeNull(); shortestPathList[3].ToString().Should() .Be($"Vertex: {d} - Distance: {double.MaxValue} - Previous: {null}"); } [Test] public void DijkstraMethodTest_ShouldThrow_GraphIsNull() { var graph = new DirectedWeightedGraph(5); var a = graph.AddVertex('A'); Func[]> action = () => DijkstraAlgorithm.GenerateShortestPath(null!, a); action.Should().Throw() .WithMessage($"Value cannot be null. (Parameter '{nameof(graph)}')"); } [Test] public void DijkstraMethodTest_ShouldThrow_VertexDoesntBelongToGraph() { var graph = new DirectedWeightedGraph(5); var startVertex = graph.AddVertex('A'); Func[]> action = () => DijkstraAlgorithm.GenerateShortestPath( new DirectedWeightedGraph(5), startVertex); action.Should().Throw() .WithMessage($"Value cannot be null. (Parameter '{nameof(graph)}')"); } } ================================================ FILE: Algorithms.Tests/Graph/FloydWarshallTests.cs ================================================ using Algorithms.Graph; using DataStructures.Graph; namespace Algorithms.Tests.Graph; public class FloydWarshallTests { [Test] public void CorrectMatrixTest() { var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); var vertex4 = graph.AddVertex(4); var vertex5 = graph.AddVertex(5); graph.AddEdge(vertex1, vertex2, 3); graph.AddEdge(vertex1, vertex5, -4); graph.AddEdge(vertex1, vertex3, 8); graph.AddEdge(vertex2, vertex5, 7); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex3, vertex2, 4); graph.AddEdge(vertex4, vertex3, -5); graph.AddEdge(vertex4, vertex1, 2); graph.AddEdge(vertex5, vertex4, 6); var actualDistances = new double[,] { { 0, 1, -3, 2, -4 }, { 3, 0, -4, 1, -1 }, { 7, 4, 0, 5, 3 }, { 2, -1, -5, 0, -2 }, { 8, 5, 1, 6, 0 }, }; var floydWarshaller = new FloydWarshall(); floydWarshaller.Run(graph).Should().BeEquivalentTo(actualDistances); } } ================================================ FILE: Algorithms.Tests/Graph/KosarajuTests.cs ================================================ using Algorithms.Graph; using DataStructures.Graph; namespace Algorithms.Tests.Graph; public class KosarajuTests { [Test] public void GetRepresentativesTest() { // Create a graph with some SCC. var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); var vertex4 = graph.AddVertex(4); var vertex5 = graph.AddVertex(5); var vertex6 = graph.AddVertex(6); var vertex7 = graph.AddVertex(7); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex3, vertex1, 1); graph.AddEdge(vertex3, vertex2, 1); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex4, vertex5, 1); graph.AddEdge(vertex5, vertex4, 1); graph.AddEdge(vertex5, vertex6, 1); // Run the algorithm and obtain the representative vertex of the SCC to which each vertex belongs. Dictionary, Vertex> result = Kosaraju.GetRepresentatives(graph); // Check every Vertex belongs to a SCC result.Should().ContainKey(vertex1); result.Should().ContainKey(vertex2); result.Should().ContainKey(vertex3); result.Should().ContainKey(vertex4); result.Should().ContainKey(vertex5); result.Should().ContainKey(vertex6); result.Should().ContainKey(vertex7); // There should be 4 SCC: {1,2,3}, {4,5}, {6} and {7} // Vertices 1, 2 and 3 are a SCC result[vertex1].Should().Be(result[vertex2]).And.Be(result[vertex3]); // Vertices 4 and 5 are another SCC result[vertex4].Should().Be(result[vertex5]); // And the should have a different representative vertex result[vertex1].Should().NotBe(result[vertex4]); // Vertices 6 and 7 are their own SCC result[vertex6].Should().Be(vertex6); result[vertex7].Should().Be(vertex7); } [Test] public void GetSccTest() { // Create a graph with some SCC. var graph = new DirectedWeightedGraph(10); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); var vertex4 = graph.AddVertex(4); var vertex5 = graph.AddVertex(5); var vertex6 = graph.AddVertex(6); var vertex7 = graph.AddVertex(7); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); graph.AddEdge(vertex3, vertex1, 1); graph.AddEdge(vertex3, vertex2, 1); graph.AddEdge(vertex2, vertex4, 1); graph.AddEdge(vertex4, vertex5, 1); graph.AddEdge(vertex5, vertex4, 1); graph.AddEdge(vertex5, vertex6, 1); // Run the algorithm and get SCC as lists of vertices. var scc = Kosaraju.GetScc(graph); // There should be 4 SCC: {1,2,3}, {4,5}, {6} and {7} scc.Should().HaveCount(4); // Vertices 1, 2 and 3 are a SCC scc.First(c => c.Contains(vertex1)).Should().Contain(vertex2).And.Contain(vertex3); // Vertices 4 and 5 are another SCC scc.First(c => c.Contains(vertex4)).Should().Contain(vertex5); // Vertices 6 and 7 are their own SCC scc.First(c => c.Contains(vertex6)).Should().HaveCount(1); scc.First(c => c.Contains(vertex7)).Should().HaveCount(1); } } ================================================ FILE: Algorithms.Tests/Graph/MinimumSpanningTree/KruskalTests.cs ================================================ using Algorithms.Graph.MinimumSpanningTree; namespace Algorithms.Tests.Graph.MinimumSpanningTree; internal class KruskalTests { [Test] public void ValidateGraph_adjWrongSize_ThrowsException() { // Wrong number of columns var adj = new[,] { { 0, 3, 4, float.PositiveInfinity }, { 3, 0, 5, 6 }, { 4, 5, 0, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0 }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity }, }; Assert.Throws(() => Kruskal.Solve(adj), "adj must be square!"); // Wrong number of rows adj = new[,] { { 0, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, }; Assert.Throws(() => Kruskal.Solve(adj), "adj must be square!"); } [Test] public void ValidateGraph_adjDirectedGraph_ThrowsException() { // Nodes 1 and 2 have a directed edge var adj = new[,] { { 0, float.PositiveInfinity, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, 0 }, }; Assert.Throws(() => Kruskal.Solve(adj), "adj must be symmetric!"); } [Test] public void Solve_adjGraph1_CorrectAnswer() { /* Graph * (1) * / \ * 3 2 * / \ * (0)--2--(2) */ var adj = new float[,] { { 0, 3, 2 }, { 3, 0, 2 }, { 2, 2, 0 }, }; /* Expected MST * (1) * \ * 2 * \ * (0)--2--(2) */ var expected = new[,] { { float.PositiveInfinity, float.PositiveInfinity, 2 }, { float.PositiveInfinity, float.PositiveInfinity, 2 }, { 2, 2, float.PositiveInfinity }, }; Kruskal.Solve(adj).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } [Test] public void Solve_adjGraph2_CorrectAnswer() { /* Graph * (0) (4) * |\ / * | 3 2 * | \ / * 4 (1) * | / \ * | 5 6 * |/ \ * (2) (3) */ var adj = new[,] { { 0, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, 0 }, }; /* Expected MST * (0) (4) * |\ / * | 3 2 * | \ / * 4 (1) * | \ * | 6 * | \ * (2) (3) */ var expected = new[,] { { float.PositiveInfinity, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, float.PositiveInfinity, float.PositiveInfinity, 6, 2 }, { 4, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, }; Kruskal.Solve(adj).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } [Test] public void Solve_adjGraph3_CorrectAnswer() { /* Graph * (0)--3--(2) (4)--2--(5) * \ / \ / * 4 1 4 6 * \ / \ / * (1)--2--(3) */ var adj = new[,] { { 0, 4, 3, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 4, 0, 1, 2, float.PositiveInfinity, float.PositiveInfinity }, { 3, 1, 0, 4, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 2, 4, 0, 6, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, 0, 2 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 2, 0 }, }; /* Graph * (0)--3--(2) (4)--2--(5) * / / * 1 6 * / / * (1)--2--(3) */ var expected = new[,] { { float.PositiveInfinity, float.PositiveInfinity, 3, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 1, 2, float.PositiveInfinity, float.PositiveInfinity }, { 3, 1, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity, 2 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 2, float.PositiveInfinity }, }; Kruskal.Solve(adj).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } [Test] public void Solve_adjGraph4_CorrectAnswer() { /* Graph * (0)--7--(1)--8--(2) * \ / \ / * 5 9 7 5 * \ / \ / * (3)--15-(4) * \ / \ * 6 8 9 * \ / \ * (5)--11-(6) */ var adj = new[,] { { 0, 7, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 7, 0, 8, 9, 7, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 8, 0, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity }, { 5, 9, float.PositiveInfinity, 0, 15, 6, float.PositiveInfinity }, { float.PositiveInfinity, 7, 5, 15, 0, 8, 9 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, 8, 0, 11 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9, 11, 0 }, }; /* Expected MST * (0)--7--(1) (2) * \ \ / * 5 7 5 * \ \ / * (3) (4) * \ \ * 6 9 * \ \ * (5) (6) */ var expected = new[,] { { float.PositiveInfinity, 7, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 7, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 7, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity }, { 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity }, { float.PositiveInfinity, 7, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity }, }; Kruskal.Solve(adj).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } [Test] public void Solve_adjGraph5_CorrectAnswer() { /* Graph * (0)--8--(1)--15-(2) * |\ / __/|\ * | 4 5 __25 13 12 * | \ /__/ | \ * 10 (3)----14---(4) (5) * | / \ _/| / * | 9 6 __16 18 30 * |/ \ / |/ * (6)--18-(7)--20-(8) */ var adj = new[,] { { 0, 8, float.PositiveInfinity, 4, float.PositiveInfinity, float.PositiveInfinity, 10, float.PositiveInfinity, float.PositiveInfinity }, { 8, 0, 15, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 15, 0, 25, 13, 12, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 4, 5, 25, 0, 14, float.PositiveInfinity, 9, 6, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 13, 14, 0, float.PositiveInfinity, float.PositiveInfinity, 16, 18 }, { float.PositiveInfinity, float.PositiveInfinity, 12, float.PositiveInfinity, float.PositiveInfinity, 0, float.PositiveInfinity, float.PositiveInfinity, 30 }, { 10, float.PositiveInfinity, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity, 0, 18, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, 16, float.PositiveInfinity, 18, 0, 20 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 18, 30, float.PositiveInfinity, 20, 0 }, }; /* Expected MST * (0) (1) (2) * \ / |\ * 4 5 13 12 * \ / | \ * (3)----14---(4) (5) * / \ | * 9 6 18 * / \ | * (6) (7) (8) */ var expected = new[,] { { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 4, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 13, 12, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { 4, 5, float.PositiveInfinity, float.PositiveInfinity, 14, float.PositiveInfinity, 9, 6, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, 13, 14, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 18, }, { float.PositiveInfinity, float.PositiveInfinity, 12, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 18, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, }; Kruskal.Solve(adj).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } [Test] public void Solve_adjGraph6_CorrectAnswer() { /* Graph * (0)--7--(1) (2) * \ / /| * 5 9 5 | * \ / / | * (3) (4) 2 * / \ | * 8 9 | * / \| * (5)--11-(6) */ var adj = new[,] { { 0, 7, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 7, 0, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 0, float.PositiveInfinity, 5, float.PositiveInfinity, 2 }, { 5, 9, float.PositiveInfinity, 0, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, 0, 8, 9 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 8, 0, 11 }, { float.PositiveInfinity, float.PositiveInfinity, 2, float.PositiveInfinity, 9, 11, 0 }, }; /* Expected MST * (0)--7--(1) (2) * \ /| * 5 5 | * \ / | * (3) (4) 2 * / | * 8 | * / | * (5) (6) */ var expected = new[,] { { float.PositiveInfinity, 7, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 7, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, 2 }, { 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, 8, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 8, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, }; Kruskal.Solve(adj).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } [Test] public void ValidateGraph_ListDirectedGraph_ThrowsException() { var adj = new[] { new Dictionary{ { 2, 4 } }, new Dictionary{ { 0, 3 }, { 2, 5 }, { 3, 6 }, { 4, 2 } }, new Dictionary{ { 0, 4 }, { 1, 5 } }, new Dictionary{ { 1, 6 } }, new Dictionary{ { 1, 2 } }, }; Assert.Throws(() => Kruskal.Solve(adj), "Graph must be undirected!"); } [Test] public void Solve_ListGraph1_CorrectAnswer() { /* Graph * (1) * / \ * 3 2 * / \ * (0)--2--(2) */ var adj = new[] { new Dictionary{ { 1, 3 }, { 2, 2 } }, new Dictionary{ { 0, 3 }, { 2, 2 } }, new Dictionary{ { 0, 2 }, { 1, 2 } }, }; /* Expected MST * (1) * \ * 2 * \ * (0)--2--(2) */ var expected = new[] { new Dictionary{ { 2, 2 } }, new Dictionary{ { 2, 2 } }, new Dictionary{ { 0, 2 }, { 1, 2 } }, }; var res = Kruskal.Solve(adj); for (var i = 0; i < adj.Length; i++) { res[i].OrderBy(edge => edge.Key).SequenceEqual(expected[i]).Should().BeTrue(); } } [Test] public void Solve_ListGraph2_CorrectAnswer() { /* Graph * (0) (4) * |\ / * | 3 2 * | \ / * 4 (1) * | / \ * | 5 6 * |/ \ * (2) (3) */ var adj = new[] { new Dictionary{ { 1, 3 }, { 2, 4 } }, new Dictionary{ { 0, 3 }, { 2, 5 }, { 3, 6 }, { 4, 2 } }, new Dictionary{ { 0, 4 }, { 1, 5 } }, new Dictionary{ { 1, 6 } }, new Dictionary{ { 1, 2 } }, }; /* Expected MST * (0) (4) * |\ / * | 3 2 * | \ / * 4 (1) * | \ * | 6 * | \ * (2) (3) */ var expected = new[] { new Dictionary{ { 1, 3 }, { 2, 4 } }, new Dictionary{ { 0, 3 }, { 3, 6 }, { 4, 2 } }, new Dictionary{ { 0, 4 } }, new Dictionary{ { 1, 6 } }, new Dictionary{ { 1, 2 } }, }; var res = Kruskal.Solve(adj); for (var i = 0; i < adj.Length; i++) { res[i].OrderBy(edge => edge.Key).SequenceEqual(expected[i]).Should().BeTrue(); } } [Test] public void Solve_ListGraph3_CorrectAnswer() { /* Graph * (0)--3--(2) (4)--2--(5) * \ / \ / * 4 1 4 6 * \ / \ / * (1)--2--(3) */ var adj = new[] { new Dictionary{ { 1, 4 }, { 2, 3 } }, new Dictionary{ { 0, 4 }, { 2, 1 }, { 3, 2 } }, new Dictionary{ { 0, 3 }, { 1, 1 }, { 3, 4 } }, new Dictionary{ { 1, 2 }, { 2, 4 }, { 4, 6 } }, new Dictionary{ { 3, 6 }, { 5, 2 } }, new Dictionary{ { 4, 2 } }, }; /* Graph * (0)--3--(2) (4)--2--(5) * / / * 1 6 * / / * (1)--2--(3) */ var expected = new[] { new Dictionary{ { 2, 3 } }, new Dictionary{ { 2, 1 }, { 3, 2 } }, new Dictionary{ { 0, 3 }, { 1, 1 } }, new Dictionary{ { 1, 2 }, { 4, 6 } }, new Dictionary{ { 3, 6 }, { 5, 2 } }, new Dictionary{ { 4, 2 } }, }; var res = Kruskal.Solve(adj); for (var i = 0; i < adj.Length; i++) { res[i].OrderBy(edge => edge.Key).SequenceEqual(expected[i]).Should().BeTrue(); } } [Test] public void Solve_ListGraph4_CorrectAnswer() { /* Graph * (0)--7--(1)--8--(2) * \ / \ / * 5 9 7 5 * \ / \ / * (3)--15-(4) * \ / \ * 6 8 9 * \ / \ * (5)--11-(6) */ var adj = new[] { new Dictionary{ { 1, 7 }, { 3, 5 } }, new Dictionary{ { 0, 7 }, { 2, 8 }, { 3, 9 }, { 4, 7 } }, new Dictionary{ { 1, 8 }, { 4, 5 } }, new Dictionary{ { 0, 5 }, { 1, 9 }, { 4, 15 }, { 5, 6 } }, new Dictionary{ { 1, 7 }, { 2, 5 }, { 3, 15 }, { 5, 8 }, { 6, 9 } }, new Dictionary{ { 3, 6 }, { 4, 8 }, { 6, 11 } }, new Dictionary{ { 4, 9 }, { 5, 11 } }, }; /* Expected MST * (0)--7--(1) (2) * \ \ / * 5 7 5 * \ \ / * (3) (4) * \ \ * 6 9 * \ \ * (5) (6) */ var expected = new[] { new Dictionary{ { 1, 7 }, { 3, 5 } }, new Dictionary{ { 0, 7 }, { 4, 7 } }, new Dictionary{ { 4, 5 } }, new Dictionary{ { 0, 5 }, { 5, 6 } }, new Dictionary{ { 1, 7 }, { 2, 5 }, { 6, 9 } }, new Dictionary{ { 3, 6 } }, new Dictionary{ { 4, 9 } }, }; var res = Kruskal.Solve(adj); for (var i = 0; i < adj.Length; i++) { res[i].OrderBy(edge => edge.Key).SequenceEqual(expected[i]).Should().BeTrue(); } } [Test] public void Solve_ListGraph5_CorrectAnswer() { /* Graph * (0)--8--(1)--15-(2) * |\ / __/|\ * | 4 5 __25 13 12 * | \ /__/ | \ * 10 (3)----14---(4) (5) * | / \ _/| / * | 9 6 __16 18 30 * |/ \ / |/ * (6)--18-(7)--20-(8) */ var adj = new[] { new Dictionary{ { 1, 8 }, { 3, 4 }, { 6, 10 } }, new Dictionary{ { 0, 8 }, { 2, 15 }, { 3, 5 } }, new Dictionary{ { 1, 15 }, { 3, 25 }, { 4, 13 }, { 5, 12 } }, new Dictionary{ { 0, 4 }, { 1, 5 }, { 2, 25 }, { 4, 14 }, { 6, 9 }, { 7, 6 } }, new Dictionary{ { 2, 13 }, { 3, 14 }, { 7, 16 }, { 8, 18 } }, new Dictionary{ { 2, 12 }, { 8, 30 } }, new Dictionary{ { 0, 10 }, { 3, 9 }, { 7, 18 } }, new Dictionary{ { 3, 6 }, { 4, 16 }, { 6, 18 }, { 8, 20 } }, new Dictionary{ { 4, 18 }, { 5, 30 }, { 7, 20 } }, }; /* Expected MST * (0) (1) (2) * \ / |\ * 4 5 13 12 * \ / | \ * (3)----14---(4) (5) * / \ | * 9 6 18 * / \ | * (6) (7) (8) */ var expected = new[] { new Dictionary{ { 3, 4 } }, new Dictionary{ { 3, 5 } }, new Dictionary{ { 4, 13 }, { 5, 12 } }, new Dictionary{ { 0, 4 }, { 1, 5 }, { 4, 14 }, { 6, 9 }, { 7, 6 } }, new Dictionary{ { 2, 13 }, { 3, 14 }, { 8, 18 } }, new Dictionary{ { 2, 12 } }, new Dictionary{ { 3, 9 } }, new Dictionary{ { 3, 6 } }, new Dictionary{ { 4, 18 } }, }; var res = Kruskal.Solve(adj); for (var i = 0; i < adj.Length; i++) { res[i].OrderBy(edge => edge.Key).SequenceEqual(expected[i]).Should().BeTrue(); } } [Test] public void Solve_ListGraph6_CorrectAnswer() { /* Graph * (0)--7--(1) (2) * \ / /| * 5 9 5 | * \ / / | * (3) (4) 2 * / \ | * 8 9 | * / \| * (5)--11-(6) */ var adj = new[] { new Dictionary{ { 1, 7 }, { 3, 5 } }, new Dictionary{ { 0, 7 }, { 3, 9 } }, new Dictionary{ { 4, 5 }, { 6, 2 } }, new Dictionary{ { 0, 5 }, { 1, 9 } }, new Dictionary{ { 2, 5 }, { 5, 8 }, { 6, 9 } }, new Dictionary{ { 4, 8 }, { 6, 11 } }, new Dictionary{ { 2, 2 }, { 4, 9 }, { 5, 11 } }, }; /* Expected MST * (0)--7--(1) (2) * \ /| * 5 5 | * \ / | * (3) (4) 2 * / | * 8 | * / | * (5) (6) */ var expected = new[] { new Dictionary{ { 1, 7 }, { 3, 5 } }, new Dictionary{ { 0, 7 } }, new Dictionary{ { 4, 5 }, { 6, 2 } }, new Dictionary{ { 0, 5 } }, new Dictionary{ { 2, 5 }, { 5, 8 } }, new Dictionary{ { 4, 8 } }, new Dictionary{ { 2, 2 } }, }; var res = Kruskal.Solve(adj); for (var i = 0; i < adj.Length; i++) { res[i].OrderBy(edge => edge.Key).SequenceEqual(expected[i]).Should().BeTrue(); } } } ================================================ FILE: Algorithms.Tests/Graph/MinimumSpanningTree/PrimMatrixTests.cs ================================================ using Algorithms.Graph.MinimumSpanningTree; namespace Algorithms.Tests.Graph.MinimumSpanningTree; internal class PrimTests { [Test] public void ValidateMatrix_WrongSize_ThrowsException() { // Wrong number of columns var matrix = new[,] { { 0, 3, 4, float.PositiveInfinity }, { 3, 0, 5, 6 }, { 4, 5, 0, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0 }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity }, }; Assert.Throws(() => PrimMatrix.Solve(matrix, 0)); // Wrong number of rows matrix = new[,] { { 0, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, }; Assert.Throws(() => PrimMatrix.Solve(matrix, 0)); } [Test] public void ValidateMatrix_UnconnectedGraph_ThrowsException() { // Last node does not connect to any other nodes var matrix = new[,] { { 0, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 0 }, }; Assert.Throws(() => PrimMatrix.Solve(matrix, 0)); } [Test] public void ValidateMatrix_DirectedGraph_ThrowsException() { // Nodes 1 and 2 have a directed edge var matrix = new[,] { { 0, float.PositiveInfinity, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, 0 }, }; Assert.Throws(() => PrimMatrix.Solve(matrix, 0)); } [Test] public void SolveMatrix_Graph1_CorrectAnswer() { /* Graph * (1) * / \ * 3 2 * / \ * (0)--2--(2) */ var matrix = new float[,] { { 0, 3, 2 }, { 3, 0, 2 }, { 2, 2, 0 }, }; /* Expected MST * (1) * \ * 2 * \ * (0)--2--(2) */ var expected = new[,] { { float.PositiveInfinity, float.PositiveInfinity, 2 }, { float.PositiveInfinity, float.PositiveInfinity, 2 }, { 2, 2, float.PositiveInfinity }, }; for (var i = 0; i < matrix.GetLength(0); i++) { PrimMatrix.Solve(matrix, i).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } } [Test] public void SolveMatrix_Graph2_CorrectAnswer() { /* Graph * (0) (4) * |\ / * | 3 2 * | \ / * 4 (1) * | / \ * | 5 6 * |/ \ * (2) (3) */ var matrix = new[,] { { 0, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, 0, 5, 6, 2 }, { 4, 5, 0, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, 0, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, 0 }, }; /* Expected MST * (0) (4) * |\ / * | 3 2 * | \ / * 4 (1) * | \ * | 6 * | \ * (2) (3) */ var expected = new[,] { { float.PositiveInfinity, 3, 4, float.PositiveInfinity, float.PositiveInfinity }, { 3, float.PositiveInfinity, float.PositiveInfinity, 6, 2 }, { 4, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 6, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, }; for (var i = 0; i < matrix.GetLength(0); i++) { PrimMatrix.Solve(matrix, i).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } } [Test] public void SolveMatrix_Graph3_CorrectAnswer() { /* Graph * (0)--3--(2) (4)--2--(5) * \ / \ / * 4 1 4 6 * \ / \ / * (1)--2--(3) */ var matrix = new[,] { { 0, 4, 3, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 4, 0, 1, 2, float.PositiveInfinity, float.PositiveInfinity }, { 3, 1, 0, 4, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 2, 4, 0, 6, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, 0, 2 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 2, 0 }, }; /* Graph * (0)--3--(2) (4)--2--(5) * / / * 1 6 * / / * (1)--2--(3) */ var expected = new[,] { { float.PositiveInfinity, float.PositiveInfinity, 3, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 1, 2, float.PositiveInfinity, float.PositiveInfinity }, { 3, 1, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 2, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity, 2 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 2, float.PositiveInfinity }, }; for (var i = 0; i < matrix.GetLength(0); i++) { PrimMatrix.Solve(matrix, i).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } } [Test] public void SolveMatrix_Graph4_CorrectAnswer() { /* Graph * (0)--7--(1)--8--(2) * \ / \ / * 5 9 7 5 * \ / \ / * (3)--15-(4) * \ / \ * 6 8 9 * \ / \ * (5)--11-(6) */ var matrix = new[,] { { 0, 7, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 7, 0, 8, 9, 7, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 8, 0, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity }, { 5, 9, float.PositiveInfinity, 0, 15, 6, float.PositiveInfinity }, { float.PositiveInfinity, 7, 5, 15, 0, 8, 9 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, 8, 0, 11 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9, 11, 0 }, }; /* Expected MST * (0)--7--(1) (2) * \ \ / * 5 7 5 * \ \ / * (3) (4) * \ \ * 6 9 * \ \ * (5) (6) */ var expected = new[,] { { float.PositiveInfinity, 7, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 7, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 7, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity }, { 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity }, { float.PositiveInfinity, 7, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity }, }; for (var i = 0; i < matrix.GetLength(0); i++) { PrimMatrix.Solve(matrix, i).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } } [Test] public void SolveMatrix_Graph5_CorrectAnswer() { /* Graph * (0)--8--(1)--15-(2) * |\ / __/|\ * | 4 5 __25 13 12 * | \ /__/ | \ * 10 (3)----14---(4) (5) * | / \ _/| / * | 9 6 __16 18 30 * |/ \ / |/ * (6)--18-(7)--20-(8) */ var matrix = new[,] { { 0, 8, float.PositiveInfinity, 4, float.PositiveInfinity, float.PositiveInfinity, 10, float.PositiveInfinity, float.PositiveInfinity }, { 8, 0, 15, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { float.PositiveInfinity, 15, 0, 25, 13, 12, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity }, { 4, 5, 25, 0, 14, float.PositiveInfinity, 9, 6, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, 13, 14, 0, float.PositiveInfinity, float.PositiveInfinity, 16, 18 }, { float.PositiveInfinity, float.PositiveInfinity, 12, float.PositiveInfinity, float.PositiveInfinity, 0, float.PositiveInfinity, float.PositiveInfinity, 30 }, { 10, float.PositiveInfinity, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity, 0, 18, float.PositiveInfinity }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, 16, float.PositiveInfinity, 18, 0, 20 }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 18, 30, float.PositiveInfinity, 20, 0 }, }; /* Expected MST * (0) (1) (2) * \ / |\ * 4 5 13 12 * \ / | \ * (3)----14---(4) (5) * / \ | * 9 6 18 * / \ | * (6) (7) (8) */ var expected = new[,] { { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 4, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 5, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 13, 12, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { 4, 5, float.PositiveInfinity, float.PositiveInfinity, 14, float.PositiveInfinity, 9, 6, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, 13, 14, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 18, }, { float.PositiveInfinity, float.PositiveInfinity, 12, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 9, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 6, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, 18, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, }, }; for (var i = 0; i < matrix.GetLength(0); i++) { PrimMatrix.Solve(matrix, i).Cast().SequenceEqual(expected.Cast()).Should().BeTrue(); } } } ================================================ FILE: Algorithms.Tests/Graph/TarjanStronglyConnectedComponentsTests.cs ================================================ using Algorithms.Graph; using NUnit.Framework; using FluentAssertions; using System.Linq; namespace Algorithms.Tests.Graph; public class TarjanStronglyConnectedComponentsTests { [Test] public void FindSCCs_SimpleGraph_ReturnsCorrectSCCs() { var tarjan = new TarjanStronglyConnectedComponents(3); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 2); tarjan.AddEdge(2, 0); var sccs = tarjan.FindSCCs(); sccs.Should().HaveCount(1); sccs[0].Should().BeEquivalentTo(new[] { 0, 1, 2 }); } [Test] public void FindSCCs_TwoSeparateSCCs_ReturnsBothSCCs() { var tarjan = new TarjanStronglyConnectedComponents(4); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 0); tarjan.AddEdge(2, 3); tarjan.AddEdge(3, 2); var sccs = tarjan.FindSCCs(); sccs.Should().HaveCount(2); } [Test] public void FindSCCs_DisconnectedVertices_EachVertexIsSCC() { var tarjan = new TarjanStronglyConnectedComponents(3); var sccs = tarjan.FindSCCs(); sccs.Should().HaveCount(3); sccs.Should().OnlyContain(scc => scc.Count == 1); } [Test] public void FindSCCs_ComplexGraph_ReturnsCorrectSCCs() { var tarjan = new TarjanStronglyConnectedComponents(8); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 2); tarjan.AddEdge(2, 0); tarjan.AddEdge(2, 3); tarjan.AddEdge(3, 4); tarjan.AddEdge(4, 5); tarjan.AddEdge(5, 3); tarjan.AddEdge(5, 6); tarjan.AddEdge(6, 7); tarjan.AddEdge(7, 6); var sccs = tarjan.FindSCCs(); sccs.Should().HaveCount(3); } [Test] public void GetSccCount_AfterFindingSCCs_ReturnsCorrectCount() { var tarjan = new TarjanStronglyConnectedComponents(5); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 0); tarjan.AddEdge(2, 3); tarjan.AddEdge(3, 4); tarjan.AddEdge(4, 2); tarjan.FindSCCs(); var count = tarjan.GetSccCount(); count.Should().Be(2); } [Test] public void InSameScc_VerticesInSameScc_ReturnsTrue() { var tarjan = new TarjanStronglyConnectedComponents(3); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 2); tarjan.AddEdge(2, 0); var result = tarjan.InSameScc(0, 2); result.Should().BeTrue(); } [Test] public void InSameScc_VerticesInDifferentSccs_ReturnsFalse() { var tarjan = new TarjanStronglyConnectedComponents(4); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 0); tarjan.AddEdge(2, 3); var result = tarjan.InSameScc(0, 2); result.Should().BeFalse(); } [Test] public void GetScc_ValidVertex_ReturnsSccContainingVertex() { var tarjan = new TarjanStronglyConnectedComponents(3); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 2); tarjan.AddEdge(2, 0); var scc = tarjan.GetScc(1); scc.Should().NotBeNull(); scc.Should().Contain(1); scc.Should().HaveCount(3); } [Test] public void BuildCondensationGraph_ComplexGraph_ReturnsDAG() { var tarjan = new TarjanStronglyConnectedComponents(6); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 0); tarjan.AddEdge(1, 2); tarjan.AddEdge(2, 3); tarjan.AddEdge(3, 4); tarjan.AddEdge(4, 5); tarjan.AddEdge(5, 3); var condensation = tarjan.BuildCondensationGraph(); condensation.Should().NotBeNull(); condensation.Length.Should().Be(3); // {0,1}, {2}, {3,4,5} } [Test] public void AddEdge_InvalidVertex_ThrowsException() { var tarjan = new TarjanStronglyConnectedComponents(3); var act = () => tarjan.AddEdge(0, 5); act.Should().Throw(); } [Test] public void FindSCCs_SingleVertex_ReturnsSingleSCC() { var tarjan = new TarjanStronglyConnectedComponents(1); var sccs = tarjan.FindSCCs(); sccs.Should().HaveCount(1); sccs[0].Should().BeEquivalentTo(new[] { 0 }); } [Test] public void FindSCCs_LinearChain_EachVertexIsSCC() { var tarjan = new TarjanStronglyConnectedComponents(4); tarjan.AddEdge(0, 1); tarjan.AddEdge(1, 2); tarjan.AddEdge(2, 3); var sccs = tarjan.FindSCCs(); sccs.Should().HaveCount(4); } [Test] public void FindSCCs_SelfLoop_VertexIsSCC() { var tarjan = new TarjanStronglyConnectedComponents(2); tarjan.AddEdge(0, 0); tarjan.AddEdge(0, 1); var sccs = tarjan.FindSCCs(); sccs.Should().Contain(scc => scc.Contains(0) && scc.Count == 1); } } ================================================ FILE: Algorithms.Tests/Graph/TopologicalSortTests.cs ================================================ using Algorithms.Graph; using DataStructures.Graph; namespace Algorithms.Tests.Graph; public class TopologicalSortTests { /// /// Test topological sort on a simple linear DAG: A → B → C. /// Expected order: [A, B, C]. /// [Test] public void Sort_SimpleLinearGraph_ReturnsCorrectOrder() { // Arrange var graph = new DirectedWeightedGraph(3); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexB, vertexC, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(3)); Assert.That(result[0], Is.EqualTo(vertexA)); Assert.That(result[1], Is.EqualTo(vertexB)); Assert.That(result[2], Is.EqualTo(vertexC)); } /// /// Test Kahn's algorithm on a simple linear DAG: A → B → C. /// Expected order: [A, B, C]. /// [Test] public void SortKahn_SimpleLinearGraph_ReturnsCorrectOrder() { // Arrange var graph = new DirectedWeightedGraph(3); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexB, vertexC, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(3)); Assert.That(result[0], Is.EqualTo(vertexA)); Assert.That(result[1], Is.EqualTo(vertexB)); Assert.That(result[2], Is.EqualTo(vertexC)); } /// /// Test topological sort on a DAG with multiple valid orderings. /// Graph: A → C /// B → C /// Valid orderings: [A, B, C] or [B, A, C]. /// We verify that C comes after both A and B. /// [Test] public void Sort_GraphWithMultipleValidOrderings_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(3); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); graph.AddEdge(vertexA, vertexC, 1); graph.AddEdge(vertexB, vertexC, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(3)); // C should come after both A and B var indexA = result.IndexOf(vertexA); var indexB = result.IndexOf(vertexB); var indexC = result.IndexOf(vertexC); Assert.That(indexC, Is.GreaterThan(indexA)); Assert.That(indexC, Is.GreaterThan(indexB)); } /// /// Test Kahn's algorithm on a DAG with multiple valid orderings. /// Graph: A → C /// B → C /// Valid orderings: [A, B, C] or [B, A, C]. /// We verify that C comes after both A and B. /// [Test] public void SortKahn_GraphWithMultipleValidOrderings_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(3); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); graph.AddEdge(vertexA, vertexC, 1); graph.AddEdge(vertexB, vertexC, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(3)); // C should come after both A and B var indexA = result.IndexOf(vertexA); var indexB = result.IndexOf(vertexB); var indexC = result.IndexOf(vertexC); Assert.That(indexC, Is.GreaterThan(indexA)); Assert.That(indexC, Is.GreaterThan(indexB)); } /// /// Test topological sort on a more complex DAG. /// Graph: A → B → D /// A → C → D /// Valid orderings include: [A, B, C, D], [A, C, B, D]. /// We verify that A comes first and D comes last. /// [Test] public void Sort_ComplexDAG_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(4); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); var vertexD = graph.AddVertex("D"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexA, vertexC, 1); graph.AddEdge(vertexB, vertexD, 1); graph.AddEdge(vertexC, vertexD, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(4)); Assert.That(result[0], Is.EqualTo(vertexA)); // A must be first Assert.That(result[3], Is.EqualTo(vertexD)); // D must be last // B and C should come after A and before D var indexB = result.IndexOf(vertexB); var indexC = result.IndexOf(vertexC); Assert.That(indexB, Is.GreaterThan(0)); Assert.That(indexB, Is.LessThan(3)); Assert.That(indexC, Is.GreaterThan(0)); Assert.That(indexC, Is.LessThan(3)); } /// /// Test Kahn's algorithm on a more complex DAG. /// Graph: A → B → D /// A → C → D /// Valid orderings include: [A, B, C, D], [A, C, B, D]. /// We verify that A comes first and D comes last. /// [Test] public void SortKahn_ComplexDAG_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(4); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); var vertexD = graph.AddVertex("D"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexA, vertexC, 1); graph.AddEdge(vertexB, vertexD, 1); graph.AddEdge(vertexC, vertexD, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(4)); Assert.That(result[0], Is.EqualTo(vertexA)); // A must be first Assert.That(result[3], Is.EqualTo(vertexD)); // D must be last // B and C should come after A and before D var indexB = result.IndexOf(vertexB); var indexC = result.IndexOf(vertexC); Assert.That(indexB, Is.GreaterThan(0)); Assert.That(indexB, Is.LessThan(3)); Assert.That(indexC, Is.GreaterThan(0)); Assert.That(indexC, Is.LessThan(3)); } /// /// Test topological sort on a graph with a cycle. /// Graph: A → B → C → A (cycle). /// Should throw InvalidOperationException. /// [Test] public void Sort_GraphWithCycle_ThrowsInvalidOperationException() { // Arrange var graph = new DirectedWeightedGraph(3); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexB, vertexC, 1); graph.AddEdge(vertexC, vertexA, 1); // Creates a cycle var topologicalSort = new TopologicalSort(); // Act & Assert Assert.Throws(() => topologicalSort.Sort(graph)); } /// /// Test Kahn's algorithm on a graph with a cycle. /// Graph: A → B → C → A (cycle). /// Should throw InvalidOperationException. /// [Test] public void SortKahn_GraphWithCycle_ThrowsInvalidOperationException() { // Arrange var graph = new DirectedWeightedGraph(3); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexB, vertexC, 1); graph.AddEdge(vertexC, vertexA, 1); // Creates a cycle var topologicalSort = new TopologicalSort(); // Act & Assert Assert.Throws(() => topologicalSort.SortKahn(graph)); } /// /// Test topological sort on a single vertex graph. /// Graph: A (no edges). /// Expected order: [A]. /// [Test] public void Sort_SingleVertexGraph_ReturnsSingleVertex() { // Arrange var graph = new DirectedWeightedGraph(1); var vertexA = graph.AddVertex("A"); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(1)); Assert.That(result[0], Is.EqualTo(vertexA)); } /// /// Test Kahn's algorithm on a single vertex graph. /// Graph: A (no edges). /// Expected order: [A]. /// [Test] public void SortKahn_SingleVertexGraph_ReturnsSingleVertex() { // Arrange var graph = new DirectedWeightedGraph(1); var vertexA = graph.AddVertex("A"); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(1)); Assert.That(result[0], Is.EqualTo(vertexA)); } /// /// Test topological sort on a disconnected DAG. /// Graph: A → B (component 1) /// C → D (component 2) /// Valid orderings: [A, B, C, D], [A, C, B, D], [C, A, B, D], [C, D, A, B], etc. /// We verify that A comes before B and C comes before D. /// [Test] public void Sort_DisconnectedDAG_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(4); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); var vertexD = graph.AddVertex("D"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexC, vertexD, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(4)); // A should come before B var indexA = result.IndexOf(vertexA); var indexB = result.IndexOf(vertexB); Assert.That(indexB, Is.GreaterThan(indexA)); // C should come before D var indexC = result.IndexOf(vertexC); var indexD = result.IndexOf(vertexD); Assert.That(indexD, Is.GreaterThan(indexC)); } /// /// Test Kahn's algorithm on a disconnected DAG. /// Graph: A → B (component 1) /// C → D (component 2) /// Valid orderings: [A, B, C, D], [A, C, B, D], [C, A, B, D], [C, D, A, B], etc. /// We verify that A comes before B and C comes before D. /// [Test] public void SortKahn_DisconnectedDAG_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(4); var vertexA = graph.AddVertex("A"); var vertexB = graph.AddVertex("B"); var vertexC = graph.AddVertex("C"); var vertexD = graph.AddVertex("D"); graph.AddEdge(vertexA, vertexB, 1); graph.AddEdge(vertexC, vertexD, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(4)); // A should come before B var indexA = result.IndexOf(vertexA); var indexB = result.IndexOf(vertexB); Assert.That(indexB, Is.GreaterThan(indexA)); // C should come before D var indexC = result.IndexOf(vertexC); var indexD = result.IndexOf(vertexD); Assert.That(indexD, Is.GreaterThan(indexC)); } /// /// Test topological sort on a real-world scenario: course prerequisites. /// Graph represents course dependencies: /// - Intro to CS (A) is a prerequisite for Data Structures (B) and Algorithms (C). /// - Data Structures (B) is a prerequisite for Advanced Algorithms (D). /// - Algorithms (C) is a prerequisite for Advanced Algorithms (D). /// Expected: A must come first, D must come last. /// [Test] public void Sort_CoursePrerequisitesScenario_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(4); var introCS = graph.AddVertex("Intro to CS"); var dataStructures = graph.AddVertex("Data Structures"); var algorithms = graph.AddVertex("Algorithms"); var advancedAlgorithms = graph.AddVertex("Advanced Algorithms"); graph.AddEdge(introCS, dataStructures, 1); graph.AddEdge(introCS, algorithms, 1); graph.AddEdge(dataStructures, advancedAlgorithms, 1); graph.AddEdge(algorithms, advancedAlgorithms, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(4)); Assert.That(result[0], Is.EqualTo(introCS)); // Must take Intro to CS first Assert.That(result[3], Is.EqualTo(advancedAlgorithms)); // Advanced Algorithms must be last // Data Structures and Algorithms should be in the middle var indexDS = result.IndexOf(dataStructures); var indexAlgo = result.IndexOf(algorithms); Assert.That(indexDS, Is.GreaterThan(0)); Assert.That(indexDS, Is.LessThan(3)); Assert.That(indexAlgo, Is.GreaterThan(0)); Assert.That(indexAlgo, Is.LessThan(3)); } /// /// Test Kahn's algorithm on a real-world scenario: course prerequisites. /// Graph represents course dependencies: /// - Intro to CS (A) is a prerequisite for Data Structures (B) and Algorithms (C). /// - Data Structures (B) is a prerequisite for Advanced Algorithms (D). /// - Algorithms (C) is a prerequisite for Advanced Algorithms (D). /// Expected: A must come first, D must come last. /// [Test] public void SortKahn_CoursePrerequisitesScenario_ReturnsValidOrder() { // Arrange var graph = new DirectedWeightedGraph(4); var introCS = graph.AddVertex("Intro to CS"); var dataStructures = graph.AddVertex("Data Structures"); var algorithms = graph.AddVertex("Algorithms"); var advancedAlgorithms = graph.AddVertex("Advanced Algorithms"); graph.AddEdge(introCS, dataStructures, 1); graph.AddEdge(introCS, algorithms, 1); graph.AddEdge(dataStructures, advancedAlgorithms, 1); graph.AddEdge(algorithms, advancedAlgorithms, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(4)); Assert.That(result[0], Is.EqualTo(introCS)); // Must take Intro to CS first Assert.That(result[3], Is.EqualTo(advancedAlgorithms)); // Advanced Algorithms must be last // Data Structures and Algorithms should be in the middle var indexDS = result.IndexOf(dataStructures); var indexAlgo = result.IndexOf(algorithms); Assert.That(indexDS, Is.GreaterThan(0)); Assert.That(indexDS, Is.LessThan(3)); Assert.That(indexAlgo, Is.GreaterThan(0)); Assert.That(indexAlgo, Is.LessThan(3)); } /// /// Test topological sort with integer vertices. /// Graph: 1 → 2 → 3. /// Expected order: [1, 2, 3]. /// [Test] public void Sort_IntegerVertices_ReturnsCorrectOrder() { // Arrange var graph = new DirectedWeightedGraph(3); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.Sort(graph); // Assert Assert.That(result.Count, Is.EqualTo(3)); Assert.That(result[0], Is.EqualTo(vertex1)); Assert.That(result[1], Is.EqualTo(vertex2)); Assert.That(result[2], Is.EqualTo(vertex3)); } /// /// Test Kahn's algorithm with integer vertices. /// Graph: 1 → 2 → 3. /// Expected order: [1, 2, 3]. /// [Test] public void SortKahn_IntegerVertices_ReturnsCorrectOrder() { // Arrange var graph = new DirectedWeightedGraph(3); var vertex1 = graph.AddVertex(1); var vertex2 = graph.AddVertex(2); var vertex3 = graph.AddVertex(3); graph.AddEdge(vertex1, vertex2, 1); graph.AddEdge(vertex2, vertex3, 1); var topologicalSort = new TopologicalSort(); // Act var result = topologicalSort.SortKahn(graph); // Assert Assert.That(result.Count, Is.EqualTo(3)); Assert.That(result[0], Is.EqualTo(vertex1)); Assert.That(result[1], Is.EqualTo(vertex2)); Assert.That(result[2], Is.EqualTo(vertex3)); } /// /// Test topological sort on a graph with self-loop (cycle). /// Graph: A → A (self-loop). /// Should throw InvalidOperationException. /// [Test] public void Sort_GraphWithSelfLoop_ThrowsInvalidOperationException() { // Arrange var graph = new DirectedWeightedGraph(1); var vertexA = graph.AddVertex("A"); graph.AddEdge(vertexA, vertexA, 1); // Self-loop var topologicalSort = new TopologicalSort(); // Act & Assert Assert.Throws(() => topologicalSort.Sort(graph)); } /// /// Test Kahn's algorithm on a graph with self-loop (cycle). /// Graph: A → A (self-loop). /// Should throw InvalidOperationException. /// [Test] public void SortKahn_GraphWithSelfLoop_ThrowsInvalidOperationException() { // Arrange var graph = new DirectedWeightedGraph(1); var vertexA = graph.AddVertex("A"); graph.AddEdge(vertexA, vertexA, 1); // Self-loop var topologicalSort = new TopologicalSort(); // Act & Assert Assert.Throws(() => topologicalSort.SortKahn(graph)); } } ================================================ FILE: Algorithms.Tests/Helpers/IntComparer.cs ================================================ namespace Algorithms.Tests.Helpers; internal class IntComparer : IComparer { public int Compare(int x, int y) => x.CompareTo(y); } ================================================ FILE: Algorithms.Tests/Helpers/RandomHelper.cs ================================================ namespace Algorithms.Tests.Helpers; internal static class RandomHelper { public static (int[] correctArray, int[] testArray) GetArrays(int n) { var testArr = new int[n]; var correctArray = new int[n]; for (var i = 0; i < n; i++) { var t = TestContext.CurrentContext.Random.Next(1_000_000); testArr[i] = t; correctArray[i] = t; } return (correctArray, testArr); } public static (string[] correctArray, string[] testArray) GetStringArrays( int n, int maxLength, bool equalLength) { var testArr = new string[n]; var correctArray = new string[n]; var length = TestContext.CurrentContext.Random.Next(2, maxLength); for (var i = 0; i < n; i++) { if (!equalLength) { length = TestContext.CurrentContext.Random.Next(2, maxLength); } var chars = new char[length]; for (var j = 0; j < length; j++) { chars[j] = (char)TestContext.CurrentContext.Random.Next(97, 123); } var t = new string(chars); testArr[i] = t; correctArray[i] = t; } return (correctArray, testArr); } } ================================================ FILE: Algorithms.Tests/Knapsack/BranchAndBoundKnapsackSolverTests.cs ================================================ using Algorithms.Knapsack; namespace Algorithms.Tests.Knapsack; public static class BranchAndBoundKnapsackSolverTests { [Test] public static void BranchAndBoundTest_Example1_Success() { // Arrange var items = new[] { 'A', 'B', 'C', 'D' }; var values = new[] { 18, 20, 14, 18 }; var weights = new[] { 2, 4, 6, 9 }; var capacity = 15; Func weightSelector = x => weights[Array.IndexOf(items, x)]; Func valueSelector = x => values[Array.IndexOf(items, x)]; // Act var solver = new BranchAndBoundKnapsackSolver(); var actualResult = solver.Solve(items, capacity, weightSelector, valueSelector); // Assert actualResult.Should().BeEquivalentTo(new[] { 'A', 'B', 'D' }); } [Test] public static void BranchAndBoundTest_Example2_Success() { // Arrange var items = new[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' }; var values = new[] { 505, 352, 458, 220, 354, 414, 498, 545, 473, 543 }; var weights = new[] { 23, 26, 20, 18, 32, 27, 29, 26, 30, 27 }; var capacity = 67; Func weightSelector = x => weights[Array.IndexOf(items, x)]; Func valueSelector = x => values[Array.IndexOf(items, x)]; // Act var solver = new BranchAndBoundKnapsackSolver(); var actualResult = solver.Solve(items, capacity, weightSelector, valueSelector); // Assert actualResult.Should().BeEquivalentTo(new[] { 'H', 'D', 'A' }); } [Test] public static void BranchAndBoundTest_CapacityIsZero_NothingTaken() { // Arrange var items = new[] { 'A', 'B', 'C', 'D' }; var values = new[] { 18, 20, 14, 18 }; var weights = new[] { 2, 4, 6, 9 }; var capacity = 0; Func weightSelector = x => weights[Array.IndexOf(items, x)]; Func valueSelector = x => values[Array.IndexOf(items, x)]; // Act var solver = new BranchAndBoundKnapsackSolver(); var actualResult = solver.Solve(items, capacity, weightSelector, valueSelector); // Assert actualResult.Should().BeEmpty(); } [Test] public static void BranchAndBoundTest_PlentyCapacity_EverythingIsTaken() { // Arrange var items = new[] { 'A', 'B', 'C', 'D' }; var values = new[] { 18, 20, 14, 18 }; var weights = new[] { 2, 4, 6, 9 }; var capacity = 1000; Func weightSelector = x => weights[Array.IndexOf(items, x)]; Func valueSelector = x => values[Array.IndexOf(items, x)]; // Act var solver = new BranchAndBoundKnapsackSolver(); var actualResult = solver.Solve(items, capacity, weightSelector, valueSelector); // Assert actualResult.Should().BeEquivalentTo(items); } [Test] public static void BranchAndBoundTest_NoItems_NothingTaken() { // Arrange var items = Array.Empty(); var values = Array.Empty(); var weights = Array.Empty(); var capacity = 15; Func weightSelector = x => weights[Array.IndexOf(items, x)]; Func valueSelector = x => values[Array.IndexOf(items, x)]; // Act var solver = new BranchAndBoundKnapsackSolver(); var actualResult = solver.Solve(items, capacity, weightSelector, valueSelector); // Assert actualResult.Should().BeEmpty(); } } ================================================ FILE: Algorithms.Tests/Knapsack/DynamicProgrammingKnapsackSolverTests.cs ================================================ using Algorithms.Knapsack; namespace Algorithms.Tests.Knapsack; public static class DynamicProgrammingKnapsackSolverTests { [Test] public static void SmallSampleOfChar() { //Arrange var items = new[] { 'A', 'B', 'C' }; var val = new[] { 50, 100, 130 }; var wt = new[] { 10, 20, 40 }; var capacity = 50; Func weightSelector = x => wt[Array.IndexOf(items, x)]; Func valueSelector = x => val[Array.IndexOf(items, x)]; var expected = new[] { 'A', 'C' }; //Act var solver = new DynamicProgrammingKnapsackSolver(); var actual = solver.Solve(items, capacity, weightSelector, valueSelector); //Assert Assert.That(actual.OrderBy(x => x), Is.EqualTo(expected.OrderBy(x => x))); } [Test] public static void FSU_P01() { // Data from https://people.sc.fsu.edu/~jburkardt/datasets/knapsack_01/knapsack_01.html //Arrange var items = new[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' }; var val = new[] { 92, 57, 49, 68, 60, 43, 67, 84, 87, 72 }; var wt = new[] { 23, 31, 29, 44, 53, 38, 63, 85, 89, 82 }; var capacity = 165; Func weightSelector = x => wt[Array.IndexOf(items, x)]; Func valueSelector = x => val[Array.IndexOf(items, x)]; var expected = new[] { 'A', 'B', 'C', 'D', 'F' }; //Act var solver = new DynamicProgrammingKnapsackSolver(); var actual = solver.Solve(items, capacity, weightSelector, valueSelector); //Assert Assert.That(actual.OrderBy(x => x), Is.EqualTo(expected.OrderBy(x => x))); } [Test] public static void FSU_P07_WithNonIntegralValues() { // Shows how to handle weights with 1 significant digit right of the decimal // Data from https://people.sc.fsu.edu/~jburkardt/datasets/knapsack_01/knapsack_01.html //Arrange var val = new[] { 135, 139, 149, 150, 156, 163, 173, 184, 192, 201, 210, 214, 221, 229, 240 }; var wt = new[] { 7.0, 7.3, 7.7, 8.0, 8.2, 8.7, 9.0, 9.4, 9.8, 10.6, 11.0, 11.3, 11.5, 11.8, 12.0 }; var items = Enumerable.Range(1, val.Count()).ToArray(); var capacity = 75; Func weightSelector = x => (int)(wt[Array.IndexOf(items, x)] * 10); Func valueSelector = x => val[Array.IndexOf(items, x)]; var expected = new[] { 1, 3, 5, 7, 8, 9, 14, 15 }; //Act var solver = new DynamicProgrammingKnapsackSolver(); var actual = solver.Solve(items, capacity * 10, weightSelector, valueSelector); //Assert Assert.That(actual.OrderBy(x => x), Is.EqualTo(expected.OrderBy(x => x))); } [Test] public static void TakesHalf( [Random(0, 1000, 100, Distinct = true)] int length) { //Arrange var solver = new DynamicProgrammingKnapsackSolver(); var items = Enumerable.Repeat(42, 2 * length).ToArray(); var expectedResult = Enumerable.Repeat(42, length); //Act var result = solver.Solve(items, length, _ => 1, _ => 1); //Assert Assert.That(result, Is.EqualTo(expectedResult)); } } ================================================ FILE: Algorithms.Tests/Knapsack/NaiveKnapsackSolverTests.cs ================================================ using Algorithms.Knapsack; namespace Algorithms.Tests.Knapsack; public static class NaiveKnapsackSolverTests { [Test] public static void TakesHalf( [Random(0, 1000, 100, Distinct = true)] int length) { //Arrange var solver = new NaiveKnapsackSolver(); var items = Enumerable.Repeat(42, 2 * length).ToArray(); var expectedResult = Enumerable.Repeat(42, length); //Act var result = solver.Solve(items, length, _ => 1, _ => 1); //Assert Assert.That(result, Is.EqualTo(expectedResult)); } } ================================================ FILE: Algorithms.Tests/LinearAlgebra/Distances/ChebyshevTests.cs ================================================ using Algorithms.LinearAlgebra.Distances; namespace Algorithms.Tests.LinearAlgebra.Distances; public class ChebyshevTests { [TestCase(new[] { 1.0, 1.0 }, new[] { 2.0, 2.0 }, 1.0)] [TestCase(new[] { 1.0, 1.0, 9.0 }, new[] { 2.0, 2.0, -5.2 }, 14.2)] [TestCase(new[] { 1.0, 2.0, 3.0 }, new[] { 1.0, 2.0, 3.0 }, 0.0)] [TestCase(new[] { 1.0, 2.0, 3.0, 4.0 }, new[] { 1.75, 2.25, -3.0, 0.5 }, 6.0)] public void DistanceTest(double[] point1, double[] point2, double expectedDistance) { Chebyshev.Distance(point1, point2).Should().BeApproximately(expectedDistance, 0.01); } [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0 })] [TestCase(new[] { 1.0 }, new[] { 1.0, 2.0, 3.0 })] public void DistanceThrowsArgumentExceptionOnDifferentPointDimensions(double[] point1, double[] point2) { Action action = () => Chebyshev.Distance(point1, point2); action.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/LinearAlgebra/Distances/EuclideanTests.cs ================================================ using Algorithms.LinearAlgebra.Distances; namespace Algorithms.Tests.LinearAlgebra.Distances; public static class EuclideanTests { /// /// Test the result given by Euclidean distance function. /// /// Origin point. /// Target point. /// Expected result. [TestCase(new[] { 1.5 }, new[] { -1.0 }, 2.5)] [TestCase(new[] { 7.0, 4.0, 3.0 }, new[] { 17.0, 6.0, 2.0 }, 10.247)] public static void DistanceTest(double[] point1, double[] point2, double expectedResult) { Euclidean.Distance(point1, point2).Should().BeApproximately(expectedResult, 0.01); } /// /// Throws ArgumentException if two different dimension arrays are given. /// /// First point of N dimensions. /// Second point of M dimensions, M != N. [TestCase(new[] { 7.0, 4.5 }, new[] { -3.0 })] [TestCase(new[] { 12.0 }, new[] { 1.5, 7.0, 3.2 })] public static void DistanceThrowsArgumentExceptionOnDifferentPointDimensions(double[] point1, double[] point2) { Action action = () => Euclidean.Distance(point1, point2); action.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/LinearAlgebra/Distances/ManhattanTests.cs ================================================ using Algorithms.LinearAlgebra.Distances; namespace Algorithms.Tests.LinearAlgebra.Distances; public class ManhattanTests { /// /// Test the result given by Manhattan distance function. /// /// Origin point. /// Target point. /// Expected result. [TestCase(new[] { 1.5 }, new[] { -1.0 }, 2.5)] [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0, 5.0 }, 5)] [TestCase(new[] { 1.0, 2.0, 3.0 }, new[] { 1.0, 2.0, 3.0 }, 0)] [TestCase(new[] { 1.0, 2.0, 3.0, 4.0 }, new[] { 1.75, 2.25, -3.0, 0.5 }, 10.5)] public void DistanceTest(double[] point1, double[] point2, double expectedDistance) { Manhattan.Distance(point1, point2).Should().BeApproximately(expectedDistance, 0.01); } /// /// Test that it throws ArgumentException if two different dimension arrays are given. /// /// First point of N dimensions. /// Second point of M dimensions, M != N. [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0 })] [TestCase(new[] { 1.0 }, new[] { 1.0, 2.0, 3.0 })] public void DistanceThrowsArgumentExceptionOnDifferentPointDimensions(double[] point1, double[] point2) { Action action = () => Manhattan.Distance(point1, point2); action.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/LinearAlgebra/Distances/MinkowskiTests.cs ================================================ using Algorithms.LinearAlgebra.Distances; namespace Algorithms.Tests.LinearAlgebra.Distances; public class MinkowskiTests { [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0, 5.0 }, 1, 5.0)] // Simulate Manhattan condition [TestCase(new[] { 7.0, 4.0, 3.0 }, new[] { 17.0, 6.0, 2.0 }, 2, 10.247)] // Simulate Euclidean condition [TestCase(new[] { 1.0, 2.0, 3.0, 4.0 }, new[] { 1.75, 2.25, -3.0, 0.5 }, 20, 6.0)] // Simulate Chebyshev condition [TestCase(new[] { 1.0, 1.0, 9.0 }, new[] { 2.0, 2.0, -5.2 }, 3, 14.2)] [TestCase(new[] { 1.0, 2.0, 3.0 }, new[] { 1.0, 2.0, 3.0 }, 5, 0.0)] public void DistanceTest(double[] point1, double[] point2, int order, double expectedDistance) { Minkowski.Distance(point1, point2, order).Should().BeApproximately(expectedDistance, 0.01); } [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0 }, 2)] [TestCase(new[] { 1.0 }, new[] { 1.0, 2.0, 3.0 }, 1)] [TestCase(new[] { 1.0, 1.0 }, new[] { 2.0, 2.0 }, 0)] public void DistanceThrowsArgumentExceptionOnInvalidInput(double[] point1, double[] point2, int order) { Action action = () => Minkowski.Distance(point1, point2, order); action.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/LinearAlgebra/Eigenvalue/PowerIterationTests.cs ================================================ using Algorithms.LinearAlgebra.Eigenvalue; namespace Algorithms.Tests.LinearAlgebra.Eigenvalue; public class PowerIterationTests { private static readonly object[] DominantVectorTestCases = [ new object[] { 3.0, new[] { 0.7071039, 0.70710966 }, new[,] { { 2.0, 1.0 }, { 1.0, 2.0 } }, }, new object[] { 4.235889, new[] { 0.91287093, 0.40824829 }, new[,] { { 2.0, 5.0 }, { 1.0, 2.0 } }, }, ]; private readonly double epsilon = Math.Pow(10, -5); [Test] public void Dominant_ShouldThrowArgumentException_WhenSourceMatrixIsNotSquareShaped() { // Arrange var source = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, { 0, 0, 0 } }; // Act Action action = () => PowerIteration.Dominant(source, StartVector(source.GetLength(0)), epsilon); // Assert action.Should().Throw().WithMessage("The source matrix is not square-shaped."); } [Test] public void Dominant_ShouldThrowArgumentException_WhenStartVectorIsNotSameSizeAsMatrix() { // Arrange var source = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; var startVector = new double[] { 1, 0, 0, 0 }; // Act Action action = () => PowerIteration.Dominant(source, startVector, epsilon); // Assert action.Should().Throw() .WithMessage("The length of the start vector doesn't equal the size of the source matrix."); } [TestCaseSource(nameof(DominantVectorTestCases))] public void Dominant_ShouldCalculateDominantEigenvalueAndEigenvector( double eigenvalue, double[] eigenvector, double[,] source) { // Act var (actualEigVal, actualEigVec) = PowerIteration.Dominant(source, StartVector(source.GetLength(0)), epsilon); // Assert actualEigVal.Should().BeApproximately(eigenvalue, epsilon); actualEigVec.Magnitude().Should().BeApproximately(eigenvector.Magnitude(), epsilon); } private double[] StartVector(int length) => new Random(111111).NextVector(length); } ================================================ FILE: Algorithms.Tests/MachineLearning/KNearestNeighborsTests.cs ================================================ using NUnit.Framework; using Algorithms.MachineLearning; using System; namespace Algorithms.Tests.MachineLearning; [TestFixture] public class KNearestNeighborsTests { [Test] public void Constructor_InvalidK_ThrowsException() { Assert.Throws(() => new KNearestNeighbors(0)); } [Test] public void AddSample_NullFeatures_ThrowsException() { var knn = new KNearestNeighbors(3); double[]? features = null; Assert.Throws(() => knn.AddSample(features!, "A")); } [Test] public void Predict_NoTrainingData_ThrowsException() { var knn = new KNearestNeighbors(1); Assert.Throws(() => knn.Predict(new[] { 1.0 })); } [Test] public void Predict_NullFeatures_ThrowsException() { var knn = new KNearestNeighbors(1); knn.AddSample(new[] { 1.0 }, "A"); double[]? features = null; Assert.Throws(() => knn.Predict(features!)); } [Test] public void EuclideanDistance_DifferentLengths_ThrowsException() { Assert.Throws(() => KNearestNeighbors.EuclideanDistance(new[] { 1.0 }, new[] { 1.0, 2.0 })); } [Test] public void EuclideanDistance_CorrectResult() { double[] a = { 1.0, 2.0 }; double[] b = { 4.0, 6.0 }; double expected = 5.0; double actual = KNearestNeighbors.EuclideanDistance(a, b); Assert.That(actual, Is.EqualTo(expected).Within(1e-9)); } [Test] public void Predict_SingleNeighbor_CorrectLabel() { var knn = new KNearestNeighbors(1); knn.AddSample(new[] { 1.0, 2.0 }, "A"); knn.AddSample(new[] { 3.0, 4.0 }, "B"); var label = knn.Predict(new[] { 1.1, 2.1 }); Assert.That(label, Is.EqualTo("A")); } [Test] public void Predict_MajorityVote_CorrectLabel() { var knn = new KNearestNeighbors(3); knn.AddSample(new[] { 0.0, 0.0 }, "A"); knn.AddSample(new[] { 0.1, 0.1 }, "A"); knn.AddSample(new[] { 1.0, 1.0 }, "B"); var label = knn.Predict(new[] { 0.05, 0.05 }); Assert.That(label, Is.EqualTo("A")); } [Test] public void Predict_TieBreaker_ReturnsConsistentLabel() { var knn = new KNearestNeighbors(2); knn.AddSample(new[] { 0.0, 0.0 }, "A"); knn.AddSample(new[] { 1.0, 1.0 }, "B"); var label = knn.Predict(new[] { 0.5, 0.5 }); Assert.That(label, Is.EqualTo("A")); } } ================================================ FILE: Algorithms.Tests/MachineLearning/LinearRegressionTests.cs ================================================ using Algorithms.MachineLearning; namespace Algorithms.Tests.MachineLearning; /// /// Unit tests for the LinearRegression class. /// public class LinearRegressionTests { [Test] public void Fit_ThrowsException_WhenInputIsNull() { var lr = new LinearRegression(); Assert.Throws(() => lr.Fit(null!, new List { 1 })); Assert.Throws(() => lr.Fit(new List { 1 }, null!)); } [Test] public void Fit_ThrowsException_WhenInputIsEmpty() { var lr = new LinearRegression(); Assert.Throws(() => lr.Fit(new List(), new List())); } [Test] public void Fit_ThrowsException_WhenInputLengthsDiffer() { var lr = new LinearRegression(); Assert.Throws(() => lr.Fit(new List { 1 }, new List { 2, 3 })); } [Test] public void Fit_ThrowsException_WhenXVarianceIsZero() { var lr = new LinearRegression(); Assert.Throws(() => lr.Fit(new List { 1, 1, 1 }, new List { 2, 3, 4 })); } [Test] public void Predict_ThrowsException_IfNotFitted() { var lr = new LinearRegression(); Assert.Throws(() => lr.Predict(1.0)); Assert.Throws(() => lr.Predict(new List { 1.0 })); } [Test] public void FitAndPredict_WorksForSimpleData() { // y = 2x + 1 var x = new List { 1, 2, 3, 4 }; var y = new List { 3, 5, 7, 9 }; var lr = new LinearRegression(); lr.Fit(x, y); Assert.That(lr.IsFitted, Is.True); Assert.That(lr.Intercept, Is.EqualTo(1.0).Within(1e-6)); Assert.That(lr.Slope, Is.EqualTo(2.0).Within(1e-6)); Assert.That(lr.Predict(5), Is.EqualTo(11.0).Within(1e-6)); } [Test] public void FitAndPredict_WorksForNegativeSlope() { // y = -3x + 4 var x = new List { 0, 1, 2 }; var y = new List { 4, 1, -2 }; var lr = new LinearRegression(); lr.Fit(x, y); Assert.That(lr.Intercept, Is.EqualTo(4.0).Within(1e-6)); Assert.That(lr.Slope, Is.EqualTo(-3.0).Within(1e-6)); Assert.That(lr.Predict(3), Is.EqualTo(-5.0).Within(1e-6)); } [Test] public void Predict_List_WorksCorrectly() { var x = new List { 1, 2, 3 }; var y = new List { 2, 4, 6 }; var lr = new LinearRegression(); lr.Fit(x, y); // y = 2x var predictions = lr.Predict(new List { 4, 5 }); Assert.That(predictions[0], Is.EqualTo(8.0).Within(1e-6)); Assert.That(predictions[1], Is.EqualTo(10.0).Within(1e-6)); } } ================================================ FILE: Algorithms.Tests/MachineLearning/LogisticRegressionTests.cs ================================================ using NUnit.Framework; using Algorithms.MachineLearning; using System; namespace Algorithms.Tests.MachineLearning; [TestFixture] public class LogisticRegressionTests { [Test] public void Fit_ThrowsOnEmptyInput() { var model = new LogisticRegression(); Assert.Throws(() => model.Fit(Array.Empty(), Array.Empty())); } [Test] public void Fit_ThrowsOnMismatchedLabels() { var model = new LogisticRegression(); double[][] X = { new double[] { 1, 2 } }; int[] y = { 1, 0 }; Assert.Throws(() => model.Fit(X, y)); } [Test] public void FitAndPredict_WorksOnSimpleData() { // Simple AND logic double[][] X = { new[] { 0.0, 0.0 }, new[] { 0.0, 1.0 }, new[] { 1.0, 0.0 }, new[] { 1.0, 1.0 } }; int[] y = { 0, 0, 0, 1 }; var model = new LogisticRegression(); model.Fit(X, y, epochs: 2000, learningRate: 0.1); Assert.That(model.Predict(new double[] { 0, 0 }), Is.EqualTo(0)); Assert.That(model.Predict(new double[] { 0, 1 }), Is.EqualTo(0)); Assert.That(model.Predict(new double[] { 1, 0 }), Is.EqualTo(0)); Assert.That(model.Predict(new double[] { 1, 1 }), Is.EqualTo(1)); } [Test] public void PredictProbability_ThrowsOnFeatureMismatch() { var model = new LogisticRegression(); double[][] X = { new double[] { 1, 2 } }; int[] y = { 1 }; model.Fit(X, y); Assert.Throws(() => model.PredictProbability(new double[] { 1 })); } [Test] public void FeatureCount_ReturnsCorrectValue() { var model = new LogisticRegression(); double[][] X = { new double[] { 1, 2, 3 } }; int[] y = { 1 }; model.Fit(X, y); Assert.That(model.FeatureCount, Is.EqualTo(3)); } } ================================================ FILE: Algorithms.Tests/ModularArithmetic/ChineseRemainderTheoremTest.cs ================================================ using Algorithms.ModularArithmetic; namespace Algorithms.Tests.ModularArithmetic; public static class ChineseRemainderTheoremTest { [Test] public static void TestCompute1() { var expected = 43L; // Act var x = ChineseRemainderTheorem.Compute(new List { 1L, 1L, 3L, 1L }, new List { 2L, 3L, 5L, 7L }); // Assert Assert.That(x, Is.EqualTo(expected)); } [Test] public static void TestCompute2() { var expected = 100L; // Act var x = ChineseRemainderTheorem.Compute(new List { 0L, 0L, 2L, 1L, 1L }, new List { 2L, 5L, 7L, 9L, 11L }); // Assert Assert.That(x, Is.EqualTo(expected)); } [Test] public static void TestCompute3() { var expected = 13L; // Act var x = ChineseRemainderTheorem.Compute(new List { 1L, 4L, 13L }, new List { 4L, 9L, 25L }); // Assert Assert.That(x, Is.EqualTo(expected)); } [Test] public static void TestCompute_RequirementsNotMet_ArgumentLengthDifferent() { // Act void Act() => ChineseRemainderTheorem.Compute(new List(), new List { 5L }); // Assert _ = Assert.Throws(Act); } [Test] public static void TestCompute_RequirementsNotMet_NTooSmall() { foreach (var n in new List { long.MinValue, -1L, 0L, 1L }) { // Act void Act() => ChineseRemainderTheorem.Compute(new List { 1L }, new List { n }); // Assert _ = Assert.Throws(Act); } } [Test] public static void TestCompute_RequirementsNotMet_ATooSmall() { foreach (var a in new List { long.MinValue, -2L, -1L }) { // Act void Act() => ChineseRemainderTheorem.Compute(new List { a }, new List { 3L }); // Assert _ = Assert.Throws(Act); } } [Test] public static void TestCompute_RequirementsNotMet_NNotCoprime() { foreach (var n in new List { 3L, 9L, 15L, 27L }) { // Act void Act() => ChineseRemainderTheorem.Compute(new List { 1L, 1L, 1L, 1L, 1L }, new List { 2L, 3L, 5L, 7L, n }); // Assert _ = Assert.Throws(Act); } } [Test] public static void TestCompute_BigInteger_1() { var expected = new BigInteger(43); // Act var x = ChineseRemainderTheorem.Compute( new List { BigInteger.One, BigInteger.One, new BigInteger(3), BigInteger.One }, new List { new BigInteger(2), new BigInteger(3), new BigInteger(5), new BigInteger(7) } ); // Assert Assert.That(x, Is.EqualTo(expected)); } [Test] public static void TestCompute_BigInteger_2() { var expected = new BigInteger(100); // Act var x = ChineseRemainderTheorem.Compute( new List { BigInteger.Zero, BigInteger.Zero, new BigInteger(2), BigInteger.One, BigInteger.One }, new List { new BigInteger(2), new BigInteger(5), new BigInteger(7), new BigInteger(9), new BigInteger(11) } ); // Assert Assert.That(x, Is.EqualTo(expected)); } [Test] public static void TestCompute_BigInteger_3() { var expected = new BigInteger(13); // Act var x = ChineseRemainderTheorem.Compute( new List { BigInteger.One, new BigInteger(4), new BigInteger(13) }, new List { new BigInteger(4), new BigInteger(9), new BigInteger(25) } ); // Assert Assert.That(x, Is.EqualTo(expected)); } [Test] public static void TestCompute_BigInteger_RequirementsNotMet_ArgumentLengthDifferent() { // Act void Act() => ChineseRemainderTheorem.Compute(new List(), new List { new BigInteger(5) }); // Assert _ = Assert.Throws(Act); } [Test] public static void TestCompute_BigInteger_RequirementsNotMet_NTooSmall() { foreach (var n in new List { new BigInteger(long.MinValue), BigInteger.MinusOne, BigInteger.Zero, BigInteger.One }) { // Act void Act() => ChineseRemainderTheorem.Compute(new List { BigInteger.One }, new List { n }); // Assert _ = Assert.Throws(Act); } } [Test] public static void TestCompute_BigInteger_RequirementsNotMet_ATooSmall() { foreach (var a in new List { new BigInteger(long.MinValue), new BigInteger(-2), BigInteger.MinusOne }) { // Act void Act() => ChineseRemainderTheorem.Compute(new List { a }, new List { new BigInteger(3) }); // Assert _ = Assert.Throws(Act); } } [Test] public static void TestCompute_BigInteger_RequirementsNotMet_NNotCoprime() { foreach (var n in new List { new BigInteger(3), new BigInteger(9), new BigInteger(15), new BigInteger(27) }) { // Act void Act() => ChineseRemainderTheorem.Compute( new List { BigInteger.One, BigInteger.One, BigInteger.One, BigInteger.One, BigInteger.One }, new List { new BigInteger(2), new BigInteger(3), new BigInteger(5), new BigInteger(7), n } ); // Assert _ = Assert.Throws(Act); } } } ================================================ FILE: Algorithms.Tests/ModularArithmetic/ExtendedEuclideanAlgorithmTest.cs ================================================ using Algorithms.ModularArithmetic; namespace Algorithms.Tests.ModularArithmetic; public static class ExtendedEuclideanAlgorithmTest { [TestCase(240, 46, 2, -9, 47)] [TestCase(46, 240, 2, 47, -9)] [TestCase(2, 3, 1, -1, 1)] [TestCase(1, 1, 1, 0, 1)] [TestCase(13, 17, 1, 4, -3)] [TestCase(0, 17, 17, 0, 1)] [TestCase(17, 0, 17, 1, 0)] [TestCase(17, 17, 17, 0, 1)] [TestCase(2 * 17, 17, 17, 0, 1)] [TestCase(0, 0, 0, 1, 0)] [TestCase(2 * 13 * 17, 4 * 9 * 13, 2 * 13, -1, 1)] public static void TestCompute(long a, long b, long expectedGCD, long expectedBezoutOfA, long expectedBezoutOfB) { // Act var eeaResult = ExtendedEuclideanAlgorithm.Compute(a, b); // Assert Assert.That(eeaResult.Gcd, Is.EqualTo(expectedGCD)); Assert.That(eeaResult.BezoutA, Is.EqualTo(expectedBezoutOfA)); Assert.That(eeaResult.BezoutB, Is.EqualTo(expectedBezoutOfB)); } [TestCase(240, 46, 2, -9, 47)] [TestCase(46, 240, 2, 47, -9)] [TestCase(2, 3, 1, -1, 1)] [TestCase(1, 1, 1, 0, 1)] [TestCase(13, 17, 1, 4, -3)] [TestCase(0, 17, 17, 0, 1)] [TestCase(17, 0, 17, 1, 0)] [TestCase(17, 17, 17, 0, 1)] [TestCase(2 * 17, 17, 17, 0, 1)] [TestCase(0, 0, 0, 1, 0)] [TestCase(2 * 13 * 17, 4 * 9 * 13, 2 * 13, -1, 1)] public static void TestCompute_BigInteger(long a, long b, long expectedGCD, long expectedBezoutOfA, long expectedBezoutOfB) { // Act var eeaResult = ExtendedEuclideanAlgorithm.Compute(new BigInteger(a), new BigInteger(b)); // Assert Assert.That(eeaResult.Gcd, Is.EqualTo(new BigInteger(expectedGCD))); Assert.That(eeaResult.BezoutA, Is.EqualTo(new BigInteger(expectedBezoutOfA))); Assert.That(eeaResult.BezoutB, Is.EqualTo(new BigInteger(expectedBezoutOfB))); } } ================================================ FILE: Algorithms.Tests/ModularArithmetic/ModularMultiplicativeInverseTest.cs ================================================ using Algorithms.ModularArithmetic; namespace Algorithms.Tests.ModularArithmetic; public static class ModularMultiplicativeInverseTest { [TestCase(2, 3, 2)] [TestCase(1, 1, 0)] [TestCase(13, 17, 4)] public static void TestCompute(long a, long n, long expected) { // Act var inverse = ModularMultiplicativeInverse.Compute(a, n); // Assert Assert.That(inverse, Is.EqualTo(expected)); } [TestCase(46, 240)] [TestCase(0, 17)] [TestCase(17, 0)] [TestCase(17, 17)] [TestCase(0, 0)] [TestCase(2 * 13 * 17, 4 * 9 * 13)] public static void TestCompute_Irrevertible(long a, long n) { // Act void Act() => ModularMultiplicativeInverse.Compute(a, n); // Assert _ = Assert.Throws(Act); } [TestCase(2, 3, 2)] [TestCase(1, 1, 0)] [TestCase(13, 17, 4)] public static void TestCompute_BigInteger(long a, long n, long expected) { // Act var inverse = ModularMultiplicativeInverse.Compute(new BigInteger(a), new BigInteger(n)); // Assert Assert.That(inverse, Is.EqualTo(new BigInteger(expected))); } [TestCase(46, 240)] [TestCase(0, 17)] [TestCase(17, 0)] [TestCase(17, 17)] [TestCase(0, 0)] [TestCase(2 * 13 * 17, 4 * 9 * 13)] public static void TestCompute_BigInteger_Irrevertible(long a, long n) { // Act void Act() => ModularMultiplicativeInverse.Compute(new BigInteger(a), new BigInteger(n)); // Assert _ = Assert.Throws(Act); } } ================================================ FILE: Algorithms.Tests/Numeric/AbsTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class AbsTests { [TestCase(0, 0)] [TestCase(34, 34)] [TestCase(-100000000000.0d, 100000000000.0d)] [TestCase(-3, 3)] [TestCase(-3.1443123d, 3.1443123d)] public static void GetsAbsVal(T inputNum, T expected) where T : INumber { // Act var result = Abs.AbsVal(inputNum); // Assert Assert.That(result, Is.EqualTo(expected)); } [TestCase(new[] { -3, -1, 2, -11 }, -11)] [TestCase(new[] { 0, 5, 1, 11 }, 11)] [TestCase(new[] { 3.0, -10.0, -2.0 }, -10.0d)] public static void GetAbsMax(T[] inputNums, T expected) where T : INumber { // Act var result = Abs.AbsMax(inputNums); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public static void AbsMaxThrowsArgumentException() { // Arrange var inputNums = Array.Empty(); // Assert Assert.Throws(() => Abs.AbsMax(inputNums)); } [TestCase(new[] { -3, -1, 2, -11 }, -1)] [TestCase(new[] { -3, -5, 1, -11 }, 1)] [TestCase(new[] { 0, 5, 1, 11 }, 0)] public static void GetAbsMin(T[] inputNums, T expected) where T : INumber { // Act var result = Abs.AbsMin(inputNums); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public static void AbsMinThrowsArgumentException() { // Arrange var inputNums = Array.Empty(); // Assert Assert.Throws(() => Abs.AbsMin(inputNums)); } } ================================================ FILE: Algorithms.Tests/Numeric/AdditionWithoutArithmeticsTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class AdditionWithoutArithmeticTests { [TestCase(3, 5, 8)] [TestCase(13, 5, 18)] [TestCase(-7, 2, -5)] [TestCase(0, -7, -7)] [TestCase(-321, 0, -321)] public static void CalculateAdditionWithoutArithmetic_Test(int first, int second, int expectedResult) { // Act var result = AdditionWithoutArithmetic.CalculateAdditionWithoutArithmetic(first, second); // Assert Assert.That(result, Is.EqualTo(expectedResult)); } } ================================================ FILE: Algorithms.Tests/Numeric/AliquotSumCalculatorTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class AliquotSumCalculatorTests { [TestCase(1, 0)] [TestCase(3, 1)] [TestCase(25, 6)] [TestCase(99, 57)] public static void CalculateSum_SumIsCorrect(int number, int expectedSum) { // Arrange // Act var result = AliquotSumCalculator.CalculateAliquotSum(number); // Assert result.Should().Be(expectedSum); } [TestCase(-2)] public static void CalculateSum_NegativeInput_ExceptionIsThrown(int number) { // Arrange Action act = () => AliquotSumCalculator.CalculateAliquotSum(number); // Assert act.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/Numeric/AmicableNumbersTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class AmicableNumbersTest { [TestCase(220, 284)] [TestCase(1184, 1210)] [TestCase(2620, 2924)] [TestCase(5020, 5564)] public static void AmicableNumbersChecker_Test(int x, int y) { // Arrange // Act var result = AmicableNumbersChecker.AreAmicableNumbers(x, y); // Assert Assert.That(result, Is.True); } } ================================================ FILE: Algorithms.Tests/Numeric/AutomorphicNumberTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public class AutomorphicNumberTests { [TestCase(1)] [TestCase(5)] [TestCase(6)] [TestCase(25)] [TestCase(76)] [TestCase(376)] [TestCase(625)] [TestCase(9376)] [TestCase(90625)] [TestCase(109376)] public void TestAutomorphicNumbers(int number) { Assert.That(AutomorphicNumber.IsAutomorphic(number), Is.True); } [TestCase(2)] [TestCase(3)] [TestCase(7)] [TestCase(18)] [TestCase(79)] [TestCase(356)] [TestCase(623)] [TestCase(9876)] [TestCase(90635)] [TestCase(119376)] [TestCase(891625)] [TestCase(2990625)] [TestCase(7209376)] [TestCase(12891625)] [TestCase(87129396)] public void TestNonAutomorphicNumbers(int number) { Assert.That(AutomorphicNumber.IsAutomorphic(number), Is.False); } [TestCase(0)] [TestCase(-1)] public void TestInvalidAutomorphicNumbers(int number) { Assert.Throws(Is.TypeOf() .And.Message.EqualTo($"An automorphic number must always be positive."), delegate { AutomorphicNumber.IsAutomorphic(number); }); } [TestCase(1, 100)] public void TestAutomorphicNumberSequence(int lower, int upper) { List automorphicList = [1, 5, 6, 25, 76]; Assert.That(AutomorphicNumber.GetAutomorphicNumbers(lower, upper), Is.EqualTo(automorphicList)); } [TestCase(8, 12)] public void TestNoAutomorphicNumberInTheSequence(int lower, int upper) { List automorphicList = []; Assert.That(AutomorphicNumber.GetAutomorphicNumbers(lower, upper), Is.EqualTo(automorphicList)); } [TestCase(25, 25)] public void TestAutomorphicNumberSequenceSameBounds(int lower, int upper) { List automorphicList = [25]; Assert.That(AutomorphicNumber.GetAutomorphicNumbers(lower, upper), Is.EqualTo(automorphicList)); } [TestCase(-1, 1)] [TestCase(0, 1)] public void TestAutomorphicNumberSequenceInvalidLowerBound(int lower, int upper) { Assert.Throws(Is.TypeOf() .And.Message.EqualTo($"Lower bound must be greater than 0."), delegate { AutomorphicNumber.GetAutomorphicNumbers(lower, upper); }); } [TestCase(1, -1)] [TestCase(10, -1)] public void TestAutomorphicNumberSequenceInvalidUpperBound(int lower, int upper) { Assert.Throws(Is.TypeOf() .And.Message.EqualTo($"Upper bound must be greater than 0."), delegate { AutomorphicNumber.GetAutomorphicNumbers(lower, upper); }); } [TestCase(25, 2)] public void TestAutomorphicNumberSequenceReversedBounds(int lower, int upper) { Assert.Throws(Is.TypeOf() .And.Message.EqualTo($"The lower bound must be less than or equal to the upper bound."), delegate { AutomorphicNumber.GetAutomorphicNumbers(lower, upper); }); } } ================================================ FILE: Algorithms.Tests/Numeric/BinomialCoefficientTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class BinomialCoefficientTests { [TestCase(4, 2, 6)] [TestCase(7, 3, 35)] public static void CalculateFromPairs(int n, int k, int expected) { // Arrange // Act var result = BinomialCoefficient.Calculate(new BigInteger(n), new BigInteger(k)); // Assert Assert.That(result, Is.EqualTo(new BigInteger(expected))); } [TestCase(3, 7)] public static void TeoremCalculateThrowsException(int n, int k) { // Arrange // Act // Assert _ = Assert.Throws(() => BinomialCoefficient.Calculate(new BigInteger(n), new BigInteger(k))); } } ================================================ FILE: Algorithms.Tests/Numeric/CeilTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class CeilTests { [TestCase(0.0, 0)] [TestCase(1.1, 2)] [TestCase(1.9, 2)] [TestCase(1.0, 1)] [TestCase(-1.1, -1)] [TestCase(-1.9, -1)] [TestCase(-1.0, -1)] [TestCase(1000000000.1, 1000000001)] [TestCase(1, 1)] public static void GetsCeilVal(T inputNum, T expected) where T : INumber { // Act var result = Ceil.CeilVal(inputNum); // Assert Assert.That(result, Is.EqualTo(expected)); } } ================================================ FILE: Algorithms.Tests/Numeric/Decomposition/LUTests.cs ================================================ using Algorithms.Numeric.Decomposition; namespace Algorithms.Tests.Numeric.Decomposition; public class LuTests { private readonly double epsilon = Math.Pow(10, -6); [Test] public void DecomposeIdentityMatrix() { // Arrange var identityMatrix = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; var expectedLower = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; var expectedUpper = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; // Act (double[,] lower, double[,] upper) = Lu.Decompose(identityMatrix); // Assert Assert.That(lower, Is.EqualTo(expectedLower)); Assert.That(upper, Is.EqualTo(expectedUpper)); Assert.That(identityMatrix, Is.EqualTo(lower.Multiply(upper))); } [Test] public void DecomposeMatrix_Case3X3() { // Arrange var source = new double[,] { { 2, 1, 4 }, { 7, 1, 1 }, { 4, 2, 9 } }; var expectedLower = new[,] { { 1, 0, 0 }, { 3.5, 1, 0 }, { 2, 0, 1 } }; var expectedUpper = new[,] { { 2, 1, 4 }, { 0, -2.5, -13 }, { 0, 0, 1 } }; // Act (double[,] lower, double[,] upper) = Lu.Decompose(source); // Assert Assert.That(lower, Is.EqualTo(expectedLower)); Assert.That(upper, Is.EqualTo(expectedUpper)); Assert.That(source, Is.EqualTo(lower.Multiply(upper))); } [Test] public void DecomposeMatrix_Case4X4() { // Arrange var source = new[,] { { 1, 2, 4.5, 7 }, { 3, 8, 0.5, 2 }, { 2, 6, 4, 1.5 }, { 4, 14, 2, 10.5 } }; var expectedLower = new[,] { { 1, 0, 0, 0 }, { 3, 1, 0, 0 }, { 2, 1, 1, 0 }, { 4, 3, 2.875, 1 } }; var expectedUpper = new[,] { { 1, 2, 4.5, 7 }, { 0, 2, -13, -19 }, { 0, 0, 8, 6.5 }, { 0, 0, 0, 20.8125 } }; // Act (double[,] lower, double[,] upper) = Lu.Decompose(source); // Assert Assert.That(lower, Is.EqualTo(expectedLower)); Assert.That(upper, Is.EqualTo(expectedUpper)); Assert.That(source, Is.EqualTo(lower.Multiply(upper))); } [Test] public void FailOnDecomposeNonSquareMatrix() { // Arrange var nonSquareMatrix = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, { 0, 0, 0 } }; // Act void Act(double[,] source) => Lu.Decompose(source); // Assert Assert.Throws(() => Act(nonSquareMatrix)); } [Test] public void EliminateIdentityEquation() { // Arrange var identityMatrix = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; var coefficients = new double[] { 1, 2, 3 }; // Act var solution = Lu.Eliminate(identityMatrix, coefficients); // Assert Assert.That(solution, Is.EqualTo(coefficients)); } [Test] public void EliminateEquation_Case3X3() { // Arrange var source = new double[,] { { 2, 1, -1 }, { -3, -1, 2 }, { -2, 1, 2 } }; var coefficients = new double[] { 8, -11, -3 }; var expectedSolution = new double[] { 2, 3, -1 }; // Act var solution = Lu.Eliminate(source, coefficients); // Assert Assert.That(VectorMembersAreEqual(expectedSolution, solution), Is.True); } [Test] public void EliminateEquation_Case4X4() { // Arrange var source = new[,] { { 1.0, 2.0, -3.0, -1.0 }, { 0.0, -3.0, 2.0, 6.0 }, { 0.0, 5.0, -6.0, -2.0 }, { 0.0, -1.0, 8.0, 1.0 }, }; var coefficients = new[] { 0.0, -8.0, 0.0, -8.0 }; var expectedSolution = new[] { -1.0, -2.0, -1.0, -2.0 }; // Act var solution = Lu.Eliminate(source, coefficients); // Assert Assert.That(VectorMembersAreEqual(expectedSolution, solution), Is.True); } [Test] public void FailOnEliminateEquationWithNonSquareMatrix() { // Arrange var nonSquareMatrix = new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, { 0, 0, 0 } }; var coefficients = new double[] { 1, 2, 3, 4 }; // Act void Act(double[,] source, double[] c) => Lu.Eliminate(source, c); // Assert Assert.Throws(() => Act(nonSquareMatrix, coefficients)); } private bool VectorMembersAreEqual(double[] expected, double[] actual) => expected .Zip(actual, (e, a) => new { Expected = e, Actual = a }) .All(pair => Math.Abs(pair.Expected - pair.Actual) < epsilon); } ================================================ FILE: Algorithms.Tests/Numeric/Decomposition/MaclaurinTests.cs ================================================ using Algorithms.Numeric.Series; namespace Algorithms.Tests.Numeric.Decomposition; public class MaclaurinTests { [TestCase(0.01, 3, 0.01)] [TestCase(1, 7, 0.001)] [TestCase(-1.2, 7, 0.001)] public void Exp_TermsForm_ValidCases(double point, int terms, double expectedError) { // Arrange var expected = Math.Exp(point); // Act var actual = Maclaurin.Exp(point, terms); // Assert Assert.That(Math.Abs(expected - actual) < expectedError, Is.True); } [Test] public void Exp_TermsForm_InvalidCase() => Assert.Throws(() => Maclaurin.Exp(0, -1)); [TestCase(0, 1, 0.001)] [TestCase(1, 7, 0.001)] [TestCase(1.57, 7, 0.001)] [TestCase(3.14, 7, 0.001)] public void Sin_TermsForm_ValidCases(double point, int terms, double expectedError) { // Arrange var expected = Math.Sin(point); // Act var actual = Maclaurin.Sin(point, terms); // Assert Assert.That(Math.Abs(expected - actual) < expectedError, Is.True); } [Test] public void Sin_TermsForm_InvalidCase() => Assert.Throws(() => Maclaurin.Sin(0, -1)); [TestCase(0, 1, 0.001)] [TestCase(1, 7, 0.001)] [TestCase(1.57, 7, 0.001)] [TestCase(3.14, 7, 0.001)] public void Cos_TermsForm_ValidCases(double point, int terms, double expectedError) { // Arrange var expected = Math.Cos(point); // Act var actual = Maclaurin.Cos(point, terms); // Assert Assert.That(Math.Abs(expected - actual) < expectedError, Is.True); } [Test] public void Cos_TermsForm_InvalidCase() => Assert.Throws(() => Maclaurin.Cos(0, -1)); [TestCase(0.1, 0.001)] [TestCase(0.1, 0.00001)] [TestCase(2.1, 0.001)] [TestCase(-1.2, 0.001)] public void Exp_ErrorForm_ValidCases(double point, double error) { // Arrange var expected = Math.Exp(point); // Act var actual = Maclaurin.Exp(point, error); // Assert Assert.That(Math.Abs(expected - actual) < error, Is.True); } [TestCase(0.0)] [TestCase(1.0)] public void Exp_ErrorForm_InvalidCases(double error) => Assert.Throws(() => Maclaurin.Exp(0.0, error)); [TestCase(0, 0.001)] [TestCase(1, 0.00001)] [TestCase(1.57, 0.0001)] [TestCase(3.14, 0.0001)] public void Sin_ErrorForm_ValidCases(double point, double error) { // Arrange var expected = Math.Sin(point); // Act var actual = Maclaurin.Sin(point, error); // Assert Assert.That(Math.Abs(expected - actual) < error, Is.True); } [TestCase(0.0)] [TestCase(1.0)] public void Sin_ErrorForm_InvalidCases(double error) => Assert.Throws(() => Maclaurin.Sin(0.0, error)); [TestCase(0, 0.001)] [TestCase(1, 0.00001)] [TestCase(1.57, 0.0001)] [TestCase(3.14, 0.0001)] public void Cos_ErrorForm_ValidCases(double point, double error) { // Arrange var expected = Math.Cos(point); // Act var actual = Maclaurin.Cos(point, error); // Assert Assert.That(Math.Abs(expected - actual) < error, Is.True); } [TestCase(0.0)] [TestCase(1.0)] public void Cos_ErrorForm_InvalidCases(double error) => Assert.Throws(() => Maclaurin.Cos(0.0, error)); } ================================================ FILE: Algorithms.Tests/Numeric/Decomposition/SVDTests.cs ================================================ using Algorithms.Numeric.Decomposition; namespace Algorithms.Tests.Numeric.Decomposition; public class SvdTests { [Test] public void RandomUnitVector() { var epsilon = 0.0001; // unit vector should have length 1 ThinSvd.RandomUnitVector(10).Magnitude().Should().BeApproximately(1, epsilon); // unit vector with single element should be [-1] or [+1] Math.Abs(ThinSvd.RandomUnitVector(1)[0]).Should().BeApproximately(1, epsilon); // two randomly generated unit vectors should not be equal ThinSvd.RandomUnitVector(10).Should().NotBeEquivalentTo(ThinSvd.RandomUnitVector(10)); } [Test] public void Svd_Decompose() { CheckSvd(new double[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }); CheckSvd(new double[,] { { 1, 2, 3 }, { 4, 5, 6 } }); CheckSvd(new double[,] { { 1, 0, 0, 0, 2 }, { 0, 3, 0, 0, 0 }, { 0, 0, 0, 0, 0 }, { 0, 2, 0, 0, 0 } }); } [Test] public void Svd_Random([Random(3, 10, 5)] int m, [Random(3, 10, 5)] int n) { double[,] matrix = GenerateRandomMatrix(m, n); CheckSvd(matrix); } private void AssertMatrixEqual(double[,] matrix1, double[,] matrix2, double epsilon) { matrix1.GetLength(0).Should().Be(matrix2.GetLength(0)); matrix1.GetLength(1).Should().Be(matrix2.GetLength(1)); for (var i = 0; i < matrix1.GetLength(0); i++) { for (var j = 0; j < matrix1.GetLength(1); j++) { Assert.That(matrix1[i, j], Is.EqualTo(matrix2[i, j]).Within(epsilon), $"At index ({i}, {j})"); } } } private double[,] GenerateRandomMatrix(int m, int n) { double[,] result = new double[m, n]; Random random = new(); for (var i = 0; i < m; i++) { for (var j = 0; j < n; j++) { result[i, j] = random.NextDouble() - 0.5; } } return result; } private void CheckSvd(double[,] testMatrix) { var epsilon = 1E-6; double[,] u; double[,] v; double[] s; (u, s, v) = ThinSvd.Decompose(testMatrix, 1e-6 * epsilon, 1000); for (var i = 1; i < s.Length; i++) { // singular values should be arranged from greatest to smallest // but there are rounding errors (s[i] - s[i - 1]).Should().BeLessThan(1); } for (var i = 0; i < u.GetLength(1); i++) { double[] extracted = new double[u.GetLength(0)]; // extract a column of u for (var j = 0; j < extracted.Length; j++) { extracted[j] = u[j, i]; } if (s[i] > epsilon) { // if the singular value is non-zero, then the basis vector in u should be a unit vector extracted.Magnitude().Should().BeApproximately(1, epsilon); } else { // if the singular value is zero, then the basis vector in u should be zeroed out extracted.Magnitude().Should().BeApproximately(0, epsilon); } } for (var i = 0; i < v.GetLength(1); i++) { double[] extracted = new double[v.GetLength(0)]; // extract column of v for (var j = 0; j < extracted.Length; j++) { extracted[j] = v[j, i]; } if (s[i] > epsilon) { // if the singular value is non-zero, then the basis vector in v should be a unit vector Assert.That(extracted.Magnitude(), Is.EqualTo(1).Within(epsilon)); } else { // if the singular value is zero, then the basis vector in v should be zeroed out Assert.That(extracted.Magnitude(), Is.EqualTo(0).Within(epsilon)); } } // convert singular values to a diagonal matrix double[,] expanded = new double[s.Length, s.Length]; for (var i = 0; i < s.Length; i++) { expanded[i, i] = s[i]; } // matrix = U * S * V^t, definition of Singular Vector Decomposition AssertMatrixEqual(testMatrix, u.Multiply(expanded).Multiply(v.Transpose()), epsilon); AssertMatrixEqual(testMatrix, u.Multiply(expanded.Multiply(v.Transpose())), epsilon); } } ================================================ FILE: Algorithms.Tests/Numeric/DoubleFactorialTests.cs ================================================ using System.Numerics; using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; /// /// Tests for the DoubleFactorial class methods. /// public static class DoubleFactorialTests { /// /// Tests the calculation of double factorial for non-negative integers. /// Includes base cases (0, 1), odd numbers (5, 11), even numbers (6, 12), /// and a large number (20) that benefits from BigInteger. /// /// The number N to calculate N!!. /// The expected result as a string (for BigInteger parsing). [TestCase(0, "1")] // Base Case: 0!! = 1 [TestCase(1, "1")] // Base Case: 1!! = 1 [TestCase(5, "15")] // Odd: 5 * 3 * 1 = 15 [TestCase(6, "48")] // Even: 6 * 4 * 2 = 48 [TestCase(11, "10395")]// Larger Odd: 11 * 9 * 7 * 5 * 3 * 1 = 10395 [TestCase(12, "46080")] // Larger Even: 12 * 10 * 8 * 6 * 4 * 2 = 46080 [TestCase(20, "3715891200")] // Large Even public static void GetsDoubleFactorial(int input, string expected) { // Arrange BigInteger expectedBigInt = BigInteger.Parse(expected); // Act var result = DoubleFactorial.Calculate(input); // Assert Assert.That(result, Is.EqualTo(expectedBigInt)); } /// /// Tests that calculating double factorial for negative numbers throws an ArgumentException. /// /// A negative integer input. [TestCase(-1)] [TestCase(-5)] [TestCase(-10)] public static void GetsDoubleFactorialExceptionForNegativeNumbers(int num) { // Arrange // Act void Act() => DoubleFactorial.Calculate(num); // Assert _ = Assert.Throws(Act); } } ================================================ FILE: Algorithms.Tests/Numeric/EulerMethodTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class EulerMethodTest { [Test] public static void TestLinearEquation() { Func exampleEquation = (x, _) => x; List points = EulerMethod.EulerFull(0, 4, 0.001, 0, exampleEquation); var yEnd = points[^1][1]; yEnd.Should().BeApproximately(8, 0.01); } [Test] public static void TestExampleWikipedia() { // example from https://en.wikipedia.org/wiki/Euler_method Func exampleEquation = (_, y) => y; List points = EulerMethod.EulerFull(0, 4, 0.0125, 1, exampleEquation); var yEnd = points[^1][1]; yEnd.Should().BeApproximately(53.26, 0.01); } [Test] public static void TestExampleGeeksForGeeks() { // example from https://www.geeksforgeeks.org/euler-method-solving-differential-equation/ // Euler method: y_n+1 = y_n + stepSize * f(x_n, y_n) // differential equation: f(x, y) = x + y + x * y // initial conditions: x_0 = 0; y_0 = 1; stepSize = 0.025 // solution: // y_1 = 1 + 0.025 * (0 + 1 + 0 * 1) = 1.025 // y_2 = 1.025 + 0.025 * (0.025 + 1.025 + 0.025 * 1.025) = 1.051890625 Func exampleEquation = (x, y) => x + y + x * y; List points = EulerMethod.EulerFull(0, 0.05, 0.025, 1, exampleEquation); var y1 = points[1][1]; var y2 = points[2][1]; Assert.That(1.025, Is.EqualTo(y1)); Assert.That(1.051890625, Is.EqualTo(y2)); } [Test] public static void StepsizeIsZeroOrNegative_ThrowsArgumentOutOfRangeException() { Func exampleEquation = (x, _) => x; Assert.Throws(() => EulerMethod.EulerFull(0, 4, 0, 0, exampleEquation)); } [Test] public static void StartIsLargerThanEnd_ThrowsArgumentOutOfRangeException() { Func exampleEquation = (x, _) => x; Assert.Throws(() => EulerMethod.EulerFull(0, -4, 0.1, 0, exampleEquation)); } } ================================================ FILE: Algorithms.Tests/Numeric/FactorialTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class FactorialTests { [TestCase(0, "1")] [TestCase(1, "1")] [TestCase(4, "24")] [TestCase(10, "3628800")] [TestCase(18, "6402373705728000")] public static void GetsFactorial(int input, string expected) { // Arrange BigInteger expectedBigInt = BigInteger.Parse(expected); // Act var result = Factorial.Calculate(input); // Assert Assert.That(result, Is.EqualTo(expectedBigInt)); } [TestCase(-5)] [TestCase(-10)] public static void GetsFactorialExceptionForNegativeNumbers(int num) { // Arrange // Act void Act() => Factorial.Calculate(num); // Assert _ = Assert.Throws(Act); } } ================================================ FILE: Algorithms.Tests/Numeric/Factorization/TrialDivisionFactorizerTests.cs ================================================ using Algorithms.Numeric.Factorization; namespace Algorithms.Tests.Numeric.Factorization; public static class TrialDivisionFactorizerTests { [TestCase(2)] [TestCase(3)] [TestCase(29)] [TestCase(31)] public static void PrimeNumberFactorizationFails(int p) { // Arrange var factorizer = new TrialDivisionFactorizer(); // Act var success = factorizer.TryFactor(p, out _); // Assert Assert.That(success, Is.False); } [TestCase(4, 2)] [TestCase(6, 2)] [TestCase(8, 2)] [TestCase(9, 3)] [TestCase(15, 3)] [TestCase(35, 5)] [TestCase(49, 7)] [TestCase(77, 7)] public static void PrimeNumberFactorizationSucceeds(int n, int expected) { // Arrange var factorizer = new TrialDivisionFactorizer(); // Act var success = factorizer.TryFactor(n, out var factor); // Assert Assert.That(success, Is.True); Assert.That(factor, Is.EqualTo(expected)); } } ================================================ FILE: Algorithms.Tests/Numeric/FloorTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class FloorTests { [TestCase(0.0, 0)] [TestCase(1.1, 1)] [TestCase(1.9, 1)] [TestCase(1.0, 1)] [TestCase(-1.1, -2)] [TestCase(-1.9, -2)] [TestCase(-1.0, -1)] [TestCase(1000000000.1, 1000000000)] [TestCase(1, 1)] public static void GetsFloorVal(T inputNum, T expected) where T : INumber { // Act var result = Floor.FloorVal(inputNum); // Assert Assert.That(result, Is.EqualTo(expected)); } } ================================================ FILE: Algorithms.Tests/Numeric/GaussJordanEliminationTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; /// /// Class for testing Gauss-Jordan Elimination Algorithm. /// public static class GaussJordanEliminationTests { [Test] public static void NonSquaredMatrixThrowsException() { // Arrange var solver = new GaussJordanElimination(); var input = new double[,] { { 2, -1, 5 }, { 0, 2, 1 }, { 3, 17, 7 } }; // Act void Act() => solver.Solve(input); // Assert _ = Assert.Throws(Act); } [Test] public static void UnableToSolveSingularMatrix() { // Arrange var solver = new GaussJordanElimination(); var input = new double[,] { { 0, 0, 0 }, { 0, 0, 0 } }; // Act var result = solver.Solve(input); // Assert Assert.That(result, Is.False); } } ================================================ FILE: Algorithms.Tests/Numeric/GreatestCommonDivisor/BinaryGreatestCommonDivisorFinderTests.cs ================================================ using Algorithms.Numeric.GreatestCommonDivisor; namespace Algorithms.Tests.Numeric.GreatestCommonDivisor; public static class BinaryGreatestCommonDivisorFinderTests { [TestCase(2, 3, 1)] [TestCase(1, 1, 1)] [TestCase(13, 17, 1)] [TestCase(0, 17, 17)] [TestCase(17, 0, 17)] [TestCase(17, 17, 17)] [TestCase(2 * 17, 17, 17)] [TestCase(0, 0, 0)] [TestCase(2 * 13 * 17, 4 * 9 * 13, 2 * 13)] public static void GreatestCommonDivisorCorrect(int a, int b, int expectedGcd) { // Arrange var gcdFinder = new BinaryGreatestCommonDivisorFinder(); // Act var actualGcd = gcdFinder.FindGcd(a, b); // Assert Assert.That(actualGcd, Is.EqualTo(expectedGcd)); } } ================================================ FILE: Algorithms.Tests/Numeric/GreatestCommonDivisor/EuclideanGreatestCommonDivisorFinderTests.cs ================================================ using Algorithms.Numeric.GreatestCommonDivisor; namespace Algorithms.Tests.Numeric.GreatestCommonDivisor; public static class EuclideanGreatestCommonDivisorFinderTests { [TestCase(2, 3, 1)] [TestCase(1, 1, 1)] [TestCase(13, 17, 1)] [TestCase(0, 17, 17)] [TestCase(17, 0, 17)] [TestCase(17, 17, 17)] [TestCase(2 * 17, 17, 17)] [TestCase(0, 0, int.MaxValue)] [TestCase(2 * 13 * 17, 4 * 9 * 13, 2 * 13)] public static void GreatestCommonDivisorCorrect(int a, int b, int expectedGcd) { // Arrange var gcdFinder = new EuclideanGreatestCommonDivisorFinder(); // Act var actualGcd = gcdFinder.FindGcd(a, b); // Assert Assert.That(actualGcd, Is.EqualTo(expectedGcd)); } } ================================================ FILE: Algorithms.Tests/Numeric/JosephusProblemTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public class JosephusProblemTest { [TestCase(10, 0)] [TestCase(10, -1)] public void JosephusProblemInvalidStepSize(long groupSize, long step) { Assert.Throws(Is.TypeOf() .And.Message.EqualTo("The step cannot be smaller than 1"), delegate { JosephusProblem.FindWinner(groupSize, step); }); } [TestCase(10, 12)] public void JosephusProblemStepSizeGreaterThanGroup(long groupSize, long step) { Assert.Throws(Is.TypeOf() .And.Message.EqualTo("The step cannot be greater than the size of the group"), delegate { JosephusProblem.FindWinner(groupSize, step); }); } [TestCase(10, 2, 5)] [TestCase(10, 8, 1)] [TestCase(254, 18, 92)] [TestCase(3948, 614, 2160)] [TestCase(86521, 65903, 29473)] public void JosephusProblemWinnerCalculation(long groupSize, long step, long position) { Assert.That(JosephusProblem.FindWinner(groupSize, step), Is.EqualTo(position)); } } ================================================ FILE: Algorithms.Tests/Numeric/KeithNumberTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class KeithNumberTest { [TestCase(14)] [TestCase(47)] [TestCase(197)] [TestCase(7909)] public static void KeithNumberWork(int number) { // Act var result = KeithNumberChecker.IsKeithNumber(number); // Assert Assert.That(result, Is.True); } [TestCase(-2)] public static void KeithNumberShouldThrowEx(int number) { // Arrange // Assert Assert.Throws(() => KeithNumberChecker.IsKeithNumber(number)); } } ================================================ FILE: Algorithms.Tests/Numeric/KrishnamurthyNumberCheckerTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public class KrishnamurthyNumberCheckerTests { [TestCase(1)] [TestCase(2)] [TestCase(145)] [TestCase(40585)] public void KrishnamurthyNumberCheckerKnownNumbers(int number) { var result = KrishnamurthyNumberChecker.IsKMurthyNumber(number); Assert.That(result, Is.True); } [TestCase(3)] [TestCase(4)] [TestCase(239847)] [TestCase(12374)] [TestCase(0)] [TestCase(-1)] public void KrishnamurthyNumberCheckerNotKMNumber(int number) { var result = KrishnamurthyNumberChecker.IsKMurthyNumber(number); Assert.That(result, Is.False); } } ================================================ FILE: Algorithms.Tests/Numeric/MillerRabinPrimalityTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class MillerRabinPrimalityTest { [TestCase("7", ExpectedResult = true)] // true [TestCase("47", ExpectedResult = true)] // true [TestCase("247894109041876714378152933343208766493", ExpectedResult = true)] // true [TestCase("247894109041876714378152933343208766493", 1, ExpectedResult = true)] // true [TestCase("315757551269487563269454472438030700351", ExpectedResult = true)] // true [TestCase("2476099", 12445, ExpectedResult = false)] // false 19^5 // false 247894109041876714378152933343208766493*315757551269487563269454472438030700351 [TestCase("78274436845194327170519855212507883195883737501141260366253362532531612139043", ExpectedResult = false)] [Retry(3)] public static bool MillerRabinPrimalityWork(string testcase, int? seed = null) { // Arrange BigInteger number = BigInteger.Parse(testcase); // Recommended number of checks' rounds = Log2(number) as BigInteger has no Log2 function we need to convert Log10 BigInteger rounds = (BigInteger)(BigInteger.Log10(number) / BigInteger.Log10(2)); // Act var result = MillerRabinPrimalityChecker.IsProbablyPrimeNumber(number, rounds, seed); // Assert return result; } [TestCase("-2")] [TestCase("0")] [TestCase("3")] // By the algorithm definition the number which is checked should be more than 3 public static void MillerRabinPrimalityShouldThrowEx(string testcase) { // Arrange BigInteger number = BigInteger.Parse(testcase); BigInteger rounds = 1; // Assert Assert.Throws(() => MillerRabinPrimalityChecker.IsProbablyPrimeNumber(number, rounds)); } } ================================================ FILE: Algorithms.Tests/Numeric/ModularExponentiationTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public class ModularExponentiationTest { [TestCase(3, 6, 11, 3)] [TestCase(5, 3, 13, 8)] [TestCase(2, 7, 17, 9)] [TestCase(7, 4, 16, 1)] [TestCase(7, 2, 11, 5)] [TestCase(4, 13, 497, 445)] [TestCase(13, 3, 1, 0)] public void ModularExponentiationCorrect(int b, int e, int m, int expectedRes) { var modularExponentiation = new ModularExponentiation(); var actualRes = modularExponentiation.ModularPow(b, e, m); actualRes.Should().Be(expectedRes); } [TestCase(17, 7, -3)] [TestCase(11, 3, -5)] [TestCase(14, 3, 0)] public void ModularExponentiationNegativeMod(int b, int e, int m) { var modularExponentiation = new ModularExponentiation(); Action res = () => modularExponentiation.ModularPow(b, e, m); res.Should().Throw() .WithMessage(String.Format("{0} is not a positive integer", m)); } } ================================================ FILE: Algorithms.Tests/Numeric/NarcissisticNumberTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class NarcissisticNumberTest { [TestCase(2, ExpectedResult = true)] [TestCase(3, ExpectedResult = true)] [TestCase(28, ExpectedResult = false)] [TestCase(153, ExpectedResult = true)] [TestCase(170, ExpectedResult = false)] [TestCase(371, ExpectedResult = true)] public static bool NarcissisticNumberWork(int number) { // Arrange // Act var result = NarcissisticNumberChecker.IsNarcissistic(number); // Assert return result; } } ================================================ FILE: Algorithms.Tests/Numeric/NewtonSquareRootTests.cs ================================================ namespace Algorithms.Tests.Numeric; public class NewtonSquareRootTests { private static readonly object[] CalculateSquareRootInput = [ new object[] {BigInteger.One, BigInteger.One}, new object[] {new BigInteger(221295376), new BigInteger(14876)}, new object[] {new BigInteger(2530995481), new BigInteger(50309)}, new object[] {new BigInteger(3144293476), new BigInteger(56074)}, new object[] {new BigInteger(3844992064), new BigInteger(62008)}, new object[] {new BigInteger(5301150481), new BigInteger(72809)}, new object[] {new BigInteger(5551442064), new BigInteger(74508)}, new object[] {new BigInteger(6980435401), new BigInteger(83549)}, new object[] {new BigInteger(8036226025), new BigInteger(89645)}, ]; [TestCaseSource(nameof(CalculateSquareRootInput))] public void CalculateSquareRootTest(BigInteger number, BigInteger result) { Assert.That(NewtonSquareRoot.Calculate(number), Is.EqualTo(result)); } [Test] public void CalculateSquareRootOfZero() { Assert.That(NewtonSquareRoot.Calculate(0), Is.EqualTo(BigInteger.Zero)); } [Test] public void CalculateSquareRootNegativeNumber() { Assert.Throws(Is.TypeOf() .And.Message.EqualTo("Cannot calculate the square root of a negative number."), delegate { NewtonSquareRoot.Calculate(BigInteger.MinusOne); }); } } ================================================ FILE: Algorithms.Tests/Numeric/PerfectCubeTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class PerfectCubeTests { [TestCase(-27, ExpectedResult = true)] [TestCase(27, ExpectedResult = true)] [TestCase(4, ExpectedResult = false)] [TestCase(64, ExpectedResult = true)] [TestCase(0, ExpectedResult = true)] [TestCase(1, ExpectedResult = true)] [TestCase(8, ExpectedResult = true)] [TestCase(9, ExpectedResult = false)] public static bool IsPerfectCube_ResultIsCorrect(int number) { // Act var result = PerfectCubeChecker.IsPerfectCube(number); // Assert return result; } [TestCase(-27, ExpectedResult = true)] [TestCase(27, ExpectedResult = true)] [TestCase(4, ExpectedResult = false)] [TestCase(64, ExpectedResult = true)] [TestCase(0, ExpectedResult = true)] [TestCase(1, ExpectedResult = true)] [TestCase(8, ExpectedResult = true)] [TestCase(9, ExpectedResult = false)] public static bool IsPerfectCubeBinarySearch_ResultIsCorrect(int number) { // Act var result = PerfectCubeChecker.IsPerfectCubeBinarySearch(number); // Assert return result; } } ================================================ FILE: Algorithms.Tests/Numeric/PerfectNumberTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class PerfectNumberTests { [TestCase(6)] [TestCase(28)] [TestCase(496)] [TestCase(8128)] public static void PerfectNumberWork(int number) { // Arrange // Act var result = PerfectNumberChecker.IsPerfectNumber(number); // Assert Assert.That(result, Is.True); } [TestCase(-2)] public static void PerfectNumberShouldThrowEx(int number) { // Arrange // Assert Assert.Throws(() => PerfectNumberChecker.IsPerfectNumber(number)); } } ================================================ FILE: Algorithms.Tests/Numeric/PerfectSquareTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class PerfectSquareTests { [TestCase(-4, ExpectedResult = false)] [TestCase(4, ExpectedResult = true)] [TestCase(9, ExpectedResult = true)] [TestCase(10, ExpectedResult = false)] [TestCase(16, ExpectedResult = true)] [TestCase(70, ExpectedResult = false)] [TestCase(81, ExpectedResult = true)] public static bool IsPerfectSquare_ResultIsCorrect(int number) { // Arrange // Act var result = PerfectSquareChecker.IsPerfectSquare(number); // Assert return result; } } ================================================ FILE: Algorithms.Tests/Numeric/PrimeNumberTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class PrimeNumberTests { /// /// Tests the PrimeChecker.IsPrime method with various inputs (primes, composites, edge cases) /// to ensure the result is correct. /// [TestCase(-5, ExpectedResult = false)] // Negative number [TestCase(0, ExpectedResult = false)] // Zero [TestCase(1, ExpectedResult = false)] // One [TestCase(2, ExpectedResult = true)] // Smallest prime [TestCase(3, ExpectedResult = true)] // Prime [TestCase(4, ExpectedResult = false)] // Composite (2*2) [TestCase(7, ExpectedResult = true)] // Prime [TestCase(9, ExpectedResult = false)] // Composite (3*3) [TestCase(13, ExpectedResult = true)] // Prime [TestCase(15, ExpectedResult = false)] // Composite (3*5) [TestCase(25, ExpectedResult = false)] // Composite (5*5) [TestCase(29, ExpectedResult = true)] // Prime [TestCase(35, ExpectedResult = false)] // Composite (5*7) [TestCase(49, ExpectedResult = false)] // Composite (7*7) [TestCase(97, ExpectedResult = true)] // Larger prime [TestCase(100, ExpectedResult = false)] // Larger composite public static bool IsPrime_ResultIsCorrect(int number) { // Arrange is implicit here // Act var result = PrimeChecker.IsPrime(number); // Assert return result; } } ================================================ FILE: Algorithms.Tests/Numeric/PseudoInverse/PseudoInverseTests.cs ================================================ namespace Algorithms.Tests.Numeric.PseudoInverse; public static class PseudoInverseTests { [Test] public static void SquaredMatrixInverseWorks() { // Arrange var inMat = new double[,] { { 2, 4, 6 }, { 2, 0, 2 }, { 6, 8, 14 } }; var inMatCopy = new double[,] { { 2, 4, 6 }, { 2, 0, 2 }, { 6, 8, 14 } }; // Act // using AA+A = A var result = Algorithms.Numeric.Pseudoinverse.PseudoInverse.PInv(inMat); var aainva = inMatCopy.Multiply(result).Multiply(inMatCopy); var rounded = aainva.RoundToNextInt(); var isequal = rounded.IsEqual(inMatCopy); // Assert Assert.That(isequal, Is.True); } [Test] public static void NonSquaredMatrixPseudoInverseMatrixWorks() { // Arrange var inMat = new double[,] { { 1, 2, 3, 4 }, { 0, 1, 4, 7 }, { 5, 6, 0, 1 } }; var inMatCopy = new double[,] { { 1, 2, 3, 4 }, { 0, 1, 4, 7 }, { 5, 6, 0, 1 } }; // Act // using (A+)+ = A var result = Algorithms.Numeric.Pseudoinverse.PseudoInverse.PInv(inMat); var result2 = Algorithms.Numeric.Pseudoinverse.PseudoInverse.PInv(result); var rounded = result2.RoundToNextInt(); var isequal = rounded.IsEqual(inMatCopy); // Assert Assert.That(isequal, Is.True); } } ================================================ FILE: Algorithms.Tests/Numeric/ReluTest.cs ================================================ using Algorithms.Numeric; using NUnit.Framework; using System; namespace Algorithms.Tests.Numeric; [TestFixture] public static class ReluTests { // Tolerance for floating-point comparisons private const double Tolerance = 1e-9; // --- SCALAR TESTS (Relu.Compute(double)) --- [TestCase(0.0, 0.0)] [TestCase(1.0, 1.0)] [TestCase(-1.0, 0.0)] [TestCase(5.0, 5.0)] [TestCase(-5.0, 0.0)] public static void ReluFunction_Scalar_ReturnsCorrectValue(double input, double expected) { var result = Relu.Compute(input); Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); } [Test] public static void ReluFunction_Scalar_HandlesLimitsAndNaN() { // Positive infinity stays +Infinity, negative infinity becomes 0, NaN propagates Assert.That(RelUComputePositiveInfinity(), Is.EqualTo(double.PositiveInfinity)); Assert.That(RelUComputeNegativeInfinity(), Is.EqualTo(0.0).Within(Tolerance)); Assert.That(RelUComputeNaN(), Is.NaN); static double RelUComputePositiveInfinity() => Relu.Compute(double.PositiveInfinity); static double RelUComputeNegativeInfinity() => Relu.Compute(double.NegativeInfinity); static double RelUComputeNaN() => Relu.Compute(double.NaN); } [TestCase(100.0)] [TestCase(0.0001)] [TestCase(-100.0)] public static void ReluFunction_Scalar_ResultIsNonNegative(double input) { var result = Relu.Compute(input); Assert.That(result, Is.GreaterThanOrEqualTo(0.0)); } // --- VECTOR TESTS (Relu.Compute(double[])) --- [Test] public static void ReluFunction_Vector_ReturnsCorrectValues() { var input = new[] { 0.0, 1.0, -2.0 }; var expected = new[] { 0.0, 1.0, 0.0 }; var result = Relu.Compute(input); Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); } [Test] public static void ReluFunction_Vector_HandlesLimitsAndNaN() { var input = new[] { double.PositiveInfinity, 0.0, double.NaN }; var result = Relu.Compute(input); Assert.That(result.Length, Is.EqualTo(input.Length)); Assert.That(result[0], Is.EqualTo(double.PositiveInfinity)); Assert.That(result[1], Is.EqualTo(0.0).Within(Tolerance)); Assert.That(result[2], Is.NaN); } // --- EXCEPTION TESTS --- [Test] public static void ReluFunction_Vector_ThrowsOnNullInput() { double[]? input = null; Assert.Throws(() => Relu.Compute(input!)); } [Test] public static void ReluFunction_Vector_ThrowsOnEmptyInput() { var input = Array.Empty(); Assert.Throws(() => Relu.Compute(input)); } } ================================================ FILE: Algorithms.Tests/Numeric/RungeKuttaMethodTest.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class RungeKuttaTest { [Test] public static void TestLinearEquation() { Func exampleEquation = (x, _) => x; List points = RungeKuttaMethod.ClassicRungeKuttaMethod(0, 4, 0.001, 0, exampleEquation); var yEnd = points[^1][1]; yEnd.Should().BeApproximately(8, 0.01); } [Test] public static void TestExampleFunciton() { Func exampleEquation = (_, y) => y; List points = RungeKuttaMethod.ClassicRungeKuttaMethod(0, 4, 0.0125, 1, exampleEquation); var yEnd = points[^1][1]; yEnd.Should().BeApproximately(54.598, 0.0005); } [Test] public static void StepsizeIsZeroOrNegative_ThrowsArgumentOutOfRangeException() { Func exampleEquation = (x, _) => x; Assert.Throws(() => RungeKuttaMethod.ClassicRungeKuttaMethod(0, 4, 0, 0, exampleEquation)); } [Test] public static void StartIsLargerThanEnd_ThrowsArgumentOutOfRangeException() { Func exampleEquation = (x, _) => x; Assert.Throws(() => RungeKuttaMethod.ClassicRungeKuttaMethod(0, -4, 0.1, 0, exampleEquation)); } } ================================================ FILE: Algorithms.Tests/Numeric/SigmoidTests.cs ================================================ using Algorithms.Numeric; using NUnit.Framework; using System; namespace Algorithms.Tests.Numeric; /// /// Tests for the Sigmoid class, which implements the sigmoid activation function. /// public static class SigmoidTests { // Standard tolerance for floating-point comparisons. private const double Tolerance = 1e-15; /// /// Tests that the sigmoid function correctly calculates the center point (x=0). /// Sigmoid(0) should equal 0.5. /// [Test] public static void GetsCenterValue() { // Arrange double x = 0.0; double expected = 0.5; // Act var result = Sigmoid.Calculate(x); // Assert Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); } /// /// Tests that the sigmoid function approaches 1 for large positive inputs (asymptotic behavior). /// [Test] public static void GetsAsymptoticValueForLargePositiveX() { // Arrange double x = 100.0; double expected = 1.0; // Act var result = Sigmoid.Calculate(x); // Assert // The result should be extremely close to 1.0. Assert.That(result, Is.EqualTo(expected).Within(1e-10)); } /// /// Tests that the sigmoid function approaches 0 for large negative inputs (asymptotic behavior). /// [Test] public static void GetsAsymptoticValueForLargeNegativeX() { // Arrange double x = -100.0; double expected = 0.0; // Act var result = Sigmoid.Calculate(x); // Assert // The result should be extremely close to 0.0. Assert.That(result, Is.EqualTo(expected).Within(1e-10)); } /// /// Tests the sigmoid calculation for various general positive and negative values. /// Values are confirmed against a reference calculation (or manually verified). /// /// The input value. /// The expected sigmoid output. [TestCase(1.0, 0.7310585786300049)] [TestCase(5.0, 0.9933071490757153)] [TestCase(-1.0, 0.2689414213699951)] [TestCase(-5.0, 0.006692850924284855)] [TestCase(0.5, 0.6224593312018546)] [TestCase(-0.5, 0.3775406687981454)] public static void GetsStandardSigmoidValues(double input, double expected) { // Act var result = Sigmoid.Calculate(input); // Assert Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); } /// /// Tests that the calculation correctly handles floating-point values and large numbers. /// [Test] public static void HandlesFractionalAndLargeInput() { // Arrange double x1 = 3.14159; // PI approximation // Corrected expected value: 1 / (1 + e^-3.14159) double expected1 = 0.9585760624650355; double x2 = -20.0; // Expected = 1 / (1 + e^20) - Should be very close to 0 double expected2 = 2.0611536224385583E-9; // Act & Assert 1 var result1 = Sigmoid.Calculate(x1); Assert.That(result1, Is.EqualTo(expected1).Within(Tolerance)); // Act & Assert 2 var result2 = Sigmoid.Calculate(x2); Assert.That(result2, Is.EqualTo(expected2).Within(Tolerance)); } } ================================================ FILE: Algorithms.Tests/Numeric/SoftMaxTests.cs ================================================ using Algorithms.Numeric; namespace Algorithms.Tests.Numeric; public static class SoftMaxTests { [TestCase(new[] { 5.0, 5.0 }, new[] { 0.5, 0.5 })] [TestCase(new[] { 1.0, 2.0, 3.0 }, new[] { 0.09003057317038046, 0.24472847105479767, 0.6652409557748219 })] [TestCase(new[] { 0.0 }, new[] { 1.0 })] public static void SoftMaxFunction(double[] input, double[] expected) { // Act var result = SoftMax.Compute(input); // Assert Assert.That(result, Is.EqualTo(expected).Within(1e-9)); } [Test] public static void SoftMaxFunctionThrowsArgumentException() { // Arrange var input = Array.Empty(); // Assert Assert.Throws(() => SoftMax.Compute(input)); } [TestCase(new[] { 1.0, 2.0, 3.0, 4.0, 5.0 })] [TestCase(new[] { 0.0, 0.0, 0.0, 0.0, 0.0 })] [TestCase(new[] { 5.0 })] public static void SoftMaxFunctionSumsToOne(double[] input) { // Act var result = SoftMax.Compute(input); var sum = 0.0; foreach (var value in result) { sum += value; } // Assert Assert.That(sum, Is.EqualTo(1.0).Within(1e-9)); } } ================================================ FILE: Algorithms.Tests/Numeric/SumOfDigitsTest.cs ================================================ using Algorithms.Numeric; using NUnit.Framework; using System; namespace Algorithms.Tests.Numeric; /// /// Tests for the SumOfDigits class. /// public static class SumOfDigitsTests { /// /// Tests the calculation of the sum of digits for various non-negative integers. /// /// The input number. /// The expected sum of its digits. [TestCase(0, 0)] [TestCase(7, 7)] [TestCase(10, 1)] [TestCase(42, 6)] [TestCase(12345, 15)] [TestCase(9999, 36)] [TestCase(8675309, 38)] [TestCase(2147483647, 46)] // Max value for int public static void GetsCorrectSumOfDigits(int input, int expectedSum) { // Act var result = SumOfDigits.Calculate(input); // Assert Assert.That(result, Is.EqualTo(expectedSum)); } /// /// Tests that the method throws an ArgumentException when a negative number is provided. /// /// The negative input number. [TestCase(-1)] [TestCase(-100)] [TestCase(-54321)] public static void ThrowsExceptionForNegativeNumbers(int num) { // Act void Act() => SumOfDigits.Calculate(num); // Assert _ = Assert.Throws(Act); } } ================================================ FILE: Algorithms.Tests/Numeric/TanhTest.cs ================================================ using Algorithms.Numeric; using NUnit.Framework; using System; namespace Algorithms.Tests.Numeric; [TestFixture] public static class TanhTests { // Tolerance for floating-point comparisons private const double Tolerance = 1e-9; // --- SCALAR TESTS (Tanh.Compute(double)) --- /// /// Tests Tanh function for specific values, including zero and symmetric positive/negative inputs. /// [TestCase(0.0, 0.0)] [TestCase(1.0, 0.7615941559557649)] [TestCase(-1.0, -0.7615941559557649)] [TestCase(5.0, 0.999909204262595)] [TestCase(-5.0, -0.999909204262595)] public static void TanhFunction_Scalar_ReturnsCorrectValue(double input, double expected) { var result = Tanh.Compute(input); Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); } /// /// Ensures the Tanh output approaches 1.0 for positive infinity and -1.0 for negative infinity. /// [Test] public static void TanhFunction_Scalar_ApproachesLimits() { Assert.That(Tanh.Compute(double.PositiveInfinity), Is.EqualTo(1.0).Within(Tolerance)); Assert.That(Tanh.Compute(double.NegativeInfinity), Is.EqualTo(-1.0).Within(Tolerance)); Assert.That(Tanh.Compute(double.NaN), Is.NaN); } /// /// Checks that the Tanh result is always bounded between -1.0 and 1.0. /// [TestCase(100.0)] [TestCase(-100.0)] [TestCase(0.0001)] public static void TanhFunction_Scalar_ResultIsBounded(double input) { var result = Tanh.Compute(input); Assert.That(result, Is.GreaterThanOrEqualTo(-1.0)); Assert.That(result, Is.LessThanOrEqualTo(1.0)); } // --- VECTOR TESTS (Tanh.Compute(double[])) --- /// /// Tests the element-wise computation for a vector input. /// [Test] public static void TanhFunction_Vector_ReturnsCorrectValues() { // Input: [0.0, 1.0, -2.0] var input = new[] { 0.0, 1.0, -2.0 }; // Expected: [Tanh(0.0), Tanh(1.0), Tanh(-2.0)] var expected = new[] { 0.0, 0.7615941559557649, -0.9640275800758169 }; var result = Tanh.Compute(input); // Assert deep equality within tolerance Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); } /// /// Tests vector handling of edge cases like infinity and NaN. /// [Test] public static void TanhFunction_Vector_HandlesLimitsAndNaN() { var input = new[] { double.PositiveInfinity, 0.0, double.NaN }; var expected = new[] { 1.0, 0.0, double.NaN }; var result = Tanh.Compute(input); Assert.That(result.Length, Is.EqualTo(expected.Length)); Assert.That(result[0], Is.EqualTo(expected[0]).Within(Tolerance)); // Pos Inf -> 1.0 Assert.That(result[2], Is.NaN); // NaN } // --- EXCEPTION TESTS --- /// /// Checks if the vector computation throws ArgumentNullException for null input. /// [Test] public static void TanhFunction_Vector_ThrowsOnNullInput() { double[]? input = null; Assert.Throws(() => Tanh.Compute(input!)); } /// /// Checks if the vector computation throws ArgumentException for an empty input array. /// [Test] public static void TanhFunction_Vector_ThrowsOnEmptyInput() { var input = Array.Empty(); Assert.Throws(() => Tanh.Compute(input)); } } ================================================ FILE: Algorithms.Tests/Other/BoyerMooreMajorityVoteTests.cs ================================================ using Algorithms.Other; using NUnit.Framework; using FluentAssertions; namespace Algorithms.Tests.Other; public class BoyerMooreMajorityVoteTests { [Test] public void FindMajority_SimpleMajority_ReturnsCorrectElement() { var nums = new[] { 3, 3, 4, 2, 3, 3, 3 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().Be(3); } [Test] public void FindMajority_AllSameElements_ReturnsThatElement() { var nums = new[] { 5, 5, 5, 5 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().Be(5); } [Test] public void FindMajority_NoMajority_ReturnsNull() { var nums = new[] { 1, 2, 3, 4 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().BeNull(); } [Test] public void FindMajority_EmptyArray_ReturnsNull() { var nums = Array.Empty(); var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().BeNull(); } [Test] public void FindMajority_NullArray_ReturnsNull() { int[]? nums = null; var result = BoyerMooreMajorityVote.FindMajority(nums!); result.Should().BeNull(); } [Test] public void FindMajority_SingleElement_ReturnsThatElement() { var nums = new[] { 7 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().Be(7); } [Test] public void FindMajority_MajorityAtEnd_ReturnsCorrectElement() { var nums = new[] { 1, 2, 2, 2, 2 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().Be(2); } [Test] public void FindMajority_MajorityAtStart_ReturnsCorrectElement() { var nums = new[] { 8, 8, 8, 8, 1, 2 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().Be(8); } [Test] public void FindMajority_NegativeNumbers_ReturnsCorrectElement() { var nums = new[] { -1, -1, -1, 2, 2 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().Be(-1); } [Test] public void FindMajority_ExactlyHalf_ReturnsNull() { var nums = new[] { 1, 1, 2, 2 }; var result = BoyerMooreMajorityVote.FindMajority(nums); result.Should().BeNull(); } } ================================================ FILE: Algorithms.Tests/Other/DecisionsConvolutionsTest.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class DecisionsConvolutionsTest { [Test] public static void Verify_Linear_Convolution() { // Arrange var matrix = new List> { new List { 7, 6, 5, 8, 5, 6 }, new List { 4, 8, 4, 4, 5, 3 }, new List { 3, 8, 1, 4, 5, 2 }, new List { 5, 6, 3, 6, 4, 5 }, new List { 1, 4, 8, 6, 3, 6 }, new List { 5, 1, 8, 6, 5, 1 }, new List { 6, 8, 3, 6, 3, 5 } }; var expectedMatrix = new List { 7, 6, 5, 8, 5, 6 }; var priorities = new List { 1, 1, 1, 1, 0.545m, 0.583m }; // Act var optimizedMatrix = DecisionsConvolutions.Linear(matrix, priorities); // Assert Assert.That(expectedMatrix, Is.EqualTo(optimizedMatrix)); } [Test] public static void Verify_MaxMin_Convolution() { // Arrange var matrix = new List> { new List { 7, 6, 5, 8, 5, 6 }, new List { 4, 8, 4, 4, 5, 3 }, new List { 3, 8, 1, 4, 5, 2 }, new List { 5, 6, 3, 6, 4, 5 }, new List { 1, 4, 8, 6, 3, 6 }, new List { 5, 1, 8, 6, 5, 1 }, new List { 6, 8, 3, 6, 3, 5 } }; var expectedMatrix = new List { 7, 6, 5, 8, 5, 6 }; var priorities = new List { 1, 1, 1, 1, 0.545m, 0.583m }; // Act var optimizedMatrix = DecisionsConvolutions.MaxMin(matrix, priorities); // Assert Assert.That(expectedMatrix, Is.EqualTo(optimizedMatrix)); } } ================================================ FILE: Algorithms.Tests/Other/FermatPrimeCheckerTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class FermatPrimeCheckerTests { [TestCase(5, true)] [TestCase(2633, true)] [TestCase(9439, true)] [TestCase(1, false)] [TestCase(8, false)] public static void IsProbablePrime(int inputNum, bool expected) { // Arrange var random = new Randomizer(); var times = random.Next(1, 1000); // Act var result = FermatPrimeChecker.IsPrime(inputNum, times); // Assert Assert.That(result, Is.EqualTo(expected)); } } ================================================ FILE: Algorithms.Tests/Other/FloodFillTest.cs ================================================ using SkiaSharp; namespace Algorithms.Tests.Other; public static class Tests { private const byte Alpha = 255; private static readonly SKColor Black = new(0, 0, 0, Alpha); private static readonly SKColor Green = new(0, 255, 0, Alpha); private static readonly SKColor Violet = new(255, 0, 255, Alpha); private static readonly SKColor White = new(255, 255, 255, Alpha); private static readonly SKColor Orange = new(255, 128, 0, Alpha); [Test] public static void BreadthFirstSearch_ThrowsArgumentOutOfRangeException() { Action act = () => Algorithms.Other.FloodFill.BreadthFirstSearch(GenerateTestBitmap(), (10, 10), Black, White); act.Should().Throw(); } [Test] public static void DepthFirstSearch_ThrowsArgumentOutOfRangeException() { Action act = () => Algorithms.Other.FloodFill.DepthFirstSearch(GenerateTestBitmap(), (-1, -1), Black, White); act.Should().Throw(); } [Test] public static void BreadthFirstSearch_Test1() { TestAlgorithm(Algorithms.Other.FloodFill.BreadthFirstSearch, (1, 1), Green, Orange, (1, 1), Orange); } [Test] public static void BreadthFirstSearch_Test2() { TestAlgorithm(Algorithms.Other.FloodFill.BreadthFirstSearch, (1, 1), Green, Orange, (0, 1), Violet); } [Test] public static void BreadthFirstSearch_Test3() { TestAlgorithm(Algorithms.Other.FloodFill.BreadthFirstSearch, (1, 1), Green, Orange, (6, 4), White); } [Test] public static void DepthFirstSearch_Test1() { TestAlgorithm(Algorithms.Other.FloodFill.DepthFirstSearch, (1, 1), Green, Orange, (1, 1), Orange); } [Test] public static void DepthFirstSearch_Test2() { TestAlgorithm(Algorithms.Other.FloodFill.DepthFirstSearch, (1, 1), Green, Orange, (0, 1), Violet); } [Test] public static void DepthFirstSearch_Test3() { TestAlgorithm(Algorithms.Other.FloodFill.DepthFirstSearch, (1, 1), Green, Orange, (6, 4), White); } private static SKBitmap GenerateTestBitmap() { SKColor[,] layout = { {Violet, Violet, Green, Green, Black, Green, Green}, {Violet, Green, Green, Black, Green, Green, Green}, {Green, Green, Green, Black, Green, Green, Green}, {Black, Black, Green, Black, White, White, Green}, {Violet, Violet, Black, Violet, Violet, White, White}, {Green, Green, Green, Violet, Violet, Violet, Violet}, {Violet, Violet, Violet, Violet, Violet, Violet, Violet}, }; SKBitmap bitmap = new(7, 7); for (int x = 0; x < layout.GetLength(0); x++) { for (int y = 0; y < layout.GetLength(1); y++) { bitmap.SetPixel(x, y, layout[y, x]); } } return bitmap; } private static void TestAlgorithm( Action, SKColor, SKColor> algorithm, ValueTuple fillLocation, SKColor targetColor, SKColor replacementColor, ValueTuple testLocation, SKColor expectedColor) { SKBitmap bitmap = GenerateTestBitmap(); algorithm(bitmap, fillLocation, targetColor, replacementColor); SKColor actualColor = bitmap.GetPixel(testLocation.Item1, testLocation.Item2); actualColor.Should().Be(expectedColor); } } ================================================ FILE: Algorithms.Tests/Other/GaussOptimizationTest.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class GaussOptimizationTest { [Test] public static void Verify_Gauss_Optimization_Positive() { // Arrange var gaussOptimization = new GaussOptimization(); // Declaration of the constants that are used in the function var coefficients = new List { 0.3, 0.6, 2.6, 0.3, 0.2, 1.4 }; // Description of the function var func = (double x1, double x2) => { if (x1 > 1 || x1 < 0 || x2 > 1 || x2 < 0) { return 0; } return coefficients[0] + coefficients[1] * x1 + coefficients[2] * x2 + coefficients[3] * x1 * x2 + coefficients[4] * x1 * x1 + coefficients[5] * x2 * x2; }; // The parameter that identifies how much step size will be decreased each iteration double n = 2.4; // Default values of x1 and x2. These values will be used for the calculation of the next // coordinates by Gauss optimization method double x1 = 0.5; double x2 = 0.5; // Default optimization step double step = 0.5; // This value is used to control the accuracy of the optimization. In case if the error is less // than eps, optimization will be stopped double eps = Math.Pow(0.1, 10); // Act (x1, x2) = gaussOptimization.Optimize(func, n, step, eps, x1, x2); // Assert Assert.That(x1, Is.EqualTo(1).Within(0.3)); Assert.That(x2, Is.EqualTo(1).Within(0.3)); } [Test] public static void Verify_Gauss_Optimization_Negative() { // Arrange var gaussOptimization = new GaussOptimization(); // Declaration of the constants that are used in the function var coefficients = new List { -0.3, -0.6, -2.6, -0.3, -0.2, -1.4 }; // Description of the function var func = (double x1, double x2) => { if (x1 > 0 || x1 < -1 || x2 > 0 || x2 < -1) { return 0; } return coefficients[0] + coefficients[1] * x1 + coefficients[2] * x2 + coefficients[3] * x1 * x2 + coefficients[4] * x1 * x1 + coefficients[5] * x2 * x2; }; // The parameter that identifies how much step size will be decreased each iteration double n = 2.4; // Default values of x1 and x2. These values will be used for the calculation of the next // coordinates by Gauss optimization method double x1 = -0.5; double x2 = -0.5; // Default optimization step double step = 0.5; // This value is used to control the accuracy of the optimization. In case if the error is less // than eps, optimization will be stopped double eps = Math.Pow(0.1, 10); // Act (x1, x2) = gaussOptimization.Optimize(func, n, step, eps, x1, x2); // Assert Assert.That(x1, Is.EqualTo(-1).Within(0.3)); Assert.That(x2, Is.EqualTo(-1).Within(0.3)); } } ================================================ FILE: Algorithms.Tests/Other/GeoLocationTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class GeoLocationTests { [TestCase(53.430488d, -2.96129d, 53.430488d, -2.96129d, 0d)] [TestCase(53.430971d, -2.959806d, 53.430242d, -2.960830d, 105d)] public static void CalculateDistanceFromLatLngTest( double lat1, double lng1, double lat2, double lng2, double expectedValue) { var result = GeoLocation.CalculateDistanceFromLatLng(lat1, lng1, lat2, lng2); var actualValue = Convert.ToDouble(result); // Assert Assert.That(actualValue, Is.EqualTo(expectedValue).Within(1d)); // Accept if distance diff is +/-1 meters. } } ================================================ FILE: Algorithms.Tests/Other/GeofenceTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other { [TestFixture] public class GeofenceTests { private Geofence? geofence; [SetUp] public void Setup() { geofence = new Geofence(10.8231, 106.6297, 500); } [Test] public void IsInside_ShouldReturnTrue_WhenUserIsInsideGeofence() { double userLat = 10.8221; double userLon = 106.6289; bool? result = geofence?.IsInside(userLat, userLon); Assert.That(result, Is.True); } [Test] public void IsInside_ShouldReturnFalse_WhenUserIsOutsideGeofence() { double userLat = 10.8300; double userLon = 106.6400; bool? result = geofence?.IsInside(userLat, userLon); Assert.That(result, Is.False); } [Test] public void IsInside_ShouldReturnTrue_WhenUserIsExactlyOnGeofenceBoundary() { double userLat = 10.8231; double userLon = 106.6297; bool? result = geofence?.IsInside(userLat, userLon); Assert.That(result, Is.True); } [Test] public void IsInside_ShouldReturnFalse_WhenUserIsFarFromGeofence() { double userLat = 20.0000; double userLon = 100.0000; bool? result = geofence?.IsInside(userLat, userLon); Assert.That(result, Is.False); } } } ================================================ FILE: Algorithms.Tests/Other/GeohashTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other { [TestFixture] public class GeohashTests { [Test] public void Encode_ShouldReturnCorrectGeohash_ForHoChiMinhCity() { double latitude = 10.8231; double longitude = 106.6297; string result = Geohash.Encode(latitude, longitude); Assert.That(result, Is.EqualTo("w3gvd6m3hh54")); } [Test] public void Encode_ShouldReturnCorrectGeohash_ForHanoi() { double latitude = 21.0285; double longitude = 105.8542; string result = Geohash.Encode(latitude, longitude); Assert.That(result, Is.EqualTo("w7er8u0evss2")); } [Test] public void Encode_ShouldReturnCorrectGeohash_ForDaNang() { double latitude = 16.0544; double longitude = 108.2022; string result = Geohash.Encode(latitude, longitude); Assert.That(result, Is.EqualTo("w6ugq4w7wj04")); } [Test] public void Encode_ShouldReturnCorrectGeohash_ForNhaTrang() { double latitude = 12.2388; double longitude = 109.1967; string result = Geohash.Encode(latitude, longitude); Assert.That(result, Is.EqualTo("w6jtsu485t8v")); } [Test] public void Encode_ShouldReturnCorrectGeohash_ForVungTau() { double latitude = 10.3460; double longitude = 107.0843; string result = Geohash.Encode(latitude, longitude); Assert.That(result, Is.EqualTo("w3u4ug2mv41m")); } } } ================================================ FILE: Algorithms.Tests/Other/Int2BinaryTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class Int2BinaryTests { [TestCase((ushort)0, "0000000000000000")] [TestCase((ushort)0b1, "0000000000000001")] [TestCase((ushort)0b0001010100111000, "0001010100111000")] [TestCase((ushort)0b1110111100110010, "1110111100110010")] [TestCase((ushort)(ushort.MaxValue - 1), "1111111111111110")] [TestCase(ushort.MaxValue, "1111111111111111")] public static void GetsBinary(ushort input, string expected) { // Arrange // Act var result = Int2Binary.Int2Bin(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [TestCase((uint)0, "00000000000000000000000000000000")] [TestCase((uint)0b1, "00000000000000000000000000000001")] [TestCase((uint)0b0001010100111000, "00000000000000000001010100111000")] [TestCase((uint)0b1110111100110010, "00000000000000001110111100110010")] [TestCase(0b10101100001110101110111100110010, "10101100001110101110111100110010")] [TestCase(uint.MaxValue - 1, "11111111111111111111111111111110")] [TestCase(uint.MaxValue, "11111111111111111111111111111111")] public static void GetsBinary(uint input, string expected) { // Arrange // Act var result = Int2Binary.Int2Bin(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [TestCase((ulong)0, "0000000000000000000000000000000000000000000000000000000000000000")] [TestCase((ulong)0b1, "0000000000000000000000000000000000000000000000000000000000000001")] [TestCase((ulong)0b0001010100111000, "0000000000000000000000000000000000000000000000000001010100111000")] [TestCase((ulong)0b1110111100110010, "0000000000000000000000000000000000000000000000001110111100110010")] [TestCase((ulong)0b10101100001110101110111100110010, "0000000000000000000000000000000010101100001110101110111100110010")] [TestCase(0b1000101110100101000011010101110101010101110101001010000011111000, "1000101110100101000011010101110101010101110101001010000011111000")] [TestCase(ulong.MaxValue - 1, "1111111111111111111111111111111111111111111111111111111111111110")] [TestCase(ulong.MaxValue, "1111111111111111111111111111111111111111111111111111111111111111")] public static void GetsBinary(ulong input, string expected) { // Arrange // Act var result = Int2Binary.Int2Bin(input); // Assert Assert.That(result, Is.EqualTo(expected)); } } ================================================ FILE: Algorithms.Tests/Other/JulianEasterTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; /// /// A class for testing the Meeus's Julian Easter algorithm. /// public static class JulianEasterTest { [TestCaseSource(nameof(CalculateCases))] public static void CalculateTest(int year, DateTime expected) { var result = JulianEaster.Calculate(year); Assert.That(result, Is.EqualTo(expected)); } private static readonly object[] CalculateCases = [ new object[] { 1800, new DateTime(1800, 04, 08, 00, 00, 00, DateTimeKind.Utc) }, new object[] { 1950, new DateTime(1950, 03, 27, 00, 00, 00, DateTimeKind.Utc) }, new object[] { 1991, new DateTime(1991, 03, 25, 00, 00, 00, DateTimeKind.Utc) }, new object[] { 2000, new DateTime(2000, 04, 17, 00, 00, 00, DateTimeKind.Utc) }, new object[] { 2199, new DateTime(2199, 04, 07, 00, 00, 00, DateTimeKind.Utc) } ]; } ================================================ FILE: Algorithms.Tests/Other/KadanesAlgorithmTests.cs ================================================ using Algorithms.Other; using NUnit.Framework; using System; namespace Algorithms.Tests.Other; /// /// Comprehensive test suite for Kadane's Algorithm implementation. /// Tests cover various scenarios including: /// - Arrays with all positive numbers /// - Arrays with mixed positive and negative numbers /// - Arrays with all negative numbers /// - Edge cases (single element, empty array, null array) /// - Index tracking functionality /// - Long integer support for large numbers /// public static class KadanesAlgorithmTests { [Test] public static void FindMaximumSubarraySum_WithPositiveNumbers_ReturnsCorrectSum() { // Arrange: When all numbers are positive, the entire array is the maximum subarray int[] array = { 1, 2, 3, 4, 5 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Sum of all elements = 1 + 2 + 3 + 4 + 5 = 15 Assert.That(result, Is.EqualTo(15)); } [Test] public static void FindMaximumSubarraySum_WithMixedNumbers_ReturnsCorrectSum() { // Arrange: Classic example with mixed positive and negative numbers // The maximum subarray is [4, -1, 2, 1] starting at index 3 int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Maximum sum is 4 + (-1) + 2 + 1 = 6 Assert.That(result, Is.EqualTo(6)); // Subarray [4, -1, 2, 1] } [Test] public static void FindMaximumSubarraySum_WithAllNegativeNumbers_ReturnsLargestNegative() { // Arrange: When all numbers are negative, the algorithm returns the least negative number // This represents a subarray of length 1 containing the largest (least negative) element int[] array = { -5, -2, -8, -1, -4 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: -1 is the largest (least negative) number in the array Assert.That(result, Is.EqualTo(-1)); } [Test] public static void FindMaximumSubarraySum_WithSingleElement_ReturnsThatElement() { // Arrange: Edge case with only one element // The only possible subarray is the element itself int[] array = { 42 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: The single element is both the subarray and its sum Assert.That(result, Is.EqualTo(42)); } [Test] public static void FindMaximumSubarraySum_WithNullArray_ThrowsArgumentException() { // Arrange: Test defensive programming - null input validation int[]? array = null; // Act & Assert: Should throw ArgumentException for null input Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarraySum(array!)); } [Test] public static void FindMaximumSubarraySum_WithEmptyArray_ThrowsArgumentException() { // Arrange int[] array = Array.Empty(); // Act & Assert Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarraySum(array)); } [Test] public static void FindMaximumSubarraySum_WithAlternatingNumbers_ReturnsCorrectSum() { // Arrange: Alternating positive and negative numbers // Despite negative values, the entire array gives the maximum sum int[] array = { 5, -3, 5, -3, 5 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Sum of entire array = 5 - 3 + 5 - 3 + 5 = 9 Assert.That(result, Is.EqualTo(9)); // Entire array } [Test] public static void FindMaximumSubarrayWithIndices_ReturnsCorrectIndices() { // Arrange: Test the variant that returns indices of the maximum subarray // Array: [-2, 1, -3, 4, -1, 2, 1, -5, 4] // Index: 0 1 2 3 4 5 6 7 8 int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; // Act var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array); // Assert: Maximum subarray is [4, -1, 2, 1] from index 3 to 6 Assert.That(maxSum, Is.EqualTo(6)); Assert.That(startIndex, Is.EqualTo(3)); Assert.That(endIndex, Is.EqualTo(6)); } [Test] public static void FindMaximumSubarrayWithIndices_WithSingleElement_ReturnsZeroIndices() { // Arrange int[] array = { 10 }; // Act var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array); // Assert Assert.That(maxSum, Is.EqualTo(10)); Assert.That(startIndex, Is.EqualTo(0)); Assert.That(endIndex, Is.EqualTo(0)); } [Test] public static void FindMaximumSubarrayWithIndices_WithNullArray_ThrowsArgumentException() { // Arrange int[]? array = null; // Act & Assert Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarrayWithIndices(array!)); } [Test] public static void FindMaximumSubarraySum_WithLongArray_ReturnsCorrectSum() { // Arrange: Test the long integer overload with same values as int test // Verifies that the algorithm works correctly with long data type long[] array = { -2L, 1L, -3L, 4L, -1L, 2L, 1L, -5L, 4L }; // Act long result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Should produce same result as int version Assert.That(result, Is.EqualTo(6L)); } [Test] public static void FindMaximumSubarraySum_WithLargeLongNumbers_ReturnsCorrectSum() { // Arrange: Test with large numbers that would overflow int type // This demonstrates why the long overload is necessary // Sum would be 1,500,000,000 which fits in long but is near int.MaxValue long[] array = { 1000000000L, -500000000L, 1000000000L }; // Act long result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Entire array sum = 1,000,000,000 - 500,000,000 + 1,000,000,000 = 1,500,000,000 Assert.That(result, Is.EqualTo(1500000000L)); } [Test] public static void FindMaximumSubarraySum_WithLongNullArray_ThrowsArgumentException() { // Arrange long[]? array = null; // Act & Assert Assert.Throws(() => KadanesAlgorithm.FindMaximumSubarraySum(array!)); } [Test] public static void FindMaximumSubarraySum_WithZeros_ReturnsZero() { // Arrange: Edge case with all zeros // Any subarray will have sum of 0 int[] array = { 0, 0, 0, 0 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Maximum sum is 0 Assert.That(result, Is.EqualTo(0)); } [Test] public static void FindMaximumSubarraySum_WithMixedZerosAndNegatives_ReturnsZero() { // Arrange: Mix of zeros and negative numbers // The best subarray is any single zero (or multiple zeros) int[] array = { -5, 0, -3, 0, -2 }; // Act int result = KadanesAlgorithm.FindMaximumSubarraySum(array); // Assert: Zero is better than any negative number Assert.That(result, Is.EqualTo(0)); } } ================================================ FILE: Algorithms.Tests/Other/KochSnowflakeTest.cs ================================================ using Algorithms.Other; using SkiaSharp; namespace Algorithms.Tests.Other; public static class KochSnowflakeTest { [Test] public static void TestIterateMethod() { List vectors = [new Vector2(0, 0), new Vector2(1, 0)]; List result = KochSnowflake.Iterate(vectors, 1); result[0].Should().Be(new Vector2(0, 0)); result[1].Should().Be(new Vector2((float)1 / 3, 0)); /* Should().BeApproximately() is not defined for Vector2 or float so the x-y-components have to be tested separately and the y-component needs to be cast to double */ result[2].X.Should().Be(0.5f); ((double)result[2].Y).Should().BeApproximately(Math.Sin(Math.PI / 3) / 3, 0.0001); result[3].Should().Be(new Vector2((float)2 / 3, 0)); result[4].Should().Be(new Vector2(1, 0)); } [Test] public static void BitmapWidthIsZeroOrNegative_ThrowsArgumentOutOfRangeException() { Assert.Throws(() => KochSnowflake.GetKochSnowflake(-200)); } [Test] public static void TestKochSnowflakeExample() { var bitmapWidth = 600; var offsetX = bitmapWidth / 10f; var offsetY = bitmapWidth / 3.7f; SKBitmap bitmap = KochSnowflake.GetKochSnowflake(); bitmap.GetPixel(0, 0) .Should() .Be(new SKColor(255, 255, 255, 255), "because the background should be white"); bitmap.GetPixel((int)offsetX, (int)offsetY) .Should() .Be(new SKColor(0, 0, 0, 255), "because the snowflake is drawn in black and this is the position of the first vector"); } } ================================================ FILE: Algorithms.Tests/Other/LuhnTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; /// /// A class for testing the Luhn algorithm. /// public class LuhnTests { [TestCase("89014103211118510720")] // ICCID [TestCase("071052120")] // Social Security Code [TestCase("449125546588769")] // IMEI [TestCase("4417123456789113")] // Bank card public void ValidateTrue(string number) { // Arrange bool validate; // Act validate = Luhn.Validate(number); // Assert Assert.That(validate, Is.True); } [TestCase("89012104211118510720")] // ICCID [TestCase("021053120")] // Social Security Code [TestCase("449145545588969")] // IMEI [TestCase("4437113456749113")] // Bank card public void ValidateFalse(string number) { // Arrange bool validate; // Act validate = Luhn.Validate(number); // Assert Assert.That(validate, Is.False); } [TestCase("x9012104211118510720")] // ICCID [TestCase("0210x3120")] // Social Security Code [TestCase("44914554558896x")] // IMEI [TestCase("4437113456x49113")] // Bank card public void GetLostNum(string number) { // Arrange int lostNum; bool validate; // Act lostNum = Luhn.GetLostNum(number); validate = Luhn.Validate(number.Replace("x", lostNum.ToString())); // Assert Assert.That(validate, Is.True); } [TestCase("")] [TestCase("xxxx")] [TestCase("abcde")] [TestCase("1x345678901234567")] [TestCase("x1234567890123456")] [TestCase("1234567890123456x")] [TestCase("1111111111111111")] [TestCase("1a2b3c4d5e6f7g8h9i0j")] public void EdgeCases_GetLostNum(string number) { // Act int lostNum = Luhn.GetLostNum(number.Replace("x", "0")); // Assert Assert.That(lostNum, Is.InRange(0, 9)); } [TestCase("1a2b3c4d5e6f7g8h9i0j")] public void EdgeCases_Validate(string number) { // Act bool result = Luhn.Validate(number); // Assert Assert.That(result, Is.False); } } ================================================ FILE: Algorithms.Tests/Other/MandelbrotTest.cs ================================================ using Algorithms.Other; using SkiaSharp; namespace Algorithms.Tests.Other; public static class MandelbrotTest { [Test] public static void BitmapWidthIsZeroOrNegative_ThrowsArgumentOutOfRangeException() { Assert.Throws(() => Mandelbrot.GetBitmap(-200)); } [Test] public static void BitmapHeightIsZeroOrNegative_ThrowsArgumentOutOfRangeException() { Assert.Throws(() => Mandelbrot.GetBitmap(bitmapHeight: 0)); } [Test] public static void MaxStepIsZeroOrNegative_ThrowsArgumentOutOfRangeException() { Assert.Throws(() => Mandelbrot.GetBitmap(maxStep: -1)); } [Test] public static void TestBlackAndWhite() { SKBitmap bitmap = Mandelbrot.GetBitmap(useDistanceColorCoding: false); // Pixel outside the Mandelbrot set should be white. Assert.That(new SKColor(255, 255, 255, 255), Is.EqualTo(bitmap.GetPixel(0, 0))); // Pixel inside the Mandelbrot set should be black. Assert.That(new SKColor(0, 0, 0, 255), Is.EqualTo(bitmap.GetPixel(400, 300))); } [Test] public static void TestColorCoded() { SKBitmap bitmap = Mandelbrot.GetBitmap(useDistanceColorCoding: true); // Pixel distant to the Mandelbrot set should be red. Assert.That(new SKColor(255, 0, 0, 255), Is.EqualTo(bitmap.GetPixel(0, 0))); // Pixel inside the Mandelbrot set should be black. Assert.That(new SKColor(0, 0, 0, 255), Is.EqualTo(bitmap.GetPixel(400, 300))); } } ================================================ FILE: Algorithms.Tests/Other/ParetoOptimizationTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class ParetoOptimizationTests { [Test] public static void Verify_Pareto_Optimization() { // Arrange var paretoOptimization = new ParetoOptimization(); var matrix = new List> { new List { 7, 6, 5, 8, 5, 6 }, new List { 4, 8, 4, 4, 5, 3 }, new List { 3, 8, 1, 4, 5, 2 }, new List { 5, 6, 3, 6, 4, 5 }, new List { 1, 4, 8, 6, 3, 6 }, new List { 5, 1, 8, 6, 5, 1 }, new List { 6, 8, 3, 6, 3, 5 } }; var expectedMatrix = new List> { new List { 7, 6, 5, 8, 5, 6 }, new List { 4, 8, 4, 4, 5, 3 }, new List { 1, 4, 8, 6, 3, 6 }, new List { 5, 1, 8, 6, 5, 1 }, new List { 6, 8, 3, 6, 3, 5 } }; // Act var optimizedMatrix = paretoOptimization.Optimize(matrix); // Assert Assert.That(expectedMatrix, Is.EqualTo(optimizedMatrix)); } } ================================================ FILE: Algorithms.Tests/Other/PollardsRhoFactorizingTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public class PollardsRhoFactorizingTests { [TestCase(8051, 97)] [TestCase(105, 21)] [TestCase(253, 11)] [TestCase(10403, 101)] [TestCase(187, 11)] public void SimpleTest(int number, int expectedResult) { var result = PollardsRhoFactorizing.Calculate(number); Assert.That(result, Is.EqualTo(expectedResult)); } } ================================================ FILE: Algorithms.Tests/Other/RGBHSVConversionTest.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class RgbHsvConversionTest { [Test] public static void HueOutOfRange_ThrowsArgumentOutOfRangeException() { Action act = () => RgbHsvConversion.HsvToRgb(400, 0, 0); act.Should().Throw(); } [Test] public static void SaturationOutOfRange_ThrowsArgumentOutOfRangeException() { Action act = () => RgbHsvConversion.HsvToRgb(0, 2, 0); act.Should().Throw(); } [Test] public static void ValueOutOfRange_ThrowsArgumentOutOfRangeException() { Action act = () => RgbHsvConversion.HsvToRgb(0, 0, 2); act.Should().Throw(); } // expected RGB-values taken from https://www.rapidtables.com/convert/color/hsv-to-rgb.html [TestCase(0, 0, 0, 0, 0, 0)] [TestCase(0, 0, 1, 255, 255, 255)] [TestCase(0, 1, 1, 255, 0, 0)] [TestCase(60, 1, 1, 255, 255, 0)] [TestCase(120, 1, 1, 0, 255, 0)] [TestCase(240, 1, 1, 0, 0, 255)] [TestCase(300, 1, 1, 255, 0, 255)] [TestCase(180, 0.5, 0.5, 64, 128, 128)] [TestCase(234, 0.14, 0.88, 193, 196, 224)] [TestCase(330, 0.75, 0.5, 128, 32, 80)] public static void TestRgbOutput( double hue, double saturation, double value, byte expectedRed, byte exptectedGreen, byte exptectedBlue) { var rgb = RgbHsvConversion.HsvToRgb(hue, saturation, value); rgb.Item1.Should().Be(expectedRed); rgb.Item2.Should().Be(exptectedGreen); rgb.Item3.Should().Be(exptectedBlue); } // Parameters of test-cases for TestRGBOutput reversed [TestCase(0, 0, 0, 0, 0, 0)] [TestCase(255, 255, 255, 0, 0, 1)] [TestCase(255, 0, 0, 0, 1, 1)] [TestCase(255, 255, 0, 60, 1, 1)] [TestCase(0, 255, 0, 120, 1, 1)] [TestCase(0, 0, 255, 240, 1, 1)] [TestCase(255, 0, 255, 300, 1, 1)] [TestCase(64, 128, 128, 180, 0.5, 0.5)] [TestCase(193, 196, 224, 234, 0.14, 0.88)] [TestCase(128, 32, 80, 330, 0.75, 0.5)] public static void TestHsvOutput( byte red, byte green, byte blue, double expectedHue, double expectedSaturation, double expectedValue) { var hsv = RgbHsvConversion.RgbToHsv(red, green, blue); // approximate-assertions needed because of small deviations due to converting between byte-values and double-values. hsv.Item1.Should().BeApproximately(expectedHue, 0.2); hsv.Item2.Should().BeApproximately(expectedSaturation, 0.002); hsv.Item3.Should().BeApproximately(expectedValue, 0.002); } } ================================================ FILE: Algorithms.Tests/Other/SieveOfEratosthenesTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public static class SieveOfEratosthenesTests { private static readonly long[] First10000PrimeNumbers = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177, 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343, 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567, 10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739, 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059, 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443, 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109, 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211, 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289, 12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487, 12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739, 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, 12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923, 12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109, 13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187, 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619, 13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781, 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879, 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967, 13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197, 14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519, 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, 14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699, 14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851, 14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947, 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073, 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259, 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319, 15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497, 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607, 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679, 15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881, 15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183, 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, 16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381, 16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603, 16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691, 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811, 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993, 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093, 17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317, 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389, 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477, 17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669, 17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891, 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971, 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059, 18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143, 18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233, 18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313, 18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427, 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517, 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749, 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899, 18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009, 19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121, 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219, 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319, 19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423, 19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477, 19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571, 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699, 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793, 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891, 19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991, 19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071, 20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149, 20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261, 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357, 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551, 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693, 20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771, 20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897, 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983, 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067, 21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169, 21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277, 21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383, 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491, 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563, 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647, 21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751, 21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841, 21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943, 21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039, 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123, 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307, 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441, 22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543, 22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643, 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727, 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817, 22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943, 22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029, 23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099, 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203, 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321, 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447, 23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561, 23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629, 23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743, 23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827, 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909, 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091, 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169, 24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281, 24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413, 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517, 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659, 24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767, 24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877, 24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977, 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097, 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183, 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303, 25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391, 25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471, 25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603, 25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693, 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799, 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999, 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111, 26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203, 26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297, 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399, 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497, 26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633, 26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711, 26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801, 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891, 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987, 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077, 27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211, 27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329, 27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449, 27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551, 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691, 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827, 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947, 27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051, 28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151, 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283, 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403, 28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499, 28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579, 28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649, 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729, 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837, 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933, 28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059, 29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167, 29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251, 29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363, 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443, 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671, 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819, 29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921, 29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059, 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137, 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241, 30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341, 30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469, 30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559, 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689, 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803, 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871, 30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983, 31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091, 31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31183, 31189, 31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259, 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721, 31723, 31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817, 31847, 31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973, 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063, 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, 32173, 32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261, 32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, 32359, 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531, 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609, 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, 32719, 32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, 32833, 32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939, 32941, 32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023, 33029, 33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113, 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533, 33547, 33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613, 33617, 33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713, 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797, 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, 33911, 33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033, 34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, 34159, 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337, 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457, 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, 34543, 34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, 34651, 34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739, 34747, 34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847, 34849, 34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961, 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401, 35407, 35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509, 35521, 35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593, 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759, 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, 35869, 35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977, 35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, 36067, 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277, 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383, 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, 36523, 36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, 36599, 36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691, 36697, 36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781, 36787, 36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877, 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309, 37313, 37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397, 37409, 37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507, 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, 37691, 37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831, 37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957, 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047, 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, 38183, 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, 38273, 38281, 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371, 38377, 38393, 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543, 38557, 38561, 38567, 38569, 38593, 38603, 38609, 38611, 38629, 38639, 38651, 38653, 38669, 38671, 38677, 38693, 38699, 38707, 38711, 38713, 38723, 38729, 38737, 38747, 38749, 38767, 38783, 38791, 38803, 38821, 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921, 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133, 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, 39217, 39227, 39229, 39233, 39239, 39241, 39251, 39293, 39301, 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, 39383, 39397, 39409, 39419, 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, 39521, 39541, 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, 39671, 39679, 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, 39791, 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, 39863, 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, 40087, 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, 40163, 40169, 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283, 40289, 40343, 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433, 40459, 40471, 40483, 40487, 40493, 40499, 40507, 40519, 40529, 40531, 40543, 40559, 40577, 40583, 40591, 40597, 40609, 40627, 40637, 40639, 40693, 40697, 40699, 40709, 40739, 40751, 40759, 40763, 40771, 40787, 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, 41179, 41183, 41189, 41201, 41203, 41213, 41221, 41227, 41231, 41233, 41243, 41257, 41263, 41269, 41281, 41299, 41333, 41341, 41351, 41357, 41381, 41387, 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, 41491, 41507, 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603, 41609, 41611, 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669, 41681, 41687, 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801, 41809, 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897, 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, 41981, 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, 42071, 42073, 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187, 42193, 42197, 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283, 42293, 42299, 42307, 42323, 42331, 42337, 42349, 42359, 42373, 42379, 42391, 42397, 42403, 42407, 42409, 42433, 42437, 42443, 42451, 42457, 42461, 42463, 42467, 42473, 42487, 42491, 42499, 42509, 42533, 42557, 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677, 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841, 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, 42943, 42953, 42961, 42967, 42979, 42989, 43003, 43013, 43019, 43037, 43049, 43051, 43063, 43067, 43093, 43103, 43117, 43133, 43151, 43159, 43177, 43189, 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, 43313, 43319, 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451, 43457, 43481, 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579, 43591, 43597, 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661, 43669, 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783, 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, 43933, 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, 43997, 44017, 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101, 44111, 44119, 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201, 44203, 44207, 44221, 44249, 44257, 44263, 44267, 44269, 44273, 44279, 44281, 44293, 44351, 44357, 44371, 44381, 44383, 44389, 44417, 44449, 44453, 44483, 44491, 44497, 44501, 44507, 44519, 44531, 44533, 44537, 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641, 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843, 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, 44939, 44953, 44959, 44963, 44971, 44983, 44987, 45007, 45013, 45053, 45061, 45077, 45083, 45119, 45121, 45127, 45131, 45137, 45139, 45161, 45179, 45181, 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, 45293, 45307, 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403, 45413, 45427, 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533, 45541, 45553, 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641, 45659, 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757, 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, 45853, 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, 45971, 45979, 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099, 46103, 46133, 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199, 46219, 46229, 46237, 46261, 46271, 46273, 46279, 46301, 46307, 46309, 46327, 46337, 46349, 46351, 46381, 46399, 46411, 46439, 46441, 46447, 46451, 46457, 46471, 46477, 46489, 46499, 46507, 46511, 46523, 46549, 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643, 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831, 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, 46957, 46993, 46997, 47017, 47041, 47051, 47057, 47059, 47087, 47093, 47111, 47119, 47123, 47129, 47137, 47143, 47147, 47149, 47161, 47189, 47207, 47221, 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, 47309, 47317, 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419, 47431, 47441, 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527, 47533, 47543, 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629, 47639, 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717, 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, 47819, 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, 47933, 47939, 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049, 48073, 48079, 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179, 48187, 48193, 48197, 48221, 48239, 48247, 48259, 48271, 48281, 48299, 48311, 48313, 48337, 48341, 48353, 48371, 48383, 48397, 48407, 48409, 48413, 48437, 48449, 48463, 48473, 48479, 48481, 48487, 48491, 48497, 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611, 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817, 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, 48889, 48907, 48947, 48953, 48973, 48989, 48991, 49003, 49009, 49019, 49031, 49033, 49037, 49043, 49057, 49069, 49081, 49103, 49109, 49117, 49121, 49123, 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, 49207, 49211, 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339, 49363, 49367, 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433, 49451, 49459, 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537, 49547, 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663, 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, 49757, 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, 49843, 49853, 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957, 49991, 49993, 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069, 50077, 50087, 50093, 50101, 50111, 50119, 50123, 50129, 50131, 50147, 50153, 50159, 50177, 50207, 50221, 50227, 50231, 50261, 50263, 50273, 50287, 50291, 50311, 50321, 50329, 50333, 50341, 50359, 50363, 50377, 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503, 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753, 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, 50857, 50867, 50873, 50891, 50893, 50909, 50923, 50929, 50951, 50957, 50969, 50971, 50989, 50993, 51001, 51031, 51043, 51047, 51059, 51061, 51071, 51109, 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, 51199, 51203, 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329, 51341, 51343, 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421, 51427, 51431, 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487, 51503, 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593, 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, 51683, 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, 51797, 51803, 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899, 51907, 51913, 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009, 52021, 52027, 52051, 52057, 52067, 52069, 52081, 52103, 52121, 52127, 52147, 52153, 52163, 52177, 52181, 52183, 52189, 52201, 52223, 52237, 52249, 52253, 52259, 52267, 52289, 52291, 52301, 52313, 52321, 52361, 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501, 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709, 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, 52807, 52813, 52817, 52837, 52859, 52861, 52879, 52883, 52889, 52901, 52903, 52919, 52937, 52951, 52957, 52963, 52967, 52973, 52981, 52999, 53003, 53017, 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, 53113, 53117, 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231, 53233, 53239, 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327, 53353, 53359, 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441, 53453, 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593, 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, 53657, 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, 53777, 53783, 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891, 53897, 53899, 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993, 54001, 54011, 54013, 54037, 54049, 54059, 54083, 54091, 54101, 54121, 54133, 54139, 54151, 54163, 54167, 54181, 54193, 54217, 54251, 54269, 54277, 54287, 54293, 54311, 54319, 54323, 54331, 54347, 54361, 54367, 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443, 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629, 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, 54727, 54751, 54767, 54773, 54779, 54787, 54799, 54829, 54833, 54851, 54869, 54877, 54881, 54907, 54917, 54919, 54941, 54949, 54959, 54973, 54979, 54983, 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, 55079, 55103, 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217, 55219, 55229, 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337, 55339, 55343, 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457, 55469, 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603, 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, 55673, 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, 55787, 55793, 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849, 55871, 55889, 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949, 55967, 55987, 55997, 56003, 56009, 56039, 56041, 56053, 56081, 56087, 56093, 56099, 56101, 56113, 56123, 56131, 56149, 56167, 56171, 56179, 56197, 56207, 56209, 56237, 56239, 56249, 56263, 56267, 56269, 56299, 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431, 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599, 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, 56701, 56711, 56713, 56731, 56737, 56747, 56767, 56773, 56779, 56783, 56807, 56809, 56813, 56821, 56827, 56843, 56857, 56873, 56891, 56893, 56897, 56909, 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, 56983, 56989, 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097, 57107, 57119, 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191, 57193, 57203, 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283, 57287, 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389, 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, 57529, 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, 57649, 57653, 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737, 57751, 57773, 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839, 57847, 57853, 57859, 57881, 57899, 57901, 57917, 57923, 57943, 57947, 57973, 57977, 57991, 58013, 58027, 58031, 58043, 58049, 58057, 58061, 58067, 58073, 58099, 58109, 58111, 58129, 58147, 58151, 58153, 58169, 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237, 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453, 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, 58579, 58601, 58603, 58613, 58631, 58657, 58661, 58679, 58687, 58693, 58699, 58711, 58727, 58733, 58741, 58757, 58763, 58771, 58787, 58789, 58831, 58889, 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, 58963, 58967, 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053, 59063, 59069, 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141, 59149, 59159, 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233, 59239, 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359, 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, 59443, 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, 59539, 59557, 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659, 59663, 59669, 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747, 59753, 59771, 59779, 59791, 59797, 59809, 59833, 59863, 59879, 59887, 59921, 59929, 59951, 59957, 59971, 59981, 59999, 60013, 60017, 60029, 60037, 60041, 60077, 60083, 60089, 60091, 60101, 60103, 60107, 60127, 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251, 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497, 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, 60617, 60623, 60631, 60637, 60647, 60649, 60659, 60661, 60679, 60689, 60703, 60719, 60727, 60733, 60737, 60757, 60761, 60763, 60773, 60779, 60793, 60811, 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, 60917, 60919, 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043, 61051, 61057, 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169, 61211, 61223, 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333, 61339, 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441, 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, 61543, 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, 61627, 61631, 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717, 61723, 61729, 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861, 61871, 61879, 61909, 61927, 61933, 61949, 61961, 61967, 61979, 61981, 61987, 61991, 62003, 62011, 62017, 62039, 62047, 62053, 62057, 62071, 62081, 62099, 62119, 62129, 62131, 62137, 62141, 62143, 62171, 62189, 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303, 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549, 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, 62639, 62653, 62659, 62683, 62687, 62701, 62723, 62731, 62743, 62753, 62761, 62773, 62791, 62801, 62819, 62827, 62851, 62861, 62869, 62873, 62897, 62903, 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, 62987, 62989, 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127, 63131, 63149, 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281, 63299, 63311, 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367, 63377, 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463, 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, 63559, 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, 63629, 63647, 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719, 63727, 63737, 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809, 63823, 63839, 63841, 63853, 63857, 63863, 63901, 63907, 63913, 63929, 63949, 63977, 63997, 64007, 64013, 64019, 64033, 64037, 64063, 64067, 64081, 64091, 64109, 64123, 64151, 64153, 64157, 64171, 64187, 64189, 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319, 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601, 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, 64679, 64693, 64709, 64717, 64747, 64763, 64781, 64783, 64793, 64811, 64817, 64849, 64853, 64871, 64877, 64879, 64891, 64901, 64919, 64921, 64927, 64937, 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, 65053, 65063, 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147, 65167, 65171, 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267, 65269, 65287, 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381, 65393, 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497, 65519, 65521, 65537, 65539, 65543, 65551, 65557, 65563, 65579, 65581, 65587, 65599, 65609, 65617, 65629, 65633, 65647, 65651, 65657, 65677, 65687, 65699, 65701, 65707, 65713, 65717, 65719, 65729, 65731, 65761, 65777, 65789, 65809, 65827, 65831, 65837, 65839, 65843, 65851, 65867, 65881, 65899, 65921, 65927, 65929, 65951, 65957, 65963, 65981, 65983, 65993, 66029, 66037, 66041, 66047, 66067, 66071, 66083, 66089, 66103, 66107, 66109, 66137, 66161, 66169, 66173, 66179, 66191, 66221, 66239, 66271, 66293, 66301, 66337, 66343, 66347, 66359, 66361, 66373, 66377, 66383, 66403, 66413, 66431, 66449, 66457, 66463, 66467, 66491, 66499, 66509, 66523, 66529, 66533, 66541, 66553, 66569, 66571, 66587, 66593, 66601, 66617, 66629, 66643, 66653, 66683, 66697, 66701, 66713, 66721, 66733, 66739, 66749, 66751, 66763, 66791, 66797, 66809, 66821, 66841, 66851, 66853, 66863, 66877, 66883, 66889, 66919, 66923, 66931, 66943, 66947, 66949, 66959, 66973, 66977, 67003, 67021, 67033, 67043, 67049, 67057, 67061, 67073, 67079, 67103, 67121, 67129, 67139, 67141, 67153, 67157, 67169, 67181, 67187, 67189, 67211, 67213, 67217, 67219, 67231, 67247, 67261, 67271, 67273, 67289, 67307, 67339, 67343, 67349, 67369, 67391, 67399, 67409, 67411, 67421, 67427, 67429, 67433, 67447, 67453, 67477, 67481, 67489, 67493, 67499, 67511, 67523, 67531, 67537, 67547, 67559, 67567, 67577, 67579, 67589, 67601, 67607, 67619, 67631, 67651, 67679, 67699, 67709, 67723, 67733, 67741, 67751, 67757, 67759, 67763, 67777, 67783, 67789, 67801, 67807, 67819, 67829, 67843, 67853, 67867, 67883, 67891, 67901, 67927, 67931, 67933, 67939, 67943, 67957, 67961, 67967, 67979, 67987, 67993, 68023, 68041, 68053, 68059, 68071, 68087, 68099, 68111, 68113, 68141, 68147, 68161, 68171, 68207, 68209, 68213, 68219, 68227, 68239, 68261, 68279, 68281, 68311, 68329, 68351, 68371, 68389, 68399, 68437, 68443, 68447, 68449, 68473, 68477, 68483, 68489, 68491, 68501, 68507, 68521, 68531, 68539, 68543, 68567, 68581, 68597, 68611, 68633, 68639, 68659, 68669, 68683, 68687, 68699, 68711, 68713, 68729, 68737, 68743, 68749, 68767, 68771, 68777, 68791, 68813, 68819, 68821, 68863, 68879, 68881, 68891, 68897, 68899, 68903, 68909, 68917, 68927, 68947, 68963, 68993, 69001, 69011, 69019, 69029, 69031, 69061, 69067, 69073, 69109, 69119, 69127, 69143, 69149, 69151, 69163, 69191, 69193, 69197, 69203, 69221, 69233, 69239, 69247, 69257, 69259, 69263, 69313, 69317, 69337, 69341, 69371, 69379, 69383, 69389, 69401, 69403, 69427, 69431, 69439, 69457, 69463, 69467, 69473, 69481, 69491, 69493, 69497, 69499, 69539, 69557, 69593, 69623, 69653, 69661, 69677, 69691, 69697, 69709, 69737, 69739, 69761, 69763, 69767, 69779, 69809, 69821, 69827, 69829, 69833, 69847, 69857, 69859, 69877, 69899, 69911, 69929, 69931, 69941, 69959, 69991, 69997, 70001, 70003, 70009, 70019, 70039, 70051, 70061, 70067, 70079, 70099, 70111, 70117, 70121, 70123, 70139, 70141, 70157, 70163, 70177, 70181, 70183, 70199, 70201, 70207, 70223, 70229, 70237, 70241, 70249, 70271, 70289, 70297, 70309, 70313, 70321, 70327, 70351, 70373, 70379, 70381, 70393, 70423, 70429, 70439, 70451, 70457, 70459, 70481, 70487, 70489, 70501, 70507, 70529, 70537, 70549, 70571, 70573, 70583, 70589, 70607, 70619, 70621, 70627, 70639, 70657, 70663, 70667, 70687, 70709, 70717, 70729, 70753, 70769, 70783, 70793, 70823, 70841, 70843, 70849, 70853, 70867, 70877, 70879, 70891, 70901, 70913, 70919, 70921, 70937, 70949, 70951, 70957, 70969, 70979, 70981, 70991, 70997, 70999, 71011, 71023, 71039, 71059, 71069, 71081, 71089, 71119, 71129, 71143, 71147, 71153, 71161, 71167, 71171, 71191, 71209, 71233, 71237, 71249, 71257, 71261, 71263, 71287, 71293, 71317, 71327, 71329, 71333, 71339, 71341, 71347, 71353, 71359, 71363, 71387, 71389, 71399, 71411, 71413, 71419, 71429, 71437, 71443, 71453, 71471, 71473, 71479, 71483, 71503, 71527, 71537, 71549, 71551, 71563, 71569, 71593, 71597, 71633, 71647, 71663, 71671, 71693, 71699, 71707, 71711, 71713, 71719, 71741, 71761, 71777, 71789, 71807, 71809, 71821, 71837, 71843, 71849, 71861, 71867, 71879, 71881, 71887, 71899, 71909, 71917, 71933, 71941, 71947, 71963, 71971, 71983, 71987, 71993, 71999, 72019, 72031, 72043, 72047, 72053, 72073, 72077, 72089, 72091, 72101, 72103, 72109, 72139, 72161, 72167, 72169, 72173, 72211, 72221, 72223, 72227, 72229, 72251, 72253, 72269, 72271, 72277, 72287, 72307, 72313, 72337, 72341, 72353, 72367, 72379, 72383, 72421, 72431, 72461, 72467, 72469, 72481, 72493, 72497, 72503, 72533, 72547, 72551, 72559, 72577, 72613, 72617, 72623, 72643, 72647, 72649, 72661, 72671, 72673, 72679, 72689, 72701, 72707, 72719, 72727, 72733, 72739, 72763, 72767, 72797, 72817, 72823, 72859, 72869, 72871, 72883, 72889, 72893, 72901, 72907, 72911, 72923, 72931, 72937, 72949, 72953, 72959, 72973, 72977, 72997, 73009, 73013, 73019, 73037, 73039, 73043, 73061, 73063, 73079, 73091, 73121, 73127, 73133, 73141, 73181, 73189, 73237, 73243, 73259, 73277, 73291, 73303, 73309, 73327, 73331, 73351, 73361, 73363, 73369, 73379, 73387, 73417, 73421, 73433, 73453, 73459, 73471, 73477, 73483, 73517, 73523, 73529, 73547, 73553, 73561, 73571, 73583, 73589, 73597, 73607, 73609, 73613, 73637, 73643, 73651, 73673, 73679, 73681, 73693, 73699, 73709, 73721, 73727, 73751, 73757, 73771, 73783, 73819, 73823, 73847, 73849, 73859, 73867, 73877, 73883, 73897, 73907, 73939, 73943, 73951, 73961, 73973, 73999, 74017, 74021, 74027, 74047, 74051, 74071, 74077, 74093, 74099, 74101, 74131, 74143, 74149, 74159, 74161, 74167, 74177, 74189, 74197, 74201, 74203, 74209, 74219, 74231, 74257, 74279, 74287, 74293, 74297, 74311, 74317, 74323, 74353, 74357, 74363, 74377, 74381, 74383, 74411, 74413, 74419, 74441, 74449, 74453, 74471, 74489, 74507, 74509, 74521, 74527, 74531, 74551, 74561, 74567, 74573, 74587, 74597, 74609, 74611, 74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, 74729, 74731, 74747, 74759, 74761, 74771, 74779, 74797, 74821, 74827, 74831, 74843, 74857, 74861, 74869, 74873, 74887, 74891, 74897, 74903, 74923, 74929, 74933, 74941, 74959, 75011, 75013, 75017, 75029, 75037, 75041, 75079, 75083, 75109, 75133, 75149, 75161, 75167, 75169, 75181, 75193, 75209, 75211, 75217, 75223, 75227, 75239, 75253, 75269, 75277, 75289, 75307, 75323, 75329, 75337, 75347, 75353, 75367, 75377, 75389, 75391, 75401, 75403, 75407, 75431, 75437, 75479, 75503, 75511, 75521, 75527, 75533, 75539, 75541, 75553, 75557, 75571, 75577, 75583, 75611, 75617, 75619, 75629, 75641, 75653, 75659, 75679, 75683, 75689, 75703, 75707, 75709, 75721, 75731, 75743, 75767, 75773, 75781, 75787, 75793, 75797, 75821, 75833, 75853, 75869, 75883, 75913, 75931, 75937, 75941, 75967, 75979, 75983, 75989, 75991, 75997, 76001, 76003, 76031, 76039, 76079, 76081, 76091, 76099, 76103, 76123, 76129, 76147, 76157, 76159, 76163, 76207, 76213, 76231, 76243, 76249, 76253, 76259, 76261, 76283, 76289, 76303, 76333, 76343, 76367, 76369, 76379, 76387, 76403, 76421, 76423, 76441, 76463, 76471, 76481, 76487, 76493, 76507, 76511, 76519, 76537, 76541, 76543, 76561, 76579, 76597, 76603, 76607, 76631, 76649, 76651, 76667, 76673, 76679, 76697, 76717, 76733, 76753, 76757, 76771, 76777, 76781, 76801, 76819, 76829, 76831, 76837, 76847, 76871, 76873, 76883, 76907, 76913, 76919, 76943, 76949, 76961, 76963, 76991, 77003, 77017, 77023, 77029, 77041, 77047, 77069, 77081, 77093, 77101, 77137, 77141, 77153, 77167, 77171, 77191, 77201, 77213, 77237, 77239, 77243, 77249, 77261, 77263, 77267, 77269, 77279, 77291, 77317, 77323, 77339, 77347, 77351, 77359, 77369, 77377, 77383, 77417, 77419, 77431, 77447, 77471, 77477, 77479, 77489, 77491, 77509, 77513, 77521, 77527, 77543, 77549, 77551, 77557, 77563, 77569, 77573, 77587, 77591, 77611, 77617, 77621, 77641, 77647, 77659, 77681, 77687, 77689, 77699, 77711, 77713, 77719, 77723, 77731, 77743, 77747, 77761, 77773, 77783, 77797, 77801, 77813, 77839, 77849, 77863, 77867, 77893, 77899, 77929, 77933, 77951, 77969, 77977, 77983, 77999, 78007, 78017, 78031, 78041, 78049, 78059, 78079, 78101, 78121, 78137, 78139, 78157, 78163, 78167, 78173, 78179, 78191, 78193, 78203, 78229, 78233, 78241, 78259, 78277, 78283, 78301, 78307, 78311, 78317, 78341, 78347, 78367, 78401, 78427, 78437, 78439, 78467, 78479, 78487, 78497, 78509, 78511, 78517, 78539, 78541, 78553, 78569, 78571, 78577, 78583, 78593, 78607, 78623, 78643, 78649, 78653, 78691, 78697, 78707, 78713, 78721, 78737, 78779, 78781, 78787, 78791, 78797, 78803, 78809, 78823, 78839, 78853, 78857, 78877, 78887, 78889, 78893, 78901, 78919, 78929, 78941, 78977, 78979, 78989, 79031, 79039, 79043, 79063, 79087, 79103, 79111, 79133, 79139, 79147, 79151, 79153, 79159, 79181, 79187, 79193, 79201, 79229, 79231, 79241, 79259, 79273, 79279, 79283, 79301, 79309, 79319, 79333, 79337, 79349, 79357, 79367, 79379, 79393, 79397, 79399, 79411, 79423, 79427, 79433, 79451, 79481, 79493, 79531, 79537, 79549, 79559, 79561, 79579, 79589, 79601, 79609, 79613, 79621, 79627, 79631, 79633, 79657, 79669, 79687, 79691, 79693, 79697, 79699, 79757, 79769, 79777, 79801, 79811, 79813, 79817, 79823, 79829, 79841, 79843, 79847, 79861, 79867, 79873, 79889, 79901, 79903, 79907, 79939, 79943, 79967, 79973, 79979, 79987, 79997, 79999, 80021, 80039, 80051, 80071, 80077, 80107, 80111, 80141, 80147, 80149, 80153, 80167, 80173, 80177, 80191, 80207, 80209, 80221, 80231, 80233, 80239, 80251, 80263, 80273, 80279, 80287, 80309, 80317, 80329, 80341, 80347, 80363, 80369, 80387, 80407, 80429, 80447, 80449, 80471, 80473, 80489, 80491, 80513, 80527, 80537, 80557, 80567, 80599, 80603, 80611, 80621, 80627, 80629, 80651, 80657, 80669, 80671, 80677, 80681, 80683, 80687, 80701, 80713, 80737, 80747, 80749, 80761, 80777, 80779, 80783, 80789, 80803, 80809, 80819, 80831, 80833, 80849, 80863, 80897, 80909, 80911, 80917, 80923, 80929, 80933, 80953, 80963, 80989, 81001, 81013, 81017, 81019, 81023, 81031, 81041, 81043, 81047, 81049, 81071, 81077, 81083, 81097, 81101, 81119, 81131, 81157, 81163, 81173, 81181, 81197, 81199, 81203, 81223, 81233, 81239, 81281, 81283, 81293, 81299, 81307, 81331, 81343, 81349, 81353, 81359, 81371, 81373, 81401, 81409, 81421, 81439, 81457, 81463, 81509, 81517, 81527, 81533, 81547, 81551, 81553, 81559, 81563, 81569, 81611, 81619, 81629, 81637, 81647, 81649, 81667, 81671, 81677, 81689, 81701, 81703, 81707, 81727, 81737, 81749, 81761, 81769, 81773, 81799, 81817, 81839, 81847, 81853, 81869, 81883, 81899, 81901, 81919, 81929, 81931, 81937, 81943, 81953, 81967, 81971, 81973, 82003, 82007, 82009, 82013, 82021, 82031, 82037, 82039, 82051, 82067, 82073, 82129, 82139, 82141, 82153, 82163, 82171, 82183, 82189, 82193, 82207, 82217, 82219, 82223, 82231, 82237, 82241, 82261, 82267, 82279, 82301, 82307, 82339, 82349, 82351, 82361, 82373, 82387, 82393, 82421, 82457, 82463, 82469, 82471, 82483, 82487, 82493, 82499, 82507, 82529, 82531, 82549, 82559, 82561, 82567, 82571, 82591, 82601, 82609, 82613, 82619, 82633, 82651, 82657, 82699, 82721, 82723, 82727, 82729, 82757, 82759, 82763, 82781, 82787, 82793, 82799, 82811, 82813, 82837, 82847, 82883, 82889, 82891, 82903, 82913, 82939, 82963, 82981, 82997, 83003, 83009, 83023, 83047, 83059, 83063, 83071, 83077, 83089, 83093, 83101, 83117, 83137, 83177, 83203, 83207, 83219, 83221, 83227, 83231, 83233, 83243, 83257, 83267, 83269, 83273, 83299, 83311, 83339, 83341, 83357, 83383, 83389, 83399, 83401, 83407, 83417, 83423, 83431, 83437, 83443, 83449, 83459, 83471, 83477, 83497, 83537, 83557, 83561, 83563, 83579, 83591, 83597, 83609, 83617, 83621, 83639, 83641, 83653, 83663, 83689, 83701, 83717, 83719, 83737, 83761, 83773, 83777, 83791, 83813, 83833, 83843, 83857, 83869, 83873, 83891, 83903, 83911, 83921, 83933, 83939, 83969, 83983, 83987, 84011, 84017, 84047, 84053, 84059, 84061, 84067, 84089, 84121, 84127, 84131, 84137, 84143, 84163, 84179, 84181, 84191, 84199, 84211, 84221, 84223, 84229, 84239, 84247, 84263, 84299, 84307, 84313, 84317, 84319, 84347, 84349, 84377, 84389, 84391, 84401, 84407, 84421, 84431, 84437, 84443, 84449, 84457, 84463, 84467, 84481, 84499, 84503, 84509, 84521, 84523, 84533, 84551, 84559, 84589, 84629, 84631, 84649, 84653, 84659, 84673, 84691, 84697, 84701, 84713, 84719, 84731, 84737, 84751, 84761, 84787, 84793, 84809, 84811, 84827, 84857, 84859, 84869, 84871, 84913, 84919, 84947, 84961, 84967, 84977, 84979, 84991, 85009, 85021, 85027, 85037, 85049, 85061, 85081, 85087, 85091, 85093, 85103, 85109, 85121, 85133, 85147, 85159, 85193, 85199, 85201, 85213, 85223, 85229, 85237, 85243, 85247, 85259, 85297, 85303, 85313, 85331, 85333, 85361, 85363, 85369, 85381, 85411, 85427, 85429, 85439, 85447, 85451, 85453, 85469, 85487, 85513, 85517, 85523, 85531, 85549, 85571, 85577, 85597, 85601, 85607, 85619, 85621, 85627, 85639, 85643, 85661, 85667, 85669, 85691, 85703, 85711, 85717, 85733, 85751, 85781, 85793, 85817, 85819, 85829, 85831, 85837, 85843, 85847, 85853, 85889, 85903, 85909, 85931, 85933, 85991, 85999, 86011, 86017, 86027, 86029, 86069, 86077, 86083, 86111, 86113, 86117, 86131, 86137, 86143, 86161, 86171, 86179, 86183, 86197, 86201, 86209, 86239, 86243, 86249, 86257, 86263, 86269, 86287, 86291, 86293, 86297, 86311, 86323, 86341, 86351, 86353, 86357, 86369, 86371, 86381, 86389, 86399, 86413, 86423, 86441, 86453, 86461, 86467, 86477, 86491, 86501, 86509, 86531, 86533, 86539, 86561, 86573, 86579, 86587, 86599, 86627, 86629, 86677, 86689, 86693, 86711, 86719, 86729, 86743, 86753, 86767, 86771, 86783, 86813, 86837, 86843, 86851, 86857, 86861, 86869, 86923, 86927, 86929, 86939, 86951, 86959, 86969, 86981, 86993, 87011, 87013, 87037, 87041, 87049, 87071, 87083, 87103, 87107, 87119, 87121, 87133, 87149, 87151, 87179, 87181, 87187, 87211, 87221, 87223, 87251, 87253, 87257, 87277, 87281, 87293, 87299, 87313, 87317, 87323, 87337, 87359, 87383, 87403, 87407, 87421, 87427, 87433, 87443, 87473, 87481, 87491, 87509, 87511, 87517, 87523, 87539, 87541, 87547, 87553, 87557, 87559, 87583, 87587, 87589, 87613, 87623, 87629, 87631, 87641, 87643, 87649, 87671, 87679, 87683, 87691, 87697, 87701, 87719, 87721, 87739, 87743, 87751, 87767, 87793, 87797, 87803, 87811, 87833, 87853, 87869, 87877, 87881, 87887, 87911, 87917, 87931, 87943, 87959, 87961, 87973, 87977, 87991, 88001, 88003, 88007, 88019, 88037, 88069, 88079, 88093, 88117, 88129, 88169, 88177, 88211, 88223, 88237, 88241, 88259, 88261, 88289, 88301, 88321, 88327, 88337, 88339, 88379, 88397, 88411, 88423, 88427, 88463, 88469, 88471, 88493, 88499, 88513, 88523, 88547, 88589, 88591, 88607, 88609, 88643, 88651, 88657, 88661, 88663, 88667, 88681, 88721, 88729, 88741, 88747, 88771, 88789, 88793, 88799, 88801, 88807, 88811, 88813, 88817, 88819, 88843, 88853, 88861, 88867, 88873, 88883, 88897, 88903, 88919, 88937, 88951, 88969, 88993, 88997, 89003, 89009, 89017, 89021, 89041, 89051, 89057, 89069, 89071, 89083, 89087, 89101, 89107, 89113, 89119, 89123, 89137, 89153, 89189, 89203, 89209, 89213, 89227, 89231, 89237, 89261, 89269, 89273, 89293, 89303, 89317, 89329, 89363, 89371, 89381, 89387, 89393, 89399, 89413, 89417, 89431, 89443, 89449, 89459, 89477, 89491, 89501, 89513, 89519, 89521, 89527, 89533, 89561, 89563, 89567, 89591, 89597, 89599, 89603, 89611, 89627, 89633, 89653, 89657, 89659, 89669, 89671, 89681, 89689, 89753, 89759, 89767, 89779, 89783, 89797, 89809, 89819, 89821, 89833, 89839, 89849, 89867, 89891, 89897, 89899, 89909, 89917, 89923, 89939, 89959, 89963, 89977, 89983, 89989, 90001, 90007, 90011, 90017, 90019, 90023, 90031, 90053, 90059, 90067, 90071, 90073, 90089, 90107, 90121, 90127, 90149, 90163, 90173, 90187, 90191, 90197, 90199, 90203, 90217, 90227, 90239, 90247, 90263, 90271, 90281, 90289, 90313, 90353, 90359, 90371, 90373, 90379, 90397, 90401, 90403, 90407, 90437, 90439, 90469, 90473, 90481, 90499, 90511, 90523, 90527, 90529, 90533, 90547, 90583, 90599, 90617, 90619, 90631, 90641, 90647, 90659, 90677, 90679, 90697, 90703, 90709, 90731, 90749, 90787, 90793, 90803, 90821, 90823, 90833, 90841, 90847, 90863, 90887, 90901, 90907, 90911, 90917, 90931, 90947, 90971, 90977, 90989, 90997, 91009, 91019, 91033, 91079, 91081, 91097, 91099, 91121, 91127, 91129, 91139, 91141, 91151, 91153, 91159, 91163, 91183, 91193, 91199, 91229, 91237, 91243, 91249, 91253, 91283, 91291, 91297, 91303, 91309, 91331, 91367, 91369, 91373, 91381, 91387, 91393, 91397, 91411, 91423, 91433, 91453, 91457, 91459, 91463, 91493, 91499, 91513, 91529, 91541, 91571, 91573, 91577, 91583, 91591, 91621, 91631, 91639, 91673, 91691, 91703, 91711, 91733, 91753, 91757, 91771, 91781, 91801, 91807, 91811, 91813, 91823, 91837, 91841, 91867, 91873, 91909, 91921, 91939, 91943, 91951, 91957, 91961, 91967, 91969, 91997, 92003, 92009, 92033, 92041, 92051, 92077, 92083, 92107, 92111, 92119, 92143, 92153, 92173, 92177, 92179, 92189, 92203, 92219, 92221, 92227, 92233, 92237, 92243, 92251, 92269, 92297, 92311, 92317, 92333, 92347, 92353, 92357, 92363, 92369, 92377, 92381, 92383, 92387, 92399, 92401, 92413, 92419, 92431, 92459, 92461, 92467, 92479, 92489, 92503, 92507, 92551, 92557, 92567, 92569, 92581, 92593, 92623, 92627, 92639, 92641, 92647, 92657, 92669, 92671, 92681, 92683, 92693, 92699, 92707, 92717, 92723, 92737, 92753, 92761, 92767, 92779, 92789, 92791, 92801, 92809, 92821, 92831, 92849, 92857, 92861, 92863, 92867, 92893, 92899, 92921, 92927, 92941, 92951, 92957, 92959, 92987, 92993, 93001, 93047, 93053, 93059, 93077, 93083, 93089, 93097, 93103, 93113, 93131, 93133, 93139, 93151, 93169, 93179, 93187, 93199, 93229, 93239, 93241, 93251, 93253, 93257, 93263, 93281, 93283, 93287, 93307, 93319, 93323, 93329, 93337, 93371, 93377, 93383, 93407, 93419, 93427, 93463, 93479, 93481, 93487, 93491, 93493, 93497, 93503, 93523, 93529, 93553, 93557, 93559, 93563, 93581, 93601, 93607, 93629, 93637, 93683, 93701, 93703, 93719, 93739, 93761, 93763, 93787, 93809, 93811, 93827, 93851, 93871, 93887, 93889, 93893, 93901, 93911, 93913, 93923, 93937, 93941, 93949, 93967, 93971, 93979, 93983, 93997, 94007, 94009, 94033, 94049, 94057, 94063, 94079, 94099, 94109, 94111, 94117, 94121, 94151, 94153, 94169, 94201, 94207, 94219, 94229, 94253, 94261, 94273, 94291, 94307, 94309, 94321, 94327, 94331, 94343, 94349, 94351, 94379, 94397, 94399, 94421, 94427, 94433, 94439, 94441, 94447, 94463, 94477, 94483, 94513, 94529, 94531, 94541, 94543, 94547, 94559, 94561, 94573, 94583, 94597, 94603, 94613, 94621, 94649, 94651, 94687, 94693, 94709, 94723, 94727, 94747, 94771, 94777, 94781, 94789, 94793, 94811, 94819, 94823, 94837, 94841, 94847, 94849, 94873, 94889, 94903, 94907, 94933, 94949, 94951, 94961, 94993, 94999, 95003, 95009, 95021, 95027, 95063, 95071, 95083, 95087, 95089, 95093, 95101, 95107, 95111, 95131, 95143, 95153, 95177, 95189, 95191, 95203, 95213, 95219, 95231, 95233, 95239, 95257, 95261, 95267, 95273, 95279, 95287, 95311, 95317, 95327, 95339, 95369, 95383, 95393, 95401, 95413, 95419, 95429, 95441, 95443, 95461, 95467, 95471, 95479, 95483, 95507, 95527, 95531, 95539, 95549, 95561, 95569, 95581, 95597, 95603, 95617, 95621, 95629, 95633, 95651, 95701, 95707, 95713, 95717, 95723, 95731, 95737, 95747, 95773, 95783, 95789, 95791, 95801, 95803, 95813, 95819, 95857, 95869, 95873, 95881, 95891, 95911, 95917, 95923, 95929, 95947, 95957, 95959, 95971, 95987, 95989, 96001, 96013, 96017, 96043, 96053, 96059, 96079, 96097, 96137, 96149, 96157, 96167, 96179, 96181, 96199, 96211, 96221, 96223, 96233, 96259, 96263, 96269, 96281, 96289, 96293, 96323, 96329, 96331, 96337, 96353, 96377, 96401, 96419, 96431, 96443, 96451, 96457, 96461, 96469, 96479, 96487, 96493, 96497, 96517, 96527, 96553, 96557, 96581, 96587, 96589, 96601, 96643, 96661, 96667, 96671, 96697, 96703, 96731, 96737, 96739, 96749, 96757, 96763, 96769, 96779, 96787, 96797, 96799, 96821, 96823, 96827, 96847, 96851, 96857, 96893, 96907, 96911, 96931, 96953, 96959, 96973, 96979, 96989, 96997, 97001, 97003, 97007, 97021, 97039, 97073, 97081, 97103, 97117, 97127, 97151, 97157, 97159, 97169, 97171, 97177, 97187, 97213, 97231, 97241, 97259, 97283, 97301, 97303, 97327, 97367, 97369, 97373, 97379, 97381, 97387, 97397, 97423, 97429, 97441, 97453, 97459, 97463, 97499, 97501, 97511, 97523, 97547, 97549, 97553, 97561, 97571, 97577, 97579, 97583, 97607, 97609, 97613, 97649, 97651, 97673, 97687, 97711, 97729, 97771, 97777, 97787, 97789, 97813, 97829, 97841, 97843, 97847, 97849, 97859, 97861, 97871, 97879, 97883, 97919, 97927, 97931, 97943, 97961, 97967, 97973, 97987, 98009, 98011, 98017, 98041, 98047, 98057, 98081, 98101, 98123, 98129, 98143, 98179, 98207, 98213, 98221, 98227, 98251, 98257, 98269, 98297, 98299, 98317, 98321, 98323, 98327, 98347, 98369, 98377, 98387, 98389, 98407, 98411, 98419, 98429, 98443, 98453, 98459, 98467, 98473, 98479, 98491, 98507, 98519, 98533, 98543, 98561, 98563, 98573, 98597, 98621, 98627, 98639, 98641, 98663, 98669, 98689, 98711, 98713, 98717, 98729, 98731, 98737, 98773, 98779, 98801, 98807, 98809, 98837, 98849, 98867, 98869, 98873, 98887, 98893, 98897, 98899, 98909, 98911, 98927, 98929, 98939, 98947, 98953, 98963, 98981, 98993, 98999, 99013, 99017, 99023, 99041, 99053, 99079, 99083, 99089, 99103, 99109, 99119, 99131, 99133, 99137, 99139, 99149, 99173, 99181, 99191, 99223, 99233, 99241, 99251, 99257, 99259, 99277, 99289, 99317, 99347, 99349, 99367, 99371, 99377, 99391, 99397, 99401, 99409, 99431, 99439, 99469, 99487, 99497, 99523, 99527, 99529, 99551, 99559, 99563, 99571, 99577, 99581, 99607, 99611, 99623, 99643, 99661, 99667, 99679, 99689, 99707, 99709, 99713, 99719, 99721, 99733, 99761, 99767, 99787, 99793, 99809, 99817, 99823, 99829, 99833, 99839, 99859, 99871, 99877, 99881, 99901, 99907, 99923, 99929, 99961, 99971, 99989, 99991, 100003, 100019, 100043, 100049, 100057, 100069, 100103, 100109, 100129, 100151, 100153, 100169, 100183, 100189, 100193, 100207, 100213, 100237, 100267, 100271, 100279, 100291, 100297, 100313, 100333, 100343, 100357, 100361, 100363, 100379, 100391, 100393, 100403, 100411, 100417, 100447, 100459, 100469, 100483, 100493, 100501, 100511, 100517, 100519, 100523, 100537, 100547, 100549, 100559, 100591, 100609, 100613, 100621, 100649, 100669, 100673, 100693, 100699, 100703, 100733, 100741, 100747, 100769, 100787, 100799, 100801, 100811, 100823, 100829, 100847, 100853, 100907, 100913, 100927, 100931, 100937, 100943, 100957, 100981, 100987, 100999, 101009, 101021, 101027, 101051, 101063, 101081, 101089, 101107, 101111, 101113, 101117, 101119, 101141, 101149, 101159, 101161, 101173, 101183, 101197, 101203, 101207, 101209, 101221, 101267, 101273, 101279, 101281, 101287, 101293, 101323, 101333, 101341, 101347, 101359, 101363, 101377, 101383, 101399, 101411, 101419, 101429, 101449, 101467, 101477, 101483, 101489, 101501, 101503, 101513, 101527, 101531, 101533, 101537, 101561, 101573, 101581, 101599, 101603, 101611, 101627, 101641, 101653, 101663, 101681, 101693, 101701, 101719, 101723, 101737, 101741, 101747, 101749, 101771, 101789, 101797, 101807, 101833, 101837, 101839, 101863, 101869, 101873, 101879, 101891, 101917, 101921, 101929, 101939, 101957, 101963, 101977, 101987, 101999, 102001, 102013, 102019, 102023, 102031, 102043, 102059, 102061, 102071, 102077, 102079, 102101, 102103, 102107, 102121, 102139, 102149, 102161, 102181, 102191, 102197, 102199, 102203, 102217, 102229, 102233, 102241, 102251, 102253, 102259, 102293, 102299, 102301, 102317, 102329, 102337, 102359, 102367, 102397, 102407, 102409, 102433, 102437, 102451, 102461, 102481, 102497, 102499, 102503, 102523, 102533, 102539, 102547, 102551, 102559, 102563, 102587, 102593, 102607, 102611, 102643, 102647, 102653, 102667, 102673, 102677, 102679, 102701, 102761, 102763, 102769, 102793, 102797, 102811, 102829, 102841, 102859, 102871, 102877, 102881, 102911, 102913, 102929, 102931, 102953, 102967, 102983, 103001, 103007, 103043, 103049, 103067, 103069, 103079, 103087, 103091, 103093, 103099, 103123, 103141, 103171, 103177, 103183, 103217, 103231, 103237, 103289, 103291, 103307, 103319, 103333, 103349, 103357, 103387, 103391, 103393, 103399, 103409, 103421, 103423, 103451, 103457, 103471, 103483, 103511, 103529, 103549, 103553, 103561, 103567, 103573, 103577, 103583, 103591, 103613, 103619, 103643, 103651, 103657, 103669, 103681, 103687, 103699, 103703, 103723, 103769, 103787, 103801, 103811, 103813, 103837, 103841, 103843, 103867, 103889, 103903, 103913, 103919, 103951, 103963, 103967, 103969, 103979, 103981, 103991, 103993, 103997, 104003, 104009, 104021, 104033, 104047, 104053, 104059, 104087, 104089, 104107, 104113, 104119, 104123, 104147, 104149, 104161, 104173, 104179, 104183, 104207, 104231, 104233, 104239, 104243, 104281, 104287, 104297, 104309, 104311, 104323, 104327, 104347, 104369, 104381, 104383, 104393, 104399, 104417, 104459, 104471, 104473, 104479, 104491, 104513, 104527, 104537, 104543, 104549, 104551, 104561, 104579, 104593, 104597, 104623, 104639, 104651, 104659, 104677, 104681, 104683, 104693, 104701, 104707, 104711, 104717, 104723, 104729, ]; [Test] public static void First10_000PrimesCorrect() => Assert.That(new SieveOfEratosthenes(104729).GetPrimes(), Is.EqualTo(First10000PrimeNumbers)); [Test] public static void TestMaxNumber() => Assert.That(new SieveOfEratosthenes(69).MaximumNumber, Is.EqualTo(69)); [TestCase(13, true)] [TestCase(10, false)] public static void TestIsPrime(int input, bool expected) { Assert.That(expected, Is.EqualTo(new SieveOfEratosthenes(100).IsPrime(input))); } } ================================================ FILE: Algorithms.Tests/Other/TriangulatorTests.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other { [TestFixture] public class TriangulatorTests { [Test] public void CalculatePosition_ValidCoordinatesAndDistances_ReturnsExpectedPosition() { var triangulator = new Triangulator(); var baseLocations = new List<(double Latitude, double Longitude)> { (16.054407, 108.202167), (16.049807, 108.218991), (16.063597, 108.215553) }; var distances = new List { 0.5, 0.7, 0.6 }; var expectedPosition = (Latitude: 16.054, Longitude: 108.210); var result = triangulator.CalculatePosition(baseLocations, distances); Assert.That(result.Latitude, Is.EqualTo(expectedPosition.Latitude).Within(0.01)); Assert.That(result.Longitude, Is.EqualTo(expectedPosition.Longitude).Within(0.01)); } [Test] public void CalculatePosition_InvalidBaseLocations_ThrowsArgumentException() { var triangulator = new Triangulator(); var baseLocations = new List<(double Latitude, double Longitude)> { (10.762622, 106.660172) }; var distances = new List { 1.0 }; Assert.That(() => triangulator.CalculatePosition(baseLocations, distances), Throws.ArgumentException); } [Test] public void CalculatePosition_InvalidDistances_ThrowsArgumentException() { var triangulator = new Triangulator(); var baseLocations = new List<(double Latitude, double Longitude)> { (10.762622, 106.660172), (10.774981, 106.665504), (10.771817, 106.681179) }; var distances = new List { 1.0 }; Assert.That(() => triangulator.CalculatePosition(baseLocations, distances), Throws.ArgumentException); } } } ================================================ FILE: Algorithms.Tests/Other/WelfordsVarianceTest.cs ================================================ using Algorithms.Other; namespace Algorithms.Tests.Other; public class WelfordsVarianceTest { [Test] public void WelfordVariance_Example1() { var welfordsVariance = new WelfordsVariance(); welfordsVariance.AddValue(4); welfordsVariance.AddValue(7); welfordsVariance.AddValue(13); welfordsVariance.AddValue(16); Assert.That(welfordsVariance.Count, Is.EqualTo(4)); Assert.That(welfordsVariance.Mean, Is.EqualTo(10).Within(0.0000001)); Assert.That(welfordsVariance.Variance, Is.EqualTo(22.5).Within(0.0000001)); Assert.That(welfordsVariance.SampleVariance, Is.EqualTo(30).Within(0.0000001)); } [Test] public void WelfordVariance_Example2() { var stats = new WelfordsVariance(); stats.AddValue(100000004); stats.AddValue(100000007); stats.AddValue(100000013); stats.AddValue(100000016); Assert.That(stats.Count, Is.EqualTo(4)); Assert.That(stats.Mean, Is.EqualTo(100000010).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(22.5).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(30).Within(0.0000001)); } [Test] public void WelfordVariance_Example3() { var stats = new WelfordsVariance(); stats.AddValue(1000000004); stats.AddValue(1000000007); stats.AddValue(1000000013); stats.AddValue(1000000016); Assert.That(stats.Count, Is.EqualTo(4)); Assert.That(stats.Mean, Is.EqualTo(1000000010).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(22.5).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(30).Within(0.0000001)); } [Test] public void WelfordVariance_Example4() { var stats = new WelfordsVariance(); stats.AddValue(6); stats.AddValue(2); stats.AddValue(3); stats.AddValue(1); Assert.That(stats.Count, Is.EqualTo(4)); Assert.That(stats.Mean, Is.EqualTo(3).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(3.5).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(4.6666667).Within(0.0000001)); } [Test] public void WelfordVariance_Example5() { var stats = new WelfordsVariance([2, 2, 5, 7]); Assert.That(stats.Count, Is.EqualTo(4)); Assert.That(stats.Mean, Is.EqualTo(4).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(4.5).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(6).Within(0.0000001)); } [Test] public void WelfordVariance_Example6() { var stats = new WelfordsVariance(); stats.AddRange([2, 4, 4, 4, 5, 5, 7, 9]); Assert.That(stats.Count, Is.EqualTo(8)); Assert.That(stats.Mean, Is.EqualTo(5).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(4).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(4.5714286).Within(0.0000001)); } [Test] public void WelfordVariance_Example7() { var stats = new WelfordsVariance(); stats.AddRange([9, 2, 5, 4, 12, 7, 8, 11, 9, 3, 7, 4, 12, 5, 4, 10, 9, 6, 9, 4]); Assert.That(stats.Count, Is.EqualTo(20)); Assert.That(stats.Mean, Is.EqualTo(7).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(8.9).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(9.3684211).Within(0.0000001)); } [Test] public void WelfordVariance_Example8() { var stats = new WelfordsVariance(); stats.AddRange([51.3, 55.6, 49.9, 52.0]); Assert.That(stats.Count, Is.EqualTo(4)); Assert.That(stats.Mean, Is.EqualTo(52.2).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(4.4250000).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(5.9000000).Within(0.0000001)); } [Test] public void WelfordVariance_Example9() { var stats = new WelfordsVariance(); stats.AddRange([-5, -3, -1, 1, 3]); Assert.That(stats.Count, Is.EqualTo(5)); Assert.That(stats.Mean, Is.EqualTo(-1).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(8).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(10).Within(0.0000001)); } [Test] public void WelfordVariance_Example10() { var stats = new WelfordsVariance(); stats.AddRange([-1, 0, 1]); Assert.That(stats.Count, Is.EqualTo(3)); Assert.That(stats.Mean, Is.EqualTo(0).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(0.6666667).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(1).Within(0.0000001)); } [Test] public void WelfordVariance_NoValue() { var stats = new WelfordsVariance(); Assert.That(stats.Count, Is.EqualTo(0)); Assert.That(stats.Mean, Is.EqualTo(double.NaN)); Assert.That(stats.Variance, Is.EqualTo(double.NaN)); Assert.That(stats.SampleVariance, Is.EqualTo(double.NaN)); } [Test] public void WelfordVariance_OneValue() { var stats = new WelfordsVariance(); stats.AddValue(1); Assert.That(stats.Count, Is.EqualTo(1)); Assert.That(stats.Mean, Is.EqualTo(double.NaN)); Assert.That(stats.Variance, Is.EqualTo(double.NaN)); Assert.That(stats.SampleVariance, Is.EqualTo(double.NaN)); } [Test] public void WelfordVariance_TwoValues() { var stats = new WelfordsVariance(); stats.AddValue(1); stats.AddValue(2); Assert.That(stats.Count, Is.EqualTo(2)); Assert.That(stats.Mean, Is.EqualTo(1.5).Within(0.0000001)); Assert.That(stats.Variance, Is.EqualTo(0.25).Within(0.0000001)); Assert.That(stats.SampleVariance, Is.EqualTo(0.5).Within(0.0000001)); } } ================================================ FILE: Algorithms.Tests/Problems/DynamicProgramming/CoinChange/GenerateChangesDictionaryTests.cs ================================================ using Algorithms.Problems.DynamicProgramming.CoinChange; namespace Algorithms.Tests.Problems.DynamicProgramming.CoinChange; [TestFixture] public class GenerateChangesDictionaryTests { [Test] public void GenerateChangesDictionaryTest_Success() { const int coin = 6; var coins = new[] { 1, 3, 4 }; var changeDictionary = DynamicCoinChangeSolver.GenerateChangesDictionary(coin, coins); changeDictionary[1].SequenceEqual(new[] { 0 }).Should().BeTrue(); changeDictionary[2].SequenceEqual(new[] { 1 }).Should().BeTrue(); changeDictionary[3].SequenceEqual(new[] { 0, 2 }).Should().BeTrue(); changeDictionary[4].SequenceEqual(new[] { 0, 1, 3 }).Should().BeTrue(); changeDictionary[5].SequenceEqual(new[] { 1, 2, 4 }).Should().BeTrue(); changeDictionary[6].SequenceEqual(new[] { 2, 3, 5 }).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Problems/DynamicProgramming/CoinChange/GenerateSingleCoinChangesTests.cs ================================================ using Algorithms.Problems.DynamicProgramming.CoinChange; namespace Algorithms.Tests.Problems.DynamicProgramming.CoinChange; [TestFixture] public class GenerateSingleCoinChangesTests { [Test] public void GenerateSingleCoinChangesTests_Success() { DynamicCoinChangeSolver .GenerateSingleCoinChanges(6, [1, 2, 3]) .SequenceEqual(new[] { 3, 4, 5 }) .Should().BeTrue(); DynamicCoinChangeSolver .GenerateSingleCoinChanges(10, [1, 2, 3, 7, 12, 15, 14]) .SequenceEqual(new[] { 3, 7, 8, 9 }) .Should().BeTrue(); DynamicCoinChangeSolver .GenerateSingleCoinChanges(1, [1, 2, 3, 7, 12, 15, 14]) .SequenceEqual(new[] { 0 }) .Should().BeTrue(); DynamicCoinChangeSolver .GenerateSingleCoinChanges(2, [1, 2, 3, 7, 12, 15, 14]) .SequenceEqual(new[] { 0, 1 }) .Should().BeTrue(); } [Test] public void GenerateSingleCoinChangesTests_ShouldThrow_CoinCannotBeLesserOrEqualZero() { const int coin = 0; var arr = new[] { 1, 2, 3 }; Func act = () => DynamicCoinChangeSolver.GenerateSingleCoinChanges(coin, arr); act.Should().Throw() .WithMessage($"The coin cannot be lesser or equal to zero {nameof(coin)}."); } [Test] public void GenerateSingleCoinChangesTests_ShouldThrow_CoinsArrayCannotBeEmpty() { const int coin = 10; var coinsAsArray = Array.Empty(); Func act = () => DynamicCoinChangeSolver.GenerateSingleCoinChanges(coin, coinsAsArray); act.Should().Throw() .WithMessage($"Coins array cannot be empty {nameof(coinsAsArray)}."); } [Test] public void GenerateSingleCoinChangesTests_ShouldThrow_CoinsArrayMustContainOne() { const int coin = 10; var coinsAsArray = new[] { 2, 3, 4 }; Func act = () => DynamicCoinChangeSolver.GenerateSingleCoinChanges(coin, coinsAsArray); act.Should().Throw() .WithMessage($"Coins array must contain coin 1 {nameof(coinsAsArray)}."); } [Test] public void GenerateSingleCoinChangesTests_ShouldThrow_CoinsArrayCannotContainNegativeValues() { const int coin = 10; var coinsAsArray = new[] { 1, 2, -3, 4 }; Func act = () => DynamicCoinChangeSolver.GenerateSingleCoinChanges(coin, coinsAsArray); act.Should().Throw() .WithMessage($"{nameof(coinsAsArray)} cannot contain numbers less than or equal to zero"); } [Test] public void GenerateSingleCoinChangesTests_ShouldThrow_CoinsArrayCannotContainDuplicates() { const int coin = 10; var coinsAsArray = new[] { 1, 2, 3, 3, 4 }; Func act = () => DynamicCoinChangeSolver.GenerateSingleCoinChanges(coin, coinsAsArray); act.Should().Throw() .WithMessage($"Coins array cannot contain duplicates {nameof(coinsAsArray)}."); } } ================================================ FILE: Algorithms.Tests/Problems/DynamicProgramming/CoinChange/GetMinimalNextCoinTests.cs ================================================ using Algorithms.Problems.DynamicProgramming.CoinChange; namespace Algorithms.Tests.Problems.DynamicProgramming.CoinChange; public class GetMinimalNextCoinTests { [Test] public void GetMinimalNextCoinTest_Success() { const int coin = 6; var coins = new[] { 1, 3, 4 }; var exchangeDict = DynamicCoinChangeSolver.GenerateChangesDictionary(coin, coins); var nextCoin = DynamicCoinChangeSolver.GetMinimalNextCoin(6, exchangeDict); nextCoin.Should().Be(3); } } ================================================ FILE: Algorithms.Tests/Problems/DynamicProgramming/CoinChange/MakeCoinChangeDynamicTests.cs ================================================ using Algorithms.Problems.DynamicProgramming.CoinChange; namespace Algorithms.Tests.Problems.DynamicProgramming.CoinChange; [TestFixture] public class MakeCoinChangeDynamicTests { [Test] public void MakeCoinChangeDynamicTest_Success() { DynamicCoinChangeSolver .MakeCoinChangeDynamic(6, [1, 3, 4]) .SequenceEqual(new[] { 3, 3 }) .Should().BeTrue(); DynamicCoinChangeSolver .MakeCoinChangeDynamic(8, [1, 3, 4]) .SequenceEqual(new[] { 4, 4 }) .Should().BeTrue(); DynamicCoinChangeSolver .MakeCoinChangeDynamic(25, [1, 3, 4, 12, 13, 14]) .SequenceEqual(new[] { 13, 12 }) .Should().BeTrue(); DynamicCoinChangeSolver .MakeCoinChangeDynamic(26, [1, 3, 4, 12, 13, 14]) .SequenceEqual(new[] { 14, 12 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistanceTests.cs ================================================ using Algorithms.Problems.DynamicProgramming; namespace Algorithms.Tests.DynamicProgramming; public class LevenshteinDistanceTests { [TestCase("kitten", "sitting", 3)] [TestCase("bob", "bond", 2)] [TestCase("algorithm", "logarithm", 3)] [TestCase("star", "", 4)] [TestCase("", "star", 4)] [TestCase("abcde", "12345", 5)] public void Calculate_ReturnsCorrectLevenshteinDistance(string source, string destination, int expectedDistance) { var result = LevenshteinDistance.Calculate(source, destination); Assert.That(result, Is.EqualTo(expectedDistance)); } } ================================================ FILE: Algorithms.Tests/Problems/GraphColoring/GraphColoringSolverTests.cs ================================================ using Algorithms.Problems.GraphColoring; namespace Algorithms.Tests.Problems.GraphColoring; [TestFixture] public sealed class GraphColoringSolverTests { /// /// Helper method to validate that a coloring is valid for a given graph. /// /// The graph adjacency matrix. /// The color assignment to validate. /// /// A valid coloring must satisfy: /// 1. All vertices are colored (no -1 values). /// 2. No two adjacent vertices have the same color. /// private static void AssertValidColoring(bool[,] adjacencyMatrix, int[] colors) { var numVertices = adjacencyMatrix.GetLength(0); Assert.That(colors, Has.Length.EqualTo(numVertices), "Color array length must match number of vertices."); // Check all vertices are colored for (var i = 0; i < numVertices; i++) { Assert.That(colors[i], Is.GreaterThanOrEqualTo(0), $"Vertex {i} is not colored (has value -1)."); } // Check no adjacent vertices have the same color for (var i = 0; i < numVertices; i++) { for (var j = i + 1; j < numVertices; j++) { if (adjacencyMatrix[i, j]) { Assert.That(colors[i], Is.Not.EqualTo(colors[j]), $"Adjacent vertices {i} and {j} have the same color {colors[i]}."); } } } } /// /// Helper method to create an empty graph (no edges). /// private static bool[,] CreateEmptyGraph(int vertices) { return new bool[vertices, vertices]; } /// /// Helper method to create a complete graph where all vertices are connected. /// private static bool[,] CreateCompleteGraph(int vertices) { var graph = new bool[vertices, vertices]; for (var i = 0; i < vertices; i++) { for (var j = 0; j < vertices; j++) { if (i != j) { graph[i, j] = true; } } } return graph; } /// /// Helper method to create a bipartite graph (two sets with edges only between sets). /// private static bool[,] CreateBipartiteGraph(int setASize, int setBSize) { var total = setASize + setBSize; var graph = new bool[total, total]; // Connect all vertices in set A to all vertices in set B for (var i = 0; i < setASize; i++) { for (var j = setASize; j < total; j++) { graph[i, j] = true; graph[j, i] = true; } } return graph; } /// /// Helper method to create a cycle graph. /// private static bool[,] CreateCycleGraph(int vertices) { var graph = new bool[vertices, vertices]; for (var i = 0; i < vertices; i++) { var next = (i + 1) % vertices; graph[i, next] = true; graph[next, i] = true; } return graph; } /// /// Helper method to create a path graph (linear chain). /// private static bool[,] CreatePathGraph(int vertices) { var graph = new bool[vertices, vertices]; for (var i = 0; i < vertices - 1; i++) { graph[i, i + 1] = true; graph[i + 1, i] = true; } return graph; } [Test] public void ColorGraph_ThrowsArgumentNullException_WhenAdjacencyMatrixIsNull() { var solver = new GraphColoringSolver(); Assert.Throws(() => solver.ColorGraph(null!, 3)); } [Test] public void ColorGraph_ThrowsArgumentException_WhenAdjacencyMatrixIsNotSquare() { var solver = new GraphColoringSolver(); var nonSquareMatrix = new bool[3, 4]; Assert.Throws(() => solver.ColorGraph(nonSquareMatrix, 3)); } [TestCase(0)] [TestCase(-1)] [TestCase(-5)] public void ColorGraph_ThrowsArgumentException_WhenNumColorsIsNonPositive(int numColors) { var solver = new GraphColoringSolver(); var graph = CreateEmptyGraph(3); Assert.Throws(() => solver.ColorGraph(graph, numColors)); } [Test] public void ColorGraph_ReturnsEmptyArray_ForEmptyGraph() { var solver = new GraphColoringSolver(); var graph = new bool[0, 0]; var result = solver.ColorGraph(graph, 1); Assert.That(result, Is.Empty); } [Test] public void ColorGraph_ColorsSingleVertex_WithOneColor() { var solver = new GraphColoringSolver(); var graph = CreateEmptyGraph(1); var result = solver.ColorGraph(graph, 1); Assert.That(result, Has.Length.EqualTo(1)); Assert.That(result[0], Is.EqualTo(0)); AssertValidColoring(graph, result); } [Test] public void ColorGraph_ColorsDisconnectedVertices_WithOneColor() { var solver = new GraphColoringSolver(); var graph = CreateEmptyGraph(5); // No edges var result = solver.ColorGraph(graph, 1); Assert.That(result, Has.Length.EqualTo(5)); AssertValidColoring(graph, result); // All vertices should have the same color since there are no edges Assert.That(result.Distinct().Count(), Is.EqualTo(1)); } [Test] public void ColorGraph_ColorsBipartiteGraph_WithTwoColors() { var solver = new GraphColoringSolver(); var graph = CreateBipartiteGraph(3, 3); var result = solver.ColorGraph(graph, 2); Assert.That(result, Has.Length.EqualTo(6)); AssertValidColoring(graph, result); // Bipartite graph should use exactly 2 colors Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); } [Test] public void ColorGraph_ThrowsArgumentException_ForBipartiteGraphWithOneColor() { var solver = new GraphColoringSolver(); var graph = CreateBipartiteGraph(2, 2); // Bipartite graph with edges requires at least 2 colors Assert.Throws(() => solver.ColorGraph(graph, 1)); } [Test] public void ColorGraph_ColorsPathGraph_WithTwoColors() { var solver = new GraphColoringSolver(); var graph = CreatePathGraph(5); var result = solver.ColorGraph(graph, 2); Assert.That(result, Has.Length.EqualTo(5)); AssertValidColoring(graph, result); // Path graph can be colored with 2 colors Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); } [Test] public void ColorGraph_ColorsEvenCycle_WithTwoColors() { var solver = new GraphColoringSolver(); var graph = CreateCycleGraph(6); // Even cycle var result = solver.ColorGraph(graph, 2); Assert.That(result, Has.Length.EqualTo(6)); AssertValidColoring(graph, result); // Even cycle can be colored with 2 colors Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); } [Test] public void ColorGraph_ThrowsArgumentException_ForOddCycleWithTwoColors() { var solver = new GraphColoringSolver(); var graph = CreateCycleGraph(5); // Odd cycle // Odd cycle requires at least 3 colors Assert.Throws(() => solver.ColorGraph(graph, 2)); } [Test] public void ColorGraph_ColorsOddCycle_WithThreeColors() { var solver = new GraphColoringSolver(); var graph = CreateCycleGraph(5); // Odd cycle var result = solver.ColorGraph(graph, 3); Assert.That(result, Has.Length.EqualTo(5)); AssertValidColoring(graph, result); } [Test] public void ColorGraph_ColorsTriangle_WithThreeColors() { var solver = new GraphColoringSolver(); var graph = CreateCompleteGraph(3); // Triangle (K3) var result = solver.ColorGraph(graph, 3); Assert.That(result, Has.Length.EqualTo(3)); AssertValidColoring(graph, result); // Complete graph K3 requires exactly 3 colors Assert.That(result.Distinct().Count(), Is.EqualTo(3)); } [Test] public void ColorGraph_ThrowsArgumentException_ForTriangleWithTwoColors() { var solver = new GraphColoringSolver(); var graph = CreateCompleteGraph(3); // Triangle (K3) // Triangle requires 3 colors Assert.Throws(() => solver.ColorGraph(graph, 2)); } [Test] public void ColorGraph_ColorsCompleteGraphK4_WithFourColors() { var solver = new GraphColoringSolver(); var graph = CreateCompleteGraph(4); // K4 var result = solver.ColorGraph(graph, 4); Assert.That(result, Has.Length.EqualTo(4)); AssertValidColoring(graph, result); // Complete graph K4 requires exactly 4 colors Assert.That(result.Distinct().Count(), Is.EqualTo(4)); } [Test] public void ColorGraph_ThrowsArgumentException_ForCompleteGraphK4WithThreeColors() { var solver = new GraphColoringSolver(); var graph = CreateCompleteGraph(4); // K4 // K4 requires 4 colors Assert.Throws(() => solver.ColorGraph(graph, 3)); } [Test] public void ColorGraph_ColorsStarGraph_WithTwoColors() { var solver = new GraphColoringSolver(); // Star graph: one central vertex connected to all others var graph = new bool[5, 5]; for (var i = 1; i < 5; i++) { graph[0, i] = true; graph[i, 0] = true; } var result = solver.ColorGraph(graph, 2); Assert.That(result, Has.Length.EqualTo(5)); AssertValidColoring(graph, result); // Star graph requires only 2 colors Assert.That(result.Distinct().Count(), Is.LessThanOrEqualTo(2)); } [Test] public void ColorGraph_HandlesPetersenGraph_WithThreeColors() { var solver = new GraphColoringSolver(); // Simplified version: a graph that requires 3 colors // Creating a graph with a triangle and additional connections var graph = new bool[5, 5]; // Triangle: vertices 0, 1, 2 graph[0, 1] = graph[1, 0] = true; graph[1, 2] = graph[2, 1] = true; graph[2, 0] = graph[0, 2] = true; // Additional edges to vertex 3 graph[0, 3] = graph[3, 0] = true; graph[1, 3] = graph[3, 1] = true; // Additional edges to vertex 4 graph[2, 4] = graph[4, 2] = true; graph[3, 4] = graph[4, 3] = true; var result = solver.ColorGraph(graph, 3); Assert.That(result, Has.Length.EqualTo(5)); AssertValidColoring(graph, result); } [Test] public void ColorGraph_AllColorsWithinRange() { var solver = new GraphColoringSolver(); var graph = CreateCompleteGraph(4); var numColors = 4; var result = solver.ColorGraph(graph, numColors); // Verify all colors are in range [0, numColors) foreach (var color in result) { Assert.That(color, Is.InRange(0, numColors - 1)); } } [Test] public void ColorGraph_SymmetricMatrix_ProducesSameResult() { var solver = new GraphColoringSolver(); // Create a symmetric graph var graph = new bool[4, 4]; graph[0, 1] = graph[1, 0] = true; graph[1, 2] = graph[2, 1] = true; graph[2, 3] = graph[3, 2] = true; var result = solver.ColorGraph(graph, 3); Assert.That(result, Has.Length.EqualTo(4)); AssertValidColoring(graph, result); } [Test] public void ColorGraph_LargerGraph_ProducesValidColoring() { var solver = new GraphColoringSolver(); // Create a larger graph (10 vertices, random edges) var graph = new bool[10, 10]; // Create edges forming a more complex structure for (var i = 0; i < 9; i++) { graph[i, i + 1] = graph[i + 1, i] = true; } // Add some cross edges graph[0, 5] = graph[5, 0] = true; graph[2, 7] = graph[7, 2] = true; graph[3, 8] = graph[8, 3] = true; var result = solver.ColorGraph(graph, 3); Assert.That(result, Has.Length.EqualTo(10)); AssertValidColoring(graph, result); } } ================================================ FILE: Algorithms.Tests/Problems/JobScheduling/IntervalSchedulingSolverTests.cs ================================================ using System; using System.Collections.Generic; using Algorithms.Problems.JobScheduling; namespace Algorithms.Tests.Problems.JobScheduling; public class IntervalSchedulingSolverTests { [Test] public void Schedule_ReturnsEmpty_WhenNoJobs() { var result = IntervalSchedulingSolver.Schedule(new List()); Assert.That(result, Is.Empty); } [Test] public void Schedule_ReturnsSingleJob_WhenOnlyOneJob() { var jobs = new List { new Job(1, 3) }; var result = IntervalSchedulingSolver.Schedule(jobs); Assert.That(result, Has.Count.EqualTo(1)); Assert.That(result[0], Is.EqualTo(jobs[0])); } [Test] public void Schedule_ThrowsArgumentNullException_WhenJobsIsNull() { Assert.Throws(() => IntervalSchedulingSolver.Schedule(jobs: null!)); } [Test] public void Schedule_SelectsJobsWithEqualEndTime() { var jobs = new List { new Job(1, 4), new Job(2, 4), new Job(4, 6) }; var result = IntervalSchedulingSolver.Schedule(jobs); Assert.That(result, Has.Count.EqualTo(2)); Assert.That(result, Does.Contain(new Job(1, 4))); Assert.That(result, Does.Contain(new Job(4, 6))); } [Test] public void Schedule_SelectsJobStartingAtLastEnd() { var jobs = new List { new Job(1, 3), new Job(3, 5), new Job(5, 7) }; var result = IntervalSchedulingSolver.Schedule(jobs); Assert.That(result, Has.Count.EqualTo(3)); Assert.That(result[0], Is.EqualTo(jobs[0])); Assert.That(result[1], Is.EqualTo(jobs[1])); Assert.That(result[2], Is.EqualTo(jobs[2])); } [Test] public void Schedule_HandlesJobsWithNegativeTimes() { var jobs = new List { new Job(-5, -3), new Job(-2, 1), new Job(0, 2) }; var result = IntervalSchedulingSolver.Schedule(jobs); Assert.That(result, Has.Count.EqualTo(2)); Assert.That(result, Does.Contain(new Job(-5, -3))); Assert.That(result, Does.Contain(new Job(-2, 1))); } [Test] public void Schedule_SelectsNonOverlappingJobs() { var jobs = new List { new Job(1, 4), new Job(3, 5), new Job(0, 6), new Job(5, 7), new Job(8, 9), new Job(5, 9) }; var result = IntervalSchedulingSolver.Schedule(jobs); // Expected: (1,4), (5,7), (8,9) Assert.That(result, Has.Count.EqualTo(3)); Assert.That(result, Does.Contain(new Job(1, 4))); Assert.That(result, Does.Contain(new Job(5, 7))); Assert.That(result, Does.Contain(new Job(8, 9))); } [Test] public void Schedule_HandlesFullyOverlappingJobs() { var jobs = new List { new Job(1, 5), new Job(2, 6), new Job(3, 7) }; var result = IntervalSchedulingSolver.Schedule(jobs); // Only one job can be selected Assert.That(result, Has.Count.EqualTo(1)); } } ================================================ FILE: Algorithms.Tests/Problems/KnightTour/OpenKnightTourTests.cs ================================================ using Algorithms.Problems.KnightTour; namespace Algorithms.Tests.Problems.KnightTour { [TestFixture] public sealed class OpenKnightTourTests { private static bool IsKnightMove((int r, int c) a, (int r, int c) b) { var dr = Math.Abs(a.r - b.r); var dc = Math.Abs(a.c - b.c); return (dr == 1 && dc == 2) || (dr == 2 && dc == 1); } private static Dictionary MapVisitOrder(int[,] board) { var n = board.GetLength(0); var map = new Dictionary(n * n); for (var r = 0; r < n; r++) { for (var c = 0; c < n; c++) { var v = board[r, c]; if (v <= 0) { continue; } // ignore zeros in partial/invalid boards if (!map.TryAdd(v, (r, c))) { throw new AssertionException($"Duplicate visit number detected: {v}."); } } } return map; } private static void AssertIsValidTour(int[,] board) { var n = board.GetLength(0); Assert.That(board.GetLength(1), Is.EqualTo(n), "Board must be square."); // 1) All cells visited and within [1..n*n] int min = int.MaxValue; int max = int.MinValue; var seen = new bool[n * n + 1]; // 1..n*n for (var r = 0; r < n; r++) { for (var c = 0; c < n; c++) { var v = board[r, c]; Assert.That(v, Is.InRange(1, n * n), $"Cell [{r},{c}] has out-of-range value {v}."); Assert.That(seen[v], Is.False, $"Duplicate value {v} found."); seen[v] = true; if (v < min) { min = v; } if (v > max) { max = v; } } } Assert.That(min, Is.EqualTo(1), "Tour must start at 1."); Assert.That(max, Is.EqualTo(n * n), "Tour must end at n*n."); // 2) Each successive step is a legal knight move var pos = MapVisitOrder(board); // throws if duplicates for (var step = 1; step < n * n; step++) { var a = pos[step]; var b = pos[step + 1]; Assert.That(IsKnightMove(a, b), $"Step {step}->{step + 1} is not a legal knight move: {a} -> {b}."); } } [Test] public void Tour_Throws_On_NonPositiveN() { var solver = new OpenKnightTour(); Assert.Throws(() => solver.Tour(0)); Assert.Throws(() => solver.Tour(-1)); Assert.Throws(() => solver.Tour(-5)); } [TestCase(2)] [TestCase(3)] [TestCase(4)] public void Tour_Throws_On_Unsolvable_N_2_3_4(int n) { var solver = new OpenKnightTour(); Assert.Throws(() => solver.Tour(n)); } [Test] public void Tour_Returns_Valid_1x1() { var solver = new OpenKnightTour(); var board = solver.Tour(1); Assert.That(board.GetLength(0), Is.EqualTo(1)); Assert.That(board.GetLength(1), Is.EqualTo(1)); Assert.That(board[0, 0], Is.EqualTo(1)); AssertIsValidTour(board); } /// /// The plain backtracking search can take some time on 5x5 depending on move ordering, /// but should still be manageable. We mark it as "Slow" and add a generous timeout. /// [Test, Category("Slow"), CancelAfterAttribute(30000)] public void Tour_Returns_Valid_5x5() { var solver = new OpenKnightTour(); var board = solver.Tour(5); // Shape checks Assert.That(board.GetLength(0), Is.EqualTo(5)); Assert.That(board.GetLength(1), Is.EqualTo(5)); // Structural validity checks AssertIsValidTour(board); } [Test] public void Tour_Fills_All_Cells_No_Zeros_On_Successful_Boards() { var solver = new OpenKnightTour(); var board = solver.Tour(5); for (var r = 0; r < board.GetLength(0); r++) { for (var c = 0; c < board.GetLength(1); c++) { Assert.That(board[r, c], Is.Not.EqualTo(0), $"Found unvisited cell at [{r},{c}]."); } } } [Test] public void Tour_Produces_Values_In_Valid_Range_And_Unique() { var solver = new OpenKnightTour(); var n = 5; var board = solver.Tour(n); var values = new List(n * n); for (var r = 0; r < n; r++) { for (var c = 0; c < n; c++) { values.Add(board[r, c]); } } values.Sort(); // Expect [1..n*n] var expected = Enumerable.Range(1, n * n).ToArray(); Assert.That(values, Is.EqualTo(expected), "Board must contain each number exactly once from 1 to n*n."); } [Test] public void Tour_Returns_Square_Array() { var solver = new OpenKnightTour(); var board = solver.Tour(5); Assert.That(board.GetLength(0), Is.EqualTo(board.GetLength(1))); } } } ================================================ FILE: Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs ================================================ using Algorithms.Problems.NQueens; namespace Algorithms.Tests.Problems.NQueens; public static class BacktrackingNQueensSolverTests { [TestCase(0, 0)] [TestCase(1, 1)] [TestCase(2, 0)] [TestCase(3, 0)] [TestCase(4, 2)] [TestCase(5, 10)] [TestCase(6, 4)] [TestCase(7, 40)] [TestCase(8, 92)] [TestCase(8, 92)] [TestCase(9, 352)] [TestCase(10, 724)] [TestCase(11, 2680)] public static void SolvesCorrectly(int n, int expectedNumberOfSolutions) { // Arrange // Act var result = new BacktrackingNQueensSolver().BacktrackSolve(n).ToList(); // Assert result.Should().HaveCount(expectedNumberOfSolutions); foreach (var solution in result) { ValidateOneQueenPerRow(solution); ValidateOneQueenPerColumn(solution); ValidateOneQueenPerTopLeftBottomRightDiagonalLine(solution); ValidateOneQueenPerBottomLeftTopRightDiagonalLine(solution); } } [Test] public static void NCannotBeNegative() { var n = -1; Action act = () => new BacktrackingNQueensSolver().BacktrackSolve(n); act.Should().Throw(); } private static void ValidateOneQueenPerRow(bool[,] solution) { for (var i = 0; i < solution.GetLength(1); i++) { var foundQueen = false; for (var j = 0; j < solution.GetLength(0); j++) { foundQueen = ValidateCell(foundQueen, solution[j, i]); } } } private static void ValidateOneQueenPerColumn(bool[,] solution) { for (var i = 0; i < solution.GetLength(0); i++) { var foundQueen = false; for (var j = 0; j < solution.GetLength(1); j++) { foundQueen = ValidateCell(foundQueen, solution[i, j]); } } } private static void ValidateOneQueenPerTopLeftBottomRightDiagonalLine(bool[,] solution) { for (var i = 0; i < solution.GetLength(0); i++) { var foundQueen = false; for (var j = 0; i + j < solution.GetLength(1); j++) { foundQueen = ValidateCell(foundQueen, solution[i + j, i]); } } for (var i = 0; i < solution.GetLength(1); i++) { var foundQueen = false; for (var j = 0; i + j < solution.GetLength(0); j++) { foundQueen = ValidateCell(foundQueen, solution[j, i + j]); } } } private static void ValidateOneQueenPerBottomLeftTopRightDiagonalLine(bool[,] solution) { for (var i = 0; i < solution.GetLength(0); i++) { var foundQueen = false; for (var j = 0; i - j >= 0; j++) { foundQueen = ValidateCell(foundQueen, solution[i - j, i]); } } for (var i = 0; i < solution.GetLength(1); i++) { var foundQueen = false; for (var j = 0; i - j >= 0 && solution.GetLength(0) - j > 0; j++) { foundQueen = ValidateCell(foundQueen, solution[solution.GetLength(0) - j - 1, i - j]); } } } private static bool ValidateCell(bool foundQueen, bool currentCell) { if (foundQueen) { currentCell.Should().BeFalse(); } return foundQueen || currentCell; } } ================================================ FILE: Algorithms.Tests/Problems/StableMarriage/GaleShapleyTests.cs ================================================ using Algorithms.Problems.StableMarriage; namespace Algorithms.Tests.Problems.StableMarriage; /// /// The stable marriage problem (also stable matching problem or SMP) /// is the problem of finding a stable matching between two equally sized sets of elements given an ordering of /// preferences for each element. /// public static class GaleShapleyTests { /// /// Checks that all parties are engaged and stable. /// [Test] public static void MatchingIsSuccessful() { var random = new Random(7); var proposers = Enumerable.Range(1, 10).Select(_ => new Proposer()).ToArray(); var acceptors = Enumerable.Range(1, 10).Select(_ => new Accepter()).ToArray(); foreach (var proposer in proposers) { proposer.PreferenceOrder = new LinkedList(acceptors.OrderBy(_ => random.Next())); } foreach (var acceptor in acceptors) { acceptor.PreferenceOrder = proposers.OrderBy(_ => random.Next()).ToList(); } GaleShapley.Match(proposers, acceptors); Assert.That(acceptors.ToList().TrueForAll(x => x.EngagedTo is not null)); Assert.That(proposers.ToList().TrueForAll(x => x.EngagedTo is not null)); Assert.That(AreMatchesStable(proposers, acceptors), Is.True); } private static bool AreMatchesStable(Proposer[] proposers, Accepter[] accepters) => proposers.ToList().TrueForAll(p => p.EngagedTo is not null && Score(p, p.EngagedTo) <= accepters .Where(a => a.PrefersOverCurrent(p)) .Min(a => Score(p, a))); private static int Score(Proposer proposer, Accepter accepter) => proposer.PreferenceOrder.ToList().IndexOf(accepter); } ================================================ FILE: Algorithms.Tests/Problems/TravelingSalesman/TravelingSalesmanSolverTests.cs ================================================ using Algorithms.Problems.TravelingSalesman; using NUnit.Framework; namespace Algorithms.Tests.Problems.TravelingSalesman; /// /// Unit tests for TravelingSalesmanSolver. Covers brute-force and nearest neighbor methods, including edge cases and invalid input. /// [TestFixture] public class TravelingSalesmanSolverTests { /// /// Tests brute-force TSP solver on a 4-city symmetric distance matrix with known optimal route. /// [Test] public void SolveBruteForce_KnownOptimalRoute_ReturnsCorrectResult() { // Distance matrix for 4 cities (symmetric, triangle inequality holds) double[,] matrix = { { 0, 10, 15, 20 }, { 10, 0, 35, 25 }, { 15, 35, 0, 30 }, { 20, 25, 30, 0 } }; var (route, distance) = TravelingSalesmanSolver.SolveBruteForce(matrix); // Optimal route: 0 -> 1 -> 3 -> 2 -> 0, total distance = 80 Assert.That(distance, Is.EqualTo(80)); Assert.That(route, Is.EquivalentTo(new[] { 0, 1, 3, 2, 0 })); } /// /// Tests nearest neighbor heuristic on the same matrix. May not be optimal. /// [Test] public void SolveNearestNeighbor_Heuristic_ReturnsFeasibleRoute() { double[,] matrix = { { 0, 10, 15, 20 }, { 10, 0, 35, 25 }, { 15, 35, 0, 30 }, { 20, 25, 30, 0 } }; var (route, distance) = TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0); // Route: 0 -> 1 -> 3 -> 2 -> 0, total distance = 80 Assert.That(route.Length, Is.EqualTo(5)); Assert.That(route.First(), Is.EqualTo(0)); Assert.That(route.Last(), Is.EqualTo(0)); Assert.That(distance, Is.GreaterThanOrEqualTo(80)); // Heuristic may be optimal or suboptimal } /// /// Tests nearest neighbor with invalid start index. /// [Test] public void SolveNearestNeighbor_InvalidStart_ThrowsException() { double[,] matrix = { { 0, 1 }, { 1, 0 } }; Assert.Throws(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, -1)); Assert.Throws(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 2)); } /// /// Tests nearest neighbor when no unvisited cities remain (should throw InvalidOperationException). /// [Test] public void SolveNearestNeighbor_NoUnvisitedCities_ThrowsException() { // Construct a matrix where one city cannot be reached (simulate unreachable city) double[,] matrix = { { 0, double.MaxValue, 1 }, { double.MaxValue, 0, double.MaxValue }, { 1, double.MaxValue, 0 } }; // Start at city 0, city 1 is unreachable from both 0 and 2 Assert.Throws(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0)); } /// /// Tests brute-force and nearest neighbor with non-square matrix. /// [Test] public void NonSquareMatrix_ThrowsException() { double[,] matrix = new double[2, 3]; Assert.Throws(() => TravelingSalesmanSolver.SolveBruteForce(matrix)); Assert.Throws(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0)); } /// /// Tests brute-force with less than two cities (invalid case). /// [Test] public void SolveBruteForce_TooFewCities_ThrowsException() { double[,] matrix = { { 0 } }; Assert.Throws(() => TravelingSalesmanSolver.SolveBruteForce(matrix)); } /// /// Tests nearest neighbor with only two cities (trivial case). /// [Test] public void SolveNearestNeighbor_TwoCities_ReturnsCorrectRoute() { double[,] matrix = { { 0, 5 }, { 5, 0 } }; var (route, distance) = TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0); Assert.That(route, Is.EquivalentTo(new[] { 0, 1, 0 })); Assert.That(distance, Is.EqualTo(10)); } } ================================================ FILE: Algorithms.Tests/RecommenderSystem/CollaborativeFilteringTests.cs ================================================ using Algorithms.RecommenderSystem; using Moq; namespace Algorithms.Tests.RecommenderSystem; [TestFixture] public class CollaborativeFilteringTests { private Mock? mockSimilarityCalculator; private CollaborativeFiltering? recommender; private Dictionary> testRatings = null!; [SetUp] public void Setup() { mockSimilarityCalculator = new Mock(); recommender = new CollaborativeFiltering(mockSimilarityCalculator.Object); testRatings = new Dictionary> { ["user1"] = new() { ["item1"] = 5.0, ["item2"] = 3.0, ["item3"] = 4.0 }, ["user2"] = new() { ["item1"] = 4.0, ["item2"] = 2.0, ["item3"] = 5.0 }, ["user3"] = new() { ["item1"] = 3.0, ["item2"] = 4.0, ["item4"] = 3.0 } }; } [Test] [TestCase("item1", 4.0, 5.0)] [TestCase("item2", 2.0, 4.0)] public void CalculateSimilarity_WithValidInputs_ReturnsExpectedResults( string commonItem, double rating1, double rating2) { var user1Ratings = new Dictionary { [commonItem] = rating1 }; var user2Ratings = new Dictionary { [commonItem] = rating2 }; var similarity = recommender?.CalculateSimilarity(user1Ratings, user2Ratings); Assert.That(similarity, Is.InRange(-1.0, 1.0)); } [Test] public void CalculateSimilarity_WithNoCommonItems_ReturnsZero() { var user1Ratings = new Dictionary { ["item1"] = 5.0 }; var user2Ratings = new Dictionary { ["item2"] = 4.0 }; var similarity = recommender?.CalculateSimilarity(user1Ratings, user2Ratings); Assert.That(similarity, Is.EqualTo(0)); } [Test] public void PredictRating_WithNonexistentItem_ReturnsZero() { var predictedRating = recommender?.PredictRating("nonexistentItem", "user1", testRatings); Assert.That(predictedRating, Is.EqualTo(0)); } [NonParallelizable] [Test] public void PredictRating_WithOtherUserHavingRatedTargetItem_ShouldCalculateSimilarityAndWeightedSum() { var targetItem = "item1"; var targetUser = "user1"; mockSimilarityCalculator? .Setup(s => s.CalculateSimilarity(It.IsAny>(), It.IsAny>())) .Returns(0.8); var predictedRating = recommender?.PredictRating(targetItem, targetUser, testRatings); Assert.That(predictedRating, Is.Not.EqualTo(0.0d)); Assert.That(predictedRating, Is.EqualTo(3.5d).Within(0.01)); } } ================================================ FILE: Algorithms.Tests/Search/AStarTests.cs ================================================ using System.Reflection; using Algorithms.Search.AStar; namespace Algorithms.Tests.Search; public static class AStarTests { [Test] public static void ResetNodes_ResetsAllNodeProperties() { var node = new Node(new VecN(0, 0), true, 1.0) { CurrentCost = 5, EstimatedCost = 10, Parent = new Node(new VecN(1, 1), true, 1.0), State = NodeState.Closed }; var nodes = new List { node }; AStar.ResetNodes(nodes); node.CurrentCost.Should().Be(0); node.EstimatedCost.Should().Be(0); node.Parent.Should().BeNull(); node.State.Should().Be(NodeState.Unconsidered); } [Test] public static void GeneratePath_ReturnsPathFromTargetToRoot() { var start = new Node(new VecN(0, 0), true, 1.0); var mid = new Node(new VecN(1, 0), true, 1.0) { Parent = start }; var end = new Node(new VecN(2, 0), true, 1.0) { Parent = mid }; var path = AStar.GeneratePath(end); path.Should().HaveCount(3); path[0].Should().BeSameAs(start); path[1].Should().BeSameAs(mid); path[2].Should().BeSameAs(end); } [Test] public static void Compute_ReturnsEmptyList_WhenNoPathExists() { var start = new Node(new VecN(0, 0), true, 1.0); var end = new Node(new VecN(1, 0), true, 1.0); start.ConnectedNodes = []; end.ConnectedNodes = []; var path = AStar.Compute(start, end); path.Should().BeEmpty(); } [Test] public static void Compute_ReturnsPath_WhenPathExists() { var start = new Node(new VecN(0, 0), true, 1.0); var mid = new Node(new VecN(1, 0), true, 1.0); var end = new Node(new VecN(2, 0), true, 1.0); start.ConnectedNodes = [mid]; mid.ConnectedNodes = [end]; end.ConnectedNodes = []; var path = AStar.Compute(start, end); path.Should().NotBeEmpty(); path[0].Should().Be(start); path[^1].Should().Be(end); } [Test] public static void VecN_Equality_WorksAsExpected() { var a = new VecN(1, 2); var b = new VecN(1, 2); var c = new VecN(2, 1); a.Equals(b).Should().BeTrue(); a.Equals(c).Should().BeFalse(); } [Test] public static void AddOrUpdateConnected_ThrowsPathfindingException_OnSelfReference() { var node = new Node(new VecN(0, 0), true, 1.0); node.ConnectedNodes = [node]; node.State = NodeState.Open; var queue = new PriorityQueue(); Action act = () => { // Directly call the private method using reflection, otherwise we can't test this case var method = typeof(AStar).GetMethod("AddOrUpdateConnected", BindingFlags.NonPublic | BindingFlags.Static); method!.Invoke(null, [node, node, queue]); }; act.Should().Throw() .WithInnerException() .WithMessage("*same node twice*"); } } ================================================ FILE: Algorithms.Tests/Search/BinarySearcherTests.cs ================================================ using Algorithms.Search; namespace Algorithms.Tests.Search; public static class BinarySearcherTests { [Test] public static void FindIndex_ItemPresent_IndexCorrect([Random(1, 1000, 100)] int n) { // Arrange var searcher = new BinarySearcher(); var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n).Select(_ => random.Next(0, 1000)).OrderBy(x => x).ToArray(); var selectedIndex = random.Next(0, n); // Act var actualIndex = searcher.FindIndex(arrayToSearch, arrayToSearch[selectedIndex]); // Assert Assert.That(arrayToSearch[actualIndex], Is.EqualTo(arrayToSearch[selectedIndex])); } [Test] public static void FindIndex_ItemMissing_MinusOneReturned( [Random(0, 1000, 10)] int n, [Random(-100, 1100, 10)] int missingItem) { // Arrange var searcher = new BinarySearcher(); var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n) .Select(_ => random.Next(0, 1000)) .Where(x => x != missingItem) .OrderBy(x => x).ToArray(); // Act var actualIndex = searcher.FindIndex(arrayToSearch, missingItem); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } [Test] public static void FindIndex_ArrayEmpty_MinusOneReturned([Random(100)] int itemToSearch) { // Arrange var searcher = new BinarySearcher(); var arrayToSearch = new int[0]; // Act var actualIndex = searcher.FindIndex(arrayToSearch, itemToSearch); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } } ================================================ FILE: Algorithms.Tests/Search/BoyerMooreTests.cs ================================================ using Algorithms.Search; namespace Algorithms.Tests.Search; public class BoyerMoore_Tests { [Test] public void BoyerMoore_Majority_Finder_Test() { var elementCount = 1000; var rnd = new Random(); var randomNumbers = new List(); while (randomNumbers.Count < elementCount / 2) { randomNumbers.Add(rnd.Next(0, elementCount)); } var majorityElement = rnd.Next(0, elementCount); randomNumbers.AddRange(Enumerable.Repeat(majorityElement, elementCount / 2 + 1)); randomNumbers = randomNumbers.OrderBy(x => rnd.Next()).ToList(); var expected = majorityElement; var actual = BoyerMoore.FindMajority(randomNumbers); Assert.That(expected, Is.EqualTo(actual)); } } ================================================ FILE: Algorithms.Tests/Search/FastSearcherTests.cs ================================================ using Algorithms.Search; using Utilities.Exceptions; namespace Algorithms.Tests.Search; public static class FastSearcherTests { [Test] public static void FindIndex_ItemPresent_IndexCorrect() { // Arrange var searcher = new FastSearcher(); var arr = Helper.GetSortedArray(1000); var present = Helper.GetItemIn(arr); // Act var index = searcher.FindIndex(arr, present); // Assert arr[index].Should().Be(present); } [TestCase(new[] { 1, 2 }, 1)] [TestCase(new[] { 1, 2 }, 2)] [TestCase(new[] { 1, 2, 3, 3, 3 }, 2)] public static void FindIndex_ItemPresentInSpecificCase_IndexCorrect(int[] arr, int present) { // Arrange var searcher = new FastSearcher(); // Act var index = searcher.FindIndex(arr, present); // Assert arr[index].Should().Be(present); } [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 3)] // Item in left segment (< indexBinary) [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 5)] // Item at binary index [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 8)] // Item in right segment (> indexBinary) [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 1)] // Item at start [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 10)] // Item at end [TestCase(new[] { 1, 10, 20, 30, 40, 50, 60, 70, 80, 90 }, 20)] // Non-uniform distribution, interpolation < binary [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 100 }, 100)] // Non-uniform distribution, interpolation > binary [TestCase(new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }, 50)] // Uniform spacing [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }, 7)] // Larger array [TestCase(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }, 15)] // Larger array, right side public static void FindIndex_ItemPresentDeterministic_IndexCorrect(int[] arr, int present) { // Arrange var searcher = new FastSearcher(); // Act var index = searcher.FindIndex(arr, present); // Assert arr[index].Should().Be(present); } [Test] public static void FindIndex_ItemPresentInArrayOfDuplicates_IndexCorrect() { // Arrange var searcher = new FastSearcher(); var arr = CreateArrayOfDuplicates(1000, 0); // Helper for large duplicate arrays var present = 0; // Act var index = searcher.FindIndex(arr, present); // Assert arr[index].Should().Be(0); } [TestCase(new int[0], 2)] // Empty array [TestCase(new[] { 1, 2, 3 }, 4)] // Item missing in array public static void FindIndex_ItemMissing_ItemNotFoundExceptionThrown(int[] arr, int missing) { // Arrange var searcher = new FastSearcher(); // Act Action act = () => searcher.FindIndex(arr, missing); // Assert act.Should().Throw(); } [Test] public static void FindIndex_ItemMissingInArrayOfDuplicates_ItemNotFoundExceptionThrown() { // Arrange var searcher = new FastSearcher(); var arr = CreateArrayOfDuplicates(1000, 0); // Helper for large duplicate arrays var missing = 1; // Act Action act = () => searcher.FindIndex(arr, missing); // Assert act.Should().Throw(); } [Test] public static void FindIndex_ItemOutOfRange_ItemNotFoundExceptionThrown() { // Arrange var searcher = new FastSearcher(); var arr = Helper.GetSortedArray(1000); var smaller = Helper.GetItemSmallerThanAllIn(arr); var bigger = Helper.GetItemBiggerThanAllIn(arr); // Act & Assert Action act1 = () => searcher.FindIndex(arr, smaller); Action act2 = () => searcher.FindIndex(arr, bigger); act1.Should().Throw(); act2.Should().Throw(); } private static int[] CreateArrayOfDuplicates(int length, int value) { var arr = new int[length]; Array.Fill(arr, value); return arr; } } ================================================ FILE: Algorithms.Tests/Search/FibonacciSearcherTests.cs ================================================ using Algorithms.Search; namespace Algorithms.Tests.Search; public static class FibonacciSearcherTests { [Test] public static void FindIndex_ItemPresent_IndexCorrect([Random(1, 1000, 10)] int n) { // Arranges var searcher = new FibonacciSearcher(); var arrayToSearch = Helper.GetSortedArray(n); var present = Helper.GetItemIn(arrayToSearch); // Act var actualIndex = searcher.FindIndex(arrayToSearch, present); // Assert arrayToSearch[actualIndex].Should().Be(present); } [Test] public static void FindIndex_ItemMissing_MinusOneReturned([Random(1, 1000, 10)] int n) { // Arranges var searcher = new FibonacciSearcher(); var arrayToSearch = Helper.GetSortedArray(n); var present = Helper.GetItemNotIn(arrayToSearch); var expectedIndex = -1; // Act var actualIndex = searcher.FindIndex(arrayToSearch, present); // Assert actualIndex.Should().Be(expectedIndex); } [Test] public static void FindIndex_ArrayEmpty_MinusOneReturned([Random(1, 1000, 10)] int missingItem) { // Arrange var searcher = new FibonacciSearcher(); var sortedArray = Array.Empty(); var expectedIndex = -1; // Act var actualIndex = searcher.FindIndex(sortedArray, missingItem); // Assert actualIndex.Should().Be(expectedIndex); } [TestCase(null, "a")] [TestCase(new[] { "a", "b", "c" }, null)] [TestCase(null, null)] public static void FindIndex_ArrayNull_ItemNull_ArgumentNullExceptionThrown(string[] sortedArray, string searchItem) { // Arranges var searcher = new FibonacciSearcher(); // Act Action action = () => searcher.FindIndex(sortedArray, searchItem); // Assert action.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/Search/Helper.cs ================================================ namespace Algorithms.Tests.Search; public static class Helper { public static int[] GetSortedArray(int length) => Enumerable.Range(0, length) .Select(_ => TestContext.CurrentContext.Random.Next(1_000_000)) .OrderBy(x => x) .ToArray(); public static int GetItemIn(int[] arr) => arr[TestContext.CurrentContext.Random.Next(arr.Length)]; public static int GetItemNotIn(int[] arr) { int item; do { item = TestContext.CurrentContext.Random.Next(arr.Min(), arr.Max() + 1); } while (arr.Contains(item)); return item; } public static int GetItemSmallerThanAllIn(int[] arr) => arr.Min() - 1; public static int GetItemBiggerThanAllIn(int[] arr) => arr.Max() + 1; } ================================================ FILE: Algorithms.Tests/Search/InterpolationSearchTests.cs ================================================ using Algorithms.Search; namespace Algorithms.Tests.Search; public static class InterpolationSearchTests { [Test] public static void FindIndex_ItemPresent_IndexCorrect([Random(1, 1000, 100)] int n) { // Arrange var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n).Select(_ => random.Next(0, 1000)).OrderBy(x => x).ToArray(); var selectedIndex = random.Next(0, n); // Act var actualIndex = InterpolationSearch.FindIndex(arrayToSearch, arrayToSearch[selectedIndex]); // Assert Assert.That(arrayToSearch[actualIndex], Is.EqualTo(arrayToSearch[selectedIndex])); } [Test] public static void FindIndex_ItemMissing_MinusOneReturned( [Random(0, 1000, 10)] int n, [Random(-100, 1100, 10)] int missingItem) { // Arrange var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n) .Select(_ => random.Next(0, 1000)) .Where(x => x != missingItem) .OrderBy(x => x).ToArray(); // Act var actualIndex = InterpolationSearch.FindIndex(arrayToSearch, missingItem); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } [Test] public static void FindIndex_ArrayEmpty_MinusOneReturned([Random(100)] int itemToSearch) { // Arrange var arrayToSearch = new int[0]; // Act var actualIndex = InterpolationSearch.FindIndex(arrayToSearch, itemToSearch); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } } ================================================ FILE: Algorithms.Tests/Search/JumpSearcherTests.cs ================================================ using Algorithms.Search; namespace Algorithms.Tests.Search; public class JumpSearcherTests { [Test] public void FindIndex_ItemPresent_ItemCorrect([Random(1, 1000, 100)] int n) { // Arrange var searcher = new JumpSearcher(); var sortedArray = Enumerable.Range(0, n).Select(_ => TestContext.CurrentContext.Random.Next(1_000_000)).OrderBy(x => x).ToArray(); var expectedIndex = TestContext.CurrentContext.Random.Next(sortedArray.Length); // Act var actualIndex = searcher.FindIndex(sortedArray, sortedArray[expectedIndex]); // Assert sortedArray[actualIndex].Should().Be(sortedArray[expectedIndex]); } [Test] public void FindIndex_ItemMissing_MinusOneReturned([Random(1, 1000, 10)] int n, [Random(-100, 1100, 10)] int missingItem) { // Arrange var searcher = new JumpSearcher(); var sortedArray = Enumerable.Range(0, n).Select(_ => TestContext.CurrentContext.Random.Next(1_000_000)).Where(x => x != missingItem).OrderBy(x => x).ToArray(); var expectedIndex = -1; // Act var actualIndex = searcher.FindIndex(sortedArray, missingItem); // Assert Assert.That(actualIndex, Is.EqualTo(expectedIndex)); } [Test] public void FindIndex_ArrayEmpty_MinusOneReturned([Random(-100, 1100, 10)] int missingItem) { // Arrange var searcher = new JumpSearcher(); var sortedArray = Array.Empty(); var expectedIndex = -1; // Act var actualIndex = searcher.FindIndex(sortedArray, missingItem); // Assert Assert.That(actualIndex, Is.EqualTo(expectedIndex)); } [TestCase(null, "abc")] [TestCase(new[] { "abc", "def", "ghi" }, null)] [TestCase(null, null)] public void FindIndex_ArrayNull_ItemNull_ArgumentNullExceptionThrown(string[] sortedArray, string searchItem) { // Arrange var searcher = new JumpSearcher(); // Act, Assert _ = Assert.Throws(() => searcher.FindIndex(sortedArray, searchItem)); } } ================================================ FILE: Algorithms.Tests/Search/LinearSearcherTests.cs ================================================ using Algorithms.Search; using Utilities.Exceptions; namespace Algorithms.Tests.Search; public static class LinearSearcherTests { [Test] public static void Find_ItemPresent_ItemCorrect([Random(0, 1_000_000, 100)] int n) { // Arrange var searcher = new LinearSearcher(); var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n).Select(_ => random.Next(0, 1000)).ToArray(); // Act var expectedItem = Array.Find(arrayToSearch, x => x == arrayToSearch[n / 2]); var actualItem = searcher.Find(arrayToSearch, x => x == arrayToSearch[n / 2]); // Assert Assert.That(actualItem, Is.EqualTo(expectedItem)); } [Test] public static void FindIndex_ItemPresent_IndexCorrect([Random(0, 1_000_000, 100)] int n) { // Arrange var searcher = new LinearSearcher(); var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n).Select(_ => random.Next(0, 1000)).ToArray(); // Act var expectedIndex = Array.FindIndex(arrayToSearch, x => x == arrayToSearch[n / 2]); var actualIndex = searcher.FindIndex(arrayToSearch, x => x == arrayToSearch[n / 2]); // Assert Assert.That(actualIndex, Is.EqualTo(expectedIndex)); } [Test] public static void Find_ItemMissing_ItemNotFoundExceptionThrown([Random(0, 1_000_000, 100)] int n) { // Arrange var searcher = new LinearSearcher(); var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n).Select(_ => random.Next(0, 1000)).ToArray(); // Act // Assert _ = Assert.Throws(() => searcher.Find(arrayToSearch, _ => false)); } [Test] public static void FindIndex_ItemMissing_MinusOneReturned([Random(0, 1_000_000, 100)] int n) { // Arrange var searcher = new LinearSearcher(); var random = Randomizer.CreateRandomizer(); var arrayToSearch = Enumerable.Range(0, n).Select(_ => random.Next(0, 1000)).ToArray(); // Act var actualIndex = searcher.FindIndex(arrayToSearch, _ => false); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } } ================================================ FILE: Algorithms.Tests/Search/RecursiveBinarySearcherTests.cs ================================================ using Algorithms.Search; namespace Algorithms.Tests.Search; public static class RecursiveBinarySearcherTests { [Test] public static void FindIndex_ItemPresent_IndexCorrect([Random(1, 1000, 100)] int n) { // Arrange var subject = new RecursiveBinarySearcher(); var randomizer = Randomizer.CreateRandomizer(); var selectedIndex = randomizer.Next(0, n); var collection = Enumerable.Range(0, n).Select(_ => randomizer.Next(0, 1000)).OrderBy(x => x).ToList(); // Act var actualIndex = subject.FindIndex(collection, collection[selectedIndex]); // Assert Assert.That(collection[actualIndex], Is.EqualTo(collection[selectedIndex])); } [Test] public static void FindIndex_ItemMissing_MinusOneReturned( [Random(0, 1000, 10)] int n, [Random(-100, 1100, 10)] int missingItem) { // Arrange var subject = new RecursiveBinarySearcher(); var random = Randomizer.CreateRandomizer(); var collection = Enumerable.Range(0, n) .Select(_ => random.Next(0, 1000)) .Where(x => x != missingItem) .OrderBy(x => x).ToList(); // Act var actualIndex = subject.FindIndex(collection, missingItem); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } [Test] public static void FindIndex_ArrayEmpty_MinusOneReturned([Random(100)] int itemToSearch) { // Arrange var subject = new RecursiveBinarySearcher(); var collection = new int[0]; // Act var actualIndex = subject.FindIndex(collection, itemToSearch); // Assert Assert.That(actualIndex, Is.EqualTo(-1)); } [Test] public static void FindIndex_NullCollection_Throws() { // Arrange var subject = new RecursiveBinarySearcher(); var collection = (IList?)null; // Act Action act = () => subject.FindIndex(collection, 42); // Assert act.Should().Throw(); } } ================================================ FILE: Algorithms.Tests/Sequences/AllOnesSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class AllOnesSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new AllOnesSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/AllThreesSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class AllThreesSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new AllThreesSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/AllTwosSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class AllTwosSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new AllTwosSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/BinaryPrimeConstantSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class BinaryPrimeConstantSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new BinaryPrimeConstantSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 0, 1, 1, 0, 1, 0, 1, 0, 0, 0 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/BinomialSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class BinomialSequenceTests { [Test] public void First4RowsCorrect() { var sequence = new BinomialSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 1, 1, 1, 1, 2, 1, 1, 3, 3, 1 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/CakeNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class CakeNumbersSequenceTests { [Test] public void First46ElementsCorrect() { var sequence = new CakeNumbersSequence().Sequence.Take(46); sequence.SequenceEqual(new BigInteger[] { 1, 2, 4, 8, 15, 26, 42, 64, 93, 130, 176, 232, 299, 378, 470, 576, 697, 834, 988, 1160, 1351, 1562, 1794, 2048, 2325, 2626, 2952, 3304, 3683, 4090, 4526, 4992, 5489, 6018, 6580, 7176, 7807, 8474, 9178, 9920, 10701, 11522, 12384, 13288, 14235, 15226 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/CatalanSequenceTest.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class CatalanSequenceTest { [Test] public void First30ItemsCorrect() { var sequence = new CatalanSequence().Sequence.Take(30); sequence.SequenceEqual(new BigInteger[] { 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, 18367353072152, 69533550916004, 263747951750360, 1002242216651368}) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/CentralPolygonalNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class CentralPolygonalNumbersSequenceTests { [Test] public void First53ElementsCorrect() { var sequence = new CentralPolygonalNumbersSequence().Sequence.Take(53); sequence.SequenceEqual(new BigInteger[] { 1, 2, 4, 7, 11, 16, 22, 29, 37, 46, 56, 67, 79, 92, 106, 121, 137, 154, 172, 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466, 497, 529, 562, 596, 631, 667, 704, 742, 781, 821, 862, 904, 947, 991, 1036, 1082, 1129, 1177, 1226, 1276, 1327, 1379, }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/CubesSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class CubesSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new CubesSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 0, 1, 8, 27, 64, 125, 216, 343, 512, 729 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/DivisorsCountSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class DivisorsCountSequenceTests { [Test] public void First10ElementsCorrect() { // These values are taken from https://oeis.org/A000005 for comparison. BigInteger[] oeisSource = [ 1, 2, 2, 3, 2, 4, 2, 4, 3, 4, 2, 6, 2, 4, 4, 5, 2, 6, 2, 6, 4, 4, 2, 8, 3, 4, 4, 6, 2, 8, 2, 6, 4, 4, 4, 9, 2, 4, 4, 8, 2, 8, 2, 6, 6, 4, 2, 10, 3, 6, 4, 6, 2, 8, 4, 8, 4, 4, 2, 12, 2, 4, 6, 7, 4, 8, 2, 6, 4, 8, 2, 12, 2, 4, 6, 6, 4, 8, 2, 10, 5, 4, 2, 12, 4, 4, 4, 8, 2, 12, 4, 6, 4, 4, 4, 12, 2, 6, 6, 9, 2, 8, 2, 8, ]; var sequence = new DivisorsCountSequence().Sequence.Take(oeisSource.Length); sequence.SequenceEqual(oeisSource).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/EuclidNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class EuclidNumbersSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new EuclidNumbersSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 2, 3, 7, 31, 211, 2311, 30031, 510511, 9699691, 223092871 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/EulerTotientSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class EulerTotientSequenceTests { [Test] public void FirstElementsCorrect() { // Let's be thorough. 500 phi values! // Initial test of 69 number from table at https://oeis.org/A000010/list and passed test. // Extended out to 500 values from https://primefan.tripod.com/Phi500.html and passed initial 69 // along with remaining values. BigInteger[] check = [ 1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18, 24, 16, 40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36, 28, 58, 16, 60, 30, 36, 32, 48, 20, 66, 32, 44, 24, 70, 24, 72, 36, 40, 36, 60, 24, 78, 32, 54, 40, 82, 24, 64, 42, 56, 40, 88, 24, 72, 44, 60, 46, 72, 32, 96, 42, 60, 40, 100, 32, 102, 48, 48, 52, 106, 36, 108, 40, 72, 48, 112, 36, 88, 56, 72, 58, 96, 32, 110, 60, 80, 60, 100, 36, 126, 64, 84, 48, 130, 40, 108, 66, 72, 64, 136, 44, 138, 48, 92, 70, 120, 48, 112, 72, 84, 72, 148, 40, 150, 72, 96, 60, 120, 48, 156, 78, 104, 64, 132, 54, 162, 80, 80, 82, 166, 48, 156, 64, 108, 84, 172, 56, 120, 80, 116, 88, 178, 48, 180, 72, 120, 88, 144, 60, 160, 92, 108, 72, 190, 64, 192, 96, 96, 84, 196, 60, 198, 80, 132, 100, 168, 64, 160, 102, 132, 96, 180, 48, 210, 104, 140, 106, 168, 72, 180, 108, 144, 80, 192, 72, 222, 96, 120, 112, 226, 72, 228, 88, 120, 112, 232, 72, 184, 116, 156, 96, 238, 64, 240, 110, 162, 120, 168, 80, 216, 120, 164, 100, 250, 72, 220, 126, 128, 128, 256, 84, 216, 96, 168, 130, 262, 80, 208, 108, 176, 132, 268, 72, 270, 128, 144, 136, 200, 88, 276, 138, 180, 96, 280, 92, 282, 140, 144, 120, 240, 96, 272, 112, 192, 144, 292, 84, 232, 144, 180, 148, 264, 80, 252, 150, 200, 144, 240, 96, 306, 120, 204, 120, 310, 96, 312, 156, 144, 156, 316, 104, 280, 128, 212, 132, 288, 108, 240, 162, 216, 160, 276, 80, 330, 164, 216, 166, 264, 96, 336, 156, 224, 128, 300, 108, 294, 168, 176, 172, 346, 112, 348, 120, 216, 160, 352, 116, 280, 176, 192, 178, 358, 96, 342, 180, 220, 144, 288, 120, 366, 176, 240, 144, 312, 120, 372, 160, 200, 184, 336, 108, 378, 144, 252, 190, 382, 128, 240, 192, 252, 192, 388, 96, 352, 168, 260, 196, 312, 120, 396, 198, 216, 160, 400, 132, 360, 200, 216, 168, 360, 128, 408, 160, 272, 204, 348, 132, 328, 192, 276, 180, 418, 96, 420, 210, 276, 208, 320, 140, 360, 212, 240, 168, 430, 144, 432, 180, 224, 216, 396, 144, 438, 160, 252, 192, 442, 144, 352, 222, 296, 192, 448, 120, 400, 224, 300, 226, 288, 144, 456, 228, 288, 176, 460, 120, 462, 224, 240, 232, 466, 144, 396, 184, 312, 232, 420, 156, 360, 192, 312, 238, 478, 128, 432, 240, 264, 220, 384, 162, 486, 240, 324, 168, 490, 160, 448, 216, 240, 240, 420, 164, 498, 200, ]; var sequence = new EulerTotientSequence().Sequence.Take(check.Length); sequence.SequenceEqual(check).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/FactorialSequenceTest.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class FactorialSequenceTest { [Test] public void First10ItemsCorrect() { var sequence = new FactorialSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/FermatNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class FermatNumbersSequenceTests { [Test] public void First5ElementsCorrect() { var sequence = new FermatNumbersSequence().Sequence.Take(5); sequence.SequenceEqual(new BigInteger[] { 3, 5, 17, 257, 65537 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/FermatPrimesSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class FermatPrimesSequenceTests { [Test] public void All5ElementsCorrect() { var sequence = new FermatPrimesSequence().Sequence.Take(5); sequence.SequenceEqual(new BigInteger[] { 3, 5, 17, 257, 65537 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/FibonacciSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class FibonacciSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new FibonacciSequence().Sequence.Take(10); BigInteger[] expected = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]; sequence.SequenceEqual(expected) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/GolombsSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class GolombsSequenceTests { [Test] public void First50ElementsCorrect() { // Taken from https://oeis.org/A001462 var expected = new BigInteger[] { 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13}; var sequence = new GolombsSequence().Sequence.Take(50); sequence.SequenceEqual(expected).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/KolakoskiSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class KolakoskiSequenceTests { [Test] public void First100ElementsCorrect() { // Taken from https://oeis.org/A000002 BigInteger[] expected = [ 1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, ]; var sequence = new KolakoskiSequence().Sequence.Take(100); var sequence2 = new KolakoskiSequence2().Sequence.Take(100); sequence.Should().Equal(expected); sequence2.Should().Equal(expected); } } ================================================ FILE: Algorithms.Tests/Sequences/KummerNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class KummerNumbersSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new KummerNumbersSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 1, 5, 29, 209, 2309, 30029, 510509, 9699689, 223092869, 6469693229 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/LucasNumbersBeginningAt2SequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class LucasNumbersBeginningAt2SequenceTests { [Test] public void FirstElementsCorrect() { // Initial test of 38 values from http://oeis.org/A000032/list which passed. // Testing 200 Lucas numbers, with values from https://r-knott.surrey.ac.uk/Fibonacci/lucas200.html and // compared to initial 38, which passed. // Assigning numeric values to BigInteger types performed by string parsing dues to length of value. var bigNumbers = new[] { "2", "1", "3", "4", "7", "11", "18", "29", "47", "76", "123", "199", "322", "521", "843", "1364", "2207", "3571", "5778", "9349", "15127", "24476", "39603", "64079", "103682", "167761", "271443", "439204", "710647", "1149851", "1860498", "3010349", "4870847", "7881196", "12752043", "20633239", "33385282", "54018521", "87403803", "141422324", "228826127", "370248451", "599074578", "969323029", "1568397607", "2537720636", "4106118243", "6643838879", "10749957122", "17393796001", "28143753123", "45537549124", "73681302247", "119218851371", "192900153618", "312119004989", "505019158607", "817138163596", "1322157322203", "2139295485799", "3461452808002", "5600748293801", "9062201101803", "14662949395604", "23725150497407", "38388099893011", "62113250390418", "100501350283429", "162614600673847", "263115950957276", "425730551631123", "688846502588399", "1114577054219522", "1803423556807921", "2918000611027443", "4721424167835364", "7639424778862807", "12360848946698171", "20000273725560978", "32361122672259149", "52361396397820127", "84722519070079276", "137083915467899403", "221806434537978679", "358890350005878082", "580696784543856761", "939587134549734843", "1520283919093591604", "2459871053643326447", "3980154972736918051", "6440026026380244498", "10420180999117162549", "16860207025497407047", "27280388024614569596", "44140595050111976643", "71420983074726546239", "115561578124838522882", "186982561199565069121", "302544139324403592003", "489526700523968661124", "792070839848372253127", "1281597540372340914251", "2073668380220713167378", "3355265920593054081629", "5428934300813767249007", "8784200221406821330636", "14213134522220588579643", "22997334743627409910279", "37210469265847998489922", "60207804009475408400201", "97418273275323406890123", "157626077284798815290324", "255044350560122222180447", "412670427844921037470771", "667714778405043259651218", "1080385206249964297121989", "1748099984655007556773207", "2828485190904971853895196", "4576585175559979410668403", "7405070366464951264563599", "11981655542024930675232002", "19386725908489881939795601", "31368381450514812615027603", "50755107359004694554823204", "82123488809519507169850807", "132878596168524201724674011", "215002084978043708894524818" , "347880681146567910619198829", "562882766124611619513723647", "910763447271179530132922476", "1473646213395791149646646123", "2384409660666970679779568599", "3858055874062761829426214722", "6242465534729732509205783321", "10100521408792494338631998043", "16342986943522226847837781364", "26443508352314721186469779407", "42786495295836948034307560771", "69230003648151669220777340178", "112016498943988617255084900949", "181246502592140286475862241127", "293263001536128903730947142076", "474509504128269190206809383203", "767772505664398093937756525279", "1242282009792667284144565908482", "2010054515457065378082322433761", "3252336525249732662226888342243", "5262391040706798040309210776004", "8514727565956530702536099118247", "13777118606663328742845309894251", "22291846172619859445381409012498", "36068964779283188188226718906749", "58360810951903047633608127919247", "94429775731186235821834846825996", "152790586683089283455442974745243", "247220362414275519277277821571239", "400010949097364802732720796316482", "647231311511640322009998617887721", "1047242260609005124742719414204203", "1694473572120645446752718032091924", "2741715832729650571495437446296127", "4436189404850296018248155478388051", "7177905237579946589743592924684178", "11614094642430242607991748403072229", "18791999880010189197735341327756407", "30406094522440431805727089730828636", "49198094402450621003462431058585043", "79604188924891052809189520789413679", "128802283327341673812651951847998722", "208406472252232726621841472637412401", "337208755579574400434493424485411123", "545615227831807127056334897122823524", "882823983411381527490828321608234647", "1428439211243188654547163218731058171", "2311263194654570182037991540339292818", "3739702405897758836585154759070350989", "6050965600552329018623146299409643807", "9790668006450087855208301058479994796", "15841633607002416873831447357889638603", "25632301613452504729039748416369633399", "41473935220454921602871195774259272002", "67106236833907426331910944190628905401", "108580172054362347934782139964888177403", "175686408888269774266693084155517082804", "284266580942632122201475224120405260207", "459952989830901896468168308275922343011", "744219570773534018669643532396327603218", "1204172560604435915137811840672249946229", "1948392131377969933807455373068577549447", "3152564691982405848945267213740827495676", "5100956823360375782752722586809405045123", "8253521515342781631697989800550232540799", "13354478338703157414450712387359637585922", "21607999854045939046148702187909870126721", "34962478192749096460599414575269507712643", "56570478046795035506748116763179377839364", "91532956239544131967347531338448885552007", "148103434286339167474095648101628263391371", "239636390525883299441443179440077148943378", "387739824812222466915538827541705412334749", "627376215338105766356982006981782561278127", }; var check = bigNumbers.Select(BigInteger.Parse).ToArray(); var sequence = new LucasNumbersBeginningAt2Sequence().Sequence.Take(check.Length); sequence.SequenceEqual(check).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/MakeChangeSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class MakeChangeSequenceTests { [Test] public void First100ElementsCorrect() { // Values from https://oeis.org/A000008/b000008.txt var test = new BigInteger[] { 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 16, 19, 22, 25, 28, 31, 34, 40, 43, 49, 52, 58, 64, 70, 76, 82, 88, 98, 104, 114, 120, 130, 140, 150, 160, 170, 180, 195, 205, 220, 230, 245, 260, 275, 290, 305, 320, 341, 356, 377, 392, 413, 434, 455, 476, 497, 518, 546, 567, 595, 616, 644, 672, 700, 728, 756, 784, 820, 848, 884, 912, 948, 984, 1020, 1056, 1092, 1128, 1173, 1209, 1254, 1290, 1335, 1380, 1425, 1470, 1515, 1560, 1615, 1660, 1715, 1760, 1815, 1870, 1925, 1980, 2035, 2090, }; var sequence = new MakeChangeSequence().Sequence.Take(test.Length); sequence.SequenceEqual(test).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/MatchstickTriangleSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; [TestFixture] public static class MatchstickTriangleSequenceTests { private static BigInteger[] _testList = [ 0, 1, 5, 13, 27, 48, 78, 118, 170, 235, 315, 411, 525, 658, 812, 988, 1188, 1413, 1665, 1945, 2255, 2596, 2970, 3378, 3822, 4303, 4823, 5383, 5985, 6630, 7320, 8056, 8840, 9673, 10557, 11493, 12483, 13528, 14630, 15790, 17010, 18291, 19635, 21043, 22517, ]; /// /// This test uses the list values provided from http://oeis.org/A002717/list. /// [Test] public static void TestOeisList() { var sequence = new MatchstickTriangleSequence().Sequence.Take(_testList.Length); sequence.SequenceEqual(_testList).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/NaturalSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class NaturalSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new NaturalSequence().Sequence.Take(10); BigInteger[] expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; sequence.SequenceEqual(expected) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/NegativeIntegersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class NegativeIntegersSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new NegativeIntegersSequence().Sequence.Take(10); BigInteger[] expected = [-1, -2, -3, -4, -5, -6, -7, -8, -9, -10]; sequence.SequenceEqual(expected) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/NumberOfBooleanFunctionsSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class NumberOfBooleanFunctionsSequenceTests { [Test] public void First5ElementsCorrect() { var sequence = new NumberOfBooleanFunctionsSequence().Sequence.Take(5); sequence.SequenceEqual(new BigInteger[] { 2, 4, 16, 256, 65536 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/NumberOfPrimesByNumberOfDigitsSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class NumberOfPrimesByNumberOfDigitsSequenceTests { [Test] public void First5ElementsCorrect() { var sequence = new NumberOfPrimesByNumberOfDigitsSequence().Sequence.Take(5); sequence.SequenceEqual(new BigInteger[] { 0, 4, 21, 143, 1061 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/NumberOfPrimesByPowersOf10SequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class NumberOfPrimesByPowersOf10SequenceTests { [Test] public void First5ElementsCorrect() { var sequence = new NumberOfPrimesByPowersOf10Sequence().Sequence.Take(5); sequence.SequenceEqual(new BigInteger[] { 0, 4, 25, 168, 1229 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/OnesCountingSequenceTest.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; [TestFixture] public class OnesCountingSequenceTest { /// /// /// Values taken from http://oeis.org/A000120/b000120.txt. /// /// /// While the file contains 10,000 values, this only tests 1000. /// /// private readonly BigInteger[] oeisValues = [ 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, ]; /// /// /// Performs number of ones in the binary representation of a BigInteger. /// /// /// This is used as a check to compare the provided values from OEIS. /// /// /// BigInteger value to count 1s in /// Number of 1s in binary representation of number. private int CountOnes(BigInteger i) { var temp = i; BigInteger remainder = 0; var result = 0; while (temp != BigInteger.Zero) { temp = BigInteger.DivRem(temp, 2, out remainder); result += remainder.IsOne ? 1 : 0; } return result; } [Test] public void Count1000() { // Compare generated sequence against provided data var sequence = new OnesCountingSequence().Sequence.Take(oeisValues.Length); sequence.SequenceEqual(oeisValues).Should().BeTrue(); } [Test] public void CompareAgainstCalculated() { // Calculate 1s in binary value the old fashioned way. var calculated = new List(); for (var i = 0; i < oeisValues.Length; i++) { calculated.Add(CountOnes(new BigInteger(i))); } calculated.SequenceEqual(oeisValues).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/PowersOf10SequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class PowersOf10SequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new PowersOf10Sequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/PowersOf2SequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class PowersOf2SequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new PowersOf2Sequence().Sequence.Take(10); BigInteger[] expected = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]; sequence.SequenceEqual(expected) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/PrimePiSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class PrimePiSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new PrimePiSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 0, 1, 2, 2, 3, 3, 4, 4, 4, 4 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/PrimesSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class PrimesSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new PrimesSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/PrimorialNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class PrimorialNumbersSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new PrimorialNumbersSequence().Sequence.Take(10); sequence.SequenceEqual(new BigInteger[] { 1, 2, 6, 30, 210, 2310, 30030, 510510, 9699690, 223092870 }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/RecamansSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class RecamansSequenceTests { [Test] public void First50ElementsCorrect() { // Taken from http://oeis.org/A005132 var expected = new BigInteger[] { 0, 1, 3, 6, 2, 7, 13, 20, 12, 21, 11, 22, 10, 23, 9, 24, 8, 25, 43, 62, 42, 63, 41, 18, 42, 17, 43, 16, 44, 15, 45, 14, 46, 79, 113, 78, 114, 77, 39, 78, 38, 79, 37, 80, 36, 81, 35, 82, 34, 83, }; var sequence = new RecamansSequence().Sequence.Take(50); sequence.Should().Equal(expected); } } ================================================ FILE: Algorithms.Tests/Sequences/SquaresSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class SquaresSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new SquaresSequence().Sequence.Take(10); BigInteger[] expected = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]; sequence.SequenceEqual(expected) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/TetrahedralSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; [TestFixture] public class TetrahedralSequenceTests { private static readonly BigInteger[] TestList = [ 0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364, 455, 560, 680, 816, 969, 1140, 1330, 1540, 1771, 2024, 2300, 2600, 2925, 3276, 3654, 4060, 4495, 4960, 5456, 5984, 6545, 7140, 7770, 8436, 9139, 9880, 10660, 11480, 12341, 13244, 14190, 15180, ]; /// /// This test uses the list values provided from http://oeis.org/A000292/list. /// [Test] public void TestOeisList() { var sequence = new TetrahedralSequence().Sequence.Take(TestList.Length); sequence.SequenceEqual(TestList).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/TetranacciNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class TetranacciNumbersSequenceTests { [Test] public void First35ElementsCorrect() { var sequence = new TetranacciNumbersSequence().Sequence.Take(35); BigInteger[] expected = [ 1, 1, 1, 1, 4, 7, 13, 25, 49, 94, 181, 349, 673, 1297, 2500, 4819, 9289, 17905, 34513, 66526, 128233, 247177, 476449, 918385, 1770244, 3412255, 6577333, 12678217, 24438049, 47105854, 90799453, 175021573, 337364929, 650291809, 1253477764, ]; sequence.SequenceEqual(expected) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/ThreeNPlusOneStepsSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class ThreeNPlusOneStepsSequenceTests { [Test] public void First50ElementsCorrect() { var sequence = new ThreeNPlusOneStepsSequence().Sequence.Take(50); var first50 = new BigInteger[] { 0, 1, 7, 2, 5, 8, 16, 3, 19, 6, 14, 9, 9, 17, 17, 4, 12, 20, 20, 7, 7, 15, 15, 10, 23, 10, 111, 18, 18, 18, 106, 5, 26, 13, 13, 21, 21, 21, 34, 8, 109, 8, 29, 16, 16, 16, 104, 11, 24, 24 }; sequence.SequenceEqual(first50).Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/TribonacciNumbersSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class TribonacciNumbersSequenceTests { [Test] public void First37ElementsCorrect() { var sequence = new TribonacciNumbersSequence().Sequence.Take(37); sequence.SequenceEqual(new BigInteger[] { 1, 1, 1, 3, 5, 9, 17, 31, 57, 105, 193, 355, 653, 1201, 2209, 4063, 7473, 13745, 25281, 46499, 85525, 157305, 289329, 532159, 978793, 1800281, 3311233, 6090307, 11201821, 20603361, 37895489, 69700671, 128199521, 235795681, 433695873, 797691075, 1467182629, }) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Sequences/VanEcksSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class VanEcksSequenceTests { [Test] public void First50ElementsCorrect() { // Taken from http://oeis.org/A181391 BigInteger[] expected = [ 0, 0, 1, 0, 2, 0, 2, 2, 1, 6, 0, 5, 0, 2, 6, 5, 4, 0, 5, 3, 0, 3, 2, 9, 0, 4, 9, 3, 6, 14, 0, 6, 3, 5, 15, 0, 5, 3, 5, 2, 17, 0, 6, 11, 0, 3, 8, 0, 3, 3, ]; var sequence = new VanEcksSequence().Sequence.Take(50); sequence.Should().Equal(expected); } } ================================================ FILE: Algorithms.Tests/Sequences/ZeroSequenceTests.cs ================================================ using Algorithms.Sequences; namespace Algorithms.Tests.Sequences; public class ZeroSequenceTests { [Test] public void First10ElementsCorrect() { var sequence = new ZeroSequence().Sequence.Take(10); sequence.SequenceEqual(Enumerable.Repeat(BigInteger.Zero, 10)) .Should().BeTrue(); } } ================================================ FILE: Algorithms.Tests/Shufflers/FisherYatesShufflerTests.cs ================================================ using Algorithms.Shufflers; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Shufflers; public static class FisherYatesShufflerTests { [Test] public static void ArrayShuffled_NewArrayHasSameSize( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new FisherYatesShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Length.Should().Be(correctArray.Length); } [Test] public static void ArrayShuffled_NewArrayHasSameValues( [Random(0, 100, 10, Distinct = true)] int n) { // Arrange var shuffler = new FisherYatesShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Should().BeEquivalentTo(correctArray); } [Test] public static void ArrayShuffled_SameShuffle( [Random(0, 1000, 2, Distinct = true)] int n, [Random(1000, 10000, 5, Distinct = true)] int seed) { // Arrange var shuffler = new FisherYatesShuffler(); var (array1, array2) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(array1, seed); shuffler.Shuffle(array2, seed); // Assert array1.Should().BeEquivalentTo(array2, options => options.WithStrictOrdering()); } [Test] public static void ArrayShuffled_DifferentSeedDifferentShuffle( [Random(10, 100, 2, Distinct = true)] int n, [Random(1000, 10000, 5, Distinct = true)] int seed) { // Arrange var shuffler = new FisherYatesShuffler(); var (array1, array2) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(array1, seed); shuffler.Shuffle(array2, seed + 13); // It seems the actual version of FluentAssertion has no options in NotBeEquivalentTo. // With default options, it does not check for order, but for the same elements in the collection. // So until the library is updated check that not all the items have the same order. int hits = 0; for (int i = 0; i < n; i++) { if (array1[i] == array2[i]) { hits++; } } hits.Should().BeLessThan(array2.Length); } } ================================================ FILE: Algorithms.Tests/Shufflers/LINQShufflerTests.cs ================================================ using Algorithms.Shufflers; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Shufflers { public static class LinqShufflerTests { [Test] public static void ArrayShuffled_NewArraySameSize( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new LinqShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Length.Should().Be(correctArray.Length); } [Test] public static void ArrayShuffled_NewArraySameValues( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new LinqShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Should().BeEquivalentTo(correctArray); } [Test] public static void ArrayShuffled_NewArraySameShuffle( [Random(0, 1000, 2, Distinct = true)] int n, [Random(1000, 10000, 5, Distinct = true)] int seed) { // Arrange var shuffle = new LinqShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffle.Shuffle(testArray, seed); shuffle.Shuffle(correctArray, seed); // Assert correctArray.Should().BeEquivalentTo(testArray, options => options.WithStrictOrdering()); } } } ================================================ FILE: Algorithms.Tests/Shufflers/NaiveShufflerTests.cs ================================================ using Algorithms.Shufflers; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Shufflers { public static class NaiveShufflerTests { [Test] public static void ArrayShuffled_NewArraySameSize( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new NaiveShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Length.Should().Be(correctArray.Length); } [Test] public static void ArrayShuffled_NewArraySameValues( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new NaiveShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Should().BeEquivalentTo(correctArray); } [Test] public static void ArrayShuffled_NewArraySameShuffle( [Random(0, 1000, 2, Distinct = true)] int n, [Random(1000, 10000, 5, Distinct = true)] int seed) { // Arrange var shuffle = new NaiveShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffle.Shuffle(testArray, seed); shuffle.Shuffle(correctArray, seed); // Assert correctArray.Should().BeEquivalentTo(testArray, options => options.WithStrictOrdering()); } } } ================================================ FILE: Algorithms.Tests/Shufflers/RecursiveShufflerTests.cs ================================================ using Algorithms.Shufflers; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Shufflers { public static class RecursiveShufflerTests { [Test] public static void ArrayShuffled_NewArraySameSize( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new RecursiveShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Length.Should().Be(correctArray.Length); } [Test] public static void ArrayShuffled_NewArraySameValues( [Random(10, 1000, 100, Distinct = true)] int n) { // Arrange var shuffler = new RecursiveShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray); // Assert testArray.Should().BeEquivalentTo(correctArray); } [Test] public static void ArrayShuffled_NewArraySameShuffle( [Random(0, 1000, 2, Distinct = true)] int n, [Random(1000, 10000, 5, Distinct = true)] int seed) { // Arrange var shuffler = new RecursiveShuffler(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act shuffler.Shuffle(testArray, seed); shuffler.Shuffle(correctArray, seed); // Assert correctArray.Should().BeEquivalentTo(testArray, options => options.WithStrictOrdering()); } } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/BasicTeamSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; namespace Algorithms.Tests.Sorters.Comparison { [TestFixture] public class BasicTimSorterTests { private readonly BasicTimSorter sorter = new(Comparer.Default); [Test] public void Sort_EmptyArray_DoesNotThrow() { var array = Array.Empty(); Assert.DoesNotThrow(() => sorter.Sort(array)); Assert.That(array, Is.Empty); } [Test] public void Sort_SingleElementArray_DoesNotChangeArray() { var array = new[] { 1 }; sorter.Sort(array); Assert.That(array, Is.EqualTo(new[] { 1 })); } [Test] public void Sort_AlreadySortedArray_DoesNotChangeArray() { var array = new[] { 1, 2, 3, 4, 5 }; sorter.Sort(array); Assert.That(array, Is.EqualTo(new[] { 1, 2, 3, 4, 5 })); } [Test] public void Sort_UnsortedArray_SortsCorrectly() { var array = new[] { 5, 3, 1, 4, 2 }; sorter.Sort(array); Assert.That(array, Is.EqualTo(new[] { 1, 2, 3, 4, 5 })); } [Test] public void Sort_ReverseSortedArray_SortsCorrectly() { var array = new[] { 5, 4, 3, 2, 1 }; sorter.Sort(array); Assert.That(array, Is.EqualTo(new[] { 1, 2, 3, 4, 5 })); } [Test] public void Sort_ArrayWithDuplicates_SortsCorrectly() { var array = new[] { 3, 1, 2, 3, 1, 2 }; sorter.Sort(array); Assert.That(array, Is.EqualTo(new[] { 1, 1, 2, 2, 3, 3 })); } [Test] public void Sort_LargeArray_SortsCorrectly() { var array = new int[1000]; for (var i = 0; i < 1000; i++) { array[i] = 1000 - i; } sorter.Sort(array); array.Should().BeInAscendingOrder(); } [Test] public void Sort_LargeRandomArray_SortsCorrectly() { var array = new int[1000]; var random = new Random(); for (var i = 0; i < 1000; i++) { array[i] = random.Next(1, 1001); } sorter.Sort(array); array.Should().BeInAscendingOrder(); } } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/BinaryInsertionSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class BinaryInsertionSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new BinaryInsertionSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/BogoSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class BogoSorterTests { [Test] public static void ArraySorted([Random(0, 10, 10, Distinct = true)] int n) { // Arrange var sorter = new BogoSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/BubbleSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class BubbleSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new BubbleSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/CocktailSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class CocktailSorterTests { [Test] public static void SortsArray( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new CocktailSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/CombSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class CombSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new CombSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } [Test] public static void ArraySorted_WithCustomShrinkFactor( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new CombSorter(1.5); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/CycleSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class CycleSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new CycleSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/ExchangeSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class ExchangeSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new ExchangeSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/GnomeSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class GnomeSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new GnomeSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/HeapSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class HeapSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new HeapSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/InsertionSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class InsertionSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new InsertionSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/MedianOfThreeQuickSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class MedianOfThreeQuickSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new MedianOfThreeQuickSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/MergeSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; /// /// Class for testing merge sorter algorithm. /// public static class MergeSorterTests { [Test] public static void TestOnMergeSorter( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new MergeSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/MiddlePointQuickSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class MiddlePointQuickSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new MiddlePointQuickSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/PancakeSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class PancakeSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new PancakeSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/RandomPivotQuickSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class RandomPivotQuickSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new RandomPivotQuickSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/SelectionSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class SelectionSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new SelectionSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/ShellSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class ShellSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new ShellSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs ================================================ using Algorithms.Sorters.Comparison; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Comparison; public static class TimSorterTests { private static readonly IntComparer IntComparer = new(); private static readonly TimSorterSettings Settings = new(); [Test] public static void Sort_ShouldBeEquivalentToSuccessfulBasicSort( [Random(0, 10_000, 5000)] int n) { // Arrange var sorter = new TimSorter(Settings, IntComparer); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray, IntComparer); Array.Sort(correctArray, IntComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } [Test] public static void Sort_TinyArray_ShouldSortCorrectly() { // Arrange var sorter = new TimSorter(Settings, IntComparer); var tinyArray = new[] { 1 }; var correctArray = new[] { 1 }; // Act sorter.Sort(tinyArray, IntComparer); // Assert Assert.That(correctArray, Is.EqualTo(tinyArray)); } [Test] public static void Sort_SmallChunks_ShouldSortCorrectly() { // Arrange var sorter = new TimSorter(Settings, IntComparer); var (correctArray, testArray) = RandomHelper.GetArrays(800); Array.Sort(correctArray, IntComparer); Array.Sort(testArray, IntComparer); var max = testArray.Max(); var min = testArray.Min(); correctArray[0] = max; correctArray[800 - 1] = min; testArray[0] = max; testArray[800 - 1] = min; // Act sorter.Sort(testArray, IntComparer); Array.Sort(correctArray, IntComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } [Test] public static void Sort_ThrowsArgumentNullException_WhenArrayIsNull() { // Arrange var sorter = new TimSorter(Settings, IntComparer); // Act & Assert Assert.Throws(() => sorter.Sort(null!, IntComparer)); } [Test] public static void Sort_UsesDefaultComparer_WhenComparerIsNull() { // Arrange var sorter = new TimSorter(Settings, null!); var (correctArray, testArray) = RandomHelper.GetArrays(20); // Act sorter.Sort(testArray, IntComparer); Array.Sort(correctArray, IntComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } [Test] public static void Sort_AlreadySortedArray_RemainsUnchanged() { // Arrange var sorter = new TimSorter(Settings, IntComparer); var array = new[] { 1, 2, 3, 4, 5 }; var expected = new[] { 1, 2, 3, 4, 5 }; // Act sorter.Sort(array, IntComparer); // Assert Assert.That(array, Is.EqualTo(expected)); } [Test] public static void MergeAt_ShouldReturnEarly_WhenLenAIsZero() { // Arrange: left run is all less than right run's first element var array = Enumerable.Range(1, 25).Concat(Enumerable.Range(100, 25)).ToArray(); var sortedArray = Enumerable.Range(1, 25).Concat(Enumerable.Range(100, 25)).ToArray(); var sorter = new TimSorter(new TimSorterSettings(), Comparer.Default); // Act sorter.Sort(array, Comparer.Default); // Assert: Array order will not have changed, and the lenA <= 0 branch should be hit Assert.That(sortedArray, Is.EqualTo(array)); } [Test] public static void MergeAt_ShouldReturnEarly_WhenLenBIsZero() { // Arrange: right run is all less than left run's last element var array = Enumerable.Range(100, 25).Concat(Enumerable.Range(1, 25)).ToArray(); var sortedArray = Enumerable.Range(1, 25).Concat(Enumerable.Range(100, 25)).ToArray(); var sorter = new TimSorter(new TimSorterSettings(), Comparer.Default); // Act sorter.Sort(array, Comparer.Default); // Assert: The left and right sides of the array should have swapped places, and the lenB <= 0 branch should be hit Assert.That(sortedArray, Is.EqualTo(array)); } } ================================================ FILE: Algorithms.Tests/Sorters/External/ExternalMergeSorterTests.cs ================================================ using Algorithms.Sorters.External; using Algorithms.Sorters.External.Storages; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.External; public static class ExternalMergeSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new ExternalMergeSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); var main = new IntInMemoryStorage(testArray); var temp = new IntInMemoryStorage(new int[testArray.Length]); // Act sorter.Sort(main, temp, intComparer); Array.Sort(correctArray, intComparer); // Assert Assert.That(correctArray, Is.EqualTo(testArray)); } [Test] public static void ArraySorted_OnDisk( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new ExternalMergeSorter(); var intComparer = new IntComparer(); var (correctArray, testArray) = RandomHelper.GetArrays(n); var randomizer = Randomizer.CreateRandomizer(); var main = new IntFileStorage($"sorted_{randomizer.GetString(100)}", n); var temp = new IntFileStorage($"temp_{randomizer.GetString(100)}", n); var writer = main.GetWriter(); for (var i = 0; i < n; i++) { writer.Write(correctArray[i]); } writer.Dispose(); // Act sorter.Sort(main, temp, intComparer); Array.Sort(correctArray, intComparer); // Assert var reader = main.GetReader(); for (var i = 0; i < n; i++) { testArray[i] = reader.Read(); } Assert.That(correctArray, Is.EqualTo(testArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Integer/BucketSorterTests.cs ================================================ using Algorithms.Sorters.Integer; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Integer; public static class BucketSorterTests { [Test] public static void ArraySorted( [Random(0, 1000, 1000, Distinct = true)] int n) { // Arrange var sorter = new BucketSorter(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Integer/CountingSorterTests.cs ================================================ using Algorithms.Sorters.Integer; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Integer; public static class CountingSorterTests { [Test] public static void SortsNonEmptyArray( [Random(1, 10000, 100, Distinct = true)] int n) { // Arrange var sorter = new CountingSorter(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } [Test] public static void SortsEmptyArray() { // Arrange var sorter = new CountingSorter(); var (correctArray, testArray) = RandomHelper.GetArrays(0); // Act sorter.Sort(testArray); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.Empty); } } ================================================ FILE: Algorithms.Tests/Sorters/Integer/RadixSorterTests.cs ================================================ using Algorithms.Sorters.Integer; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.Integer; public static class RadixSorterTests { [Test] public static void SortsArray( [Random(0, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new RadixSorter(); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act sorter.Sort(testArray); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/String/MsdRadixStringSorterTests.cs ================================================ using Algorithms.Sorters.String; using Algorithms.Tests.Helpers; namespace Algorithms.Tests.Sorters.String; /// /// Class for testing MSD radix sorter algorithm. /// public static class MsdRadixStringSorterTests { [Test] public static void ArraySorted( [Random(2, 1000, 100, Distinct = true)] int n) { // Arrange var sorter = new MsdRadixStringSorter(); var (correctArray, testArray) = RandomHelper.GetStringArrays(n, 100, false); // Act sorter.Sort(testArray); Array.Sort(correctArray); // Assert Assert.That(testArray, Is.EqualTo(correctArray)); } } ================================================ FILE: Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs ================================================ using Algorithms.Sorters.Utils; namespace Algorithms.Tests.Sorters.Utils { [TestFixture] public class GallopingStrategyTests { private readonly IComparer comparer = Comparer.Default; [Test] public void GallopLeft_KeyPresent_ReturnsCorrectIndex() { var array = new[] { 1, 2, 3, 4, 5 }; var index = GallopingStrategy.GallopLeft(array, 3, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(2)); } [Test] public void GallopLeft_KeyNotPresent_ReturnsCorrectIndex() { var array = new[] { 1, 2, 4, 5 }; var index = GallopingStrategy.GallopLeft(array, 3, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(2)); } [Test] public void GallopLeft_KeyLessThanAll_ReturnsZero() { var array = new[] { 2, 3, 4, 5 }; var index = GallopingStrategy.GallopLeft(array, 1, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(0)); } [Test] public void GallopLeft_KeyGreaterThanAll_ReturnsLength() { var array = new[] { 1, 2, 3, 4 }; var index = GallopingStrategy.GallopLeft(array, 5, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(array.Length)); } [Test] public void GallopRight_KeyPresent_ReturnsCorrectIndex() { var array = new[] { 1, 2, 3, 4, 5 }; var index = GallopingStrategy.GallopRight(array, 3, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(3)); } [Test] public void GallopRight_KeyNotPresent_ReturnsCorrectIndex() { var array = new[] { 1, 2, 4, 5 }; var index = GallopingStrategy.GallopRight(array, 3, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(2)); } [Test] public void GallopRight_KeyLessThanAll_ReturnsZero() { var array = new[] { 2, 3, 4, 5 }; var index = GallopingStrategy.GallopRight(array, 1, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(0)); } [Test] public void GallopRight_KeyGreaterThanAll_ReturnsLength() { var array = new[] { 1, 2, 3, 4 }; var index = GallopingStrategy.GallopRight(array, 5, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(array.Length)); } [Test] public void GallopLeft_EmptyArray_ReturnsZero() { var array = new int[] { }; var index = GallopingStrategy.GallopLeft(array, 1, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(0)); } [Test] public void GallopRight_EmptyArray_ReturnsZero() { var array = new int[] { }; var index = GallopingStrategy.GallopRight(array, 1, 0, array.Length, comparer); Assert.That(index, Is.EqualTo(0)); } // Test when (shiftable << 1) < 0 is true [Test] public void TestBoundLeftShift_WhenShiftableCausesNegativeShift_ReturnsShiftedValuePlusOne() { // Arrange int shiftable = int.MaxValue; // This should cause a negative result after left shift // Act int result = GallopingStrategy.BoundLeftShift(shiftable); // Assert Assert.That((shiftable << 1) + 1, Is.EqualTo(result)); // True branch } // Test when (shiftable << 1) < 0 is false [Test] public void TestBoundLeftShift_WhenShiftableDoesNotCauseNegativeShift_ReturnsMaxValue() { // Arrange int shiftable = 1; // This will not cause a negative result after left shift // Act int result = GallopingStrategy.BoundLeftShift(shiftable); // Assert Assert.That(int.MaxValue, Is.EqualTo(result)); // False branch } } } ================================================ FILE: Algorithms.Tests/Stack/BalancedParenthesesCheckerTests.cs ================================================ using Algorithms.Stack; namespace Algorithms.Tests.Stack { [TestFixture] public class BalancedParenthesesCheckerTests { public static bool IsBalanced(string expression) { var checker = new BalancedParenthesesChecker(); return checker.IsBalanced(expression); } [Test] public void IsBalanced_EmptyString_ThrowsArgumentException() { // Arrange var expression = string.Empty; // Act & Assert var ex = Assert.Throws(() => IsBalanced(expression)); if (ex != null) { Assert.That(ex.Message, Is.EqualTo("The input expression cannot be null or empty.")); } } [Test] public void IsBalanced_ValidBalancedExpression_ReturnsTrue() { // Arrange var expression = "{[()]}"; // Act var result = IsBalanced(expression); // Assert Assert.That(result, Is.EqualTo(true)); } [Test] public void IsBalanced_ValidUnbalancedExpression_ReturnsFalse() { // Arrange var expression = "{[(])}"; // Act var result = IsBalanced(expression); // Assert Assert.That(result, Is.EqualTo(false)); } [Test] public void IsBalanced_UnbalancedWithExtraClosingBracket_ReturnsFalse() { // Arrange var expression = "{[()]}]"; // Act var result = IsBalanced(expression); // Assert Assert.That(result, Is.EqualTo(false)); } [Test] public void IsBalanced_ExpressionWithInvalidCharacters_ThrowsArgumentException() { // Arrange var expression = "{[a]}"; // Act & Assert var ex = Assert.Throws(() => IsBalanced(expression)); if (ex != null) { Assert.That(ex.Message, Is.EqualTo("Invalid character 'a' found in the expression.")); } } [Test] public void IsBalanced_SingleOpeningBracket_ReturnsFalse() { // Arrange var expression = "("; // Act var result = IsBalanced(expression); // Assert Assert.That(result, Is.EqualTo(false)); } [Test] public void IsBalanced_SingleClosingBracket_ReturnsFalse() { // Arrange var expression = ")"; // Act var result = IsBalanced(expression); // Assert Assert.That(result, Is.EqualTo(false)); } [Test] public void IsBalanced_ExpressionWithMultipleBalancedBrackets_ReturnsTrue() { // Arrange var expression = "[{()}]()"; // Act var result = IsBalanced(expression); // Assert Assert.That(result, Is.EqualTo(true)); } } } ================================================ FILE: Algorithms.Tests/Stack/InfixToPostfixTests.cs ================================================ using System; using NUnit.Framework; using Algorithms.Stack; namespace Algorithms.Tests.Stack { [TestFixture] public class InfixToPostfixTests { [Test] public void InfixToPostfixConversion_SimpleAddition_ReturnsCorrectPostfix() { // Arrange string infix = "A+B"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB+")); } [Test] public void InfixToPostfixConversion_SimpleSubtraction_ReturnsCorrectPostfix() { // Arrange string infix = "A-B"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB-")); } [Test] public void InfixToPostfixConversion_MultiplicationAndDivision_ReturnsCorrectPostfix() { // Arrange string infix = "A*B/C"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB*C/")); } [Test] public void InfixToPostfixConversion_ExponentiationOperator_ReturnsCorrectPostfix() { // Arrange string infix = "A^B"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB^")); } [Test] public void InfixToPostfixConversion_MixedOperatorPrecedence_ReturnsCorrectPostfix() { // Arrange string infix = "A+B*C"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("ABC*+")); } [Test] public void InfixToPostfixConversion_WithParentheses_ReturnsCorrectPostfix() { // Arrange string infix = "(A+B)*C"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB+C*")); } [Test] public void InfixToPostfixConversion_NestedParentheses_ReturnsCorrectPostfix() { // Arrange string infix = "((A+B)*C)"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB+C*")); } [Test] public void InfixToPostfixConversion_ComplexExpression_ReturnsCorrectPostfix() { // Arrange string infix = "A+B*C-D/E"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("ABC*+DE/-")); } [Test] public void InfixToPostfixConversion_WithDigits_ReturnsCorrectPostfix() { // Arrange string infix = "1+2*3"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("123*+")); } [Test] public void InfixToPostfixConversion_LowercaseLetters_ReturnsCorrectPostfix() { // Arrange string infix = "a+b*c"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("abc*+")); } [Test] public void InfixToPostfixConversion_WithWhitespace_IgnoresWhitespace() { // Arrange string infix = "A + B * C"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("ABC*+")); } [Test] public void InfixToPostfixConversion_MultipleParentheses_ReturnsCorrectPostfix() { // Arrange string infix = "(A+B)*(C-D)"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB+CD-*")); } [Test] public void InfixToPostfixConversion_AllOperators_ReturnsCorrectPostfix() { // Arrange string infix = "A+B-C*D/E^F"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("AB+CD*EF^/-")); } [Test] public void InfixToPostfixConversion_NullExpression_ThrowsArgumentException() { // Arrange string infix = null!; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); Assert.That(ex!.Message, Does.Contain("Infix cannot be null or empty")); } [Test] public void InfixToPostfixConversion_EmptyExpression_ThrowsArgumentException() { // Arrange string infix = ""; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); Assert.That(ex!.Message, Does.Contain("Infix cannot be null or empty")); } [Test] public void InfixToPostfixConversion_WhitespaceOnlyExpression_ThrowsArgumentException() { // Arrange string infix = " "; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); Assert.That(ex!.Message, Does.Contain("Infix cannot be null or empty")); } [Test] public void InfixToPostfixConversion_InvalidCharacter_ThrowsArgumentException() { // Arrange string infix = "A+B$C"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); Assert.That(ex!.Message, Does.Contain("Invalid character $")); } [Test] public void InfixToPostfixConversion_MismatchedParenthesesClosingExtra_ThrowsInvalidOperationException() { // Arrange string infix = "A+B)"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); Assert.That(ex!.Message, Does.Contain("Mismatched parentheses")); } [Test] public void InfixToPostfixConversion_MismatchedParenthesesOpeningExtra_ThrowsInvalidOperationException() { // Arrange string infix = "(A+B"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); Assert.That(ex!.Message, Does.Contain("Mismatched parentheses")); } [Test] public void InfixToPostfixConversion_OnlyOpeningParenthesis_ThrowsInvalidOperationException() { // Arrange string infix = "("; // Act & Assert Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); } [Test] public void InfixToPostfixConversion_OnlyClosingParenthesis_ThrowsInvalidOperationException() { // Arrange string infix = ")"; // Act & Assert Assert.Throws(() => InfixToPostfix.InfixToPostfixConversion(infix)); } [Test] public void InfixToPostfixConversion_ExponentiationWithOtherOperators_ReturnsCorrectPostfix() { // Arrange string infix = "A+B^C*D"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("ABC^D*+")); } [Test] public void InfixToPostfixConversion_SingleOperand_ReturnsOperand() { // Arrange string infix = "A"; // Act string result = InfixToPostfix.InfixToPostfixConversion(infix); // Assert Assert.That(result, Is.EqualTo("A")); } [Test] public void PostfixExpressionEvaluation_SimpleAddition_ReturnsCorrectResult() { // Arrange string postfix = "23+"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(5)); } [Test] public void PostfixExpressionEvaluation_SimpleSubtraction_ReturnsCorrectResult() { // Arrange string postfix = "53-"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(2)); } [Test] public void PostfixExpressionEvaluation_SimpleMultiplication_ReturnsCorrectResult() { // Arrange string postfix = "34*"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(12)); } [Test] public void PostfixExpressionEvaluation_SimpleDivision_ReturnsCorrectResult() { // Arrange string postfix = "82/"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(4)); } [Test] public void PostfixExpressionEvaluation_SimpleExponentiation_ReturnsCorrectResult() { // Arrange string postfix = "23^"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(8)); } [Test] public void PostfixExpressionEvaluation_ComplexExpression_ReturnsCorrectResult() { // Arrange string postfix = "23*4+"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(10)); } [Test] public void PostfixExpressionEvaluation_WithWhitespace_ReturnsCorrectResult() { // Arrange string postfix = "2 3 + 4 *"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(20)); } [Test] public void PostfixExpressionEvaluation_SingleDigit_ReturnsDigit() { // Arrange string postfix = "5"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(5)); } [Test] public void PostfixExpressionEvaluation_NullExpression_ThrowsArgumentException() { // Arrange string postfix = null!; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Postfix cannot be null or empty")); } [Test] public void PostfixExpressionEvaluation_EmptyExpression_ThrowsArgumentException() { // Arrange string postfix = ""; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Postfix cannot be null or empty")); } [Test] public void PostfixExpressionEvaluation_WhitespaceOnlyExpression_ThrowsArgumentException() { // Arrange string postfix = " "; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Postfix cannot be null or empty")); } [Test] public void PostfixExpressionEvaluation_InsufficientOperands_ThrowsInvalidOperationException() { // Arrange string postfix = "2+"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Insufficient operands")); } [Test] public void PostfixExpressionEvaluation_DivisionByZero_ThrowsDivideByZeroException() { // Arrange string postfix = "20/"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Cannot divide by zero")); } [Test] public void PostfixExpressionEvaluation_InvalidCharacter_ThrowsInvalidOperationException() { // Arrange string postfix = "23A+"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Invalid character in expression")); } [Test] public void PostfixExpressionEvaluation_LeftoverOperands_ThrowsInvalidOperationException() { // Arrange string postfix = "234"; // Act & Assert var ex = Assert.Throws(() => InfixToPostfix.PostfixExpressionEvaluation(postfix)); Assert.That(ex!.Message, Does.Contain("Invalid postfix expression: Leftover operands")); } [Test] public void PostfixExpressionEvaluation_UnknownOperator_ThrowsInvalidOperationException() { // This test ensures the default case in the switch is covered // Note: This is difficult to test directly as IsOperator filters valid operators // But we can test by passing an operator character that somehow bypasses IsOperator } [Test] public void PostfixExpressionEvaluation_ComplexExpressionWithAllOperators_ReturnsCorrectResult() { // Arrange - (2+3)*4-6/2 = 5*4-3 = 20-3 = 17 string postfix = "23+4*62/-"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(17)); } [Test] public void PostfixExpressionEvaluation_ExponentiationInExpression_ReturnsCorrectResult() { // Arrange - 2^3*2 = 8*2 = 16 string postfix = "23^2*"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(16)); } [Test] public void PostfixExpressionEvaluation_ZeroOperands_ReturnsZero() { // Arrange string postfix = "0"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(0)); } [Test] public void PostfixExpressionEvaluation_LargerNumbers_ReturnsCorrectResult() { // Arrange - Uses single digits only: 9+8 = 17 string postfix = "98+"; // Act int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(result, Is.EqualTo(17)); } [Test] public void IntegrationTest_ConvertAndEvaluate_SimpleExpression() { // Arrange string infix = "2+3"; // Act string postfix = InfixToPostfix.InfixToPostfixConversion(infix); int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(postfix, Is.EqualTo("23+")); Assert.That(result, Is.EqualTo(5)); } [Test] public void IntegrationTest_ConvertAndEvaluate_ComplexExpression() { // Arrange string infix = "(2+3)*4"; // Act string postfix = InfixToPostfix.InfixToPostfixConversion(infix); int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(postfix, Is.EqualTo("23+4*")); Assert.That(result, Is.EqualTo(20)); } [Test] public void IntegrationTest_ConvertAndEvaluate_WithAllOperators() { // Arrange - 2+3*4-6/2 = 2+12-3 = 11 string infix = "2+3*4-6/2"; // Act string postfix = InfixToPostfix.InfixToPostfixConversion(infix); int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(postfix, Is.EqualTo("234*+62/-")); Assert.That(result, Is.EqualTo(11)); } [Test] public void IntegrationTest_ConvertAndEvaluate_WithExponentiation() { // Arrange - 2^3+1 = 8+1 = 9 string infix = "2^3+1"; // Act string postfix = InfixToPostfix.InfixToPostfixConversion(infix); int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); // Assert Assert.That(postfix, Is.EqualTo("23^1+")); Assert.That(result, Is.EqualTo(9)); } } } ================================================ FILE: Algorithms.Tests/Stack/NextGreaterElementTests.cs ================================================ using Algorithms.Stack; namespace Algorithms.Tests.Stack { [TestFixture] public class NextGreaterElementTests { private static int[] FindNextGreaterElement(int[] input) { var obj = new NextGreaterElement(); return obj.FindNextGreaterElement(input); } [Test] public void FindNextGreaterElement_InputIsEmpty_ReturnsEmptyArray() { // Arrange int[] input = Array.Empty(); int[] expected = Array.Empty(); // Act var result = FindNextGreaterElement(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public void FindNextGreaterElement_BasicScenario_ReturnsCorrectResult() { // Arrange int[] input = [4, 5, 2, 25]; int[] expected = [5, 25, 25, -1]; // Act var result = FindNextGreaterElement(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public void FindNextGreaterElement_NoNextGreaterElement_ReturnsCorrectResult() { // Arrange int[] input = [13, 7, 6, 12]; int[] expected = [-1, 12, 12, -1]; // Act var result = FindNextGreaterElement(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public void FindNextGreaterElement_AllElementsHaveNoGreaterElement_ReturnsAllNegativeOnes() { // Arrange int[] input = [5, 4, 3, 2, 1]; int[] expected = [-1, -1, -1, -1, -1]; // Act var result = FindNextGreaterElement(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public void FindNextGreaterElement_InputWithDuplicates_ReturnsCorrectResult() { // Arrange int[] input = [4, 4, 3, 2, 4]; int[] expected = [-1, -1, 4, 4, -1]; // Act var result = FindNextGreaterElement(input); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public void FindNextGreaterElement_SingleElementArray_ReturnsNegativeOne() { // Arrange int[] input = [10]; int[] expected = [-1]; // Act var result = FindNextGreaterElement(input); // Assert Assert.That(result, Is.EqualTo(expected)); } } } ================================================ FILE: Algorithms.Tests/Stack/ReverseStackTests.cs ================================================ using Algorithms.Stack; namespace Algorithms.Tests.Stack { public class ReverseStackTests { public static void Reverse(Stack stack) { var obj = new ReverseStack(); obj.Reverse(stack); } [Test] public void Reverse_EmptyStack_DoesNotChangeStack() { // Arrange Stack stack = new Stack(); // Act Reverse(stack); // Assert Assert.That(stack.Count, Is.EqualTo(0)); } [Test] public void Reverse_SingleElementStack_DoesNotChangeStack() { // Arrange Stack stack = new Stack(); stack.Push(1); // Act Reverse(stack); // Assert Assert.That(stack.Count, Is.EqualTo(1)); Assert.That(stack.Peek(), Is.EqualTo(1)); } [Test] public void Reverse_MultipleElementStack_ReturnsCorrectOrder() { // Arrange Stack stack = new Stack(); stack.Push(1); stack.Push(2); stack.Push(3); // The stack is now [3, 2, 1] (top to bottom) // Act Reverse(stack); // Assert Assert.That(stack.Count, Is.EqualTo(3)); Assert.That(stack.Pop(), Is.EqualTo(1)); // Should return 1 Assert.That(stack.Pop(), Is.EqualTo(2)); // Should return 2 Assert.That(stack.Pop(), Is.EqualTo(3)); // Should return 3 } [Test] public void Reverse_StackWithDuplicates_ReturnsCorrectOrder() { // Arrange Stack stack = new Stack(); stack.Push(1); stack.Push(2); stack.Push(1); // The stack is now [1, 2, 1] (top to bottom) // Act Reverse(stack); // Assert Assert.That(stack.Count, Is.EqualTo(3)); Assert.That(stack.Pop(), Is.EqualTo(1)); // Should return 1 Assert.That(stack.Pop(), Is.EqualTo(2)); // Should return 2 Assert.That(stack.Pop(), Is.EqualTo(1)); // Should return 1 } } } ================================================ FILE: Algorithms.Tests/Strings/GeneralStringAlgorithmsTests.cs ================================================ using Algorithms.Strings; namespace Algorithms.Tests.Strings; public static class GeneralStringAlgorithmsTests { [TestCase("Griffith", 'f', 2)] [TestCase("Randomwoooord", 'o', 4)] [TestCase("Control", 'C', 1)] public static void MaxCountCharIsObtained(string text, char expectedSymbol, int expectedCount) { // Arrange // Act var (symbol, count) = GeneralStringAlgorithms.FindLongestConsecutiveCharacters(text); // Assert Assert.That(symbol, Is.EqualTo(expectedSymbol)); Assert.That(count, Is.EqualTo(expectedCount)); } } ================================================ FILE: Algorithms.Tests/Strings/ManachersAlgorithmTests.cs ================================================ using Algorithms.Strings; using NUnit.Framework; using System; namespace Algorithms.Tests.Strings; /// /// Comprehensive test suite for Manacher's Algorithm implementation. /// Tests cover various scenarios including: /// - Odd-length palindromes /// - Even-length palindromes /// - Single character strings /// - Empty strings /// - Strings with no palindromes longer than 1 character /// - Strings that are entirely palindromic /// - Multiple palindromes of the same length /// - Edge cases (null input, special characters) /// - Palindrome detection functionality /// - Detailed palindrome information retrieval. /// public static class ManachersAlgorithmTests { [Test] public static void FindLongestPalindrome_WithOddLengthPalindrome_ReturnsCorrectPalindrome() { // Arrange: Classic example with odd-length palindrome "bab" or "aba" string input = "babad"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Either "bab" or "aba" is valid (both have length 3) Assert.That(result.Length, Is.EqualTo(3)); Assert.That(result == "bab" || result == "aba", Is.True); } [Test] public static void FindLongestPalindrome_WithEvenLengthPalindrome_ReturnsCorrectPalindrome() { // Arrange: String with even-length palindrome "abba" string input = "cbbd"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find "bb" (length 2) Assert.That(result, Is.EqualTo("bb")); } [Test] public static void FindLongestPalindrome_WithSingleCharacter_ReturnsSingleCharacter() { // Arrange: Edge case with single character string input = "a"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Single character is a palindrome of itself Assert.That(result, Is.EqualTo("a")); } [Test] public static void FindLongestPalindrome_WithEmptyString_ReturnsEmptyString() { // Arrange: Edge case with empty string string input = string.Empty; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Empty string should return empty string Assert.That(result, Is.EqualTo(string.Empty)); } [Test] public static void FindLongestPalindrome_WithNullString_ThrowsArgumentException() { // Arrange: Test defensive programming - null input validation string? input = null; // Act & Assert: Should throw ArgumentException for null input Assert.Throws(() => ManachersAlgorithm.FindLongestPalindrome(input!)); } [Test] public static void FindLongestPalindrome_WithEntirePalindrome_ReturnsEntireString() { // Arrange: String that is entirely a palindrome string input = "racecar"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should return the entire string Assert.That(result, Is.EqualTo("racecar")); } [Test] public static void FindLongestPalindrome_WithNoPalindromes_ReturnsSingleCharacter() { // Arrange: String with no palindromes longer than 1 character string input = "abcdef"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should return a single character (any character is a palindrome) Assert.That(result.Length, Is.EqualTo(1)); } [Test] public static void FindLongestPalindrome_WithLongPalindrome_ReturnsCorrectPalindrome() { // Arrange: String with a longer palindrome string input = "forgeeksskeegfor"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find "geeksskeeg" (length 10) Assert.That(result, Is.EqualTo("geeksskeeg")); } [Test] public static void FindLongestPalindrome_WithRepeatingCharacters_ReturnsCorrectPalindrome() { // Arrange: String with repeating characters string input = "aaaa"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Entire string is a palindrome Assert.That(result, Is.EqualTo("aaaa")); } [Test] public static void FindLongestPalindrome_WithSpecialCharacters_ReturnsCorrectPalindrome() { // Arrange: String with special characters and spaces string input = "A man, a plan, a canal: Panama"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find a palindrome (note: this includes spaces and punctuation) // The longest palindrome considering all characters is "anama" Assert.That(result.Length, Is.GreaterThan(0)); } [Test] public static void FindLongestPalindrome_WithTwoCharacters_ReturnsCorrectResult() { // Arrange: Edge case with two identical characters string input = "aa"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should return "aa" Assert.That(result, Is.EqualTo("aa")); } [Test] public static void FindLongestPalindrome_WithTwoDifferentCharacters_ReturnsSingleCharacter() { // Arrange: Edge case with two different characters string input = "ab"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should return a single character Assert.That(result.Length, Is.EqualTo(1)); } [Test] public static void FindLongestPalindromeWithDetails_ReturnsCorrectDetails() { // Arrange: Test the detailed version that returns index and length string input = "babad"; // Act var (palindrome, startIndex, length) = ManachersAlgorithm.FindLongestPalindromeWithDetails(input); // Assert: Verify all components Assert.That(length, Is.EqualTo(3)); Assert.That(palindrome.Length, Is.EqualTo(length)); Assert.That(input.Substring(startIndex, length), Is.EqualTo(palindrome)); Assert.That(palindrome == "bab" || palindrome == "aba", Is.True); } [Test] public static void FindLongestPalindromeWithDetails_WithEmptyString_ReturnsZeroLength() { // Arrange string input = string.Empty; // Act var (palindrome, startIndex, length) = ManachersAlgorithm.FindLongestPalindromeWithDetails(input); // Assert Assert.That(palindrome, Is.EqualTo(string.Empty)); Assert.That(startIndex, Is.EqualTo(0)); Assert.That(length, Is.EqualTo(0)); } [Test] public static void FindLongestPalindromeWithDetails_WithSingleCharacter_ReturnsCorrectDetails() { // Arrange string input = "x"; // Act var (palindrome, startIndex, length) = ManachersAlgorithm.FindLongestPalindromeWithDetails(input); // Assert Assert.That(palindrome, Is.EqualTo("x")); Assert.That(startIndex, Is.EqualTo(0)); Assert.That(length, Is.EqualTo(1)); } [Test] public static void FindLongestPalindromeWithDetails_WithNullString_ThrowsArgumentException() { // Arrange string? input = null; // Act & Assert Assert.Throws(() => ManachersAlgorithm.FindLongestPalindromeWithDetails(input!)); } [Test] public static void IsPalindrome_WithPalindromeString_ReturnsTrue() { // Arrange: Test palindrome detection with a valid palindrome string input = "racecar"; // Act bool result = ManachersAlgorithm.IsPalindrome(input); // Assert: Should return true Assert.That(result, Is.True); } [Test] public static void IsPalindrome_WithNonPalindromeString_ReturnsFalse() { // Arrange: Test palindrome detection with a non-palindrome string input = "hello"; // Act bool result = ManachersAlgorithm.IsPalindrome(input); // Assert: Should return false Assert.That(result, Is.False); } [Test] public static void IsPalindrome_WithSingleCharacter_ReturnsTrue() { // Arrange: Single character is always a palindrome string input = "a"; // Act bool result = ManachersAlgorithm.IsPalindrome(input); // Assert: Should return true Assert.That(result, Is.True); } [Test] public static void IsPalindrome_WithEmptyString_ReturnsTrue() { // Arrange: Empty string is considered a palindrome string input = string.Empty; // Act bool result = ManachersAlgorithm.IsPalindrome(input); // Assert: Should return true Assert.That(result, Is.True); } [Test] public static void IsPalindrome_WithNullString_ThrowsArgumentException() { // Arrange string? input = null; // Act & Assert Assert.Throws(() => ManachersAlgorithm.IsPalindrome(input!)); } [Test] public static void IsPalindrome_WithEvenLengthPalindrome_ReturnsTrue() { // Arrange: Even-length palindrome string input = "abba"; // Act bool result = ManachersAlgorithm.IsPalindrome(input); // Assert: Should return true Assert.That(result, Is.True); } [Test] public static void FindLongestPalindrome_WithNumericString_ReturnsCorrectPalindrome() { // Arrange: String with numbers string input = "12321"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should return the entire string Assert.That(result, Is.EqualTo("12321")); } [Test] public static void FindLongestPalindrome_WithMixedCase_ReturnsCorrectPalindrome() { // Arrange: Mixed case string (case-sensitive palindrome check) string input = "AbcbA"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find "AbcbA" as it's case-sensitive Assert.That(result, Is.EqualTo("AbcbA")); } [Test] public static void FindLongestPalindrome_WithPalindromeAtStart_ReturnsCorrectPalindrome() { // Arrange: Palindrome at the start of the string string input = "abaxyz"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find "aba" Assert.That(result, Is.EqualTo("aba")); } [Test] public static void FindLongestPalindrome_WithPalindromeAtEnd_ReturnsCorrectPalindrome() { // Arrange: Palindrome at the end of the string string input = "xyzaba"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find "aba" Assert.That(result, Is.EqualTo("aba")); } [Test] public static void FindLongestPalindrome_WithMultiplePalindromesOfSameLength_ReturnsOne() { // Arrange: Multiple palindromes of the same length // "aba" at index 0 and "cdc" at index 3 string input = "abacdc"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should return one of them (the first one found) Assert.That(result.Length, Is.EqualTo(3)); Assert.That(result == "aba" || result == "cdc", Is.True); } [Test] public static void FindLongestPalindromeWithDetails_WithLongString_PerformsEfficiently() { // Arrange: Test with a longer string to verify O(n) performance // Create a string with a palindrome in the middle string input = new string('a', 1000) + "racecar" + new string('b', 1000); // Act var (_, _, length) = ManachersAlgorithm.FindLongestPalindromeWithDetails(input); // Assert: Should find the longest palindrome (either the 'a's or 'b's or "racecar") // The 1000 'a's form a palindrome Assert.That(length, Is.GreaterThanOrEqualTo(7)); // At least "racecar" } [Test] public static void FindLongestPalindrome_WithVeryLongPalindrome_HandlesGracefully() { // Arrange: Test with a very long palindrome to verify O(n) performance string input = new string('a', 10000); // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should correctly identify the entire string as a palindrome Assert.That(result.Length, Is.EqualTo(10000)); Assert.That(result, Is.EqualTo(input)); } [Test] public static void IsPalindrome_WithVeryLongPalindrome_HandlesGracefully() { // Arrange: Test with a very long palindrome to verify performance string input = new string('x', 5000); // Act bool result = ManachersAlgorithm.IsPalindrome(input); // Assert: Should correctly identify as palindrome in O(n) time Assert.That(result, Is.True); } [Test] public static void FindLongestPalindrome_WithAlternatingCharacters_HandlesEdgeCases() { // Arrange: Test with alternating characters (minimal palindromes) string input = "abababababababababababababababab"; // Act string result = ManachersAlgorithm.FindLongestPalindrome(input); // Assert: Should find at least a 3-character palindrome like "aba" or "bab" Assert.That(result.Length, Is.GreaterThanOrEqualTo(3)); } [Test] public static void FindLongestPalindromeWithDetails_WithSpecialCharacters_HandlesCorrectly() { // Arrange: Test with special characters to ensure proper handling string input = "abc!@#@!xyz"; // Act var (palindrome, startIndex, length) = ManachersAlgorithm.FindLongestPalindromeWithDetails(input); // Assert: Should find the palindrome with special characters Assert.That(palindrome, Is.EqualTo("!@#@!")); Assert.That(length, Is.EqualTo(5)); Assert.That(startIndex, Is.EqualTo(3)); } } ================================================ FILE: Algorithms.Tests/Strings/PalindromeTests.cs ================================================ using Algorithms.Strings; namespace Algorithms.Tests.Strings; public static class PalindromeTests { [TestCase("Anna")] [TestCase("A Santa at Nasa")] public static void TextIsPalindrome_TrueExpected(string text) { // Arrange // Act var isPalindrome = Palindrome.IsStringPalindrome(text); // Assert Assert.That(isPalindrome, Is.True); } [TestCase("hallo")] [TestCase("Once upon a time")] public static void TextNotPalindrome_FalseExpected(string text) { // Arrange // Act var isPalindrome = Palindrome.IsStringPalindrome(text); // Assert Assert.That(isPalindrome, Is.False); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/BitapTests.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings.PatternMatching; [TestFixture] public class BitapTests { [Test] public void FindExactPattern_EmptyTextReturnsError() { Assert.That(Bitap.FindExactPattern("", "abc"), Is.EqualTo(-1)); } [Test] public void FindExactPattern_EmptyPatternReturnsZero() { Assert.That(Bitap.FindExactPattern("abc", ""), Is.EqualTo(0)); } [Test] public void FindExactPattern_PatternFoundAtBeginning() { Assert.That(Bitap.FindExactPattern("hello world", "hello"), Is.EqualTo(0)); } [Test] public void FindExactPattern_PatternFoundInTheMiddle() { Assert.That(Bitap.FindExactPattern("abcabc", "cab"), Is.EqualTo(2)); } [Test] public void FindExactPattern_PatternFoundAtEnd() { Assert.That(Bitap.FindExactPattern("the end", "end"), Is.EqualTo(4)); } [Test] public void FindExactPattern_PatternNotFound() { Assert.That(Bitap.FindExactPattern("abcdefg", "xyz"), Is.EqualTo(-1)); } [Test] public void FindExactPattern_PatternLongerThanText() { Assert.That(Bitap.FindExactPattern("short", "longerpattern"), Is.EqualTo(-1)); } [Test] public void FindExactPattern_OverlappingPatterns() { Assert.That(Bitap.FindExactPattern("ababab", "abab"), Is.EqualTo(0)); } [Test] public void FindExactPattern_PatternTooLongThrowsException() { var longPattern = new string('a', 32); Assert.Throws(() => Bitap.FindExactPattern("some text", longPattern)); } [Test] public void FindExactPattern_SpecialCharactersInPattern() { Assert.That(Bitap.FindExactPattern("hello, world!", ", wo"), Is.EqualTo(5)); } [Test] public void FindFuzzyPattern_EmptyTextReturnsZero() { Assert.That(Bitap.FindFuzzyPattern("", "abc", 1), Is.EqualTo(0)); } [Test] public void FindFuzzyPattern_EmptyPatternReturnsZero() { Assert.That(Bitap.FindFuzzyPattern("def", "", 1), Is.EqualTo(0)); } [Test] public void FindFuzzyPattern_ExactMatchFound() { Assert.That(Bitap.FindFuzzyPattern("hello world", "hello", 0), Is.EqualTo(0)); } [Test] public void FindFuzzyPattern_FuzzyMatchWithOneMismatch() { Assert.That(Bitap.FindFuzzyPattern("hello world", "hellp", 1), Is.EqualTo(0)); } [Test] public void FindFuzzyPattern_FuzzyMatchWithMultipleMismatches() { Assert.That(Bitap.FindFuzzyPattern("abcde", "xbcdz", 2), Is.EqualTo(0)); } [Test] public void FindFuzzyPattern_FuzzyMatchAtEnd() { Assert.That(Bitap.FindFuzzyPattern("abcdefg", "efx", 1), Is.EqualTo(4)); } [Test] public void FindFuzzyPattern_FuzzyMatchNotFound() { Assert.That(Bitap.FindFuzzyPattern("abcdefg", "xyz", 2), Is.EqualTo(-1)); } [Test] public void FindFuzzyPattern_PatternTooLongReturnsNegativeOne() { var longPattern = new string('a', 32); Assert.That(Bitap.FindFuzzyPattern("some text", longPattern, 1), Is.EqualTo(-1)); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/BoyerMoreTests.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings; public class BoyerMooreTests { [TestCase("HelloImATestcaseAndIWillPass", "Testcase", 8)] [TestCase("HelloImATestcaseAndImCaseSensitive", "TestCase", -1)] [TestCase("Hello Im a testcase and I work with whitespaces", "testcase", 11)] [TestCase("Hello Im a testcase and I work with numbers like 1 2 3 4", "testcase", 11)] public void FindFirstOccurrence_IndexCheck(string t, string p, int expectedIndex) { var resultIndex = BoyerMoore.FindFirstOccurrence(t, p); Assert.That(expectedIndex, Is.EqualTo(resultIndex)); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/KnuthMorrisPrattSearcherTests.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings; public static class KnuthMorrisPrattSearcherTests { [Test] public static void FindIndexes_ItemsPresent_PassExpected() { // Arrange var searcher = new KnuthMorrisPrattSearcher(); var str = "ABABAcdeABA"; var pat = "ABA"; // Act var expectedItem = new[] { 0, 2, 8 }; var actualItem = searcher.FindIndexes(str, pat); // Assert Assert.That(actualItem, Is.EqualTo(expectedItem)); } [Test] public static void FindIndexes_ItemsMissing_NoIndexesReturned() { // Arrange var searcher = new KnuthMorrisPrattSearcher(); var str = "ABABA"; var pat = "ABB"; // Act & Assert var indexes = searcher.FindIndexes(str, pat); // Assert Assert.That(indexes, Is.Empty); } [Test] public static void LongestPrefixSuffixArray_PrefixSuffixOfLength1_PassExpected() { // Arrange var searcher = new KnuthMorrisPrattSearcher(); var s = "ABA"; // Act var expectedItem = new[] { 0, 0, 1 }; var actualItem = searcher.FindLongestPrefixSuffixValues(s); // Assert Assert.That(actualItem, Is.EqualTo(expectedItem)); } [Test] public static void LongestPrefixSuffixArray_PrefixSuffixOfLength5_PassExpected() { // Arrange var searcher = new KnuthMorrisPrattSearcher(); var s = "AABAACAABAA"; // Act var expectedItem = new[] { 0, 1, 0, 1, 2, 0, 1, 2, 3, 4, 5 }; var actualItem = searcher.FindLongestPrefixSuffixValues(s); // Assert Assert.That(actualItem, Is.EqualTo(expectedItem)); } [Test] public static void LongestPrefixSuffixArray_PrefixSuffixOfLength0_PassExpected() { // Arrange var searcher = new KnuthMorrisPrattSearcher(); var s = "AB"; // Act var expectedItem = new[] { 0, 0 }; var actualItem = searcher.FindLongestPrefixSuffixValues(s); // Assert Assert.That(actualItem, Is.EqualTo(expectedItem)); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/NaiveStringSearchTests.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings; public static class NaiveStringSearchTests { [Test] public static void ThreeMatchesFound_PassExpected() { // Arrange var pattern = "ABB"; var content = "ABBBAAABBAABBBBAB"; // Act var expectedOccurrences = new[] { 0, 6, 10 }; var actualOccurrences = NaiveStringSearch.NaiveSearch(content, pattern); var sequencesAreEqual = expectedOccurrences.SequenceEqual(actualOccurrences); // Assert Assert.That(sequencesAreEqual, Is.True); } [Test] public static void OneMatchFound_PassExpected() { // Arrange var pattern = "BAAB"; var content = "ABBBAAABBAABBBBAB"; // Act var expectedOccurrences = new[] { 8 }; var actualOccurrences = NaiveStringSearch.NaiveSearch(content, pattern); var sequencesAreEqual = expectedOccurrences.SequenceEqual(actualOccurrences); // Assert Assert.That(sequencesAreEqual, Is.True); } [Test] public static void NoMatchFound_PassExpected() { // Arrange var pattern = "XYZ"; var content = "ABBBAAABBAABBBBAB"; // Act var expectedOccurrences = new int[0]; var actualOccurrences = NaiveStringSearch.NaiveSearch(content, pattern); var sequencesAreEqual = expectedOccurrences.SequenceEqual(actualOccurrences); // Assert Assert.That(sequencesAreEqual, Is.True); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/RabinKarpTests.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings; public class RabinKarpTest { [TestCase("HelloImATestcaseAndIWillPass", "Testcase", new[] { 8 })] [TestCase("HelloImATestcaseAndImCaseSensitive", "TestCase", new int[] { })] [TestCase("Hello Im a testcase and you can use whitespaces", "testcase", new[] { 11 })] [TestCase("Hello Im a testcase and you can use numbers like 1, 2, 3, etcetera", "etcetera", new[] { 58 })] [TestCase("HelloImATestcaseAndIHaveTwoOccurrencesOfTestcase", "Testcase", new[] { 8, 40 })] public void FindAllOccurrences_IndexCheck(string t, string p, int[] expectedIndices) { List result = RabinKarp.FindAllOccurrences(t, p); Assert.That(result, Is.EqualTo(new List(expectedIndices))); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/WildCardMatcherTests.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings.PatternMatching; public static class WildCardMatcherTests { [TestCase("aab", "c*a*b", true)] [TestCase("aaa", "aa", false)] [TestCase("aaa", "a.a", true)] [TestCase("aaab", "aa*", false)] [TestCase("aaab", ".*", true)] [TestCase("a", "bbbb", false)] [TestCase("", "bbbb", false)] [TestCase("a", "", false)] [TestCase("", "", true)] public static void MatchPattern(string inputString, string pattern, bool expected) { // Act var result = WildCardMatcher.MatchPattern(inputString, pattern); // Assert Assert.That(result, Is.EqualTo(expected)); } [Test] public static void MatchPatternThrowsArgumentException() { // Arrange var inputString = "abc"; var pattern = "*abc"; // Assert Assert.Throws(() => WildCardMatcher.MatchPattern(inputString, pattern)); } } ================================================ FILE: Algorithms.Tests/Strings/PatternMatching/ZblockSubstringSearchTest.cs ================================================ using Algorithms.Strings.PatternMatching; namespace Algorithms.Tests.Strings; public class ZblockSubstringSearchTest { [TestCase("abc", "abcdef", 1)] [TestCase("xxx", "abxxxcdexxxf", 2)] [TestCase("aa", "waapaaxcdaalaabb", 4)] [TestCase("ABC", "ABAAABCDBBABCDDEBCABC", 3)] [TestCase("xxx", "abcdefghij", 0)] [TestCase("aab", "caabxaaaz", 1)] [TestCase("abc", "xababaxbabcdabx", 1)] [TestCase("GEEK", "GEEKS FOR GEEKS", 2)] [TestCase("ground", "Hello, playground!", 1)] public void Test(string pattern, string text, int expectedOccurences) { var occurencesFound = ZblockSubstringSearch.FindSubstring(pattern, text); Assert.That(occurencesFound, Is.EqualTo(expectedOccurences)); } } ================================================ FILE: Algorithms.Tests/Strings/PermutationTests.cs ================================================ using Algorithms.Numeric; using Algorithms.Strings; namespace Algorithms.Tests.Strings; public class PermutationTests { [TestCase("")] [TestCase("A")] [TestCase("abcd")] [TestCase("aabcd")] [TestCase("aabbbcd")] [TestCase("aabbccccd")] public void Test_GetEveryUniquePermutation(string word) { var permutations = Permutation.GetEveryUniquePermutation(word); // We need to make sure that // 1. We have the right number of permutations // 2. Every string in permutations List is a permutation of word // 3. There are no repetitions // Start 1. // The number of unique permutations is // n!/(A1! * A2! * ... An!) // where n is the length of word and Ai is the number of occurrences if ith char in the string var charOccurrence = new Dictionary(); foreach (var c in word) { if (charOccurrence.ContainsKey(c)) { charOccurrence[c] += 1; } else { charOccurrence[c] = 1; } } // now we know the values of A1, A2, ..., An // evaluate the above formula var expectedNumberOfAnagrams = Factorial.Calculate(word.Length); expectedNumberOfAnagrams = charOccurrence.Aggregate(expectedNumberOfAnagrams, (current, keyValuePair) => { return current / Factorial.Calculate(keyValuePair.Value); }); Assert.That(new BigInteger(permutations.Count), Is.EqualTo(expectedNumberOfAnagrams)); // End 1. // Start 2 // string A is a permutation of string B if and only if sorted(A) == sorted(b) var wordSorted = SortString(word); foreach (var permutation in permutations) { Assert.That(SortString(permutation), Is.EqualTo(wordSorted)); } // End 2 // Start 3 Assert.That(new HashSet(permutations).Count, Is.EqualTo(permutations.Count)); // End 3 } private static string SortString(string word) { var asArray = word.ToArray(); Array.Sort(asArray); return new string(asArray); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/CosineSimilarityTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings.Similarity; [TestFixture] public class CosineSimilarityTests { [Test] public void Calculate_IdenticalStrings_ReturnsOne() { var str1 = "test"; var str2 = "test"; var result = CosineSimilarity.Calculate(str1, str2); Assert.That(result, Is.EqualTo(1.0).Within(1e-6), "Identical strings should have a cosine similarity of 1."); } [Test] public void Calculate_CompletelyDifferentStrings_ReturnsZero() { var str1 = "abc"; var str2 = "xyz"; var result = CosineSimilarity.Calculate(str1, str2); Assert.That(result, Is.EqualTo(0.0).Within(1e-6), "Completely different strings should have a cosine similarity of 0."); } [Test] public void Calculate_EmptyStrings_ReturnsZero() { var str1 = ""; var str2 = ""; var result = CosineSimilarity.Calculate(str1, str2); Assert.That(result, Is.EqualTo(0.0).Within(1e-6), "Empty strings should have a cosine similarity of 0."); } [Test] public void Calculate_OneEmptyString_ReturnsZero() { var str1 = "test"; var str2 = ""; var result = CosineSimilarity.Calculate(str1, str2); Assert.That(result, Is.EqualTo(0.0).Within(1e-6), "Empty string should have a cosine similarity of 0."); } [Test] public void Calculate_SameCharactersDifferentCases_ReturnsOne() { var str1 = "Test"; var str2 = "test"; var result = CosineSimilarity.Calculate(str1, str2); Assert.That(result, Is.EqualTo(1.0).Within(1e-6), "The method should be case-insensitive."); } [Test] public void Calculate_SpecialCharacters_ReturnsCorrectValue() { var str1 = "hello!"; var str2 = "hello!"; var result = CosineSimilarity.Calculate(str1, str2); Assert.That(result, Is.EqualTo(1.0).Within(1e-6), "Strings with special characters should have a cosine similarity of 1."); } [Test] public void Calculate_DifferentLengthWithCommonCharacters_ReturnsCorrectValue() { var str1 = "hello"; var str2 = "hello world"; var result = CosineSimilarity.Calculate(str1, str2); var expected = 10 / (Math.Sqrt(7) * Math.Sqrt(19)); // calculated manually Assert.That(result, Is.EqualTo(expected).Within(1e-6), "Strings with different lengths but some common characters should have the correct cosine similarity."); } [Test] public void Calculate_PartiallyMatchingStrings_ReturnsCorrectValue() { var str1 = "night"; var str2 = "nacht"; var result = CosineSimilarity.Calculate(str1, str2); // Assuming the correct calculation gives an expected value var expected = 3.0 / 5.0; Assert.That(result, Is.EqualTo(expected).Within(1e-6), "Partially matching strings should have the correct cosine similarity."); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/DamerauLevenshteinDistanceTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings.Similarity; [TestFixture] public class DamerauLevenshteinDistanceTests { [Test] public void Calculate_IdenticalStrings_ReturnsZero() { var str1 = "test"; var str2 = "test"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(0), "Identical strings should have a Damerau-Levenshtein distance of 0."); } [Test] public void Calculate_CompletelyDifferentStrings_ReturnsLengthOfLongestString() { var str1 = "abc"; var str2 = "xyz"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(3), "Completely different strings should have a Damerau-Levenshtein distance equal to the length of the longest string."); } [Test] public void Calculate_OneEmptyString_ReturnsLengthOfOtherString() { var str1 = "test"; var str2 = ""; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(4), "One empty string should have a Damerau-Levenshtein distance equal to the length of the other string."); } [Test] public void Calculate_BothEmptyStrings_ReturnsZero() { var str1 = ""; var str2 = ""; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(0), "Both empty strings should have a Damerau-Levenshtein distance of 0."); } [Test] public void Calculate_DifferentLengths_ReturnsCorrectValue() { var str1 = "short"; var str2 = "longer"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(6), "Strings of different lengths should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_SpecialCharacters_ReturnsCorrectValue() { var str1 = "hello!"; var str2 = "hello?"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(1), "Strings with special characters should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_DifferentCases_ReturnsCorrectValue() { var str1 = "Hello"; var str2 = "hello"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(1), "Strings with different cases should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_CommonPrefixes_ReturnsCorrectValue() { var str1 = "prefix"; var str2 = "pre"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(3), "Strings with common prefixes should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_CommonSuffixes_ReturnsCorrectValue() { var str1 = "suffix"; var str2 = "fix"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(3), "Strings with common suffixes should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_Transpositions_ReturnsCorrectValue() { var str1 = "abcd"; var str2 = "acbd"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(1), "Strings with transpositions should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_RepeatedCharacters_ReturnsCorrectValue() { var str1 = "aaa"; var str2 = "aaaaa"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(2), "Strings with repeated characters should return the correct Damerau-Levenshtein distance."); } [Test] public void Calculate_UnicodeCharacters_ReturnsCorrectValue() { var str1 = "こんにちは"; var str2 = "こんばんは"; var result = DamerauLevenshteinDistance.Calculate(str1, str2); Assert.That(result, Is.EqualTo(2), "Strings with Unicode characters should return the correct Damerau-Levenshtein distance."); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/HammingDistanceTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings; public class HammingDistanceTests { [TestCase("equal", "equal", 0)] [TestCase("dog", "dig", 1)] [TestCase("12345", "abcde", 5)] public void Calculate_ReturnsCorrectHammingDistance(string s1, string s2, int expectedDistance) { var result = HammingDistance.Calculate(s1, s2); Assert.That(result, Is.EqualTo(expectedDistance)); } [Test] public void Calculate_ThrowsArgumentExceptionWhenStringLengthsDiffer() { Assert.Throws(() => HammingDistance.Calculate("123", "12345")); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/JaccardDistanceTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings.Similarity; public class JaccardDistanceTests { private readonly JaccardDistance jaccard = new JaccardDistance(); private readonly double precision = 0.0001; [TestCase("left", null)] [TestCase(null, "right")] [TestCase(null, null)] public void Calculate_WhenStringsAreNull_ThrowsArgumentNullException(string left, string right) { Action action = () => jaccard.Calculate(left, right); action.Should().Throw(); } [TestCase("", "", 0.0d)] [TestCase("left", "", 1.0d)] [TestCase("", "right", 1.0d)] [TestCase("frog", "fog", 0.25d)] [TestCase("fly", "ant", 1.0d)] [TestCase("elephant", "hippo", 0.777777d)] [TestCase("ABC Corporation", "ABC Corp", 0.36363d)] public void Calculate_WhenProvidedWithStrings_CalculatesCorrectDistance(string left, string right, double expected) { var distance = jaccard.Calculate(left, right); distance.Should().BeApproximately(expected, precision); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/JaccardSimilarityTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings.Similarity; public class JaccardSimilarityTests { private readonly JaccardSimilarity jaccard = new JaccardSimilarity(); private readonly double precision = 0.0001; [TestCase("left", null)] [TestCase(null, "right")] [TestCase(null, null)] public void Calculate_WhenStringsAreNull_ThrowsArgumentNullException(string left, string right) { Action action = () => jaccard.Calculate(left, right); action.Should().Throw(); } [TestCase("", "", 1.0d)] [TestCase("left", "", 0.0d)] [TestCase("", "right", 0.0d)] [TestCase("frog", "fog", 0.75d)] [TestCase("fly", "ant", 0.0d)] [TestCase("elephant", "hippo", 0.22222d)] [TestCase("ABC Corporation", "ABC Corp", 0.636363d)] public void Calculate_WhenProvidedWithStrings_CalculatesTheCorrectDistance(string left, string right, double expected) { var similarity = jaccard.Calculate(left, right); similarity.Should().BeApproximately(expected, precision); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/JaroSimilarityTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings; public class JaroSimilarityTests { [TestCase("equal", "equal", 1)] [TestCase("abc", "123", 0)] [TestCase("FAREMVIEL", "FARMVILLE", 0.88d)] [TestCase("CRATE", "TRACE", 0.73d)] [TestCase("CRATE11111", "CRTAE11111", 0.96d)] [TestCase("a", "a", 1)] [TestCase("", "", 1)] public void Calculate_ReturnsCorrectJaroSimilarity(string s1, string s2, double expected) { JaroSimilarity.Calculate(s1, s2).Should().BeApproximately(expected, 0.01); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/JaroWinklerDistanceTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings; public class JaroWinklerDistanceTests { [TestCase("equal", "equal", 0)] [TestCase("abc", "123", 1)] [TestCase("Winkler", "Welfare", 0.33)] [TestCase("faremviel", "farmville", 0.08)] [TestCase("", "", 0)] public void Calculate_ReturnsCorrectJaroWinklerDistance(string s1, string s2, double expected) { JaroWinklerDistance.Calculate(s1, s2).Should().BeApproximately(expected, 0.01); } } ================================================ FILE: Algorithms.Tests/Strings/Similarity/OptimalStringAlignmentTests.cs ================================================ using Algorithms.Strings.Similarity; namespace Algorithms.Tests.Strings.Similarity { [TestFixture] public class OptimalStringAlignmentTests { [Test] public void Calculate_IdenticalStrings_ReturnsZero() { var result = OptimalStringAlignment.Calculate("example", "example"); result.Should().Be(0.0); } [Test] public void Calculate_FirstStringEmpty_ReturnsLengthOfSecondString() { var result = OptimalStringAlignment.Calculate("", "example"); result.Should().Be("example".Length); } [Test] public void Calculate_SecondStringEmpty_ReturnsLengthOfFirstString() { var result = OptimalStringAlignment.Calculate("example", ""); result.Should().Be("example".Length); } [Test] public void Calculate_BothStringsEmpty_ReturnsZero() { var result = OptimalStringAlignment.Calculate("", ""); result.Should().Be(0.0); } [Test] public void Calculate_OneInsertion_ReturnsOne() { var result = OptimalStringAlignment.Calculate("example", "examples"); result.Should().Be(1.0); } [Test] public void Calculate_OneDeletion_ReturnsOne() { var result = OptimalStringAlignment.Calculate("examples", "example"); result.Should().Be(1.0); } [Test] public void Calculate_OneSubstitution_ReturnsOne() { var result = OptimalStringAlignment.Calculate("example", "exbmple"); result.Should().Be(1.0); } [Test] public void Calculate_OneTransposition_ReturnsOne() { var result = OptimalStringAlignment.Calculate("example", "exmaple"); result.Should().Be(1.0); } [Test] public void Calculate_MultipleOperations_ReturnsCorrectDistance() { var result = OptimalStringAlignment.Calculate("kitten", "sitting"); result.Should().Be(3.0); } } } ================================================ FILE: C-Sharp.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28803.352 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{DAB16DEC-AF31-4B59-8DD5-5C76C1A23052}" ProjectSection(SolutionItems) = preProject CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md CONTRIBUTING.md = CONTRIBUTING.md LICENSE = LICENSE README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configs", "Configs", "{F3AC2246-318B-4EE4-BD9E-D751D3044901}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore stylecop.json = stylecop.json stylecop.ruleset = stylecop.ruleset EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Algorithms", "Algorithms\Algorithms.csproj", "{EC967159-73D8-4E44-8455-E2D16DB4CBBB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Algorithms.Tests", "Algorithms.Tests\Algorithms.Tests.csproj", "{56817595-1552-409B-93B8-F8082F8490A5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataStructures", "DataStructures\DataStructures.csproj", "{E9C27C73-1F95-4C6E-9DB4-F8585426A850}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataStructures.Tests", "DataStructures.Tests\DataStructures.Tests.csproj", "{39174100-3A6E-45B2-9AA9-7C69764C0750}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utilities", "Utilities\Utilities.csproj", "{3A41157D-296D-4BFC-A34E-91B5ED7F0905}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utilities.Tests", "Utilities.Tests\Utilities.Tests.csproj", "{ED47E2E2-045C-41DD-B555-A64944D6C2F5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {EC967159-73D8-4E44-8455-E2D16DB4CBBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EC967159-73D8-4E44-8455-E2D16DB4CBBB}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC967159-73D8-4E44-8455-E2D16DB4CBBB}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC967159-73D8-4E44-8455-E2D16DB4CBBB}.Release|Any CPU.Build.0 = Release|Any CPU {56817595-1552-409B-93B8-F8082F8490A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56817595-1552-409B-93B8-F8082F8490A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {56817595-1552-409B-93B8-F8082F8490A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {56817595-1552-409B-93B8-F8082F8490A5}.Release|Any CPU.Build.0 = Release|Any CPU {E9C27C73-1F95-4C6E-9DB4-F8585426A850}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9C27C73-1F95-4C6E-9DB4-F8585426A850}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9C27C73-1F95-4C6E-9DB4-F8585426A850}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9C27C73-1F95-4C6E-9DB4-F8585426A850}.Release|Any CPU.Build.0 = Release|Any CPU {39174100-3A6E-45B2-9AA9-7C69764C0750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39174100-3A6E-45B2-9AA9-7C69764C0750}.Debug|Any CPU.Build.0 = Debug|Any CPU {39174100-3A6E-45B2-9AA9-7C69764C0750}.Release|Any CPU.ActiveCfg = Release|Any CPU {39174100-3A6E-45B2-9AA9-7C69764C0750}.Release|Any CPU.Build.0 = Release|Any CPU {3A41157D-296D-4BFC-A34E-91B5ED7F0905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3A41157D-296D-4BFC-A34E-91B5ED7F0905}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A41157D-296D-4BFC-A34E-91B5ED7F0905}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A41157D-296D-4BFC-A34E-91B5ED7F0905}.Release|Any CPU.Build.0 = Release|Any CPU {ED47E2E2-045C-41DD-B555-A64944D6C2F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED47E2E2-045C-41DD-B555-A64944D6C2F5}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED47E2E2-045C-41DD-B555-A64944D6C2F5}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED47E2E2-045C-41DD-B555-A64944D6C2F5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9D733AD8-7C69-4F40-ADBB-2DD8EA9F18FB} EndGlobalSection EndGlobal ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at siryaka@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing ## General notes When contributing to this repository, if your changes are subjective, controversial or people are likely to have polarized opinions on this matter, please first discuss the change you wish to make via issue with the owners of this repository. We welcome adding new algorithms and data structures that were mentioned in books or other reputable sources. We also welcome fixing bugs in code, clarifying documentation and adding new test cases to check existing code. The framework targeted by our code is **dotnet 8**. The corresponding SDK can be found [here](https://dotnet.microsoft.com/download/dotnet/8.0). Please note that we have a code of conduct, please follow it in all your interactions with the project. ## Files For adding new algorithms, please ensure to name the cs-files corresponding to the classname, e.g. `Factorial.cs` for the class `Factorial` and add them to the most relevant pre-existing folder. Make sure to implement tests for all public methods. These tests should be added in a separate cs-file to the corresponding folder in `Algorithms.Tests` and have their classname ending in "Test", e.g. `FactorialTest`. ## Tests We use the [NUnit-library](https://nunit.org/) for testing. Instructions for the installation for local testing can be found [here](https://docs.nunit.org/articles/nunit/getting-started/installation.html). A basic test can be implemented by adding the attribute `[Test]` in front of the method performing the test and including an Assert-statement within the method, e.g. `Assert.AreEqual(result, 42)`. If possible, please use [FluentAssertions](https://fluentassertions.com/) since they are more eloquent and readable. Some of the basic assertions can be found [here](https://fluentassertions.com/basicassertions/). For getting familiar with the Nunit-tests, it might be helpful to have a look at some existing test-files. A tutorial explaining how to implement and run NUnit-tests can be found [here](https://www.c-sharpcorner.com/article/introduction-to-nunit-testing-framework/). ## Automatic checks One of the automatic checks we use is [codecov](https://about.codecov.io/). It checks whether each conditional branch is covered by a test. So if a method contains a conditional statement or operator (if-else, switch, ?: ) then there should be at least a test per branch (unless all the branches can be covered in one run). The coding style follows the default code formatter of Visual Studio. ## Comments Please use the [XML documentation features](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/how-to-use-the-xml-documentation-features) for comments. The comments should include a summary of the class/method and an explanation of the different parameters and of the return value. Including a link to Wikipedia or to another source of information on the algorithm is encouraged. ================================================ FILE: DataStructures/AATree/AATree.cs ================================================ namespace DataStructures.AATree; /// /// A simple self-balancing binary search tree. /// /// /// AA Trees are a form of self-balancing binary search tree named after their inventor /// Arne Anderson. AA Trees are designed to be simple to understand and implement. /// This is accomplished by limiting how nodes can be added to the tree. /// This simplifies rebalancing operations. /// More information: https://en.wikipedia.org/wiki/AA_tree . /// /// The type of key for the AA tree. /// /// Initializes a new instance of the class with a custom comparer. /// /// The custom comparer to use to compare keys. public class AaTree(Comparer customComparer) { /// /// The comparer function to use to compare the keys. /// private readonly Comparer comparer = customComparer; /// /// Initializes a new instance of the class. /// public AaTree() : this(Comparer.Default) { } /// /// Gets the root of the tree. /// public AaTreeNode? Root { get; private set; } /// /// Gets the number of elements in the tree. /// public int Count { get; private set; } /// /// Add a single element to the tree. /// /// The element to add to the tree. public void Add(TKey key) { Root = Add(key, Root); Count++; } /// /// Add multiple elements to the tree. /// /// The elements to add to the tree. public void AddRange(IEnumerable keys) { foreach (var key in keys) { Root = Add(key, Root); Count++; } } /// /// Remove a single element from the tree. /// /// Element to remove. public void Remove(TKey key) { if (!Contains(key, Root)) { throw new InvalidOperationException($"{nameof(key)} is not in the tree"); } Root = Remove(key, Root); Count--; } /// /// Checks if the specified element is in the tree. /// /// The element to look for. /// true if the element is in the tree, false otherwise. public bool Contains(TKey key) => Contains(key, Root); /// /// Gets the largest element in the tree. (ie. the element in the right most node). /// /// The largest element in the tree according to the stored comparer. /// Thrown if the tree is empty. public TKey GetMax() { if (Root is null) { throw new InvalidOperationException("Tree is empty!"); } return GetMax(Root).Key; } /// /// Gets the smallest element in the tree. (ie. the element in the left most node). /// /// The smallest element in the tree according to the stored comparer. /// InvalidOperationException if the tree is empty. public TKey GetMin() { if (Root is null) { throw new InvalidOperationException("Tree is empty!"); } return GetMin(Root).Key; } /// /// Gets all the elements in the tree in in-order order. /// /// Sequence of elements in in-order order. public IEnumerable GetKeysInOrder() { var result = new List(); InOrderWalk(Root); return result; void InOrderWalk(AaTreeNode? node) { if (node is null) { return; } InOrderWalk(node.Left); result.Add(node.Key); InOrderWalk(node.Right); } } /// /// Gets all the elements in the tree in pre-order order. /// /// Sequence of elements in pre-order order. public IEnumerable GetKeysPreOrder() { var result = new List(); PreOrderWalk(Root); return result; void PreOrderWalk(AaTreeNode? node) { if (node is null) { return; } result.Add(node.Key); PreOrderWalk(node.Left); PreOrderWalk(node.Right); } } /// /// Gets all the elements in the tree in post-order order. /// /// Sequence of elements in post-order order. public IEnumerable GetKeysPostOrder() { var result = new List(); PostOrderWalk(Root); return result; void PostOrderWalk(AaTreeNode? node) { if (node is null) { return; } PostOrderWalk(node.Left); PostOrderWalk(node.Right); result.Add(node.Key); } } /// /// Recursive function to add an element to the tree. /// /// The element to add. /// The node to search for a empty spot. /// The node with the added element. /// Thrown if key is already in the tree. private AaTreeNode Add(TKey key, AaTreeNode? node) { if (node is null) { return new AaTreeNode(key, 1); } if (comparer.Compare(key, node.Key) < 0) { node.Left = Add(key, node.Left); } else if (comparer.Compare(key, node.Key) > 0) { node.Right = Add(key, node.Right); } else { throw new ArgumentException($"""Key "{key}" already in tree!""", nameof(key)); } return Split(Skew(node))!; } /// /// Recursive function to remove an element from the tree. /// /// The element to remove. /// The node to search from. /// The node with the specified element removed. private AaTreeNode? Remove(TKey key, AaTreeNode? node) { if (node is null) { return null; } if (comparer.Compare(key, node.Key) < 0) { node.Left = Remove(key, node.Left); } else if (comparer.Compare(key, node.Key) > 0) { node.Right = Remove(key, node.Right); } else { if (node.Left is null && node.Right is null) { return null; } if (node.Left is null) { var successor = GetMin(node.Right!); node.Right = Remove(successor.Key, node.Right); node.Key = successor.Key; } else { var predecessor = GetMax(node.Left); node.Left = Remove(predecessor.Key, node.Left); node.Key = predecessor.Key; } } node = DecreaseLevel(node); node = Skew(node); node!.Right = Skew(node.Right); if (node.Right is not null) { node.Right.Right = Skew(node.Right.Right); } node = Split(node); node!.Right = Split(node.Right); return node; } /// /// Recursive function to check if the element exists in the tree. /// /// The element to check for. /// The node to search from. /// true if the element exists in the tree, false otherwise. private bool Contains(TKey key, AaTreeNode? node) => node is { } && comparer.Compare(key, node.Key) is { } v && v switch { { } when v > 0 => Contains(key, node.Right), { } when v < 0 => Contains(key, node.Left), _ => true, }; /// /// Recursive to find the maximum/right-most element. /// /// The node to traverse from. /// The node with the maximum/right-most element. private AaTreeNode GetMax(AaTreeNode node) { while (true) { if (node.Right is null) { return node; } node = node.Right; } } /// /// Recursive to find the minimum/left-most element. /// /// The node to traverse from. /// The node with the minimum/left-most element. private AaTreeNode GetMin(AaTreeNode node) { while (true) { if (node.Left is null) { return node; } node = node.Left; } } /// /// Remove right-horizontal links and replaces them with left-horizontal links. /// Accomplishes this by performing a right rotation. /// /// The node to rebalance from. /// The rebalanced node. private AaTreeNode? Skew(AaTreeNode? node) { if (node?.Left is null || node.Left.Level != node.Level) { return node; } var left = node.Left; node.Left = left.Right; left.Right = node; return left; } /// /// Reduces the number of right-horizontal links. /// Accomplishes this by performing a left rotation, and incrementing level. /// /// The node to rebalance from. /// The rebalanced node. private AaTreeNode? Split(AaTreeNode? node) { if (node?.Right?.Right is null || node.Level != node.Right.Right.Level) { return node; } var right = node.Right; node.Right = right.Left; right.Left = node; right.Level++; return right; } /// /// Decreases the level of node if necessary. /// /// The node to decrease level from. /// The node with modified level. private AaTreeNode DecreaseLevel(AaTreeNode node) { var newLevel = Math.Min(GetLevel(node.Left), GetLevel(node.Right)) + 1; if (newLevel >= node.Level) { return node; } node.Level = newLevel; if (node.Right is { } && newLevel < node.Right.Level) { node.Right.Level = newLevel; } return node; static int GetLevel(AaTreeNode? x) => x?.Level ?? 0; } } ================================================ FILE: DataStructures/AATree/AATreeNode.cs ================================================ namespace DataStructures.AATree; /// /// Generic node class for AATree. /// /// Type of key for node. /// /// Initializes a new instance of the class. /// /// The initial key of this node. /// The level of this node. See for more details. public class AaTreeNode(TKey key, int level) { /// /// Gets or Sets key for this node. /// public TKey Key { get; set; } = key; /// /// Gets or Sets level for this node. /// public int Level { get; set; } = level; /// /// Gets or sets the left subtree of this node. /// public AaTreeNode? Left { get; set; } /// /// Gets or sets the right subtree of this node. /// public AaTreeNode? Right { get; set; } } ================================================ FILE: DataStructures/AVLTree/AVLTree.cs ================================================ namespace DataStructures.AVLTree; /// /// A simple self-balancing binary tree. /// /// /// An AVL tree is a self-balancing binary search tree (BST) named after /// its inventors: Adelson, Velsky, and Landis. It is the first self- /// balancing BST invented. The primary property of an AVL tree is that /// the height of both child subtrees for any node only differ by one. /// Due to the balanced nature of the tree, its time complexities for /// insertion, deletion, and search all have a worst-case time /// complexity of O(log n). Which is an improvement over the worst-case /// O(n) for a regular BST. /// See https://en.wikipedia.org/wiki/AVL_tree for more information. /// Visualizer: https://visualgo.net/en/bst. /// /// Type of key for the tree. public class AvlTree { /// /// Gets the number of nodes in the tree. /// public int Count { get; private set; } /// /// Comparer to use when comparing key values. /// private readonly Comparer comparer; /// /// Reference to the root node. /// private AvlTreeNode? root; /// /// Initializes a new instance of the /// class. /// public AvlTree() { comparer = Comparer.Default; } /// /// Initializes a new instance of the /// class using the specified comparer. /// /// /// Comparer to use when comparing keys. /// public AvlTree(Comparer customComparer) { comparer = customComparer; } /// /// Add a single node to the tree. /// /// Key value to add. public void Add(TKey key) { if (root is null) { root = new AvlTreeNode(key); } else { root = Add(root, key); } Count++; } /// /// Add multiple nodes to the tree. /// /// Key values to add. public void AddRange(IEnumerable keys) { foreach (var key in keys) { Add(key); } } /// /// Remove a node from the tree. /// /// Key value to remove. public void Remove(TKey key) { root = Remove(root, key); Count--; } /// /// Check if given node is in the tree. /// /// Key value to search for. /// Whether or not the node is in the tree. public bool Contains(TKey key) { var node = root; while (node is not null) { var compareResult = comparer.Compare(key, node.Key); if (compareResult < 0) { node = node.Left; } else if (compareResult > 0) { node = node.Right; } else { return true; } } return false; } /// /// Get the minimum value in the tree. /// /// Minimum value in tree. public TKey GetMin() { if (root is null) { throw new InvalidOperationException("AVL tree is empty."); } return GetMin(root).Key; } /// /// Get the maximum value in the tree. /// /// Maximum value in tree. public TKey GetMax() { if (root is null) { throw new InvalidOperationException("AVL tree is empty."); } return GetMax(root).Key; } /// /// Get keys in order from smallest to largest as defined by the /// comparer. /// /// Keys in tree in order from smallest to largest. public IEnumerable GetKeysInOrder() { List result = []; InOrderWalk(root); return result; void InOrderWalk(AvlTreeNode? node) { if (node is null) { return; } InOrderWalk(node.Left); result.Add(node.Key); InOrderWalk(node.Right); } } /// /// Get keys in the pre-order order. /// /// Keys in pre-order order. public IEnumerable GetKeysPreOrder() { var result = new List(); PreOrderWalk(root); return result; void PreOrderWalk(AvlTreeNode? node) { if (node is null) { return; } result.Add(node.Key); PreOrderWalk(node.Left); PreOrderWalk(node.Right); } } /// /// Get keys in the post-order order. /// /// Keys in the post-order order. public IEnumerable GetKeysPostOrder() { var result = new List(); PostOrderWalk(root); return result; void PostOrderWalk(AvlTreeNode? node) { if (node is null) { return; } PostOrderWalk(node.Left); PostOrderWalk(node.Right); result.Add(node.Key); } } /// /// Helper function to rebalance the tree so that all nodes have a /// balance factor in the range [-1, 1]. /// /// Node to rebalance. /// New node that has been rebalanced. private static AvlTreeNode Rebalance(AvlTreeNode node) { if (node.BalanceFactor > 1) { if (node.Right!.BalanceFactor == -1) { node.Right = RotateRight(node.Right); } return RotateLeft(node); } if (node.BalanceFactor < -1) { if (node.Left!.BalanceFactor == 1) { node.Left = RotateLeft(node.Left); } return RotateRight(node); } return node; } /// /// Perform a left (counter-clockwise) rotation. /// /// Node to rotate about. /// New node with rotation applied. private static AvlTreeNode RotateLeft(AvlTreeNode node) { var temp1 = node; var temp2 = node.Right!.Left; node = node.Right; node.Left = temp1; node.Left.Right = temp2; node.Left.UpdateBalanceFactor(); node.UpdateBalanceFactor(); return node; } /// /// Perform a right (clockwise) rotation. /// /// Node to rotate about. /// New node with rotation applied. private static AvlTreeNode RotateRight(AvlTreeNode node) { var temp1 = node; var temp2 = node.Left!.Right; node = node.Left; node.Right = temp1; node.Right.Left = temp2; node.Right.UpdateBalanceFactor(); node.UpdateBalanceFactor(); return node; } /// /// Helper function to get node instance with minimum key value /// in the specified subtree. /// /// Node specifying root of subtree. /// Minimum value in node's subtree. private static AvlTreeNode GetMin(AvlTreeNode node) { while (node.Left is not null) { node = node.Left; } return node; } /// /// Helper function to get node instance with maximum key value /// in the specified subtree. /// /// Node specifying root of subtree. /// Maximum value in node's subtree. private static AvlTreeNode GetMax(AvlTreeNode node) { while (node.Right is not null) { node = node.Right; } return node; } /// /// Recursively function to add a node to the tree. /// /// Node to check for null leaf. /// Key value to add. /// New node with key inserted. private AvlTreeNode Add(AvlTreeNode node, TKey key) { // Regular binary search tree insertion var compareResult = comparer.Compare(key, node.Key); if (compareResult < 0) { if (node.Left is null) { var newNode = new AvlTreeNode(key); node.Left = newNode; } else { node.Left = Add(node.Left, key); } } else if (compareResult > 0) { if (node.Right is null) { var newNode = new AvlTreeNode(key); node.Right = newNode; } else { node.Right = Add(node.Right, key); } } else { throw new ArgumentException($"""Key "{key}" already exists in AVL tree."""); } // Check all of the new node's ancestors for imbalance and perform // necessary rotations node.UpdateBalanceFactor(); return Rebalance(node); } /// /// Recursive function to remove node from tree. /// /// Node to check for key. /// Key value to remove. /// New node with key removed. private AvlTreeNode? Remove(AvlTreeNode? node, TKey key) { if (node == null) { throw new KeyNotFoundException( $"""Key "{key}" is not in the AVL tree."""); } // Normal binary search tree removal var compareResult = comparer.Compare(key, node.Key); if (compareResult < 0) { node.Left = Remove(node.Left, key); } else if (compareResult > 0) { node.Right = Remove(node.Right, key); } else { if (node.Left is null && node.Right is null) { return null; } if (node.Left is null) { var successor = GetMin(node.Right!); node.Right = Remove(node.Right!, successor.Key); node.Key = successor.Key; } else { var predecessor = GetMax(node.Left!); node.Left = Remove(node.Left!, predecessor.Key); node.Key = predecessor.Key; } } // Check all of the removed node's ancestors for rebalance and // perform necessary rotations. node.UpdateBalanceFactor(); return Rebalance(node); } } ================================================ FILE: DataStructures/AVLTree/AVLTreeNode.cs ================================================ namespace DataStructures.AVLTree; /// /// Generic class to represent nodes in an /// instance. /// /// The type of key for the node. /// /// Initializes a new instance of the /// class. /// /// Key value for node. internal class AvlTreeNode(TKey key) { /// /// Gets or sets key value of node. /// public TKey Key { get; set; } = key; /// /// Gets the balance factor of the node. /// public int BalanceFactor { get; private set; } /// /// Gets or sets the left child of the node. /// public AvlTreeNode? Left { get; set; } /// /// Gets or sets the right child of the node. /// public AvlTreeNode? Right { get; set; } /// /// Gets or sets the height of the node. /// private int Height { get; set; } /// /// Update the node's height and balance factor. /// public void UpdateBalanceFactor() { if (Left is null && Right is null) { Height = 0; BalanceFactor = 0; } else if (Left is null) { Height = Right!.Height + 1; BalanceFactor = Height; } else if (Right is null) { Height = Left!.Height + 1; BalanceFactor = -Height; } else { Height = Math.Max(Left.Height, Right.Height) + 1; BalanceFactor = Right.Height - Left.Height; } } } ================================================ FILE: DataStructures/BTree/BTree.cs ================================================ namespace DataStructures.BTree; /// /// A self-balancing search tree data structure that maintains sorted data /// and allows searches, sequential access, insertions, and deletions in /// logarithmic time. /// /// /// A B-Tree is a generalization of a binary search tree in which a node /// can have more than two children. Unlike self-balancing binary search /// trees, the B-Tree is optimized for systems that read and write large /// blocks of data. It is commonly used in databases and file systems. /// /// Properties of a B-Tree of minimum degree t: /// 1. Every node has at most 2t-1 keys. /// 2. Every node (except root) has at least t-1 keys. /// 3. The root has at least 1 key (if tree is not empty). /// 4. All leaves are at the same level. /// 5. A non-leaf node with k keys has k+1 children. /// /// Time Complexity: /// - Search: O(log n) /// - Insert: O(log n) /// - Delete: O(log n) /// /// Space Complexity: O(n) /// /// See https://en.wikipedia.org/wiki/B-tree for more information. /// Visualizer: https://www.cs.usfca.edu/~galles/visualization/BTree.html. /// /// Type of key for the tree. public class BTree { /// /// Gets the number of keys in the tree. /// public int Count { get; private set; } /// /// Gets the minimum degree (t) of the B-Tree. /// Each node can contain at most 2t-1 keys and at least t-1 keys (except root). /// public int MinimumDegree { get; } /// /// Comparer to use when comparing key values. /// private readonly Comparer comparer; /// /// Reference to the root node. /// private BTreeNode? root; /// /// Initializes a new instance of the /// class with the specified minimum degree. /// /// /// Minimum degree (t) of the B-Tree. Must be at least 2. /// Each node can contain at most 2t-1 keys. /// /// /// Thrown when minimumDegree is less than 2. /// public BTree(int minimumDegree = 2) { if (minimumDegree < 2) { throw new ArgumentException("Minimum degree must be at least 2.", nameof(minimumDegree)); } MinimumDegree = minimumDegree; comparer = Comparer.Default; } /// /// Initializes a new instance of the /// class with the specified minimum degree and custom comparer. /// /// /// Minimum degree (t) of the B-Tree. Must be at least 2. /// /// /// Comparer to use when comparing keys. /// /// /// Thrown when minimumDegree is less than 2. /// public BTree(int minimumDegree, Comparer customComparer) { if (minimumDegree < 2) { throw new ArgumentException("Minimum degree must be at least 2.", nameof(minimumDegree)); } MinimumDegree = minimumDegree; comparer = customComparer; } /// /// Add a single key to the tree. /// /// Key value to add. public void Add(TKey key) { if (root is null) { root = new BTreeNode(MinimumDegree, true); root.InsertKey(0, key); Count++; return; } if (root.IsFull()) { var newRoot = new BTreeNode(MinimumDegree, false); newRoot.InsertChild(0, root); SplitChild(newRoot, 0); root = newRoot; } InsertNonFull(root, key); Count++; } /// /// Add multiple keys to the tree. /// /// Key values to add. public void AddRange(IEnumerable keys) { foreach (var key in keys) { Add(key); } } /// /// Remove a key from the tree. /// /// Key value to remove. /// /// Thrown when the key is not found in the tree. /// public void Remove(TKey key) { if (root is null) { throw new KeyNotFoundException($"""Key "{key}" is not in the B-Tree."""); } Remove(root, key); if (root.KeyCount == 0) { root = root.IsLeaf ? null : root.Children[0]; } Count--; } /// /// Check if given key is in the tree. /// /// Key value to search for. /// Whether or not the key is in the tree. public bool Contains(TKey key) { return Search(root, key) is not null; } /// /// Get the minimum key in the tree. /// /// Minimum key in tree. /// /// Thrown when the tree is empty. /// public TKey GetMin() { if (root is null) { throw new InvalidOperationException("B-Tree is empty."); } return GetMin(root); } /// /// Get the maximum key in the tree. /// /// Maximum key in tree. /// /// Thrown when the tree is empty. /// public TKey GetMax() { if (root is null) { throw new InvalidOperationException("B-Tree is empty."); } return GetMax(root); } /// /// Get keys in order from smallest to largest as defined by the /// comparer. /// /// Keys in tree in order from smallest to largest. public IEnumerable GetKeysInOrder() { List result = []; InOrderTraversal(root); return result; void InOrderTraversal(BTreeNode? node) { if (node is null) { return; } for (var i = 0; i < node.KeyCount; i++) { if (!node.IsLeaf) { InOrderTraversal(node.Children[i]); } result.Add(node.Keys[i]); } if (!node.IsLeaf) { InOrderTraversal(node.Children[node.KeyCount]); } } } /// /// Get keys in the pre-order order. /// /// Keys in pre-order order. public IEnumerable GetKeysPreOrder() { var result = new List(); PreOrderTraversal(root); return result; void PreOrderTraversal(BTreeNode? node) { if (node is null) { return; } for (var i = 0; i < node.KeyCount; i++) { result.Add(node.Keys[i]); } if (!node.IsLeaf) { for (var i = 0; i <= node.KeyCount; i++) { PreOrderTraversal(node.Children[i]); } } } } /// /// Get keys in the post-order order. /// /// Keys in the post-order order. public IEnumerable GetKeysPostOrder() { var result = new List(); PostOrderTraversal(root); return result; void PostOrderTraversal(BTreeNode? node) { if (node is null) { return; } if (!node.IsLeaf) { for (var i = 0; i <= node.KeyCount; i++) { PostOrderTraversal(node.Children[i]); } } for (var i = 0; i < node.KeyCount; i++) { result.Add(node.Keys[i]); } } } /// /// Search for a key in the subtree rooted at the given node. /// /// Root of the subtree to search. /// Key to search for. /// Node containing the key, or null if not found. private BTreeNode? Search(BTreeNode? node, TKey key) { if (node is null) { return null; } var i = 0; while (i < node.KeyCount && comparer.Compare(key, node.Keys[i]) > 0) { i++; } if (i < node.KeyCount && comparer.Compare(key, node.Keys[i]) == 0) { return node; } if (node.IsLeaf) { return null; } return Search(node.Children[i], key); } /// /// Insert a key into a non-full node. /// /// Node to insert the key into. /// Key to insert. /// /// Thrown when the key already exists in the tree. /// private void InsertNonFull(BTreeNode node, TKey key) { if (node.IsLeaf) { InsertIntoLeaf(node, key); } else { InsertIntoNonLeaf(node, key); } } /// /// Insert a key into a leaf node. /// /// Leaf node to insert into. /// Key to insert. private void InsertIntoLeaf(BTreeNode node, TKey key) { var i = node.KeyCount - 1; while (i >= 0 && comparer.Compare(key, node.Keys[i]) < 0) { node.Keys[i + 1] = node.Keys[i]; i--; } if (i >= 0 && comparer.Compare(key, node.Keys[i]) == 0) { throw new ArgumentException($"""Key "{key}" already exists in B-Tree."""); } node.Keys[i + 1] = key; node.KeyCount++; } /// /// Insert a key into a non-leaf node. /// /// Non-leaf node to insert into. /// Key to insert. private void InsertIntoNonLeaf(BTreeNode node, TKey key) { var i = FindInsertionIndex(node, key); if (node.Children[i]!.IsFull()) { SplitChild(node, i); if (comparer.Compare(key, node.Keys[i]) > 0) { i++; } } InsertNonFull(node.Children[i]!, key); } /// /// Find the index where a key should be inserted in a non-leaf node. /// /// Node to search in. /// Key to insert. /// Index where the key should be inserted. private int FindInsertionIndex(BTreeNode node, TKey key) { var i = node.KeyCount - 1; while (i >= 0 && comparer.Compare(key, node.Keys[i]) < 0) { i--; } if (i >= 0 && comparer.Compare(key, node.Keys[i]) == 0) { throw new ArgumentException($"""Key "{key}" already exists in B-Tree."""); } return i + 1; } /// /// Split a full child of a node. /// /// Parent node. /// Index of the child to split. private void SplitChild(BTreeNode parent, int childIndex) { var fullChild = parent.Children[childIndex]!; var newChild = new BTreeNode(MinimumDegree, fullChild.IsLeaf); var midIndex = MinimumDegree - 1; for (var j = 0; j < MinimumDegree - 1; j++) { newChild.Keys[j] = fullChild.Keys[j + MinimumDegree]; newChild.KeyCount++; } if (!fullChild.IsLeaf) { for (var j = 0; j < MinimumDegree; j++) { newChild.Children[j] = fullChild.Children[j + MinimumDegree]; } } fullChild.KeyCount = MinimumDegree - 1; for (var j = parent.KeyCount; j > childIndex; j--) { parent.Children[j + 1] = parent.Children[j]; } parent.Children[childIndex + 1] = newChild; for (var j = parent.KeyCount - 1; j >= childIndex; j--) { parent.Keys[j + 1] = parent.Keys[j]; } parent.Keys[childIndex] = fullChild.Keys[midIndex]; parent.KeyCount++; } /// /// Remove a key from the subtree rooted at the given node. /// /// Root of the subtree. /// Key to remove. /// /// Thrown when the key is not found in the subtree. /// private void Remove(BTreeNode node, TKey key) { var idx = FindKey(node, key); if (idx < node.KeyCount && comparer.Compare(node.Keys[idx], key) == 0) { if (node.IsLeaf) { RemoveFromLeaf(node, idx); } else { RemoveFromNonLeaf(node, idx); } } else { if (node.IsLeaf) { throw new KeyNotFoundException($"""Key "{key}" is not in the B-Tree."""); } var isInSubtree = idx == node.KeyCount; if (node.Children[idx]!.KeyCount < MinimumDegree) { Fill(node, idx); } if (isInSubtree && idx > node.KeyCount) { Remove(node.Children[idx - 1]!, key); } else { Remove(node.Children[idx]!, key); } } } /// /// Find the index of the first key greater than or equal to the given key. /// /// Node to search in. /// Key to find. /// Index of the first key >= key. private int FindKey(BTreeNode node, TKey key) { var idx = 0; while (idx < node.KeyCount && comparer.Compare(node.Keys[idx], key) < 0) { idx++; } return idx; } /// /// Remove a key from a leaf node. /// /// Leaf node to remove from. /// Index of the key to remove. private void RemoveFromLeaf(BTreeNode node, int idx) { node.RemoveKey(idx); } /// /// Remove a key from a non-leaf node. /// /// Non-leaf node to remove from. /// Index of the key to remove. private void RemoveFromNonLeaf(BTreeNode node, int idx) { var key = node.Keys[idx]; if (node.Children[idx]!.KeyCount >= MinimumDegree) { var predecessor = GetPredecessor(node, idx); node.Keys[idx] = predecessor; Remove(node.Children[idx]!, predecessor); } else if (node.Children[idx + 1]!.KeyCount >= MinimumDegree) { var successor = GetSuccessor(node, idx); node.Keys[idx] = successor; Remove(node.Children[idx + 1]!, successor); } else { Merge(node, idx); Remove(node.Children[idx]!, key); } } /// /// Get the predecessor key of the key at the given index. /// /// Node containing the key. /// Index of the key. /// Predecessor key. private TKey GetPredecessor(BTreeNode node, int idx) { var current = node.Children[idx]!; while (!current.IsLeaf) { current = current.Children[current.KeyCount]!; } return current.Keys[current.KeyCount - 1]; } /// /// Get the successor key of the key at the given index. /// /// Node containing the key. /// Index of the key. /// Successor key. private TKey GetSuccessor(BTreeNode node, int idx) { var current = node.Children[idx + 1]!; while (!current.IsLeaf) { current = current.Children[0]!; } return current.Keys[0]; } /// /// Fill a child node that has fewer than minimum degree - 1 keys. /// /// Parent node. /// Index of the child to fill. private void Fill(BTreeNode node, int idx) { if (idx != 0 && node.Children[idx - 1]!.KeyCount >= MinimumDegree) { BorrowFromPrevious(node, idx); } else if (idx != node.KeyCount && node.Children[idx + 1]!.KeyCount >= MinimumDegree) { BorrowFromNext(node, idx); } else { Merge(node, idx != node.KeyCount ? idx : idx - 1); } } /// /// Borrow a key from the previous sibling. /// /// Parent node. /// Index of the child that needs a key. private void BorrowFromPrevious(BTreeNode node, int childIdx) { var child = node.Children[childIdx]!; var sibling = node.Children[childIdx - 1]!; for (var i = child.KeyCount - 1; i >= 0; i--) { child.Keys[i + 1] = child.Keys[i]; } if (!child.IsLeaf) { for (var i = child.KeyCount; i >= 0; i--) { child.Children[i + 1] = child.Children[i]; } } child.Keys[0] = node.Keys[childIdx - 1]; if (!child.IsLeaf) { child.Children[0] = sibling.Children[sibling.KeyCount]; } node.Keys[childIdx - 1] = sibling.Keys[sibling.KeyCount - 1]; child.KeyCount++; sibling.KeyCount--; } /// /// Borrow a key from the next sibling. /// /// Parent node. /// Index of the child that needs a key. private void BorrowFromNext(BTreeNode node, int childIdx) { var child = node.Children[childIdx]!; var sibling = node.Children[childIdx + 1]!; child.Keys[child.KeyCount] = node.Keys[childIdx]; if (!child.IsLeaf) { child.Children[child.KeyCount + 1] = sibling.Children[0]; } node.Keys[childIdx] = sibling.Keys[0]; for (var i = 1; i < sibling.KeyCount; i++) { sibling.Keys[i - 1] = sibling.Keys[i]; } if (!sibling.IsLeaf) { for (var i = 1; i <= sibling.KeyCount; i++) { sibling.Children[i - 1] = sibling.Children[i]; } } child.KeyCount++; sibling.KeyCount--; } /// /// Merge a child with its sibling. /// /// Parent node. /// Index of the child to merge. private void Merge(BTreeNode node, int idx) { var child = node.Children[idx]!; var sibling = node.Children[idx + 1]!; child.Keys[MinimumDegree - 1] = node.Keys[idx]; for (var i = 0; i < sibling.KeyCount; i++) { child.Keys[i + MinimumDegree] = sibling.Keys[i]; } if (!child.IsLeaf) { for (var i = 0; i <= sibling.KeyCount; i++) { child.Children[i + MinimumDegree] = sibling.Children[i]; } } for (var i = idx + 1; i < node.KeyCount; i++) { node.Keys[i - 1] = node.Keys[i]; } for (var i = idx + 2; i <= node.KeyCount; i++) { node.Children[i - 1] = node.Children[i]; } child.KeyCount += sibling.KeyCount + 1; node.KeyCount--; } /// /// Get the minimum key in the subtree rooted at the given node. /// /// Root of the subtree. /// Minimum key in the subtree. private TKey GetMin(BTreeNode node) { while (!node.IsLeaf) { node = node.Children[0]!; } return node.Keys[0]; } /// /// Get the maximum key in the subtree rooted at the given node. /// /// Root of the subtree. /// Maximum key in the subtree. private TKey GetMax(BTreeNode node) { while (!node.IsLeaf) { node = node.Children[node.KeyCount]!; } return node.Keys[node.KeyCount - 1]; } } ================================================ FILE: DataStructures/BTree/BTreeNode.cs ================================================ namespace DataStructures.BTree; /// /// Generic class to represent nodes in a /// instance. /// /// The type of key for the node. /// /// Initializes a new instance of the /// class. /// /// Minimum degree of the B-Tree. /// Whether this node is a leaf node. internal class BTreeNode(int minDegree, bool isLeaf) { /// /// Gets the minimum degree (t) of the B-Tree. /// A node can contain at most 2t-1 keys and at least t-1 keys (except root). /// public int MinDegree { get; } = minDegree; /// /// Gets or sets a value indicating whether this node is a leaf node. /// public bool IsLeaf { get; set; } = isLeaf; /// /// Gets or sets the current number of keys stored in this node. /// public int KeyCount { get; internal set; } /// /// Gets the array of keys stored in this node. /// Maximum size is 2*MinDegree - 1. /// public TKey[] Keys { get; } = new TKey[2 * minDegree - 1]; /// /// Gets the array of child pointers. /// Maximum size is 2*MinDegree. /// public BTreeNode?[] Children { get; } = new BTreeNode[2 * minDegree]; /// /// Inserts a key at the specified position in the keys array. /// /// Position to insert the key. /// Key to insert. public void InsertKey(int index, TKey key) { Keys[index] = key; KeyCount++; } /// /// Removes the key at the specified position in the keys array. /// /// Position of the key to remove. public void RemoveKey(int index) { for (var i = index; i < KeyCount - 1; i++) { Keys[i] = Keys[i + 1]; } Keys[KeyCount - 1] = default!; KeyCount--; } /// /// Inserts a child pointer at the specified position. /// /// Position to insert the child. /// Child node to insert. public void InsertChild(int index, BTreeNode child) { Children[index] = child; } /// /// Checks if the node is full (contains maximum number of keys). /// /// True if the node is full, false otherwise. public bool IsFull() => KeyCount == 2 * MinDegree - 1; } ================================================ FILE: DataStructures/Bag/Bag.cs ================================================ using System.Collections; using System.Collections.Generic; namespace DataStructures.Bag; /// /// Implementation of a Bag (or multiset) data structure using a basic linked list. /// /// /// A bag (or multiset, or mset) is a modification of the concept of a set that, unlike a set, allows for multiple instances for each of its elements. /// The number of instances given for each element is called the multiplicity of that element in the multiset. /// As a consequence, an infinite number of multisets exist that contain only elements a and b, but vary in the multiplicities of their elements. /// See https://en.wikipedia.org/wiki/Multiset for more information. /// /// Generic Type. public class Bag : IEnumerable where T : notnull { private BagNode? head; private int totalCount; /// /// Initializes a new instance of the class. /// public Bag() { head = null; totalCount = 0; } /// /// Adds an item to the bag. If the item already exists, increases its multiplicity. /// public void Add(T item) { // If the bag is empty, create the first node if (head == null) { head = new BagNode(item); totalCount = 1; return; } // Check if item already exists var current = head; BagNode? previous = null; while (current != null) { if (EqualityComparer.Default.Equals(current.Item, item)) { current.Multiplicity++; totalCount++; return; } previous = current; current = current.Next; } previous!.Next = new BagNode(item); totalCount++; } /// /// Clears the bag. /// public void Clear() { head = null; totalCount = 0; } /// /// Gets the number of items in the bag. /// public int Count => totalCount; /// /// Returns a boolean indicating whether the bag is empty. /// public bool IsEmpty() => head == null; /// /// Returns an enumerator that iterates through the bag. /// public IEnumerator GetEnumerator() { var current = head; while (current != null) { // Yield the item as many times as its multiplicity, pretending they are separate items for (var i = 0; i < current.Multiplicity; i++) { yield return current.Item; } current = current.Next; } } /// /// Returns an enumerator that iterates through the bag. /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } ================================================ FILE: DataStructures/Bag/BagNode.cs ================================================ namespace DataStructures.Bag; /// /// Generic node class for Bag. /// /// A type for node. public class BagNode(T item) { public T Item { get; } = item; public int Multiplicity { get; set; } = 1; public BagNode? Next { get; set; } } ================================================ FILE: DataStructures/BinarySearchTree/BinarySearchTree.cs ================================================ namespace DataStructures.BinarySearchTree; /// /// An ordered tree with efficient insertion, removal, and lookup. /// /// /// A Binary Search Tree (BST) is a tree that satisfies the following properties: /// /// All nodes in the tree contain two children, usually called Left and Right. /// All nodes in the Left subtree contain keys that are less than the node's key. /// All nodes in the Right subtree contain keys that are greater than the node's key. /// /// A BST will have an average complexity of O(log n) for insertion, removal, and lookup operations. /// /// Type of key for the BST. Keys must implement IComparable. public class BinarySearchTree { /// /// Comparer to use when comparing node elements/keys. /// private readonly Comparer comparer; /// /// Gets the root of the BST. /// public BinarySearchTreeNode? Root { get; private set; } public BinarySearchTree() => (Root, Count, comparer) = (null, 0, Comparer.Default); public BinarySearchTree(Comparer customComparer) => (Root, Count, comparer) = (null, 0, customComparer); /// /// Gets the number nodes currently in the BST. /// public int Count { get; private set; } /// /// Insert a key into the BST. /// /// The key to insert. /// /// Thrown if key is already in BST. /// public void Add(TKey key) { if (Root is null) { Root = new BinarySearchTreeNode(key); } else { Add(Root, key); } Count++; } /// /// Insert multiple keys into the BST. /// Keys are inserted in the order they appear in the sequence. /// /// Sequence of keys to insert. public void AddRange(IEnumerable keys) { foreach (var key in keys) { Add(key); } } /// /// Find a node with the specified key. /// /// The key to search for. /// The node with the specified key if it exists, otherwise a default value is returned. public BinarySearchTreeNode? Search(TKey key) => Search(Root, key); /// /// Checks if the specified key is in the BST. /// /// The key to search for. /// true if the key is in the BST, false otherwise. public bool Contains(TKey key) => Search(Root, key) is not null; /// /// Removes a node with a key that matches . /// /// The key to search for. /// true if the removal was successful, false otherwise. public bool Remove(TKey key) { if (Root is null) { return false; } var result = Remove(Root, Root, key); if (result) { Count--; } return result; } /// /// Returns a node with the smallest key. /// /// The node if possible, a default value otherwise. public BinarySearchTreeNode? GetMin() { if (Root is null) { return default; } return GetMin(Root); } /// /// Returns a node with the largest key. /// /// The node if possible, a default value otherwise. public BinarySearchTreeNode? GetMax() { if (Root is null) { return default; } return GetMax(Root); } /// /// Returns all the keys in the BST, sorted In-Order. /// /// A list of keys in the BST. public ICollection GetKeysInOrder() => GetKeysInOrder(Root); /// /// Returns all the keys in the BST, sorted Pre-Order. /// /// A list of keys in the BST. public ICollection GetKeysPreOrder() => GetKeysPreOrder(Root); /// /// Returns all the keys in the BST, sorted Post-Order. /// /// A list of keys in the BST. public ICollection GetKeysPostOrder() => GetKeysPostOrder(Root); /// /// Recursive method to add a key to the BST. /// /// Node to search from. /// Key to add. /// /// Thrown if key is already in the BST. /// private void Add(BinarySearchTreeNode node, TKey key) { var compareResult = comparer.Compare(node.Key, key); if (compareResult > 0) { if (node.Left is not null) { Add(node.Left, key); } else { var newNode = new BinarySearchTreeNode(key); node.Left = newNode; } } else if (compareResult < 0) { if (node.Right is not null) { Add(node.Right, key); } else { var newNode = new BinarySearchTreeNode(key); node.Right = newNode; } } // Key is already in tree. else { throw new ArgumentException($"""Key "{key}" already exists in tree!"""); } } /// /// Removes a node with the specified key from the BST. /// /// The parent node of . /// The node to check/search from. /// The key to remove. /// true if the operation was successful, false otherwise. /// /// Removing a node from the BST can be split into three cases: ///

/// 0. The node to be removed has no children. In this case, the node can just be removed from the tree. ///

/// 1. The node to be removed has one child. In this case, the node's child is moved to the node's parent, /// then the node is removed from the tree. ///

/// 2. The node to be removed has two children. In this case, we must find a suitable node among the children /// subtrees to replace the node. This can be done with either the in-order predecessor or the in-order successor. /// The in-order predecessor is the largest node in Left subtree, or the largest node that is still smaller then the /// current node. The in-order successor is the smallest node in the Right subtree, or the smallest node that is /// still larger than the current node. Either way, once this suitable node is found, remove it from the tree (it /// should be either a case 1 or 2 node) and replace the node to be removed with this suitable node. ///

/// More information: https://en.wikipedia.org/wiki/Binary_search_tree#Deletion . ///
private bool Remove(BinarySearchTreeNode? parent, BinarySearchTreeNode? node, TKey key) { if (node is null || parent is null) { return false; } var compareResult = comparer.Compare(node.Key, key); if (compareResult > 0) { return Remove(node, node.Left, key); } if (compareResult < 0) { return Remove(node, node.Right, key); } BinarySearchTreeNode? replacementNode; // Case 0: Node has no children. // Case 1: Node has one child. if (node.Left is null || node.Right is null) { replacementNode = node.Left ?? node.Right; } // Case 2: Node has two children. (This implementation uses the in-order predecessor to replace node.) else { var predecessorNode = GetMax(node.Left); Remove(Root, Root, predecessorNode.Key); replacementNode = new BinarySearchTreeNode(predecessorNode.Key) { Left = node.Left, Right = node.Right, }; } // Replace the relevant node with a replacement found in the previous stages. // Special case for replacing the root node. if (node == Root) { Root = replacementNode; } else if (parent.Left == node) { parent.Left = replacementNode; } else { parent.Right = replacementNode; } return true; } /// /// Recursive method to get node with largest key. /// /// Node to search from. /// Node with largest value. private BinarySearchTreeNode GetMax(BinarySearchTreeNode node) { if (node.Right is null) { return node; } return GetMax(node.Right); } /// /// Recursive method to get node with smallest key. /// /// Node to search from. /// Node with smallest value. private BinarySearchTreeNode GetMin(BinarySearchTreeNode node) { if (node.Left is null) { return node; } return GetMin(node.Left); } /// /// Recursive method to get a list with the keys sorted in in-order order. /// /// Node to traverse from. /// List of keys in in-order order. private IList GetKeysInOrder(BinarySearchTreeNode? node) { if (node is null) { return []; } // Use mutable list approach instead of spread operator for better performance var result = new List(); result.AddRange(GetKeysInOrder(node.Left)); result.Add(node.Key); result.AddRange(GetKeysInOrder(node.Right)); return result; } /// /// Recursive method to get a list with the keys sorted in pre-order order. /// /// Node to traverse from. /// List of keys in pre-order order. private IList GetKeysPreOrder(BinarySearchTreeNode? node) { if (node is null) { return []; } // Use mutable list approach instead of spread operator for better performance var result = new List { node.Key, }; result.AddRange(GetKeysPreOrder(node.Left)); result.AddRange(GetKeysPreOrder(node.Right)); return result; } /// /// Recursive method to get a list with the keys sorted in post-order order. /// /// Node to traverse from. /// List of keys in post-order order. private IList GetKeysPostOrder(BinarySearchTreeNode? node) { if (node is null) { return []; } // Use mutable list approach instead of spread operator for better performance var result = new List(); result.AddRange(GetKeysPostOrder(node.Left)); result.AddRange(GetKeysPostOrder(node.Right)); result.Add(node.Key); return result; } /// /// Recursive method to find a node with a matching key. /// /// Node to search from. /// Key to find. /// The node with the specified if it exists, a default value otherwise. private BinarySearchTreeNode? Search(BinarySearchTreeNode? node, TKey key) { if (node is null) { return default; } var compareResult = comparer.Compare(node.Key, key); if (compareResult > 0) { return Search(node.Left, key); } if (compareResult < 0) { return Search(node.Right, key); } return node; } } ================================================ FILE: DataStructures/BinarySearchTree/BinarySearchTreeNode.cs ================================================ namespace DataStructures.BinarySearchTree; /// /// Generic node class for BinarySearchTree. /// Keys for each node are immutable. /// /// Type of key for the node. Keys must implement IComparable. /// /// Initializes a new instance of the class. /// /// The key of this node. public class BinarySearchTreeNode(TKey key) { /// /// Gets the key for this node. /// public TKey Key { get; } = key; /// /// Gets or sets the reference to a child node that precedes/comes before this node. /// public BinarySearchTreeNode? Left { get; set; } /// /// Gets or sets the reference to a child node that follows/comes after this node. /// public BinarySearchTreeNode? Right { get; set; } } ================================================ FILE: DataStructures/BitArray.cs ================================================ // Original Author: Christian Bender // Class: BitArray // // implements IComparable, ICloneable, IEnumerator, IEnumerable // // This class implements a bit-array and provides some // useful functions/operations to deal with this type of // data structure. You see a overview about the functionality, below. // // // Overview // // Constructor (N : int) // The constructor receives a length (N) of the to create bit-field. // // Constructor (sequence : string) // setups the array with the input sequence. // assumes: the sequence may only be allowed contains onese or zeros. // // Constructor (bits : bool[] ) // setups the bit-field with the input array. // // Compile(sequence : string) // compiles a string sequence of 0's and 1's in the inner structure. // assumes: the sequence may only be allowed contains onese or zeros. // // Compile (number : int) // compiles a positive integer number in the inner data structure. // // Compile (number : long) // compiles a positive long integer number in the inner data structure. // // ToString () // returns a string representation of the inner structure. // The returned string is a sequence of 0's and 1's. // // Length : int // Is a property that returns the length of the bit-field. // // Indexer : bool // indexer for selecting the individual bits of the bit array. // // NumberOfOneBits() : int // returns the number of One-bits. // // NumberOfZeroBits() : int // returns the number of Zero-Bits. // // EvenParity() : bool // returns true if parity is even, otherwise false. // // OddParity() : bool // returns true if parity is odd, otherwise false. // // ToInt64() : long // returns a long integer representation of the bit-array. // assumes: the bit-array length must been smaller or equal to 64 bit. // // ToInt32() : int // returns a integer representation of the bit-array. // assumes: the bit-array length must been smaller or equal to 32 bit. // // ResetField() : void // sets all bits on false. // // SetAll(flag : bool) : void // sets all bits on the value of the flag. // // GetHashCode() : int // returns hash-code (ToInt32()) // // Equals (other : Object) : bool // returns true if there inputs are equal otherwise false. // assumes: the input bit-arrays must have same length. // // CompareTo (other : Object) : int (interface IComparable) // output: 0 - if the bit-arrays a equal. // -1 - if this bit-array is smaller. // 1 - if this bit-array is greater. // assumes: bit-array lentgh must been smaller or equal to 64 bit // // Clone () : object // returns a copy of this bit-array // // Current : object // returns the current selected bit. // // MoveNext() : bool // purpose: increases the position of the enumerator // returns true if 'position' successful increased otherwise false. // // Reset() : void // resets the position of the enumerator. // // GetEnumerator() : IEnumerator // returns a enumerator for this BitArray-object. // // Operations: // // & bitwise AND // | bitwise OR // ~ bitwise NOT // >> bitwise shift right // >> bitwise shift left // ^ bitwise XOR // // Each operation (above) returns a new BitArray-object. // // == equal operator. : bool // returns true if there inputs are equal otherwise false. // assumes: the input bit-arrays must have same length. // // != not-equal operator : bool // returns true if there inputs aren't equal otherwise false. // assumes: the input bit-arrays must have same length. using System.Collections; namespace DataStructures; /// /// This class implements a bit-array and provides some /// useful functions/operations to deal with this type of /// data structure. /// public sealed class BitArray : ICloneable, IEnumerator, IEnumerable { private readonly bool[] field; // the actual bit-field private int position = -1; // position for enumerator /// /// Initializes a new instance of the class. /// setups the array with false-values. /// /// length of the array. public BitArray(int n) { field = n <= 0 ? new bool[0] : new bool[n]; } /// /// Initializes a new instance of the class. /// Setups the array with the input sequence. /// purpose: Setups the array with the input sequence. /// assumes: sequence must been greater or equal to 1. /// the sequence may only contain ones or zeros. /// /// A string sequence of 0's and 1's. public BitArray(string sequence) { // precondition I if (sequence.Length <= 0) { throw new ArgumentException("Sequence must been greater than or equal to 1"); } // precondition II ThrowIfSequenceIsInvalid(sequence); field = new bool[sequence.Length]; Compile(sequence); } /// /// Initializes a new instance of the class. /// Setups the bit-array with the input array. /// /// A boolean array of bits. public BitArray(bool[] bits) => field = bits; /// /// Gets the length of the current bit array. /// private int Length => field.Length; /// /// Gets element given an offset. /// /// Position. /// Element on array. public bool this[int offset] { get => field[offset]; private set => field[offset] = value; } /// /// Returns a copy of the current bit-array. /// /// Bit-array clone. public object Clone() { var theClone = new BitArray(Length); for (var i = 0; i < Length; i++) { theClone[i] = field[i]; } return theClone; } /// /// Gets a enumerator for this BitArray-Object. /// /// Returns a enumerator for this BitArray-Object. public IEnumerator GetEnumerator() => this; /// /// Gets a enumerator for this BitArray-Object. /// /// Returns a enumerator for this BitArray-Object. IEnumerator IEnumerable.GetEnumerator() => this; /// /// Gets a value indicating whether the current bit of the array is set. /// public bool Current => field[position]; /// /// Gets a value indicating whether the current bit of the array is set. /// object IEnumerator.Current => field[position]; /// /// MoveNext (for interface IEnumerator). /// /// Returns True if 'position' successful increased; False otherwise. public bool MoveNext() { if (position + 1 >= field.Length) { return false; } position++; return true; } /// /// Resets the position of the enumerator. /// Reset (for interface IEnumerator). /// public void Reset() => position = -1; /// /// Disposes object, nothing to dispose here though. /// public void Dispose() { // Done } /// /// Returns a bit-array that represents the bit by bit AND (&). /// Assumes arrays have the same length. /// /// First bit-array. /// Second bit-array. /// bit-array. public static BitArray operator &(BitArray one, BitArray two) { var sequence1 = one.ToString(); var sequence2 = two.ToString(); var result = new StringBuilder(); var tmp = new StringBuilder(); // for scaling of same length. if (one.Length != two.Length) { int difference; if (one.Length > two.Length) { // one is greater difference = one.Length - two.Length; // fills up with 0's for (var i = 0; i < difference; i++) { tmp.Append('0'); } tmp.Append(two); sequence2 = tmp.ToString(); } else { // two is greater difference = two.Length - one.Length; // fills up with 0's for (var i = 0; i < difference; i++) { tmp.Append('0'); } tmp.Append(one); sequence1 = tmp.ToString(); } } // end scaling var len = one.Length > two.Length ? one.Length : two.Length; var ans = new BitArray(len); for (var i = 0; i < one.Length; i++) { result.Append(sequence1[i].Equals('1') && sequence2[i].Equals('1') ? '1' : '0'); } ans.Compile(result.ToString().Trim()); return ans; } /// /// Returns a bit-array that represents the bit by bit OR. /// Assumes arrays have the same length. /// /// First bit-array. /// Second bit-array. /// bit-array that represents the bit by bit OR. public static BitArray operator |(BitArray one, BitArray two) { var sequence1 = one.ToString(); var sequence2 = two.ToString(); var result = new StringBuilder(); // for scaling of same length. if (one.Length != two.Length) { var tmp = new StringBuilder(); int difference; if (one.Length > two.Length) { // one is greater difference = one.Length - two.Length; // fills up with 0's tmp.Append('0', difference); tmp.Append(two.ToString()); sequence2 = tmp.ToString(); } else { // two is greater difference = two.Length - one.Length; // fills up with 0's tmp.Append('0', difference); tmp.Append(one.ToString()); sequence1 = tmp.ToString(); } } // end scaling var len = one.Length > two.Length ? one.Length : two.Length; var ans = new BitArray(len); for (var i = 0; i < len; i++) { result.Append(sequence1[i].Equals('0') && sequence2[i].Equals('0') ? '0' : '1'); } ans.Compile(result.ToString()); return ans; } /// /// Returns a bit-array that represents the operator ~ (NOT). /// Assumes arrays have the same length. /// /// Bit-array. /// bitwise not. public static BitArray operator ~(BitArray one) { var ans = new BitArray(one.Length); var sequence = one.ToString(); var result = new StringBuilder(sequence.Length); foreach (var ch in sequence) { result.Append(ch == '1' ? '0' : '1'); } ans.Compile(result.ToString()); return ans; } /// /// Returns a bit-array that represents bitwise shift left (>>). /// Assumes arrays have the same length. /// /// Bit-array. /// Number of bits. /// Bitwise shifted BitArray. public static BitArray operator <<(BitArray other, int n) { var ans = new BitArray(other.Length + n); // actual shifting process for (var i = 0; i < other.Length; i++) { ans[i] = other[i]; } return ans; } /// /// Returns a bit-array that represents the bit by bit XOR. /// Assumes arrays have the same length. /// /// First bit-array. /// Second bit-array. /// bit-array. public static BitArray operator ^(BitArray one, BitArray two) { var sequence1 = one.ToString(); var sequence2 = two.ToString(); // for scaling of same length. if (one.Length != two.Length) { var tmp = new StringBuilder(); int difference; if (one.Length > two.Length) { // one is greater difference = one.Length - two.Length; // fills up with 0's tmp.Append('0', difference); tmp.Append(two.ToString()); sequence2 = tmp.ToString(); } else { // two is greater difference = two.Length - one.Length; // fills up with 0's tmp.Append('0', difference); tmp.Append(one.ToString()); sequence1 = tmp.ToString(); } } // end scaling var len = one.Length > two.Length ? one.Length : two.Length; var ans = new BitArray(len); var sb = new StringBuilder(len); for (var i = 0; i < len; i++) { sb.Append(sequence1[i] == sequence2[i] ? '0' : '1'); } ans.Compile(sb.ToString()); return ans; } /// /// Returns a bit-array that represents bitwise shift right (>>). /// Assumes arrays have the same length. /// /// Bit-array. /// Number of bits. /// Bitwise shifted BitArray. public static BitArray operator >>(BitArray other, int n) { var ans = new BitArray(other.Length - n); // actual shifting process. for (var i = 0; i < other.Length - n; i++) { ans[i] = other[i]; } return ans; } /// /// Checks if both arrays are == (equal). /// The input assumes arrays have the same length. /// /// First bit-array. /// Second bit-array. /// Returns True if there inputs are equal; False otherwise. public static bool operator ==(BitArray one, BitArray two) { if (ReferenceEquals(one, two)) { return true; } if (one.Length != two.Length) { return false; } var status = true; for (var i = 0; i < one.Length; i++) { if (one[i] != two[i]) { status = false; break; } } return status; } /// /// Checks if both arrays are != (not-equal). /// The input assumes arrays have the same length. /// /// First bit-array. /// Second bit-array. /// Returns True if there inputs aren't equal; False otherwise. public static bool operator !=(BitArray one, BitArray two) => !(one == two); /// /// Compiles the binary sequence into the inner data structure. /// The sequence must have the same length, as the bit-array. /// The sequence may only be allowed contains ones or zeros. /// /// A string sequence of 0's and 1's. public void Compile(string sequence) { // precondition I if (sequence.Length > field.Length) { throw new ArgumentException($"{nameof(sequence)} must be not longer than the bit array length"); } // precondition II ThrowIfSequenceIsInvalid(sequence); // for appropriate scaling if (sequence.Length < field.Length) { var difference = field.Length - sequence.Length; sequence = new string('0', difference) + sequence; } // actual compile procedure. for (var i = 0; i < sequence.Length; i++) { field[i] = sequence[i] == '1'; } } /// /// Compiles integer number into the inner data structure. /// Assumes: the number must have the same bit length. /// /// A positive integer number. public void Compile(int number) { // precondition I if (number <= 0) { throw new ArgumentException($"{nameof(number)} must be positive"); } // converts to binary representation var binaryNumber = Convert.ToString(number, 2); // precondition II if (binaryNumber.Length > field.Length) { throw new ArgumentException("Provided number is too big"); } // for appropriate scaling if (binaryNumber.Length < field.Length) { var difference = field.Length - binaryNumber.Length; binaryNumber = new string('0', difference) + binaryNumber; } // actual compile procedure. for (var i = 0; i < binaryNumber.Length; i++) { field[i] = binaryNumber[i] == '1'; } } /// /// Compiles integer number into the inner data structure. /// The number must have the same bit length. /// /// A positive long integer number. public void Compile(long number) { // precondition I if (number <= 0) { throw new ArgumentException($"{nameof(number)} must be positive"); } // converts to binary representation var binaryNumber = Convert.ToString(number, 2); // precondition II if (binaryNumber.Length > field.Length) { throw new ArgumentException("Provided number is too big"); } // for appropriate scaling if (binaryNumber.Length < field.Length) { var difference = field.Length - binaryNumber.Length; binaryNumber = new string('0', difference) + binaryNumber; } // actual compile procedure. for (var i = 0; i < binaryNumber.Length; i++) { field[i] = binaryNumber[i] == '1'; } } /// /// Is the opposit of the Compile(...) method. /// /// Returns a string representation of the inner data structure. public override string ToString() { // creates return-string return field.Aggregate(string.Empty, (current, t) => current + (t ? "1" : "0")); } /// /// Gets the number of one-bits in the field. /// /// quantity of bits in current bit-array. public int NumberOfOneBits() { // counting one-bits. return field.Count(bit => bit); } /// /// Gets the number of zero-bits in the field. /// /// quantity of bits. public int NumberOfZeroBits() { // counting zero-bits return field.Count(bit => !bit); } /// /// To check for even parity. /// /// Returns True if parity is even; False otherwise. public bool EvenParity() => NumberOfOneBits() % 2 == 0; /// /// To check for odd parity. /// /// Returns True if parity is odd; False otherwise. public bool OddParity() => NumberOfOneBits() % 2 != 0; /// /// Returns a long integer representation of the bit-array. /// Assumes the bit-array length must been smaller or equal to 64 bit. /// /// Long integer array. public long ToInt64() { // Precondition if (field.Length > 64) { throw new InvalidOperationException("Value is too big to fit into Int64"); } var sequence = ToString(); return Convert.ToInt64(sequence, 2); } /// /// Returns a long integer representation of the bit-array. /// Assumes the bit-array length must been smaller or equal to 32 bit. /// /// integer array. public int ToInt32() { // Precondition if (field.Length > 32) { throw new InvalidOperationException("Value is too big to fit into Int32"); } var sequence = ToString(); return Convert.ToInt32(sequence, 2); } /// /// Sets all bits on false. /// public void ResetField() { for (var i = 0; i < field.Length; i++) { field[i] = false; } } /// /// Sets all bits on the value of the flag. /// /// Bollean flag (false-true). public void SetAll(bool flag) { for (var i = 0; i < field.Length; i++) { field[i] = flag; } } /// /// Checks if bit-array are equal. /// Assumes the input bit-arrays must have same length. /// /// Bit-array object. /// Returns true if there inputs are equal otherwise false. public override bool Equals(object? obj) { if (obj is null) { return false; } var otherBitArray = (BitArray)obj; if (Length != otherBitArray.Length) { return false; } for (var i = 0; i < Length; i++) { if (field[i] != otherBitArray[i]) { return false; } } return true; } /// /// Gets has-code of bit-array. /// Assumes bit-array length must been smaller or equal to 32. /// /// hash-code for this BitArray instance. public override int GetHashCode() => ToInt32(); private static void ThrowIfSequenceIsInvalid(string sequence) { if (!Match(sequence)) { throw new ArgumentException("The sequence may only contain ones or zeros"); } } /// /// Utility method for checking a given sequence contains only zeros and ones. /// This method will used in Constructor (sequence : string) and Compile(sequence : string). /// /// String sequence. /// returns True if sequence contains only zeros and ones; False otherwise. private static bool Match(string sequence) => sequence.All(ch => ch == '0' || ch == '1'); } ================================================ FILE: DataStructures/Cache/LfuCache.cs ================================================ namespace DataStructures.Cache; /// /// Least Frequently Used (LFU) cache implementation. /// /// The type of the key (must be not null). /// The type of the value. /// /// Cache keeps up to capacity items. When new item is added and cache is full, /// one of the least frequently used item is removed (e.g. it keeps N items that were the most /// frequently requested using Get() or Put() methods). /// When there are multiple items with the same frequency, the least recently used item is removed. /// /// Cache is built on top of two data structures: /// - Dictionary. Allows items to be looked up by key in O(1) time. Another dictionary /// is used to store the frequency of each key. /// - LinkedList - Allows items with the same frequency to be ordered by the last /// usage in O(1) time. /// /// Useful links: /// https://en.wikipedia.org/wiki/Cache_replacement_policies /// https://www.enjoyalgorithms.com/blog/least-frequently-used-cache /// https://www.educative.io/answers/what-is-least-frequently-used-cache-replace-policy /// https://leetcode.com/problems/lfu-cache/ . /// /// /// Initializes a new instance of the class. /// /// The max number of items the cache can store. public class LfuCache(int capacity = LfuCache.DefaultCapacity) where TKey : notnull { private class CachedItem { public TKey Key { get; set; } = default!; public TValue? Value { get; set; } public int Frequency { get; set; } } private const int DefaultCapacity = 100; private readonly int capacity = capacity; // Note that Dictionary stores LinkedListNode as it allows // removing the node from the LinkedList in O(1) time. private readonly Dictionary> cache = []; // Map frequency (number of times the item was requested or updated) // to the LRU linked list. private readonly Dictionary> frequencies = []; // Track the minimum frequency with non-empty linked list in frequencies. // When the last item with the minFrequency is promoted (after being requested or updated), // the minFrequency is increased. // When a new item is added, the minFrequency is set to 1. private int minFrequency = -1; public bool Contains(TKey key) => cache.ContainsKey(key); /// /// Gets the cached item by key. /// /// The key of cached item. /// The cached item or default if item is not found. /// Time complexity: O(1). public TValue? Get(TKey key) { if (!cache.ContainsKey(key)) { return default; } var node = cache[key]; UpdateFrequency(node, isNew: false); return node.Value.Value; } /// /// Adds or updates the value in the cache. /// /// The key of item to cache. /// The value to cache. /// /// Time complexity: O(1). /// If the value is already cached, it is updated and the item is moved /// to the end of the LRU list. /// If the cache is full, one of the least frequently used items is removed. /// public void Put(TKey key, TValue value) { if (cache.ContainsKey(key)) { var existingNode = cache[key]; existingNode.Value.Value = value; UpdateFrequency(existingNode, isNew: false); return; } if (cache.Count >= capacity) { EvictOneItem(); } var item = new CachedItem { Key = key, Value = value }; var newNode = new LinkedListNode(item); UpdateFrequency(newNode, isNew: true); cache.Add(key, newNode); } private void UpdateFrequency(LinkedListNode node, bool isNew) { var item = node.Value; if (isNew) { item.Frequency = 1; minFrequency = 1; } else { // Remove the existing node from the LRU list with its previous frequency. var lruList = frequencies[item.Frequency]; lruList.Remove(node); if (lruList.Count == 0 && minFrequency == item.Frequency) { minFrequency++; } item.Frequency++; } // Insert item to the end of the LRU list that corresponds to its new frequency. if (!frequencies.ContainsKey(item.Frequency)) { frequencies[item.Frequency] = new LinkedList(); } frequencies[item.Frequency].AddLast(node); } private void EvictOneItem() { var lruList = frequencies[minFrequency]; var itemToRemove = lruList.First!.Value; lruList.RemoveFirst(); cache.Remove(itemToRemove.Key); } } ================================================ FILE: DataStructures/Cache/LruCache.cs ================================================ namespace DataStructures.Cache; /// /// Least Recently Used (LRU) cache implementation. /// /// The type of the key (must be not null). /// The type of the value. /// /// Cache keeps up to capacity items. When new item is added and cache is full, /// the least recently used item is removed (e.g. it keeps N items that were recently requested /// using Get() or Put() methods). /// /// Cache is built on top of two data structures: /// - Dictionary - allows items to be looked up by key in O(1) time. /// - LinkedList - allows items to be ordered by last usage time in O(1) time. /// /// Useful links: /// https://en.wikipedia.org/wiki/Cache_replacement_policies /// https://www.educative.io/m/implement-least-recently-used-cache /// https://leetcode.com/problems/lru-cache/ /// /// In order to make the most recently used (MRU) cache, when the cache is full, /// just remove the last node from the linked list in the method Put /// (replace RemoveFirst with RemoveLast). /// /// /// Initializes a new instance of the class. /// /// The max number of items the cache can store. public class LruCache(int capacity = LruCache.DefaultCapacity) where TKey : notnull { private class CachedItem { public TKey Key { get; set; } = default!; public TValue? Value { get; set; } } private const int DefaultCapacity = 100; private readonly int capacity = capacity; // Note that Dictionary stores LinkedListNode as it allows // removing the node from the LinkedList in O(1) time. private readonly Dictionary> cache = []; private readonly LinkedList lruList = new(); public bool Contains(TKey key) => cache.ContainsKey(key); /// /// Gets the cached item by key. /// /// The key of cached item. /// The cached item or default if item is not found. /// Time complexity: O(1). public TValue? Get(TKey key) { if (!cache.ContainsKey(key)) { return default; } var node = cache[key]; lruList.Remove(node); lruList.AddLast(node); return node.Value.Value; } /// /// Adds or updates the value in the cache. /// /// The key of item to cache. /// The value to cache. /// /// Time complexity: O(1). /// If the value is already cached, it is updated and the item is moved /// to the end of the LRU list. /// If the cache is full, the least recently used item is removed. /// public void Put(TKey key, TValue value) { if (cache.ContainsKey(key)) { var existingNode = cache[key]; existingNode.Value.Value = value; lruList.Remove(existingNode); lruList.AddLast(existingNode); return; } if (cache.Count >= capacity) { var first = lruList.First!; lruList.RemoveFirst(); cache.Remove(first.Value.Key); } var item = new CachedItem { Key = key, Value = value }; var newNode = lruList.AddLast(item); cache.Add(key, newNode); } } ================================================ FILE: DataStructures/DataStructures.csproj ================================================  net8.0 ..\stylecop.ruleset true enable ./bin/DataStructures.xml all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: DataStructures/Deque/Deque.cs ================================================ namespace DataStructures.Deque; /// /// Implementation of a Deque (Double-Ended Queue) data structure. /// A deque allows insertion and deletion of elements from both ends (front and rear). /// This implementation uses a circular array for efficient operations. /// /// Key Features: /// - O(1) time complexity for AddFront, AddRear, RemoveFront, RemoveRear operations /// - O(1) amortized time for insertions (due to dynamic resizing) /// - Space efficient with circular array implementation /// - Automatic capacity doubling when full /// /// Use Cases: /// - Implementing sliding window algorithms /// - Palindrome checking /// - Undo/Redo functionality /// - Task scheduling with priority at both ends /// /// Reference: "Data Structures and Algorithms in C#" by Michael T. Goodrich. /// /// The type of elements in the deque. public class Deque { // Internal circular array to store elements private T[] items; // Index of the front element (next element to remove from front) private int front; // Index where the next element will be added at rear private int rear; // Current number of elements in the deque private int count; /// /// Initializes a new instance of the class with default capacity. /// Default capacity is 16 elements, which provides a good balance between /// memory usage and avoiding early resizing for typical use cases. /// public Deque() : this(16) { } /// /// Initializes a new instance of the class with specified capacity. /// /// The initial capacity of the deque. /// Thrown when capacity is less than 1. public Deque(int capacity) { if (capacity < 1) { throw new ArgumentException("Capacity must be at least 1.", nameof(capacity)); } items = new T[capacity]; front = 0; rear = 0; count = 0; } /// /// Gets the number of elements in the deque. /// public int Count => count; /// /// Gets a value indicating whether the deque is empty. /// public bool IsEmpty => count == 0; /// /// Adds an element to the front of the deque. /// This operation is O(1) time complexity (amortized due to occasional resizing). /// /// The item to add. /// /// deque.AddFront(5); // Deque: [5]. /// deque.AddFront(3); // Deque: [3, 5]. /// public void AddFront(T item) { // Check if we need to resize before adding if (count == items.Length) { Resize(); } // Move front pointer backward in circular fashion // Adding items.Length ensures the result is always positive front = (front - 1 + items.Length) % items.Length; items[front] = item; count++; } /// /// Adds an element to the rear of the deque. /// This operation is O(1) time complexity (amortized due to occasional resizing). /// /// The item to add. /// /// deque.AddRear(5); // Deque: [5]. /// deque.AddRear(7); // Deque: [5, 7]. /// public void AddRear(T item) { // Check if we need to resize before adding if (count == items.Length) { Resize(); } // Add item at rear position items[rear] = item; // Move rear pointer forward in circular fashion rear = (rear + 1) % items.Length; count++; } /// /// Removes and returns the element at the front of the deque. /// This operation is O(1) time complexity. /// /// The element at the front of the deque. /// Thrown when the deque is empty. /// /// // Deque: [3, 5, 7]. /// int value = deque.RemoveFront(); // Returns 3, Deque: [5, 7]. /// public T RemoveFront() { // Validate that deque is not empty if (IsEmpty) { throw new InvalidOperationException("Deque is empty."); } // Retrieve the front element T item = items[front]; // Clear the reference to help garbage collection items[front] = default!; // Move front pointer forward in circular fashion front = (front + 1) % items.Length; count--; return item; } /// /// Removes and returns the element at the rear of the deque. /// This operation is O(1) time complexity. /// /// The element at the rear of the deque. /// Thrown when the deque is empty. /// /// // Deque: [3, 5, 7]. /// int value = deque.RemoveRear(); // Returns 7, Deque: [3, 5]. /// public T RemoveRear() { // Validate that deque is not empty if (IsEmpty) { throw new InvalidOperationException("Deque is empty."); } // Move rear pointer backward to the last element rear = (rear - 1 + items.Length) % items.Length; // Retrieve the rear element T item = items[rear]; // Clear the reference to help garbage collection items[rear] = default!; count--; return item; } /// /// Returns the element at the front of the deque without removing it. /// This operation is O(1) time complexity and does not modify the deque. /// /// The element at the front of the deque. /// Thrown when the deque is empty. /// /// // Deque: [3, 5, 7]. /// int value = deque.PeekFront(); // Returns 3, Deque unchanged: [3, 5, 7]. /// public T PeekFront() { // Validate that deque is not empty if (IsEmpty) { throw new InvalidOperationException("Deque is empty."); } return items[front]; } /// /// Returns the element at the rear of the deque without removing it. /// This operation is O(1) time complexity and does not modify the deque. /// /// The element at the rear of the deque. /// Thrown when the deque is empty. /// /// // Deque: [3, 5, 7]. /// int value = deque.PeekRear(); // Returns 7, Deque unchanged: [3, 5, 7]. /// public T PeekRear() { // Validate that deque is not empty if (IsEmpty) { throw new InvalidOperationException("Deque is empty."); } // Calculate the index of the last element (rear - 1 in circular array) int rearIndex = (rear - 1 + items.Length) % items.Length; return items[rearIndex]; } /// /// Removes all elements from the deque. /// This operation is O(n) where n is the capacity of the internal array. /// After clearing, the deque can be reused without reallocation. /// public void Clear() { // Clear all references in the array to help garbage collection Array.Clear(items, 0, items.Length); // Reset pointers to initial state front = 0; rear = 0; count = 0; } /// /// Converts the deque to an array. /// This operation is O(n) where n is the number of elements. /// The resulting array maintains the order from front to rear. /// /// An array containing all elements in the deque from front to rear. /// /// // Deque: [3, 5, 7]. /// int[] array = deque.ToArray(); // Returns [3, 5, 7]. /// public T[] ToArray() { // Create result array with exact size needed T[] result = new T[count]; int index = front; // Copy elements from front to rear, handling circular wrap-around for (int i = 0; i < count; i++) { result[i] = items[index]; index = (index + 1) % items.Length; } return result; } /// /// Determines whether the deque contains a specific element. /// This operation is O(n) where n is the number of elements. /// Uses the default equality comparer for type T. /// /// The item to locate in the deque. /// true if the item is found; otherwise, false. /// /// // Deque: [3, 5, 7]. /// bool exists = deque.Contains(5); // Returns true. /// bool missing = deque.Contains(9); // Returns false. /// public bool Contains(T item) { int index = front; // Iterate through all elements in order from front to rear for (int i = 0; i < count; i++) { // Use default equality comparer to compare elements if (EqualityComparer.Default.Equals(items[index], item)) { return true; } // Move to next element in circular array index = (index + 1) % items.Length; } return false; } /// /// Resizes the internal array to accommodate more elements. /// This is a private helper method called automatically when capacity is reached. /// Doubles the capacity and reorganizes elements to start at index 0. /// Time complexity: O(n) where n is the current number of elements. /// private void Resize() { // Double the capacity to reduce frequency of future resizes int newCapacity = items.Length * 2; T[] newItems = new T[newCapacity]; // Copy all elements to new array starting from index 0 // This "unwraps" the circular structure for simplicity int index = front; for (int i = 0; i < count; i++) { newItems[i] = items[index]; index = (index + 1) % items.Length; } // Replace old array with new larger array items = newItems; // Reset pointers: front at 0, rear at position after last element front = 0; rear = count; } } ================================================ FILE: DataStructures/DisjointSet/DisjointSet.cs ================================================ namespace DataStructures.DisjointSet; /// /// Implementation of Disjoint Set with Union By Rank and Path Compression heuristics. /// /// generic type for implementation. public class DisjointSet { /// /// make a new set and return its representative. /// /// element to add in to the DS. /// representative of x. public Node MakeSet(T x) => new(x); /// /// find the representative of a certain node. /// /// node to find representative. /// representative of x. public Node FindSet(Node node) { if (node != node.Parent) { node.Parent = FindSet(node.Parent); } return node.Parent; } /// /// merge two sets. /// /// first set member. /// second set member. public void UnionSet(Node x, Node y) { Node nx = FindSet(x); Node ny = FindSet(y); if (nx == ny) { return; } if (nx.Rank > ny.Rank) { ny.Parent = nx; } else if (ny.Rank > nx.Rank) { nx.Parent = ny; } else { nx.Parent = ny; ny.Rank++; } } } ================================================ FILE: DataStructures/DisjointSet/Node.cs ================================================ namespace DataStructures.DisjointSet; /// /// node class to be used by disjoint set to represent nodes in Disjoint Set forest. /// /// generic type for data to be stored. public class Node { public int Rank { get; set; } public Node Parent { get; set; } public T Data { get; set; } public Node(T data) { Data = data; Parent = this; } } ================================================ FILE: DataStructures/Fenwick/BinaryIndexedTree.cs ================================================ namespace DataStructures.Fenwick; /// /// Represent classical realization of Fenwiсk tree or Binary Indexed tree. /// /// BITree[0..n] --> Array that represents Binary Indexed Tree. /// arr[0..n-1] --> Input array for which prefix sum is evaluated. /// public class BinaryIndexedTree { private readonly int[] fenwickTree; /// /// Initializes a new instance of the class. /// Create Binary indexed tree from the given array. /// /// Initial array. public BinaryIndexedTree(int[] array) { fenwickTree = new int[array.Length + 1]; for (var i = 0; i < array.Length; i++) { UpdateTree(i, array[i]); } } /// /// This method assumes that the array is preprocessed and /// partial sums of array elements are stored in BITree[]. /// /// The position to sum from. /// Returns sum of arr[0..index]. public int GetSum(int index) { var sum = 0; var startFrom = index + 1; while (startFrom > 0) { sum += fenwickTree[startFrom]; startFrom -= startFrom & (-startFrom); } return sum; } /// /// Updates a node in Binary Index Tree at given index. /// The given value 'val' is added to BITree[i] and all of its ancestors in tree. /// /// Given index. /// Value to be update on. public void UpdateTree(int index, int val) { var startFrom = index + 1; while (startFrom <= fenwickTree.Length) { fenwickTree[startFrom] += val; startFrom += startFrom & (-startFrom); } } } ================================================ FILE: DataStructures/GlobalUsings.cs ================================================ // ----------------------------------------------------------------------------- // Global using directives for the C-Sharp solution. // These namespaces are imported globally so they don’t need to be repeatedly declared // in individual files, improving readability and reducing boilerplate. // // Guidelines: // - Keep only the most commonly used namespaces here. // - Add project-specific namespaces (e.g., Utilities.Extensions) only if they are // required across the majority of files in the project. // - Avoid placing rarely used namespaces here to maintain clarity. // ----------------------------------------------------------------------------- global using System; // Core base classes and fundamental types global using System.Collections.Generic; // Generic collection types (List, Dictionary, etc.) global using System.Linq; // LINQ query operators for collections global using System.Text; // Text encoding, StringBuilder, etc. ================================================ FILE: DataStructures/Graph/DirectedWeightedGraph.cs ================================================ namespace DataStructures.Graph; /// /// Implementation of the directed weighted graph via adjacency matrix. /// /// Generic Type. public class DirectedWeightedGraph : IDirectedWeightedGraph { /// /// Capacity of the graph, indicates the maximum amount of vertices. /// private readonly int capacity; /// /// Adjacency matrix which reflects the edges between vertices and their weight. /// Zero value indicates no edge between two vertices. /// private readonly double[,] adjacencyMatrix; /// /// Initializes a new instance of the class. /// /// Capacity of the graph, indicates the maximum amount of vertices. public DirectedWeightedGraph(int capacity) { ThrowIfNegativeCapacity(capacity); this.capacity = capacity; Vertices = new Vertex[capacity]; adjacencyMatrix = new double[capacity, capacity]; Count = 0; } /// /// Gets list of vertices of the graph. /// public Vertex?[] Vertices { get; private set; } /// /// Gets current amount of vertices in the graph. /// public int Count { get; private set; } /// /// Adds new vertex to the graph. /// /// Data of the vertex. /// Reference to created vertex. public Vertex AddVertex(T data) { ThrowIfOverflow(); var vertex = new Vertex(data, Count, this); Vertices[Count] = vertex; Count++; return vertex; } /// /// Creates an edge between two vertices of the graph. /// /// Vertex, edge starts at. /// Vertex, edge ends at. /// Double weight of an edge. public void AddEdge(Vertex startVertex, Vertex endVertex, double weight) { ThrowIfVertexNotInGraph(startVertex); ThrowIfVertexNotInGraph(endVertex); ThrowIfWeightZero(weight); var currentEdgeWeight = adjacencyMatrix[startVertex.Index, endVertex.Index]; ThrowIfEdgeExists(currentEdgeWeight); adjacencyMatrix[startVertex.Index, endVertex.Index] = weight; } /// /// Removes vertex from the graph. /// /// Vertex to be removed. public void RemoveVertex(Vertex vertex) { ThrowIfVertexNotInGraph(vertex); int indexToRemove = vertex.Index; vertex.Index = -1; vertex.SetGraphNull(); // Update the vertex array and the index of vertices. for (int i = indexToRemove; i < Count - 1; i++) { Vertices[i] = Vertices[i + 1]; Vertices[i]!.Index = i; } Vertices[Count - 1] = null; // Update adjacency matrix to remove the row and column of the removed vertex. for (int i = 0; i < Count; i++) { for (int j = 0; j < Count; j++) { if (i < indexToRemove && j < indexToRemove) { continue; } else if (i < indexToRemove && j >= indexToRemove && j < Count - 1) { adjacencyMatrix[i, j] = adjacencyMatrix[i, j + 1]; } else if (i >= indexToRemove && i < Count - 1 && j < indexToRemove) { adjacencyMatrix[i, j] = adjacencyMatrix[i + 1, j]; } else if (i >= indexToRemove && i < Count - 1 && j >= indexToRemove && j < Count - 1) { adjacencyMatrix[i, j] = adjacencyMatrix[i + 1, j + 1]; } else if (i == Count - 1 || j == Count - 1) { adjacencyMatrix[i, j] = 0; } else { throw new InvalidOperationException(); } } } Count--; } /// /// Removes edge between two vertices. /// /// Vertex, edge starts at. /// Vertex, edge ends at. public void RemoveEdge(Vertex startVertex, Vertex endVertex) { ThrowIfVertexNotInGraph(startVertex); ThrowIfVertexNotInGraph(endVertex); adjacencyMatrix[startVertex.Index, endVertex.Index] = 0; } /// /// Gets a neighbors of particular vertex. /// /// Vertex, method gets list of neighbors for. /// Collection of the neighbors of particular vertex. public IEnumerable?> GetNeighbors(Vertex vertex) { ThrowIfVertexNotInGraph(vertex); for (var i = 0; i < Count; i++) { if (adjacencyMatrix[vertex.Index, i] != 0) { yield return Vertices[i]; } } } /// /// Returns true, if there is an edge between two vertices. /// /// Vertex, edge starts at. /// Vertex, edge ends at. /// True if edge exists, otherwise false. public bool AreAdjacent(Vertex startVertex, Vertex endVertex) { ThrowIfVertexNotInGraph(startVertex); ThrowIfVertexNotInGraph(endVertex); return adjacencyMatrix[startVertex.Index, endVertex.Index] != 0; } /// /// Return the distance between two vertices in the graph. /// /// first vertex in edge. /// secnod vertex in edge. /// distance between the two. public double AdjacentDistance(Vertex startVertex, Vertex endVertex) { if (AreAdjacent(startVertex, endVertex)) { return adjacencyMatrix[startVertex.Index, endVertex.Index]; } return 0; } private static void ThrowIfNegativeCapacity(int capacity) { if (capacity < 0) { throw new InvalidOperationException("Graph capacity should always be a non-negative integer."); } } private static void ThrowIfWeightZero(double weight) { if (weight.Equals(0.0d)) { throw new InvalidOperationException("Edge weight cannot be zero."); } } private static void ThrowIfEdgeExists(double currentEdgeWeight) { if (!currentEdgeWeight.Equals(0.0d)) { throw new InvalidOperationException($"Vertex already exists: {currentEdgeWeight}"); } } private void ThrowIfOverflow() { if (Count == capacity) { throw new InvalidOperationException("Graph overflow."); } } private void ThrowIfVertexNotInGraph(Vertex vertex) { if (vertex.Graph != this) { throw new InvalidOperationException($"Vertex does not belong to graph: {vertex}."); } } } ================================================ FILE: DataStructures/Graph/IDirectedWeightedGraph.cs ================================================ namespace DataStructures.Graph; public interface IDirectedWeightedGraph { int Count { get; } Vertex?[] Vertices { get; } void AddEdge(Vertex startVertex, Vertex endVertex, double weight); Vertex AddVertex(T data); bool AreAdjacent(Vertex startVertex, Vertex endVertex); double AdjacentDistance(Vertex startVertex, Vertex endVertex); IEnumerable?> GetNeighbors(Vertex vertex); void RemoveEdge(Vertex startVertex, Vertex endVertex); void RemoveVertex(Vertex vertex); } ================================================ FILE: DataStructures/Graph/Vertex.cs ================================================ namespace DataStructures.Graph; /// /// Implementation of graph vertex. /// /// Generic Type. public class Vertex { /// /// Gets vertex data. /// public T Data { get; } /// /// Gets an index of the vertex in graph adjacency matrix. /// public int Index { get; internal set; } /// /// Gets reference to the graph this vertex belongs to. /// public DirectedWeightedGraph? Graph { get; private set; } /// /// Initializes a new instance of the class. /// /// Vertex data. Generic type. /// Index of the vertex in graph adjacency matrix. /// Graph this vertex belongs to. public Vertex(T data, int index, DirectedWeightedGraph? graph) { Data = data; Index = index; Graph = graph; } /// /// Initializes a new instance of the class. /// /// Vertex data. Generic type. /// Index of the vertex in graph adjacency matrix. public Vertex(T data, int index) { Data = data; Index = index; } /// /// Sets graph reference to the null. This method called when vertex removed from the graph. /// public void SetGraphNull() => Graph = null; /// /// Override of base ToString method. Prints vertex data and index in graph adjacency matrix. /// /// String which contains vertex data and index in graph adjacency matrix.. public override string ToString() => $"Vertex Data: {Data}, Index: {Index}"; } ================================================ FILE: DataStructures/Hashing/Entry.cs ================================================ namespace DataStructures.Hashing; /// /// Entry in the hash table. /// /// Type of the key. /// Type of the value. /// /// This class is used to store the key-value pairs in the hash table. /// public class Entry(TKey key, TValue value) { public TKey? Key { get; set; } = key; public TValue? Value { get; set; } = value; } ================================================ FILE: DataStructures/Hashing/HashTable.cs ================================================ using DataStructures.Hashing.NumberTheory; namespace DataStructures.Hashing; /// /// Hash table implementation. /// /// Type of the key. /// Type of the value. public class HashTable { private const int DefaultCapacity = 16; private const float DefaultLoadFactor = 0.75f; private readonly float loadFactor; private int capacity; private int size; private int threshold; private Entry?[] entries; /// /// Gets the number of elements in the hash table. /// public int Count => size; /// /// Gets the capacity of the hash table. /// public int Capacity => capacity; /// /// Gets the load factor of the hash table. /// public float LoadFactor => loadFactor; /// /// Gets the keys in the hash table. /// public IEnumerable Keys => entries.Where(e => e != null).Select(e => e!.Key!); /// /// Gets the values in the hash table. /// public IEnumerable Values => entries.Where(e => e != null).Select(e => e!.Value!); /// /// Gets or sets the value associated with the specified key. /// /// Key to get or set. /// Value associated with the key. public TValue this[TKey? key] { get { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } var entry = FindEntry(key) ?? throw new KeyNotFoundException(); return entry.Value!; } set { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } var entry = FindEntry(key) ?? throw new KeyNotFoundException(); entry.Value = value; } } /// /// Initializes a new instance of the class. /// /// Initial capacity of the hash table. /// Load factor of the hash table. /// Thrown when is less than or equal to 0. /// Thrown when is less than or equal to 0. /// Thrown when is greater than 1. /// /// is rounded to the next prime number. /// /// /// public HashTable(int capacity = DefaultCapacity, float loadFactor = DefaultLoadFactor) { if (capacity <= 0) { throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than 0"); } if (loadFactor <= 0) { throw new ArgumentOutOfRangeException(nameof(loadFactor), "Load factor must be greater than 0"); } if (loadFactor > 1) { throw new ArgumentOutOfRangeException(nameof(loadFactor), "Load factor must be less than or equal to 1"); } this.capacity = PrimeNumber.NextPrime(capacity); this.loadFactor = loadFactor; threshold = (int)(this.capacity * loadFactor); entries = new Entry[this.capacity]; } /// /// Adds a key-value pair to the hash table. /// /// Key to add. /// Value to add. /// Thrown when is null. /// Thrown when already exists in the hash table. /// /// If the number of elements in the hash table is greater than or equal to the threshold, the hash table is resized. /// public void Add(TKey? key, TValue? value) { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } if (size >= threshold) { Resize(); } var index = GetIndex(key); if (entries[index] != null && EqualityComparer.Default.Equals(entries[index]!.Key!, key)) { throw new ArgumentException("Key already exists"); } if (EqualityComparer.Default.Equals(value, default)) { throw new ArgumentNullException(nameof(value)); } entries[index] = new Entry(key!, value!); size++; } /// /// Removes the key-value pair associated with the specified key. /// /// Key to remove. /// True if the key-value pair was removed, false otherwise. /// Thrown when is null. /// /// If the number of elements in the hash table is less than or equal to the threshold divided by 4, the hash table is resized. /// public bool Remove(TKey? key) { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } var index = GetIndex(key); if (entries[index] == null) { return false; } entries[index] = null; size--; if (size <= threshold / 4) { Resize(); } return true; } /// /// Find the index of the entry associated with the specified key. /// /// Key to find. /// Index of the entry associated with the key. /// Thrown when is null. public int GetIndex(TKey? key) { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } var hash = key!.GetHashCode(); var index = hash % capacity; if (index < 0) { index += capacity; } return index; } /// /// Finds the entry associated with the specified key. /// /// Key to find. /// Entry associated with the key. /// Thrown when is null. /// /// This method uses internally. /// public Entry? FindEntry(TKey? key) { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } var index = GetIndex(key); return entries[index]; } /// /// Checks if the hash table contains the specified key. /// /// Key to check. /// True if the hash table contains the key, false otherwise. /// Thrown when is null. /// /// This method uses internally. /// public bool ContainsKey(TKey? key) { if (EqualityComparer.Default.Equals(key, default)) { throw new ArgumentNullException(nameof(key)); } return FindEntry(key) != null; } /// /// Checks if the hash table contains the specified value. /// /// Value to check. /// True if the hash table contains the value, false otherwise. public bool ContainsValue(TValue? value) { if (EqualityComparer.Default.Equals(value, default)) { throw new ArgumentNullException(nameof(value)); } return entries.Any(e => e != null && e.Value!.Equals(value)); } /// /// Clears the hash table. /// /// /// This method resets the capacity of the hash table to the default capacity. /// public void Clear() { capacity = DefaultCapacity; threshold = (int)(capacity * loadFactor); entries = new Entry[capacity]; size = 0; } /// /// Resizes the hash table. /// /// /// This method doubles or halves the capacity of the hash table and rehashes all the elements. /// public void Resize() { var newCapacity = size <= threshold / 2 ? Math.Max(capacity / 2, DefaultCapacity) : capacity * 2; var newEntries = new Entry[newCapacity]; foreach (var entry in entries) { if (entry == null) { continue; } var index = entry.Key!.GetHashCode() % newCapacity; if (index < 0) { index += newCapacity; } newEntries[index] = entry; } capacity = newCapacity; threshold = (int)(capacity * loadFactor); entries = newEntries; } } ================================================ FILE: DataStructures/Hashing/NumberTheory/PrimeNumber.cs ================================================ namespace DataStructures.Hashing.NumberTheory; /// /// Class for prime number operations. /// /// /// A prime number is a natural number greater than 1 that is not a product of two smaller natural numbers. /// public static class PrimeNumber { /// /// Checks if a number is prime or not. /// /// Number to check. /// True if number is prime, false otherwise. public static bool IsPrime(int number) { if (number <= 1) { return false; } if (number <= 3) { return true; } if (number % 2 == 0 || number % 3 == 0) { return false; } for (int i = 5; i * i <= number; i += 6) { if (number % i == 0 || number % (i + 2) == 0) { return false; } } return true; } /// /// Gets the next prime number. /// /// Number to start from. /// Factor to multiply the number by. /// True to get the previous prime number, false otherwise. /// The next prime number. public static int NextPrime(int number, int factor = 1, bool desc = false) { number = factor * number; int firstValue = number; while (!IsPrime(number)) { number += desc ? -1 : 1; } if (number == firstValue) { return NextPrime( number + (desc ? -1 : 1), factor, desc); } return number; } } ================================================ FILE: DataStructures/Heap/BinaryHeap.cs ================================================ namespace DataStructures.Heap; /// /// A generic implementation of a binary heap. /// /// /// A binary heap is a complete binary tree that satisfies the heap property; /// that is every node in the tree compares greater/less than or equal to its left and right /// child nodes. Note that this is different from a binary search tree, where every node /// must be the largest/smallest node of all of its children. /// Although binary heaps are not very efficient, they are (probably) the simpliest heaps /// to understand and implement. /// More information: https://en.wikipedia.org/wiki/Binary_heap . /// /// Type of elements in binary heap. public class BinaryHeap { /// /// Comparer to use when comparing elements. /// private readonly Comparer comparer; /// /// List to hold the elements of the heap. /// private readonly List data; /// /// Initializes a new instance of the class. /// public BinaryHeap() { data = []; comparer = Comparer.Default; } /// /// Initializes a new instance of the class with a custom comparision function. /// /// The custom comparing function to use to compare elements. public BinaryHeap(Comparer customComparer) { data = []; comparer = customComparer; } /// /// Gets the number of elements in the heap. /// public int Count => data.Count; /// /// Add an element to the binary heap. /// /// /// Adding to the heap is done by append the element to the end of the backing list, /// and pushing the added element up until the heap property is restored. /// /// The element to add to the heap. /// Thrown if element is already in heap. public void Push(T element) { data.Add(element); HeapifyUp(data.Count - 1); } /// /// Remove the top/root of the binary heap (ie: the largest/smallest element). /// /// /// Removing from the heap is done by swapping the top/root with the last element in /// the backing list, removing the last element, and pushing the new root down /// until the heap property is restored. /// /// The top/root of the heap. /// Thrown if heap is empty. public T Pop() { if (Count == 0) { throw new InvalidOperationException("Heap is empty!"); } var elem = data[0]; data[0] = data[^1]; data.RemoveAt(data.Count - 1); HeapifyDown(0); return elem; } /// /// Return the top/root of the heap without removing it. /// /// The top/root of the heap. /// Thrown if heap is empty. public T Peek() { if (Count == 0) { throw new InvalidOperationException("Heap is empty!"); } return data[0]; } /// /// Returns element if it compares larger to the top/root of the heap, else /// inserts element into the heap and returns the top/root of the heap. /// /// The element to check/insert. /// element if element compares larger than top/root of heap, top/root of heap otherwise. public T PushPop(T element) { if (Count == 0) { return element; } if (comparer.Compare(element, data[0]) < 0) { var tmp = data[0]; data[0] = element; HeapifyDown(0); return tmp; } return element; } /// /// Check if element is in the heap. /// /// The element to check for. /// true if element is in the heap, false otherwise. public bool Contains(T element) => data.Contains(element); /// /// Remove an element from the heap. /// /// /// In removing an element from anywhere in the heap, we only need to push down or up /// the replacement value depending on how the removed value compares to its /// replacement value. /// /// The element to remove from the heap. /// Thrown if element is not in heap. public void Remove(T element) { var idx = data.IndexOf(element); if (idx == -1) { throw new ArgumentException($"{element} not in heap!"); } Swap(idx, data.Count - 1); var tmp = data[^1]; data.RemoveAt(data.Count - 1); if (idx < data.Count) { if (comparer.Compare(tmp, data[idx]) > 0) { HeapifyDown(idx); } else { HeapifyUp(idx); } } } /// /// Swap the elements in the heap array at the given indices. /// /// First index. /// Second index. private void Swap(int idx1, int idx2) { var tmp = data[idx1]; data[idx1] = data[idx2]; data[idx2] = tmp; } /// /// Recursive function to restore heap properties. /// /// /// Restores heap property by swapping the element at /// with its parent if the element compares greater than its parent. /// /// The element to check with its parent. private void HeapifyUp(int elemIdx) { var parent = (elemIdx - 1) / 2; if (parent >= 0 && comparer.Compare(data[elemIdx], data[parent]) > 0) { Swap(elemIdx, parent); HeapifyUp(parent); } } /// /// Recursive function to restore heap properties. /// /// /// Restores heap property by swapping the element at /// with the larger of its children. /// /// The element to check with its children. private void HeapifyDown(int elemIdx) { var left = 2 * elemIdx + 1; var right = 2 * elemIdx + 2; var leftLargerThanElem = left < Count && comparer.Compare(data[elemIdx], data[left]) < 0; var rightLargerThanElem = right < Count && comparer.Compare(data[elemIdx], data[right]) < 0; var leftLargerThanRight = left < Count && right < Count && comparer.Compare(data[left], data[right]) > 0; if (leftLargerThanElem && leftLargerThanRight) { Swap(elemIdx, left); HeapifyDown(left); } else if (rightLargerThanElem && !leftLargerThanRight) { Swap(elemIdx, right); HeapifyDown(right); } } } ================================================ FILE: DataStructures/Heap/FibonacciHeap/FHeapNode.cs ================================================ namespace DataStructures.Heap.FibonacciHeap; /// /// These FHeapNodes are the bulk of the data structure. The have pointers to /// their parent, a left and right sibling, and to a child. A node and its /// siblings comprise a circularly doubly linked list. /// /// A type that can be compared. public class FHeapNode where T : IComparable { /// /// Initializes a new instance of the class. /// /// An item in the Fibonacci heap. public FHeapNode(T key) { Key = key; // Since even a single node must form a circularly doubly linked list, // initialize it as such Left = Right = this; Parent = Child = null; } /// /// Gets or sets the data of this node. /// public T Key { get; set; } /// /// Gets or sets a reference to the parent. /// public FHeapNode? Parent { get; set; } /// /// Gets or sets a reference to the left sibling. /// public FHeapNode Left { get; set; } /// /// Gets or sets a reference to the right sibling. /// public FHeapNode Right { get; set; } /// /// Gets or sets a reference to one of the children, there may be more that /// are siblings the this child, however this structure only maintains a /// reference to one of them. /// public FHeapNode? Child { get; set; } /// /// Gets or sets a value indicating whether this node has been marked, /// used in some operations. /// public bool Mark { get; set; } /// /// Gets or sets the number of nodes in the child linked list. /// public int Degree { get; set; } public void SetSiblings(FHeapNode left, FHeapNode right) { Left = left; Right = right; } /// /// A helper function to add a node to the right of this one in the current /// circularly doubly linked list. /// /// A node to go in the linked list. public void AddRight(FHeapNode node) { Right.Left = node; node.Right = Right; node.Left = this; Right = node; } /// /// Similar to AddRight, but adds the node as a sibling to the child node. /// /// A node to add to the child list of this node. public void AddChild(FHeapNode node) { Degree++; if (Child == null) { Child = node; Child.Parent = this; Child.Left = Child.Right = Child; return; } Child.AddRight(node); } /// /// Remove this item from the linked list it's in. /// public void Remove() { Left.Right = Right; Right.Left = Left; } /// /// Combine the linked list that otherList sits inside, with the /// linked list this is in. Do this by cutting the link between this node, /// and the node to the right of this, and inserting the contents of the /// otherList in between. /// /// /// A node from another list whose elements we want /// to concatenate to this list. /// public void ConcatenateRight(FHeapNode otherList) { Right.Left = otherList.Left; otherList.Left.Right = Right; Right = otherList; otherList.Left = this; } } ================================================ FILE: DataStructures/Heap/FibonacciHeap/FibonacciHeap.cs ================================================ namespace DataStructures.Heap.FibonacciHeap; /// /// A generic implementation of a Fibonacci heap. /// /// /// /// A Fibonacci heap is similar to a standard binary heap /// , however it uses concepts /// of amortized analysis to provide theoretical speedups on common operations like /// insert, union, and decrease-key while maintaining the same speed on all other /// operations. /// /// /// In practice, Fibonacci heaps are more complicated than binary heaps and require /// a large input problems before the benifits of the theoretical speed up /// begin to show. /// /// /// References: /// [1] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, /// and Clifford Stein. 2009. Introduction to Algorithms, Third Edition (3rd. ed.). /// The MIT Press. /// /// /// Type of elements in binary heap. public class FibonacciHeap where T : IComparable { /// /// Gets or sets the count of the number of nodes in the Fibonacci heap. /// public int Count { get; set; } /// /// Gets or sets a reference to the MinItem. The MinItem and all of its siblings /// comprise the root list, a list of trees that satisfy the heap property and /// are joined in a circularly doubly linked list. /// private FHeapNode? MinItem { get; set; } /// /// Add item x to this Fibonacci heap. /// /// /// To add an item to a Fibonacci heap, we simply add it to the "root list", /// a circularly doubly linked list where our minimum item sits. Since adding /// items to a linked list takes O(1) time, the overall time to perform this /// operation is O(1). /// /// An item to push onto the heap. /// /// A reference to the item as it is in the heap. This is used for /// operations like decresing key. /// public FHeapNode Push(T x) { Count++; var newItem = new FHeapNode(x); if (MinItem == null) { MinItem = newItem; } else { MinItem.AddRight(newItem); if (newItem.Key.CompareTo(MinItem.Key) < 0) { MinItem = newItem; } } return newItem; } /// /// Combines all the elements of two fibonacci heaps. /// /// /// To union two Fibonacci heaps is a single fibonacci heap is a single heap /// that contains all the elements of both heaps. This can be done in O(1) time /// by concatenating the root lists together. /// For more details on how two circularly linked lists are concatenated, see /// /// Finally, check to see which of this.MinItem and other.MinItem /// is smaller, and set this.MinItem accordingly /// This operation destroys other. /// /// /// Another heap whose elements we wish to add to this heap. /// The other heap will be destroyed as a result. /// public void Union(FibonacciHeap other) { // If there are no items in the other heap, then there is nothing to do. if (other.MinItem == null) { return; } // If this heap is empty, simply set it equal to the other heap if (MinItem == null) { // Set this heap to the other one MinItem = other.MinItem; Count = other.Count; // Destroy the other heap other.MinItem = null; other.Count = 0; return; } Count += other.Count; // MinItem.ConcatenateRight(other.MinItem); // Set the MinItem to the smaller of the two MinItems if (other.MinItem.Key.CompareTo(MinItem.Key) < 0) { MinItem = other.MinItem; } other.MinItem = null; other.Count = 0; } /// /// Return the MinItem and remove it from the heap. /// /// /// This function (with all of its helper functions) is the most complicated /// part of the Fibonacci Heap. However, it can be broken down into a few steps. /// /// /// Add the children of MinItem to the root list. Either one of these children, /// or another of the items in the root list is a candidate to become the new /// MinItem. /// /// /// Remove the MinItem from the root list and appoint a new MinItem temporarily. /// /// /// what's left /// of the heap. /// /// /// /// The minimum item from the heap. public T Pop() { FHeapNode? z = null; if (MinItem == null) { throw new InvalidOperationException("Heap is empty!"); } z = MinItem; // Since z is leaving the heap, add its children to the root list if (z.Child != null) { foreach (var x in SiblingIterator(z.Child)) { x.Parent = null; } // This effectively adds each child x to the root list z.ConcatenateRight(z.Child); } if (Count == 1) { MinItem = null; Count = 0; return z.Key; } // Temporarily reassign MinItem to an arbitrary item in the root // list MinItem = MinItem.Right; // Remove the old MinItem from the root list altogether z.Remove(); // Consolidate the heap Consolidate(); Count -= 1; return z.Key; } /// /// A method to see what's on top of the heap without changing its structure. /// /// /// Returns the top element without popping it from the structure of /// the heap. /// public T Peek() { if (MinItem == null) { throw new InvalidOperationException("The heap is empty"); } return MinItem.Key; } /// /// Reduce the key of x to be k. /// /// /// k must be less than x.Key, increasing the key of an item is not supported. /// /// The item you want to reduce in value. /// The new value for the item. public void DecreaseKey(FHeapNode x, T k) { if (MinItem == null) { throw new ArgumentException($"{nameof(x)} is not from the heap"); } if (x.Key == null) { throw new ArgumentException("x has no value"); } if (k.CompareTo(x.Key) > 0) { throw new InvalidOperationException("Value cannot be increased"); } x.Key = k; var y = x.Parent; if (y != null && x.Key.CompareTo(y.Key) < 0) { Cut(x, y); CascadingCut(y); } if (x.Key.CompareTo(MinItem.Key) < 0) { MinItem = x; } } /// /// Remove x from the child list of y. /// /// A child of y we just decreased the value of. /// The now former parent of x. protected void Cut(FHeapNode x, FHeapNode y) { if (MinItem == null) { throw new InvalidOperationException("Heap malformed"); } if (y.Degree == 1) { y.Child = null; MinItem.AddRight(x); } else if (y.Degree > 1) { x.Remove(); } else { throw new InvalidOperationException("Heap malformed"); } y.Degree--; x.Mark = false; x.Parent = null; } /// /// Rebalances the heap after the decrease operation takes place. /// /// An item that may no longer obey the heap property. protected void CascadingCut(FHeapNode y) { var z = y.Parent; if (z != null) { if (!y.Mark) { y.Mark = true; } else { Cut(y, z); CascadingCut(z); } } } /// /// /// Consolidate is analogous to Heapify in . /// /// /// First, an array A [0...D(H.n)] is created where H.n is the number /// of items in this heap, and D(x) is the max degree any node can have in a /// Fibonacci heap with x nodes. /// /// /// For each node x in the root list, try to add it to A[d] where /// d is the degree of x. /// If there is already a node in A[d], call it y, and make /// y a child of x. (Swap x and y beforehand if /// x is greater than y). Now that x has one more child, /// remove if from A[d] and add it to A[d+1] to reflect that its /// degree is one more. Loop this behavior until we find an empty spot to put /// x. /// /// /// With A all filled, empty the root list of the heap. And add each item /// from A into the root list, one by one, making sure to keep track of /// which is smallest. /// /// protected void Consolidate() { if (MinItem == null) { return; } // There's a fact in Intro to Algorithms: // "the max degree of any node in an n-node fibonacci heap is O(lg(n)). // In particular, we shall show that D(n) <= floor(log_phi(n)) where phi is // the golden ratio, defined in equation 3.24 as phi = (1 + sqrt(5))/2" // // For a proof, see [1] var maxDegree = 1 + (int)Math.Log(Count, (1 + Math.Sqrt(5)) / 2); // Create slots for every possible node degree of x var a = new FHeapNode?[maxDegree]; var siblings = SiblingIterator(MinItem).ToList(); foreach (var w in siblings) { var x = w; var d = x.Degree; var y = a[d]; // While A[d] is not empty, we can't blindly put x here while (y != null) { if (x.Key.CompareTo(y.Key) > 0) { // Exchange x and y var temp = x; x = y; y = temp; } // Make y a child of x FibHeapLink(y, x); // Empty out this spot since x now has a higher degree a[d] = null; // Add 1 to x's degree before going back into the loop d++; y = a[d]; } // Now that there's an empty spot for x, place it there a[d] = x; } ReconstructHeap(a); } /// /// Reconstructs the heap based on the array of node degrees created by the consolidate step. /// /// An array of FHeapNodes where a[i] represents a node of degree i. private void ReconstructHeap(FHeapNode?[] a) { // Once all items are in A, empty out the root list MinItem = null; for (var i = 0; i < a.Length; i++) { var r = a[i]; if (r == null) { continue; } if (MinItem == null) { // If the root list is completely empty, make this the new // MinItem MinItem = r; // Make a new root list with just this item. Make sure to make // it its own list. MinItem.SetSiblings(MinItem, MinItem); MinItem.Parent = null; } else { // Add A[i] to the root list MinItem.AddRight(r); // If this item is smaller, make it the new min item if (MinItem.Key.CompareTo(r.Key) > 0) { MinItem = a[i]; } } } } /// /// Make y a child of x. /// /// A node to become the child of x. /// A node to become the parent of y. private void FibHeapLink(FHeapNode y, FHeapNode x) { y.Remove(); x.AddChild(y); y.Mark = false; } /// /// A helper function to iterate through all the siblings of this node in the /// circularly doubly linked list. /// /// A node we want the siblings of. /// An iterator over all of the siblings. private IEnumerable> SiblingIterator(FHeapNode node) { var currentNode = node; yield return currentNode; currentNode = node.Right; while (currentNode != node) { yield return currentNode; currentNode = currentNode.Right; } } } ================================================ FILE: DataStructures/Heap/MinMaxHeap.cs ================================================ namespace DataStructures.Heap; /// /// This class implements min-max heap. /// It provides functionality of both min-heap and max-heap with the same time complexity. /// Therefore it provides constant time retrieval and logarithmic time removal /// of both the minimum and maximum elements in it. /// /// Generic type. public class MinMaxHeap { private readonly List heap; /// /// Initializes a new instance of the class that contains /// elements copied from a specified enumerable collection and that uses a specified comparer. /// /// The enumerable collection to be copied. /// The default comparer to use for comparing objects. public MinMaxHeap(IEnumerable? collection = null, IComparer? comparer = null) { Comparer = comparer ?? Comparer.Default; collection ??= Enumerable.Empty(); heap = collection.ToList(); for (var i = Count / 2 - 1; i >= 0; --i) { PushDown(i); } } /// /// Gets the . object that is used to order the values in the . /// public IComparer Comparer { get; } /// /// Gets the number of elements in the . /// public int Count => heap.Count; /// /// Adds an element to the heap. /// /// The element to add to the heap. public void Add(T item) { heap.Add(item); PushUp(Count - 1); } /// /// Removes the maximum node from the heap and returns its value. /// /// Thrown if heap is empty. /// Value of the removed maximum node. public T ExtractMax() { if (Count == 0) { throw new InvalidOperationException("Heap is empty"); } var max = GetMax(); RemoveNode(GetMaxNodeIndex()); return max; } /// /// Removes the minimum node from the heap and returns its value. /// /// Thrown if heap is empty. /// Value of the removed minimum node. public T ExtractMin() { if (Count == 0) { throw new InvalidOperationException("Heap is empty"); } var min = GetMin(); RemoveNode(0); return min; } /// /// Gets the maximum value in the heap, as defined by the comparer. /// /// Thrown if heap is empty. /// The maximum value in the heap. public T GetMax() { if (Count == 0) { throw new InvalidOperationException("Heap is empty"); } return heap[GetMaxNodeIndex()]; } /// /// Gets the minimum value in the heap, as defined by the comparer. /// /// Thrown if heap is empty. /// The minimum value in the heap. public T GetMin() { if (Count == 0) { throw new InvalidOperationException("Heap is empty"); } return heap[0]; } /// /// Finds maximum value among children and grandchildren of the specified node. /// /// Index of the node in the Heap array. /// Index of the maximum descendant. private int IndexOfMaxChildOrGrandchild(int index) { var descendants = new[] { 2 * index + 1, 2 * index + 2, 4 * index + 3, 4 * index + 4, 4 * index + 5, 4 * index + 6, }; var resIndex = descendants[0]; foreach (var descendant in descendants) { if (descendant >= Count) { break; } if (Comparer.Compare(heap[descendant], heap[resIndex]) > 0) { resIndex = descendant; } } return resIndex; } /// /// Finds minumum value among children and grandchildren of the specified node. /// /// Index of the node in the Heap array. /// Index of the minimum descendant. private int IndexOfMinChildOrGrandchild(int index) { var descendants = new[] { 2 * index + 1, 2 * index + 2, 4 * index + 3, 4 * index + 4, 4 * index + 5, 4 * index + 6 }; var resIndex = descendants[0]; foreach (var descendant in descendants) { if (descendant >= Count) { break; } if (Comparer.Compare(heap[descendant], heap[resIndex]) < 0) { resIndex = descendant; } } return resIndex; } private int GetMaxNodeIndex() { return Count switch { 0 => throw new InvalidOperationException("Heap is empty"), 1 => 0, 2 => 1, _ => Comparer.Compare(heap[1], heap[2]) > 0 ? 1 : 2, }; } private bool HasChild(int index) => index * 2 + 1 < Count; private bool IsGrandchild(int node, int grandchild) => grandchild > 2 && Grandparent(grandchild) == node; /// /// Checks if node at index belongs to Min or Max level of the heap. /// Root node belongs to Min level, its children - Max level, /// its grandchildren - Min level, and so on. /// /// Index to check. /// true if index is at Min level; false if it is at Max Level. private bool IsMinLevelIndex(int index) { // For all Min levels, value (index + 1) has the leftmost bit set to '1' at even position. const uint minLevelsBits = 0x55555555; const uint maxLevelsBits = 0xAAAAAAAA; return ((index + 1) & minLevelsBits) > ((index + 1) & maxLevelsBits); } private int Parent(int index) => (index - 1) / 2; private int Grandparent(int index) => ((index - 1) / 2 - 1) / 2; /// /// Assuming that children sub-trees are valid heaps, pushes node to lower levels /// to make heap valid. /// /// Node index. private void PushDown(int index) { if (IsMinLevelIndex(index)) { PushDownMin(index); } else { PushDownMax(index); } } private void PushDownMax(int index) { if (!HasChild(index)) { return; } var maxIndex = IndexOfMaxChildOrGrandchild(index); // If smaller element are put at min level (as result of swaping), it doesn't affect sub-tree validity. // If smaller element are put at max level, PushDownMax() should be called for that node. if (IsGrandchild(index, maxIndex)) { if (Comparer.Compare(heap[maxIndex], heap[index]) > 0) { SwapNodes(maxIndex, index); if (Comparer.Compare(heap[maxIndex], heap[Parent(maxIndex)]) < 0) { SwapNodes(maxIndex, Parent(maxIndex)); } PushDownMax(maxIndex); } } else { if (Comparer.Compare(heap[maxIndex], heap[index]) > 0) { SwapNodes(maxIndex, index); } } } private void PushDownMin(int index) { if (!HasChild(index)) { return; } var minIndex = IndexOfMinChildOrGrandchild(index); // If bigger element are put at max level (as result of swaping), it doesn't affect sub-tree validity. // If bigger element are put at min level, PushDownMin() should be called for that node. if (IsGrandchild(index, minIndex)) { if (Comparer.Compare(heap[minIndex], heap[index]) < 0) { SwapNodes(minIndex, index); if (Comparer.Compare(heap[minIndex], heap[Parent(minIndex)]) > 0) { SwapNodes(minIndex, Parent(minIndex)); } PushDownMin(minIndex); } } else { if (Comparer.Compare(heap[minIndex], heap[index]) < 0) { SwapNodes(minIndex, index); } } } /// /// Having a new node in the heap, swaps this node with its ancestors to make heap valid. /// For node at min level. If new node is less than its parent, then it is surely less then /// all other nodes on max levels on path to the root of the heap. So node are pushed up, by /// swaping with its grandparent, until they are ordered correctly. /// For node at max level algorithm is analogical. /// /// Index of the new node. private void PushUp(int index) { if (index == 0) { return; } var parent = Parent(index); if (IsMinLevelIndex(index)) { if (Comparer.Compare(heap[index], heap[parent]) > 0) { SwapNodes(index, parent); PushUpMax(parent); } else { PushUpMin(index); } } else { if (Comparer.Compare(heap[index], heap[parent]) < 0) { SwapNodes(index, parent); PushUpMin(parent); } else { PushUpMax(index); } } } private void PushUpMax(int index) { if (index > 2) { var grandparent = Grandparent(index); if (Comparer.Compare(heap[index], heap[grandparent]) > 0) { SwapNodes(index, grandparent); PushUpMax(grandparent); } } } private void PushUpMin(int index) { if (index > 2) { var grandparent = Grandparent(index); if (Comparer.Compare(heap[index], heap[grandparent]) < 0) { SwapNodes(index, grandparent); PushUpMin(grandparent); } } } private void RemoveNode(int index) { SwapNodes(index, Count - 1); heap.RemoveAt(Count - 1); if (Count != 0) { PushDown(index); } } private void SwapNodes(int i, int j) { var temp = heap[i]; heap[i] = heap[j]; heap[j] = temp; } } ================================================ FILE: DataStructures/Heap/PairingHeap/PairingHeap.cs ================================================ using System.Collections; namespace DataStructures.Heap.PairingHeap; /// /// A pairing minMax heap implementation. /// /// Base type. public class PairingHeap(Sorting sortDirection = Sorting.Ascending) : IEnumerable where T : IComparable { private readonly Sorting sorting = sortDirection; private readonly IComparer comparer = new PairingNodeComparer(sortDirection, Comparer.Default); private readonly Dictionary>> mapping = []; private PairingHeapNode root = null!; public int Count { get; private set; } /// /// Insert a new Node [O(1)]. /// public void Insert(T newItem) { var newNode = new PairingHeapNode(newItem); root = RebuildHeap(root, newNode); Map(newItem, newNode); Count++; } /// /// Get the element from heap [O(log(n))]. /// public T Extract() { var minMax = root; RemoveMapping(minMax.Value, minMax); RebuildHeap(root.ChildrenHead); Count--; return minMax.Value; } /// /// Update heap key [O(log(n))]. /// public void UpdateKey(T currentValue, T newValue) { if (!mapping.ContainsKey(currentValue)) { throw new ArgumentException("Current value is not present in this heap."); } var node = mapping[currentValue]?.Where(x => x.Value.Equals(currentValue)).FirstOrDefault(); if (comparer.Compare(newValue, node!.Value) > 0) { throw new ArgumentException($"New value is not {(sorting != Sorting.Descending ? "less" : "greater")} than old value."); } UpdateNodeValue(currentValue, newValue, node); if (node == root) { return; } DeleteChild(node); root = RebuildHeap(root, node); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { return mapping.SelectMany(x => x.Value).Select(x => x.Value).GetEnumerator(); } /// /// Rebuild heap on action [O(log(n))]. /// private void RebuildHeap(PairingHeapNode headNode) { if (headNode == null) { return; } var passOneResult = new List>(); var current = headNode; if (current.Next == null) { headNode.Next = null!; headNode.Previous = null!; passOneResult.Add(headNode); } else { while (true) { if (current == null) { break; } if (current.Next != null) { var next = current.Next; var nextNext = next.Next; passOneResult.Add(RebuildHeap(current, next)); current = nextNext; } else { var lastInserted = passOneResult[^1]; passOneResult[^1] = RebuildHeap(lastInserted, current); break; } } } var passTwoResult = passOneResult[^1]; if (passOneResult.Count == 1) { root = passTwoResult; return; } for (var i = passOneResult.Count - 2; i >= 0; i--) { current = passOneResult[i]; passTwoResult = RebuildHeap(passTwoResult, current); } root = passTwoResult; } private PairingHeapNode RebuildHeap(PairingHeapNode node1, PairingHeapNode node2) { if (node2 != null) { node2.Previous = null!; node2.Next = null!; } if (node1 == null) { return node2!; } node1.Previous = null!; node1.Next = null!; if (node2 != null && comparer.Compare(node1.Value, node2.Value) <= 0) { AddChild(ref node1, node2); return node1; } AddChild(ref node2!, node1); return node2; } private void AddChild(ref PairingHeapNode parent, PairingHeapNode child) { if (parent.ChildrenHead == null) { parent.ChildrenHead = child; child.Previous = parent; return; } var head = parent.ChildrenHead; child.Previous = head; child.Next = head.Next; if (head.Next != null) { head.Next.Previous = child; } head.Next = child; } private void DeleteChild(PairingHeapNode node) { if (node.IsHeadChild) { var parent = node.Previous; if (node.Next != null) { node.Next.Previous = parent; } parent.ChildrenHead = node.Next!; } else { node.Previous.Next = node.Next; if (node.Next != null) { node.Next.Previous = node.Previous; } } } private void Map(T newItem, PairingHeapNode newNode) { if (mapping.ContainsKey(newItem)) { mapping[newItem].Add(newNode); } else { mapping[newItem] = [newNode]; } } private void UpdateNodeValue(T currentValue, T newValue, PairingHeapNode node) { RemoveMapping(currentValue, node); node.Value = newValue; Map(newValue, node); } private void RemoveMapping(T currentValue, PairingHeapNode node) { mapping[currentValue].Remove(node); if (mapping[currentValue].Count == 0) { mapping.Remove(currentValue); } } } ================================================ FILE: DataStructures/Heap/PairingHeap/PairingHeapNode.cs ================================================ namespace DataStructures.Heap.PairingHeap; /// /// Node represented the value and connections. /// /// Type, supported comparing. public class PairingHeapNode(T value) { public T Value { get; set; } = value; public PairingHeapNode ChildrenHead { get; set; } = null!; public bool IsHeadChild => Previous != null && Previous.ChildrenHead == this; public PairingHeapNode Previous { get; set; } = null!; public PairingHeapNode Next { get; set; } = null!; } ================================================ FILE: DataStructures/Heap/PairingHeap/PairingNodeComparer.cs ================================================ namespace DataStructures.Heap.PairingHeap; /// /// Node comparer. /// /// Node type. public class PairingNodeComparer(Sorting sortDirection, IComparer comparer) : IComparer where T : IComparable { private readonly bool isMax = sortDirection == Sorting.Descending; private readonly IComparer nodeComparer = comparer; public int Compare(T? x, T? y) { return !isMax ? CompareNodes(x, y) : CompareNodes(y, x); } private int CompareNodes(T? one, T? second) { return nodeComparer.Compare(one, second); } } ================================================ FILE: DataStructures/Heap/PairingHeap/Sorting.cs ================================================ namespace DataStructures.Heap.PairingHeap; public enum Sorting { /// /// Ascending order. /// Ascending = 0, /// /// Descending order. /// Descending = 1, } ================================================ FILE: DataStructures/InvertedIndex.cs ================================================ namespace DataStructures; /// /// Inverted index is the simplest form of document indexing, /// allowing performing boolean queries on text data. /// /// This realization is just simplified for better understanding the process of indexing /// and working on straightforward string inputs. /// public class InvertedIndex { private readonly Dictionary> invertedIndex = []; /// /// Build inverted index with source name and source content. /// /// Name of the source. /// Content of the source. public void AddToIndex(string sourceName, string sourceContent) { var context = sourceContent.Split(' ').Distinct(); foreach (var word in context) { if (!invertedIndex.ContainsKey(word)) { invertedIndex.Add(word, [sourceName]); } else { invertedIndex[word].Add(sourceName); } } } /// /// Returns the source names contains ALL terms inside at same time. /// /// List of terms. /// Source names. public IEnumerable And(IEnumerable terms) { var entries = terms .Select(term => invertedIndex .Where(x => x.Key.Equals(term)) .SelectMany(x => x.Value)) .ToList(); var intersection = entries .Skip(1) .Aggregate(new HashSet(entries.First()), (hashSet, enumerable) => { hashSet.IntersectWith(enumerable); return hashSet; }); return intersection; } /// /// Returns the source names contains AT LEAST ONE from terms inside. /// /// List of terms. /// Source names. public IEnumerable Or(IEnumerable terms) { var sources = new List(); foreach (var term in terms) { var source = invertedIndex .Where(x => x.Key.Equals(term)) .SelectMany(x => x.Value); sources.AddRange(source); } return sources.Distinct(); } } ================================================ FILE: DataStructures/LinkedList/CircularLinkedList/CircularLinkedList.cs ================================================ namespace DataStructures.LinkedList.CircularLinkedList { /// /// CircularLinkedList. /// @author Mohit Singh. mohit-gogitter /// /// The generic type parameter. public class CircularLinkedList { /// /// Points to the last node in the Circular Linked List. /// private CircularLinkedListNode? tail; /// /// Initializes a new instance of the class. /// public CircularLinkedList() { tail = null; } /// /// Gets the head node (tail.Next) of the Circular Linked List. /// public CircularLinkedListNode? GetHead() { return tail?.Next; } /// /// Determines whether the Circular Linked List is empty. /// /// True if the list is empty; otherwise, false. public bool IsEmpty() { return tail == null; } /// /// Inserts a new node at the beginning of the Circular Linked List. /// /// The data to insert into the new node. public void InsertAtBeginning(T data) { var newNode = new CircularLinkedListNode(data); if (IsEmpty()) { tail = newNode; tail.Next = tail; } else { newNode.Next = tail!.Next; tail.Next = newNode; } } /// /// Inserts a new node at the end of the Circular Linked List. /// /// The data to insert into the new node. public void InsertAtEnd(T data) { var newNode = new CircularLinkedListNode(data); if (IsEmpty()) { tail = newNode; tail.Next = tail; } else { newNode.Next = tail!.Next; tail.Next = newNode; tail = newNode; } } /// /// Inserts a new node after a specific value in the list. /// /// The value to insert the node after. /// The data to insert into the new node. public void InsertAfter(T value, T data) { if (IsEmpty()) { throw new InvalidOperationException("List is empty."); } var current = tail!.Next; do { if (current!.Data!.Equals(value)) { var newNode = new CircularLinkedListNode(data); newNode.Next = current.Next; current.Next = newNode; return; } current = current.Next; } while (current != tail.Next); } /// /// Deletes a node with a specific value from the list. /// /// The value of the node to delete. public void DeleteNode(T value) { if (IsEmpty()) { throw new InvalidOperationException("List is empty."); } var current = tail!.Next; var previous = tail; do { if (current!.Data!.Equals(value)) { if (current == tail && current.Next == tail) { tail = null; } else if (current == tail) { previous!.Next = tail.Next; tail = previous; } else if (current == tail.Next) { tail.Next = current.Next; } else { previous!.Next = current.Next; } return; } previous = current; current = current.Next; } while (current != tail!.Next); } } } ================================================ FILE: DataStructures/LinkedList/CircularLinkedList/CircularLinkedListNode.cs ================================================ namespace DataStructures.LinkedList.CircularLinkedList { /// /// Represents a node in the Circular Linked List. /// Each node contains generic data and a reference to the next node. /// /// The type of the data stored in the node. /// /// Initializes a new instance of the class. /// /// The data to be stored in the node. public class CircularLinkedListNode(T data) { /// /// Gets or sets the data for the node. /// public T Data { get; set; } = data; /// /// Gets or sets the reference to the next node in the list. /// public CircularLinkedListNode? Next { get; set; } } } ================================================ FILE: DataStructures/LinkedList/DoublyLinkedList/DoublyLinkedList.cs ================================================ using Utilities.Exceptions; namespace DataStructures.LinkedList.DoublyLinkedList; /// /// Similar to a Singly Linked List but each node contains a refenrence to the previous node in the list. /// is a doubly linked list. /// Compared to singly linked lists it can be traversed forwards and backwards. /// Adding a node to a doubly linked list is simpler because ever node contains a reference to the previous node. /// /// Generic type. public class DoublyLinkedList { /// /// Initializes a new instance of the class. /// /// Data of the original head of the list. public DoublyLinkedList(T data) { Head = new DoublyLinkedListNode(data); Tail = Head; Count = 1; } /// /// Initializes a new instance of the class from an enumerable. /// /// Enumerable of data to be stored in the list. public DoublyLinkedList(IEnumerable data) { foreach (var d in data) { Add(d); } } /// /// Gets the amount of nodes in the list. /// public int Count { get; private set; } /// /// Gets or sets the first node of the list. /// private DoublyLinkedListNode? Head { get; set; } /// /// Gets or sets the last node of the list. /// private DoublyLinkedListNode? Tail { get; set; } /// /// Replaces the Head of the list with the new value. /// /// Value for the new Head of the list. /// The new Head node. public DoublyLinkedListNode AddHead(T data) { var node = new DoublyLinkedListNode(data); if (Head is null) { Head = node; Tail = node; Count = 1; return node; } Head.Previous = node; node.Next = Head; Head = node; Count++; return node; } /// /// Adds a new value at the end of the list. /// /// New value to be added to the list. /// The new node created based on the new value. public DoublyLinkedListNode Add(T data) { if (Head is null) { return AddHead(data); } var node = new DoublyLinkedListNode(data); Tail!.Next = node; node.Previous = Tail; Tail = node; Count++; return node; } /// /// Adds a new value after an existing node. /// /// New value to be added to the list. /// An existing node in the list. /// The new node created based on the new value. public DoublyLinkedListNode AddAfter(T data, DoublyLinkedListNode existingNode) { if (existingNode == Tail) { return Add(data); } var node = new DoublyLinkedListNode(data); node.Next = existingNode.Next; node.Previous = existingNode; existingNode.Next = node; if (node.Next is not null) { node.Next.Previous = node; } Count++; return node; } /// /// Gets an enumerable based on the data in the list. /// /// The data in the list in an IEnumerable. It can used to create a list or an array with LINQ. public IEnumerable GetData() { var current = Head; while (current is not null) { yield return current.Data; current = current.Next; } } /// /// Gets an enumerable based on the data in the list reversed. /// /// The data in the list in an IEnumerable. It can used to create a list or an array with LINQ. public IEnumerable GetDataReversed() { var current = Tail; while (current is not null) { yield return current.Data; current = current.Previous; } } /// /// Reverses the list. Because of how doubly linked list are structured this is not a complex action. /// public void Reverse() { var current = Head; DoublyLinkedListNode? temp = null; while (current is not null) { temp = current.Previous; current.Previous = current.Next; current.Next = temp; current = current.Previous; } Tail = Head; // temp can be null on empty list if (temp is not null) { Head = temp.Previous; } } /// /// Looks for a node in the list that contains the value of the parameter. /// /// Value to be looked for in a node. /// The node in the list the has the paramater as a value or null if not found. public DoublyLinkedListNode Find(T data) { var current = Head; while (current is not null) { if (current.Data is null && data is null || current.Data is not null && current.Data.Equals(data)) { return current; } current = current.Next; } throw new ItemNotFoundException(); } /// /// Looks for a node in the list that contains the value of the parameter. /// /// Position in the list. /// The node in the list the has the paramater as a value or null if not found. /// Thrown when position is negative or out range of the list. public DoublyLinkedListNode GetAt(int position) { if (position < 0 || position >= Count) { throw new ArgumentOutOfRangeException($"Max count is {Count}"); } var current = Head; for (var i = 0; i < position; i++) { current = current!.Next; } return current ?? throw new ArgumentOutOfRangeException($"{nameof(position)} must be an index in the list"); } /// /// Removes the Head and replaces it with the second node in the list. /// public void RemoveHead() { if (Head is null) { throw new InvalidOperationException(); } Head = Head.Next; if (Head is null) { Tail = null; Count = 0; return; } Head.Previous = null; Count--; } /// /// Removes the last node in the list. /// public void Remove() { if (Tail is null) { throw new InvalidOperationException("Cannot prune empty list"); } Tail = Tail.Previous; if (Tail is null) { Head = null; Count = 0; return; } Tail.Next = null; Count--; } /// /// Removes specific node. /// /// Node to be removed. public void RemoveNode(DoublyLinkedListNode node) { if (node == Head) { RemoveHead(); return; } if (node == Tail) { Remove(); return; } if (node.Previous is null || node.Next is null) { throw new ArgumentException( $"{nameof(node)} cannot have Previous or Next null if it's an internal node"); } node.Previous.Next = node.Next; node.Next.Previous = node.Previous; Count--; } /// /// Removes a node that contains the data from the parameter. /// /// Data to be removed form the list. public void Remove(T data) { var node = Find(data); RemoveNode(node); } /// /// Looks for the index of the node with the parameter as data. /// /// Data to look for. /// Returns the index of the node if it is found or -1 if the node is not found. public int IndexOf(T data) { var current = Head; var index = 0; while (current is not null) { if (current.Data is null && data is null || current.Data is not null && current.Data.Equals(data)) { return index; } index++; current = current.Next; } return -1; } /// /// List contains a node that has the parameter as data. /// /// Node to be removed. /// True if the node is found. False if it isn't. public bool Contains(T data) => IndexOf(data) != -1; } ================================================ FILE: DataStructures/LinkedList/DoublyLinkedList/DoublyLinkedListNode.cs ================================================ namespace DataStructures.LinkedList.DoublyLinkedList; /// /// Generic node class for Doubly Linked List. /// /// Generic type. /// /// Initializes a new instance of the class. /// /// Data to be stored in this node. public class DoublyLinkedListNode(T data) { /// /// Gets the data stored on this node. /// public T Data { get; } = data; /// /// Gets or sets the reference to the next node in the Doubly Linked List. /// public DoublyLinkedListNode? Next { get; set; } /// /// Gets or sets the reference to the previous node in the Doubly Linked List. /// public DoublyLinkedListNode? Previous { get; set; } } ================================================ FILE: DataStructures/LinkedList/SinglyLinkedList/SinglyLinkedList.cs ================================================ namespace DataStructures.LinkedList.SinglyLinkedList; public class SinglyLinkedList { // points to the start of the list private SinglyLinkedListNode? Head { get; set; } /// /// Adds new node to the start of the list, /// time complexity: O(1), /// space complexity: O(1). /// /// Contents of newly added node. /// Added list node. public SinglyLinkedListNode AddFirst(T data) { var newListElement = new SinglyLinkedListNode(data) { Next = Head, }; Head = newListElement; return newListElement; } /// /// Adds new node to the end of the list, /// time complexity: O(n), /// space complexity: O(1), /// where n - number of nodes in the list. /// /// Contents of newly added node. /// Added list node. public SinglyLinkedListNode AddLast(T data) { var newListElement = new SinglyLinkedListNode(data); // if head is null, the added element is the first, hence it is the head if (Head is null) { Head = newListElement; return newListElement; } // temp ListElement to avoid overwriting the original var tempElement = Head; // iterates through all elements while (tempElement.Next is not null) { tempElement = tempElement.Next; } // adds the new element to the last one tempElement.Next = newListElement; return newListElement; } /// /// Returns element at index in the list. /// /// Index of an element to be returned. /// Element at index . public T GetElementByIndex(int index) { if (index < 0) { throw new ArgumentOutOfRangeException(nameof(index)); } var tempElement = Head; for (var i = 0; tempElement is not null && i < index; i++) { tempElement = tempElement.Next; } if (tempElement is null) { throw new ArgumentOutOfRangeException(nameof(index)); } return tempElement.Data; } public int Length() { // checks if there is a head if (Head is null) { return 0; } var tempElement = Head; var length = 1; while (tempElement.Next is not null) { tempElement = tempElement.Next; length++; } return length; } public IEnumerable GetListData() { // temp ListElement to avoid overwriting the original var tempElement = Head; // all elements where a next attribute exists while (tempElement is not null) { yield return tempElement.Data; tempElement = tempElement.Next; } } public bool DeleteElement(T element) { var currentElement = Head; SinglyLinkedListNode? previousElement = null; // iterates through all elements while (currentElement is not null) { // checks if the element, which should get deleted is in this list element if (currentElement.Data is null && element is null || currentElement.Data is not null && currentElement.Data.Equals(element)) { // if element is head just take the next one as head if (currentElement.Equals(Head)) { Head = Head.Next; return true; } // else take the prev one and overwrite the next with the one behind the deleted if (previousElement is not null) { previousElement.Next = currentElement.Next; return true; } } // iterating previousElement = currentElement; currentElement = currentElement.Next; } return false; } /// /// Deletes the first element of the list. /// /// true if the operation is successul. public bool DeleteFirst() { // checks if the List is empty if (Head is null) { return false; } // if not, the head is overwritten with the next element and the old head is deleted Head = Head.Next; return true; } /// /// Deletes the last element of the list. /// /// returns true if the operation is successful. public bool DeleteLast() { // checks if the List is empty if (Head is null) { return false; } // checks if the List has only one element if (Head.Next is null) { Head = null; return true; } // if not, iterates through the list to the second last element and deletes the last one SinglyLinkedListNode? secondlast = Head; while (secondlast.Next?.Next is not null) { secondlast = secondlast.Next; } secondlast.Next = null; return true; } } ================================================ FILE: DataStructures/LinkedList/SinglyLinkedList/SinglyLinkedListNode.cs ================================================ namespace DataStructures.LinkedList.SinglyLinkedList; public class SinglyLinkedListNode(T data) { public T Data { get; } = data; public SinglyLinkedListNode? Next { get; set; } } ================================================ FILE: DataStructures/LinkedList/SkipList/SkipList.cs ================================================ using System.Diagnostics; namespace DataStructures.LinkedList.SkipList; /// /// Skip list implementation that is based on the singly linked list, /// but offers O(log n) time complexity on most operations. /// /// The type of the values in the list. /// /// Skip list nodes sorted by key. /// The "skip lanes" allow searching for a node in O(log n) time on average. /// The worst case performence is O(n) when the height of all nodes is 1 (very /// unluckily to happen on any decent list size). /// These two properties make the skip list an excellent data structure for /// implementing additional operations like finding min/max value in the list, /// finding values with the key in a given range, etc. /// /// Sourses: /// - "Skip Lists: A Probabilistic Alternative to Balanced Trees" by William Pugh. /// - https://en.wikipedia.org/wiki/Skip_list /// - https://iq.opengenus.org/skip-list/ /// - https://medium.com/simple-computer-science/data-structures-basics-skip-list-8b8c69f9a044 /// - https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/SkipList.java /// /// The key is hardcoded to be of type int to simplify the implementation, /// but it can be easily an any generic type that implements IComparable. /// [DebuggerDisplay("Count = {Count}")] public class SkipList { private const double Probability = 0.5; private readonly int maxLevels; private readonly SkipListNode head; private readonly SkipListNode tail; private readonly Random random = new Random(); /// /// Initializes a new instance of the class. /// /// Expected number of elements the list might contain. public SkipList(int capacity = 255) { maxLevels = (int)Math.Log2(capacity) + 1; head = new(int.MinValue, default(TValue), maxLevels); tail = new(int.MaxValue, default(TValue), maxLevels); for (int i = 0; i < maxLevels; i++) { head.Next[i] = tail; } } /// /// Gets the number of elements currently in the list. /// public int Count { get; private set; } /// /// Gets or sets the element with the specified key. /// /// The key is not present in the list. public TValue this[int key] { get { var previousNode = GetSkipNodes(key).First(); if (previousNode.Next[0].Key == key) { return previousNode.Next[0].Value!; } else { throw new KeyNotFoundException(); } } set => AddOrUpdate(key, value); } /// /// Adds an element with the specified key and value to the list. /// If an element with the same key already exists, updates its value. /// /// The key of the element to add. /// The value of the element to add. /// /// Time complexity: O(log n) where n is the number of elements in the list. /// public void AddOrUpdate(int key, TValue value) { var skipNodes = GetSkipNodes(key); var previousNode = skipNodes.First(); if (previousNode.Next[0].Key == key) { // Node with the given key already exists. // Update its value. previousNode.Next[0].Value = value; return; } // Node with the given key does not exist. // Insert the new one and update the skip nodes. var newNode = new SkipListNode(key, value, GetRandomHeight()); for (var level = 0; level < newNode.Height; level++) { newNode.Next[level] = skipNodes[level].Next[level]; skipNodes[level].Next[level] = newNode; } Count++; } /// /// Returns whether a value with the given key exists in the list. /// /// /// Time complexity: O(log n) where n is the number of elements in the list. /// public bool Contains(int key) { var previousNode = GetSkipNodes(key).First(); return previousNode.Next[0].Key == key; } /// /// Removes the value with the given key from the list. /// /// /// true if the value was removed; otherwise, false. /// /// /// Time complexity: O(log n) where n is the number of elements in the list. /// public bool Remove(int key) { var skipNodes = GetSkipNodes(key); var previousNode = skipNodes.First(); if (previousNode.Next[0].Key != key) { return false; } // Key exists in the list, remove it and update the skip nodes. var nodeToRemove = previousNode.Next[0]; for (var level = 0; level < nodeToRemove.Height; level++) { skipNodes[level].Next[level] = nodeToRemove.Next[level]; } Count--; return true; } /// /// Returns an enumerator that iterates through the list. /// /// /// Order of values is the ascending order of their keys. /// Time complexity: O(n) where n is the number of elements in the list. /// public IEnumerable GetValues() { var current = head.Next[0]; while (current.Key != tail.Key) { yield return current.Value!; current = current.Next[0]; } } /// /// Builds a list of skip nodes on each level that /// are closest, but smaller than the given key. /// /// /// The node on level 0 will point to the node with the given key, if it exists. /// Time complexity: O(log n) where n is the number of elements in the list. /// private SkipListNode[] GetSkipNodes(int key) { var skipNodes = new SkipListNode[maxLevels]; var current = head; for (var level = head.Height - 1; level >= 0; level--) { while (current.Next[level].Key < key) { current = current.Next[level]; } skipNodes[level] = current; } return skipNodes; } /// /// Determines the height of skip levels for the new node. /// /// /// Probability of the next level is 1/(2^level). /// private int GetRandomHeight() { int height = 1; while (random.NextDouble() < Probability && height < maxLevels) { height++; } return height; } } ================================================ FILE: DataStructures/LinkedList/SkipList/SkipListNode.cs ================================================ using System.Diagnostics; namespace DataStructures.LinkedList.SkipList; [DebuggerDisplay("Key = {Key}, Height = {Height}, Value = {Value}")] internal class SkipListNode(int key, TValue? value, int height) { public int Key { get; } = key; public TValue? Value { get; set; } = value; public SkipListNode[] Next { get; } = new SkipListNode[height]; public int Height { get; } = height; } ================================================ FILE: DataStructures/Probabilistic/BloomFilter.cs ================================================ namespace DataStructures.Probabilistic; public class BloomFilter where T : notnull { private const uint FnvPrime = 16777619; private const uint FnvOffsetBasis = 2166136261; private readonly byte[] filter; private readonly int numHashes; private readonly int sizeBits; /// /// Initializes a new instance of the class. This constructor will create a Bloom Filter /// of an optimal size with the optimal number of hashes to minimize the error rate. /// /// Expected number of unique elements that could be added to the filter. public BloomFilter(int expectedNumElements) { numHashes = (int)Math.Ceiling(.693 * 8 * expectedNumElements / expectedNumElements); // compute optimal number of hashes filter = new byte[expectedNumElements]; // set up filter with 8 times as many bits as elements sizeBits = expectedNumElements * 8; // number of bit slots in the filter } /// /// Initializes a new instance of the class. /// This constructor let's you decide how large you want the filter to be as well as allowing you to specify /// how many hashes it will use. Only use if you don't care to optimize false positivity. /// /// size in bits you want the filter to be. /// number of hash functions to be used. public BloomFilter(int sizeBits, int numHashes) { filter = new byte[sizeBits / 8 + 1]; this.numHashes = numHashes; this.sizeBits = sizeBits; } /// /// Inserts an item into the bloom filter. /// /// The item being inserted into the Bloom Filter. public void Insert(T item) { foreach (var slot in GetSlots(item)) { filter[slot / 8] |= (byte)(1 << (slot % 8)); // set the filter at the decided slot to 1. } } /// /// Searches the Bloom Filter to determine if the item exists in the Bloom Filter. /// /// The item being searched for in the Bloom Filter. /// true if the item has been added to the Bloom Filter, false otherwise. public bool Search(T item) { foreach (var slot in GetSlots(item)) { var @byte = filter[slot / 8]; // Extract the byte in the filter. var mask = 1 << (slot % 8); // Build the mask for the slot number. if ((@byte & mask) != mask) { return false; } } return true; } /// /// Yields the appropriate slots for the given item. /// /// The item to determine the slots for. /// The slots of the filter to flip or check. private IEnumerable GetSlots(T item) { var hash = item.GetHashCode(); for (var i = 0; i < numHashes; i++) { yield return Math.Abs((i + 1) * hash) % sizeBits; } } } ================================================ FILE: DataStructures/Probabilistic/CountMinSketch.cs ================================================ namespace DataStructures.Probabilistic; public class CountMinSketch where T : notnull { private readonly int[][] sketch; private readonly int numHashes; /// /// Initializes a new instance of the class based off dimensions /// passed by the user. /// /// The width of the sketch. /// The number of hashes to use in the sketch. public CountMinSketch(int width, int numHashes) { sketch = new int[numHashes][]; for (var i = 0; i < numHashes; i++) { sketch[i] = new int[width]; } this.numHashes = numHashes; } /// /// Initializes a new instance of the class based off the optimizing error rate /// and error probability formula width = e/errorRate numHashes = ln(1.0/errorProp). /// /// The amount of acceptable over counting for the sketch. /// The probability that an item will be over counted. public CountMinSketch(double errorRate, double errorProb) { var width = (int)Math.Ceiling(Math.E / errorRate); numHashes = (int)Math.Ceiling(Math.Log(1.0 / errorProb)); sketch = new int[numHashes][]; for (var i = 0; i < numHashes; i++) { sketch[i] = new int[width]; } } /// /// Inserts the provided item into the sketch. /// /// Item to insert. public void Insert(T item) { var initialHash = item.GetHashCode(); for (int i = 0; i < numHashes; i++) { var slot = GetSlot(i, initialHash); sketch[i][slot]++; } } /// /// Queries the count of the given item that have been inserted into the sketch. /// /// item to insert into the sketch. /// the number of times the provided item has been inserted into the sketch. public int Query(T item) { var initialHash = item.GetHashCode(); var min = int.MaxValue; for (int i = 0; i < numHashes; i++) { var slot = GetSlot(i, initialHash); min = Math.Min(sketch[i][slot], min); } return min; } private int GetSlot(int i, int initialHash) => Math.Abs((i + 1) * initialHash) % sketch[0].Length; } ================================================ FILE: DataStructures/Probabilistic/HyperLogLog.cs ================================================ namespace DataStructures.Probabilistic; public class HyperLogLog where T : notnull { private const int P = 16; private const double Alpha = .673; private readonly int[] registers; private readonly HashSet setRegisters; /// /// Initializes a new instance of the class. /// public HyperLogLog() { var m = 1 << P; registers = new int[m]; setRegisters = []; } /// /// Merge's two HyperLogLog's together to form a union HLL. /// /// the first HLL. /// The second HLL. /// A HyperLogLog with the combined values of the two sets of registers. public static HyperLogLog Merge(HyperLogLog first, HyperLogLog second) { var output = new HyperLogLog(); for (var i = 0; i < second.registers.Length; i++) { output.registers[i] = Math.Max(first.registers[i], second.registers[i]); } output.setRegisters.UnionWith(first.setRegisters); output.setRegisters.UnionWith(second.setRegisters); return output; } /// /// Adds an item to the HyperLogLog. /// /// The Item to be added. public void Add(T item) { var x = item.GetHashCode(); var binString = Convert.ToString(x, 2); // converts hash to binary var j = Convert.ToInt32(binString.Substring(0, Math.Min(P, binString.Length)), 2); // convert first b bits to register index var w = (int)Math.Log2(x ^ (x & (x - 1))); // find position of the right most 1. registers[j] = Math.Max(registers[j], w); // set the appropriate register to the appropriate value. setRegisters.Add(j); } /// /// Determines the approximate cardinality of the HyperLogLog. /// /// the approximate cardinality. public int Cardinality() { // calculate the bottom part of the harmonic mean of the registers double z = setRegisters.Sum(index => Math.Pow(2, -1 * registers[index])); // calculate the harmonic mean of the set registers return (int)Math.Ceiling(Alpha * setRegisters.Count * (setRegisters.Count / z)); } } ================================================ FILE: DataStructures/Queue/ArrayBasedQueue.cs ================================================ namespace DataStructures.Queue; /// /// Implementation of an array based queue. FIFO style. /// /// Generic Type. public class ArrayBasedQueue { private readonly T[] queue; private int endIndex; private bool isEmpty; private bool isFull; private int startIndex; /// /// Initializes a new instance of the class. /// public ArrayBasedQueue(int capacity) { queue = new T[capacity]; Clear(); } /// /// Clears the queue. /// public void Clear() { startIndex = 0; endIndex = 0; isEmpty = true; isFull = false; } /// /// Returns the first item in the queue and removes it from the queue. /// /// Thrown if the queue is empty. public T Dequeue() { if (IsEmpty()) { throw new InvalidOperationException("There are no items in the queue."); } var dequeueIndex = endIndex; endIndex++; if (endIndex >= queue.Length) { endIndex = 0; } isFull = false; isEmpty = startIndex == endIndex; return queue[dequeueIndex]; } /// /// Returns a boolean indicating whether the queue is empty. /// public bool IsEmpty() => isEmpty; /// /// Returns a boolean indicating whether the queue is full. /// public bool IsFull() => isFull; /// /// Returns the first item in the queue and keeps it in the queue. /// /// Thrown if the queue is empty. public T Peek() { if (IsEmpty()) { throw new InvalidOperationException("There are no items in the queue."); } return queue[endIndex]; } /// /// Adds an item at the last position in the queue. /// /// Thrown if the queue is full. public void Enqueue(T item) { if (IsFull()) { throw new InvalidOperationException("The queue has reached its capacity."); } queue[startIndex] = item; startIndex++; if (startIndex >= queue.Length) { startIndex = 0; } isEmpty = false; isFull = startIndex == endIndex; } } ================================================ FILE: DataStructures/Queue/ListBasedQueue.cs ================================================ namespace DataStructures.Queue; /// /// Implementation of a list based queue. FIFO style. /// /// Generic Type. public class ListBasedQueue { private readonly LinkedList queue; /// /// Initializes a new instance of the class. /// public ListBasedQueue() => queue = new LinkedList(); /// /// Clears the queue. /// public void Clear() { queue.Clear(); } /// /// Returns the first item in the queue and removes it from the queue. /// /// Thrown if the queue is empty. public T Dequeue() { if (queue.First is null) { throw new InvalidOperationException("There are no items in the queue."); } var item = queue.First; queue.RemoveFirst(); return item.Value; } /// /// Returns a boolean indicating whether the queue is empty. /// public bool IsEmpty() => !queue.Any(); /// /// Returns a boolean indicating whether the queue is full. /// public bool IsFull() => false; /// /// Returns the first item in the queue and keeps it in the queue. /// /// Thrown if the queue is empty. public T Peek() { if (queue.First is null) { throw new InvalidOperationException("There are no items in the queue."); } return queue.First.Value; } /// /// Adds an item at the last position in the queue. /// /// Thrown if the queue is full. public void Enqueue(T item) { queue.AddLast(item); } } ================================================ FILE: DataStructures/Queue/StackBasedQueue.cs ================================================ namespace DataStructures.Queue; /// /// Implementation of a stack based queue. FIFO style. /// /// /// Enqueue is O(1) and Dequeue is amortized O(1). /// /// Generic Type. public class StackBasedQueue { private readonly Stack input; private readonly Stack output; /// /// Initializes a new instance of the class. /// public StackBasedQueue() { input = new Stack(); output = new Stack(); } /// /// Clears the queue. /// public void Clear() { input.Clear(); output.Clear(); } /// /// Returns the first item in the queue and removes it from the queue. /// /// Thrown if the queue is empty. public T Dequeue() { if (input.Count == 0 && output.Count == 0) { throw new InvalidOperationException("The queue contains no items."); } if (output.Count == 0) { while (input.Count > 0) { var item = input.Pop(); output.Push(item); } } return output.Pop(); } /// /// Returns a boolean indicating whether the queue is empty. /// public bool IsEmpty() => input.Count == 0 && output.Count == 0; /// /// Returns a boolean indicating whether the queue is full. /// public bool IsFull() => false; /// /// Returns the first item in the queue and keeps it in the queue. /// /// Thrown if the queue is empty. public T Peek() { if (input.Count == 0 && output.Count == 0) { throw new InvalidOperationException("The queue contains no items."); } if (output.Count == 0) { while (input.Count > 0) { var item = input.Pop(); output.Push(item); } } return output.Peek(); } /// /// Adds an item at the last position in the queue. /// public void Enqueue(T item) => input.Push(item); } ================================================ FILE: DataStructures/RedBlackTree/RedBlackTree.cs ================================================ namespace DataStructures.RedBlackTree; /// /// A self-balancing bindary tree. /// /// /// A red-black tree is a self-balancing binary search tree (BST) that /// stores a color with each node. A node's color can either be red or /// black. Several properties are maintained to ensure the tree remains /// balanced. /// /// /// A red node does not have a red child. /// /// /// All null nodes are considered black. /// /// /// /// Every path from a node to its descendant leaf nodes /// has the same number of black nodes. /// /// /// /// (Optional) The root is always black. /// /// /// Red-black trees are generally slightly more unbalanced than an /// AVL tree, but insertion and deletion is generally faster. /// See https://en.wikipedia.org/wiki/Red%E2%80%93black_tree for more information. /// /// Type of key for the tree. public class RedBlackTree { /// /// Gets the number of nodes in the tree. /// public int Count { get; private set; } /// /// Comparer to use when comparing key values. /// private readonly Comparer comparer; /// /// Reference to the root node. /// private RedBlackTreeNode? root; /// /// Initializes a new instance of the class. /// public RedBlackTree() { comparer = Comparer.Default; } /// /// Initializes a new instance of the class /// using the specified comparer. /// /// Comparer to use when comparing keys. public RedBlackTree(Comparer customComparer) { comparer = customComparer; } /// /// Add a single node to the tree. /// /// Key value to add. public void Add(TKey key) { if (root is null) { // Case 3 // New node is root root = new RedBlackTreeNode(key, null) { Color = NodeColor.Black, }; Count++; return; } // Regular binary tree insertion var node = Add(root, key); // Get which side child was added to var childDir = comparer.Compare(node.Key, node.Parent!.Key); // Set node to be new node's parent for easier handling node = node.Parent; // Return tree to valid state int addCase; do { addCase = GetAddCase(node); switch (addCase) { case 1: break; case 2: var oldParent = node.Parent; node = AddCase2(node); if (node is not null) { childDir = comparer.Compare(oldParent!.Key, oldParent.Parent!.Key); } break; case 4: node.Color = NodeColor.Black; break; case 56: AddCase56(node, comparer.Compare(node.Key, node.Parent!.Key), childDir); break; default: throw new InvalidOperationException("It should not be possible to get here!"); } } while (addCase == 2 && node is not null); Count++; } /// /// Add multiple nodes to the tree. /// /// Key values to add. public void AddRange(IEnumerable keys) { foreach (var key in keys) { Add(key); } } /// /// Remove a node from the tree. /// /// Key value to remove. public void Remove(TKey key) { // Search for node var node = Remove(root, key); // Simple cases node = RemoveSimpleCases(node); // Exit if deleted node was not non-root black leaf if (node is null) { return; } // Delete node DeleteLeaf(node.Parent!, comparer.Compare(node.Key, node.Parent!.Key)); // Recolor tree do { node = RemoveRecolor(node); } while (node is not null && node.Parent is not null); // Case 2: Reached root Count--; } /// /// Check if given node is in the tree. /// /// Key value to search for. /// Whether or not the node is in the tree. public bool Contains(TKey key) { var node = root; while (node is not null) { var compareResult = comparer.Compare(key, node.Key); if (compareResult < 0) { node = node.Left; } else if (compareResult > 0) { node = node.Right; } else { return true; } } return false; } /// /// Get the minimum value in the tree. /// /// Minimum value in tree. public TKey GetMin() { if (root is null) { throw new InvalidOperationException("Tree is empty!"); } return GetMin(root).Key; } /// /// Get the maximum value in the tree. /// /// Maximum value in tree. public TKey GetMax() { if (root is null) { throw new InvalidOperationException("Tree is empty!"); } return GetMax(root).Key; } /// /// Get keys in order from smallest to largest as defined by the comparer. /// /// Keys in tree in order from smallest to largest. public IEnumerable GetKeysInOrder() { var result = new List(); InOrderWalk(root); return result; void InOrderWalk(RedBlackTreeNode? node) { if (node is null) { return; } InOrderWalk(node.Left); result.Add(node.Key); InOrderWalk(node.Right); } } /// /// Get keys in the pre-order order. /// /// Keys in pre-order order. public IEnumerable GetKeysPreOrder() { var result = new List(); PreOrderWalk(root); return result; void PreOrderWalk(RedBlackTreeNode? node) { if (node is null) { return; } result.Add(node.Key); PreOrderWalk(node.Left); PreOrderWalk(node.Right); } } /// /// Get keys in the post-order order. /// /// Keys in the post-order order. public IEnumerable GetKeysPostOrder() { var result = new List(); PostOrderWalk(root); return result; void PostOrderWalk(RedBlackTreeNode? node) { if (node is null) { return; } PostOrderWalk(node.Left); PostOrderWalk(node.Right); result.Add(node.Key); } } /// /// Perform binary tree insertion. /// /// Root of subtree to search from. /// Key value to insert. /// Node that was added. private RedBlackTreeNode Add(RedBlackTreeNode node, TKey key) { int compareResult; RedBlackTreeNode newNode; while (true) { compareResult = comparer.Compare(key, node!.Key); if (compareResult < 0) { if (node.Left is null) { newNode = new RedBlackTreeNode(key, node); node.Left = newNode; break; } else { node = node.Left; } } else if (compareResult > 0) { if (node.Right is null) { newNode = new RedBlackTreeNode(key, node); node.Right = newNode; break; } else { node = node.Right; } } else { throw new ArgumentException($"""Key "{key}" already exists in tree!"""); } } return newNode; } /// /// Perform case 2 of insertion by pushing blackness down from parent. /// /// Parent of inserted node. /// Grandparent of inserted node. private RedBlackTreeNode? AddCase2(RedBlackTreeNode node) { var grandparent = node.Parent; var parentDir = comparer.Compare(node.Key, node.Parent!.Key); var uncle = parentDir < 0 ? grandparent!.Right : grandparent!.Left; node.Color = NodeColor.Black; uncle!.Color = NodeColor.Black; grandparent.Color = NodeColor.Red; // Keep root black if (node.Parent.Parent is null) { node.Parent.Color = NodeColor.Black; } // Set current node as parent to move up tree return node.Parent.Parent; } /// /// Perform rotations needed for cases 5 and 6 of insertion. /// /// Parent of node just inserted. /// The side node is on of its parent. /// The side the child node is on. private void AddCase56(RedBlackTreeNode node, int parentDir, int childDir) { if (parentDir < 0) { // Case 5 if (childDir > 0) { node = RotateLeft(node); } // Case 6 node = RotateRight(node.Parent!); node.Color = NodeColor.Black; node.Right!.Color = NodeColor.Red; } else { // Case 5 if (childDir < 0) { node = RotateRight(node); } // Case 6 node = RotateLeft(node.Parent!); node.Color = NodeColor.Black; node.Left!.Color = NodeColor.Red; } } /// /// Determine which add case applies to inserted node. /// /// Parent of inserted node. /// Case number needed to get tree in valid state. Cases 5 and 6 are represented by 56. private int GetAddCase(RedBlackTreeNode node) { if (node.Color == NodeColor.Black) { return 1; } else if (node.Parent is null) { return 4; } else { // Remaining insert cases need uncle var grandparent = node.Parent; var parentDir = comparer.Compare(node.Key, node.Parent.Key); var uncle = parentDir < 0 ? grandparent.Right : grandparent.Left; // Case 5 & 6 if (uncle is null || uncle.Color == NodeColor.Black) { return 56; } return 2; } } /// /// Search for the node to be deleted. /// /// Node to start search from. /// Key to search for. /// Node to be deleted. private RedBlackTreeNode Remove(RedBlackTreeNode? node, TKey key) { if (node is null) { throw new InvalidOperationException("Tree is empty!"); } else if (!Contains(key)) { throw new KeyNotFoundException($"Key {key} is not in the tree!"); } else { // Find node int dir; while (true) { dir = comparer.Compare(key, node!.Key); if (dir < 0) { node = node.Left; } else if (dir > 0) { node = node.Right; } else { break; } } return node; } } /// /// Get the tree back into a valid state after removing non-root black leaf. /// /// Non-root black leaf being removed. private RedBlackTreeNode? RemoveRecolor(RedBlackTreeNode node) { var removeCase = GetRemoveCase(node); var dir = comparer.Compare(node.Key, node.Parent!.Key); // Determine current node's sibling and nephews var sibling = dir < 0 ? node.Parent.Right : node.Parent.Left; var closeNewphew = dir < 0 ? sibling!.Left : sibling!.Right; var distantNephew = dir < 0 ? sibling!.Right : sibling!.Left; switch (removeCase) { case 1: sibling.Color = NodeColor.Red; return node.Parent; case 3: RemoveCase3(node, closeNewphew, dir); break; case 4: RemoveCase4(sibling); break; case 5: RemoveCase5(node, sibling, dir); break; case 6: RemoveCase6(node, distantNephew!, dir); break; default: throw new InvalidOperationException("It should not be possible to get here!"); } return null; } /// /// Simple removal cases where black height doesn't change. /// /// Node to remove. /// Non-root black leaf node or null. Null indicates that removal was performed. private RedBlackTreeNode? RemoveSimpleCases(RedBlackTreeNode node) { // Node to delete is root and has no children if (node.Parent is null && node.Left is null && node.Right is null) { root = null; Count--; return null; } // Node has two children. Swap pointers if (node.Left is not null && node.Right is not null) { var successor = GetMin(node.Right); node.Key = successor.Key; node = successor; } // At this point node should have at most one child if (node.Color == NodeColor.Red) { // Node is red so it must have no children since it doesn't have two children DeleteLeaf(node.Parent!, comparer.Compare(node.Key, node.Parent!.Key)); Count--; return null; } else { // Node is black and may or may not be node return RemoveBlackNode(node); } } /// /// Node to delete is black. If it is a leaf then we need to recolor, otherwise remove it. /// /// Black node to examine. /// Node to start recoloring from. Null if deletion occurred. private RedBlackTreeNode? RemoveBlackNode(RedBlackTreeNode node) { // Node is black and has at most one child. If it has a child it must be red. var child = node.Left ?? node.Right; // Continue to recoloring if node is leaf if (child is null) { return node; } // Recolor child child.Color = NodeColor.Black; child.Parent = node.Parent; var childDir = node.Parent is null ? 0 : comparer.Compare(node.Key, node.Parent.Key); // Replace node with child Transplant(node.Parent, child, childDir); Count--; return null; } /// /// Perform case 3 of removal. /// /// Node that was removed. /// Close nephew of removed node. /// Side of parent the removed node was. private void RemoveCase3(RedBlackTreeNode node, RedBlackTreeNode? closeNephew, int childDir) { // Rotate and recolor var sibling = childDir < 0 ? RotateLeft(node.Parent!) : RotateRight(node.Parent!); sibling.Color = NodeColor.Black; if (childDir < 0) { sibling.Left!.Color = NodeColor.Red; } else { sibling.Right!.Color = NodeColor.Red; } // Get new distant newphew sibling = closeNephew!; var distantNephew = childDir < 0 ? sibling.Right : sibling.Left; // Parent is red, sibling is black if (distantNephew is not null && distantNephew.Color == NodeColor.Red) { RemoveCase6(node, distantNephew, childDir); return; } // Get new close nephew closeNephew = childDir < 0 ? sibling!.Left : sibling!.Right; // Sibling is black, distant nephew is black if (closeNephew is not null && closeNephew.Color == NodeColor.Red) { RemoveCase5(node, sibling!, childDir); return; } // Final recoloring RemoveCase4(sibling!); } /// /// Perform case 4 of removal. /// /// Sibling of removed node. private void RemoveCase4(RedBlackTreeNode sibling) { sibling.Color = NodeColor.Red; sibling.Parent!.Color = NodeColor.Black; } /// /// Perform case 5 of removal. /// /// Node that was removed. /// Sibling of removed node. /// Side of parent removed node was on. private void RemoveCase5(RedBlackTreeNode node, RedBlackTreeNode sibling, int childDir) { sibling = childDir < 0 ? RotateRight(sibling) : RotateLeft(sibling); var distantNephew = childDir < 0 ? sibling.Right! : sibling.Left!; sibling.Color = NodeColor.Black; distantNephew.Color = NodeColor.Red; RemoveCase6(node, distantNephew, childDir); } /// /// Perform case 6 of removal. /// /// Node that was removed. /// Distant nephew of removed node. /// Side of parent removed node was on. private void RemoveCase6(RedBlackTreeNode node, RedBlackTreeNode distantNephew, int childDir) { var oldParent = node.Parent!; node = childDir < 0 ? RotateLeft(oldParent) : RotateRight(oldParent); node.Color = oldParent.Color; oldParent.Color = NodeColor.Black; distantNephew.Color = NodeColor.Black; } /// /// Determine which removal case is required. /// /// Node being removed. /// Which removal case should be performed. private int GetRemoveCase(RedBlackTreeNode node) { var dir = comparer.Compare(node.Key, node.Parent!.Key); // Determine current node's sibling and nephews var sibling = dir < 0 ? node.Parent.Right : node.Parent.Left; var closeNewphew = dir < 0 ? sibling!.Left : sibling!.Right; var distantNephew = dir < 0 ? sibling!.Right : sibling!.Left; if (sibling.Color == NodeColor.Red) { return 3; } else if (distantNephew is not null && distantNephew.Color == NodeColor.Red) { return 6; } else if (closeNewphew is not null && closeNewphew.Color == NodeColor.Red) { return 5; } else if (node.Parent.Color == NodeColor.Red) { return 4; } else { return 1; } } /// /// Set child of node or delete leaf. /// /// Node to set child of. Set to null for root. /// Node to set as child. /// Which side of node to place child. private void Transplant(RedBlackTreeNode? node, RedBlackTreeNode? child, int dir) { if (node is null) { root = child; } else if (child is null) { DeleteLeaf(node, dir); } else if (dir < 0) { node.Left = child; } else { node.Right = child; } } /// /// Delete leaf node. /// /// Parent of leaf node to delete. /// Side of parent leaf is on. private void DeleteLeaf(RedBlackTreeNode node, int dir) { if (dir < 0) { node.Left = null; } else { node.Right = null; } } /// /// Perform a left (counter-clockwise) rotation. /// /// Node to rotate about. /// New node with rotation applied. private RedBlackTreeNode RotateLeft(RedBlackTreeNode node) { var temp1 = node; var temp2 = node!.Right!.Left; node = node.Right; node.Parent = temp1.Parent; if (node.Parent is not null) { var nodeDir = comparer.Compare(node.Key, node.Parent.Key); if (nodeDir < 0) { node.Parent.Left = node; } else { node.Parent.Right = node; } } node.Left = temp1; node.Left.Parent = node; node.Left.Right = temp2; if (temp2 is not null) { node.Left.Right!.Parent = temp1; } if (node.Parent is null) { root = node; } return node; } /// /// Perform a right (clockwise) rotation. /// /// Node to rotate about. /// New node with rotation applied. private RedBlackTreeNode RotateRight(RedBlackTreeNode node) { var temp1 = node; var temp2 = node!.Left!.Right; node = node.Left; node.Parent = temp1.Parent; if (node.Parent is not null) { var nodeDir = comparer.Compare(node.Key, node.Parent.Key); if (nodeDir < 0) { node.Parent.Left = node; } else { node.Parent.Right = node; } } node.Right = temp1; node.Right.Parent = node; node.Right.Left = temp2; if (temp2 is not null) { node.Right.Left!.Parent = temp1; } if (node.Parent is null) { root = node; } return node; } /// /// Helper function to get node instance with minimum key value /// in the specified subtree. /// /// Node specifying root of subtree. /// Minimum value in node's subtree. private RedBlackTreeNode GetMin(RedBlackTreeNode node) { while (node.Left is not null) { node = node.Left; } return node; } /// /// Helper function to get node instance with maximum key value /// in the specified subtree. /// /// Node specifyng root of subtree. /// Maximum value in node's subtree. private RedBlackTreeNode GetMax(RedBlackTreeNode node) { while (node.Right is not null) { node = node.Right; } return node; } } ================================================ FILE: DataStructures/RedBlackTree/RedBlackTreeNode.cs ================================================ namespace DataStructures.RedBlackTree; /// /// Enum to represent node colors. /// public enum NodeColor : byte { /// /// Represents red node. /// Red, /// /// Represents black node. /// Black, } /// /// Generic class to represent nodes in an instance. /// /// The type of key for the node. /// /// Initializes a new instance of the class. /// /// Key value for node. /// Parent of node. public class RedBlackTreeNode(TKey key, RedBlackTreeNode? parent) { /// /// Gets or sets key value of node. /// public TKey Key { get; set; } = key; /// /// Gets or sets the color of the node. /// public NodeColor Color { get; set; } /// /// Gets or sets the parent of the node. /// public RedBlackTreeNode? Parent { get; set; } = parent; /// /// Gets or sets left child of the node. /// public RedBlackTreeNode? Left { get; set; } /// /// Gets or sets the right child of the node. /// public RedBlackTreeNode? Right { get; set; } } ================================================ FILE: DataStructures/ScapegoatTree/Extensions.cs ================================================ namespace DataStructures.ScapegoatTree; public static class Extensions { /// /// Flattens scapegoat tree into a list of nodes. /// /// Scapegoat tree provided as root node. /// An empty list. /// Scapegoat tree node key type. public static void FlattenTree(Node root, List> list) where TKey : IComparable { if (root.Left != null) { FlattenTree(root.Left, list); } list.Add(root); if (root.Right != null) { FlattenTree(root.Right, list); } } /// /// Rebuilds a scapegoat tree from list of nodes. /// Use with method. /// /// Flattened tree. /// Start index. /// End index. /// Scapegoat tree node key type. /// Scapegoat tree root node. /// Thrown if start index is invalid. public static Node RebuildFromList(IList> list, int start, int end) where TKey : IComparable { if (start > end) { throw new ArgumentException("The parameter's value is invalid.", nameof(start)); } var pivot = Convert.ToInt32(Math.Ceiling(start + (end - start) / 2.0)); return new Node(list[pivot].Key) { Left = start > (pivot - 1) ? null : RebuildFromList(list, start, pivot - 1), Right = (pivot + 1) > end ? null : RebuildFromList(list, pivot + 1, end), }; } } ================================================ FILE: DataStructures/ScapegoatTree/Node.cs ================================================ namespace DataStructures.ScapegoatTree; /// /// Scapegoat tree node class. /// /// Scapegoat tree node key type. public class Node(TKey key) where TKey : IComparable { private Node? right; private Node? left; public TKey Key { get; } = key; public Node? Right { get => right; set { if (value != null && !value.IsGreaterThanOrSameAs(Key)) { throw new ArgumentException("The value's key is smaller than or equal to node's right child's key.", nameof(value)); } right = value; } } public Node? Left { get => left; set { if (value != null && value.IsGreaterThanOrSameAs(Key)) { throw new ArgumentException("The value's key is greater than or equal to node's left child's key.", nameof(value)); } left = value; } } public Node(TKey key, Node? right, Node? left) : this(key) { Right = right; Left = left; } /// /// Returns number of elements in the tree. /// /// Number of elements in the tree. public int GetSize() => (Left?.GetSize() ?? 0) + 1 + (Right?.GetSize() ?? 0); /// /// Gets alpha height of the current node. /// /// Alpha value. /// Alpha height value. public double GetAlphaHeight(double alpha) => Math.Floor(Math.Log(GetSize(), 1.0 / alpha)); public Node GetSmallestKeyNode() => Left?.GetSmallestKeyNode() ?? this; public Node GetLargestKeyNode() => Right?.GetLargestKeyNode() ?? this; /// /// Checks if the current node is alpha weight balanced. /// /// Alpha value. /// True - if node is alpha weight balanced. If not - false. public bool IsAlphaWeightBalanced(double a) { var isLeftBalanced = (Left?.GetSize() ?? 0) <= a * GetSize(); var isRightBalanced = (Right?.GetSize() ?? 0) <= a * GetSize(); return isLeftBalanced && isRightBalanced; } private bool IsGreaterThanOrSameAs(TKey key) { return Key.CompareTo(key) >= 0; } } ================================================ FILE: DataStructures/ScapegoatTree/ScapegoatTree.cs ================================================ namespace DataStructures.ScapegoatTree; /// /// A scapegoat implementation class. /// See https://en.wikipedia.org/wiki/Scapegoat_tree for more information about scapegoat tree. /// /// The scapegoat tree key type. public class ScapegoatTree where TKey : IComparable { /// /// Gets the α (alpha) value of the tree. /// public double Alpha { get; private set; } /// /// Gets the root node of the tree. /// public Node? Root { get; private set; } /// /// Gets the number of nodes in the tree. /// public int Size { get; private set; } /// /// Gets the maximal value of the tree Size since the last time the tree was completely rebuilt. /// public int MaxSize { get; private set; } /// /// Gets an event handler which will fire when tree is being balanced. /// public event EventHandler? TreeIsUnbalanced; public ScapegoatTree() : this(alpha: 0.5, size: 0) { } public ScapegoatTree(double alpha) : this(alpha, size: 0) { } public ScapegoatTree(Node node, double alpha) : this(alpha, size: node.GetSize()) { Root = node; } public ScapegoatTree(TKey key, double alpha = 0.5) : this(alpha, size: 1) { Root = new Node(key); } private ScapegoatTree(double alpha, int size) { CheckAlpha(alpha); Alpha = alpha; Size = size; MaxSize = size; } /// /// Checks if current instance of the scapegoat tree is alpha weight balanced. /// /// True - if tree is alpha weight balanced. Otherwise, false. public bool IsAlphaWeightBalanced() { return Root?.IsAlphaWeightBalanced(Alpha) ?? true; } /// /// Check if any node in the tree has specified key value. /// /// Key value. /// Returns true if node exists, false if not. public bool Contains(TKey key) { return Search(key) != null; } /// /// Searches current instance of the scapegoat tree for specified key. /// /// Key value. /// Node with the specified key or null. public Node? Search(TKey key) { if (Root == null) { return null; } var current = Root; while (true) { var result = current.Key.CompareTo(key); switch (result) { case 0: return current; case > 0 when current.Left != null: current = current.Left; break; case < 0 when current.Right != null: current = current.Right; break; default: return null; } } } /// /// Inserts a new key into current instance of the scapegoat tree. Rebuilds tree if it's unbalanced. /// /// Key value. /// True - if insertion is successful, false - if the key is already present in the tree. public bool Insert(TKey key) { var node = new Node(key); if (Root == null) { Root = node; UpdateSizes(); return true; } var path = new Stack>(); var current = Root; var found = false; while (!found) { path.Push(current); var result = current.Key.CompareTo(node.Key); switch (result) { case < 0 when current.Right != null: current = current.Right; continue; case < 0: current.Right = node; found = true; break; case > 0 when current.Left != null: current = current.Left; continue; case > 0: current.Left = node; found = true; break; default: return false; } } UpdateSizes(); if (path.Count > Root.GetAlphaHeight(Alpha)) { TreeIsUnbalanced?.Invoke(this, EventArgs.Empty); BalanceFromPath(path); MaxSize = Math.Max(MaxSize, Size); } return true; } /// /// Removes the specified key from the current instance of the scapegoat tree. Rebuilds tree if it's unbalanced. /// /// Key value. /// True - if key was successfully removed, false - if the key wasn't found in the tree. public bool Delete(TKey key) { if (Root == null) { return false; } if (Remove(Root, Root, key)) { Size--; if (Root != null && Size < Alpha * MaxSize) { TreeIsUnbalanced?.Invoke(this, EventArgs.Empty); var list = new List>(); Extensions.FlattenTree(Root, list); Root = Extensions.RebuildFromList(list, 0, list.Count - 1); MaxSize = Size; } return true; } return false; } /// /// Clears the tree. /// public void Clear() { Size = 0; MaxSize = 0; Root = null; } /// /// Changes value to adjust balancing. /// /// New alpha value. public void Tune(double value) { CheckAlpha(value); Alpha = value; } /// /// Searches for a scapegoat node in provided stack. /// /// Stack instance with nodes, starting with root node. /// Scapegoat node with its parent node. Parent can be null if scapegoat node is root node. /// Thrown if path stack is empty. /// Thrown if scapegoat wasn't found. public (Node? Parent, Node Scapegoat) FindScapegoatInPath(Stack> path) { if (path.Count == 0) { throw new ArgumentException("The path collection should not be empty.", nameof(path)); } var depth = 1; while (path.TryPop(out var next)) { if (depth > next.GetAlphaHeight(Alpha)) { return path.TryPop(out var parent) ? (parent, next) : (null, next); } depth++; } throw new InvalidOperationException("Scapegoat node wasn't found. The tree should be unbalanced."); } private static void CheckAlpha(double alpha) { if (alpha is < 0.5 or > 1.0) { throw new ArgumentException("The alpha parameter's value should be in 0.5..1.0 range.", nameof(alpha)); } } private bool Remove(Node? parent, Node? node, TKey key) { if (node is null || parent is null) { return false; } var compareResult = node.Key.CompareTo(key); if (compareResult > 0) { return Remove(node, node.Left, key); } if (compareResult < 0) { return Remove(node, node.Right, key); } Node? replacementNode; // Case 0: Node has no children. // Case 1: Node has one child. if (node.Left is null || node.Right is null) { replacementNode = node.Left ?? node.Right; } // Case 2: Node has two children. (This implementation uses the in-order predecessor to replace node.) else { var predecessorNode = node.Left.GetLargestKeyNode(); Remove(Root, Root, predecessorNode.Key); replacementNode = new Node(predecessorNode.Key) { Left = node.Left, Right = node.Right, }; } // Replace the relevant node with a replacement found in the previous stages. // Special case for replacing the root node. if (node == Root) { Root = replacementNode; } else if (parent.Left == node) { parent.Left = replacementNode; } else { parent.Right = replacementNode; } return true; } private void BalanceFromPath(Stack> path) { var (parent, scapegoat) = FindScapegoatInPath(path); var list = new List>(); Extensions.FlattenTree(scapegoat, list); var tree = Extensions.RebuildFromList(list, 0, list.Count - 1); if (parent == null) { Root = tree; } else { var result = parent.Key.CompareTo(tree.Key); if (result < 0) { parent.Right = tree; } else { parent.Left = tree; } } } private void UpdateSizes() { Size += 1; MaxSize = Math.Max(Size, MaxSize); } } ================================================ FILE: DataStructures/SegmentTrees/SegmentTree.cs ================================================ namespace DataStructures.SegmentTrees; /// /// Goal: Data structure with which you can quickly perform queries on an array (i.e. sum of subarray) /// and at the same time efficiently update an entry /// or apply a distributive operation to a subarray. /// Idea: Preprocessing special queries /// Hint: The query operation HAS to be associative (in this example addition). /// public class SegmentTree { /// /// Initializes a new instance of the class. /// Runtime complexity: O(n) where n equals the array-length. /// /// Array on which the queries should be made. public SegmentTree(int[] arr) { // Calculates next power of two var pow = (int)Math.Pow(2, Math.Ceiling(Math.Log(arr.Length, 2))); Tree = new int[2 * pow]; // Transfers the input array into the last half of the segment tree array Array.Copy(arr, 0, Tree, pow, arr.Length); // Calculates the first half for (var i = pow - 1; i > 0; --i) { Tree[i] = Tree[Left(i)] + Tree[Right(i)]; } } /// Gets the segment tree array. public int[] Tree { get; } /// /// Starts a query. /// Runtime complexity: O(logN) where n equals the array-length. /// /// Left border of the query. /// Right border of the query. /// Sum of the subarray between l and r (including l and r). // Editing of query start at node with 1. // Node with index 1 includes the whole input subarray. public int Query(int l, int r) => Query(++l, ++r, 1, Tree.Length / 2, 1); /// /// Calculates the right child of a node. /// /// Current node. /// Index of the right child. protected int Right(int node) => 2 * node + 1; /// /// Calculates the left child of a node. /// /// Current node. /// Index of the left child. protected int Left(int node) => 2 * node; /// /// Calculates the parent of a node. /// /// Current node. /// Index of the parent node. protected int Parent(int node) => node / 2; /// /// Edits a query. /// /// Left border of the query. /// Right border of the query. /// Left end of the subarray enclosed by i. /// Right end of the subarray enclosed by i. /// Current node. /// Sum of a subarray between l and r (including l and r). protected virtual int Query(int l, int r, int a, int b, int i) { // If a and b are in the (by l and r) specified subarray if (l <= a && b <= r) { return Tree[i]; } // If a or b are out of the by l and r specified subarray if (r < a || b < l) { // Returns the neutral value of the operation // (in this case 0, because x + 0 = x) return 0; } // Calculates index m of the node that cuts the current subarray in half var m = (a + b) / 2; // Start query of new two subarrays a:m and m+1:b // The right and left child cover this intervals return Query(l, r, a, m, Left(i)) + Query(l, r, m + 1, b, Right(i)); } } ================================================ FILE: DataStructures/SegmentTrees/SegmentTreeApply.cs ================================================ namespace DataStructures.SegmentTrees; /// /// This is an extension of a segment tree, which allows applying distributive operations to a subarray /// (in this case multiplication). /// public class SegmentTreeApply : SegmentTree { /// /// Initializes a new instance of the class. /// Runtime complexity: O(n) where n equals the array-length. /// /// Array on which the operations should be made. public SegmentTreeApply(int[] arr) : base(arr) { // Initilizes and fills "operand" array with neutral element (in this case 1, because value * 1 = value) Operand = new int[Tree.Length]; Array.Fill(Operand, 1); } /// /// Gets an array that stores for each node an operand, /// which must be applied to all direct and indirect child nodes of this node /// (but not to the node itself). /// public int[] Operand { get; } /// /// Applies a distributive operation to a subarray defined by l and r /// (in this case multiplication by value). /// Runtime complexity: O(logN) where N equals the initial array-length. /// /// Left border of the subarray. /// Right border of the subarray. /// Value with which each element of the interval is calculated. public void Apply(int l, int r, int value) { // The Application start at node with 1 // Node with index 1 includes the whole input subarray Apply(++l, ++r, value, 1, Tree.Length / 2, 1); } /// /// Edits a query. /// /// Left border of the query. /// Right border of the query. /// Left end of the subarray enclosed by i. /// Right end of the subarray enclosed by i. /// Current node. /// Sum of a subarray between l and r (including l and r). protected override int Query(int l, int r, int a, int b, int i) { if (l <= a && b <= r) { return Tree[i]; } if (r < a || b < l) { return 0; } var m = (a + b) / 2; // Application of the saved operand to the direct and indrect child nodes return Operand[i] * (Query(l, r, a, m, Left(i)) + Query(l, r, m + 1, b, Right(i))); } /// /// Applies the operation. /// /// Left border of the Application. /// Right border of the Application. /// Multiplier by which the subarray is to be multiplied. /// Left end of the subarray enclosed by i. /// Right end of the subarray enclosed by i. /// Current node. private void Apply(int l, int r, int value, int a, int b, int i) { // If a and b are in the (by l and r) specified subarray if (l <= a && b <= r) { // Applies the operation to the current node and saves it for the direct and indirect child nodes Operand[i] = value * Operand[i]; Tree[i] = value * Tree[i]; return; } // If a or b are out of the by l and r specified subarray stop application at this node if (r < a || b < l) { return; } // Calculates index m of the node that cuts the current subarray in half var m = (a + b) / 2; // Applies the operation to both halfes Apply(l, r, value, a, m, Left(i)); Apply(l, r, value, m + 1, b, Right(i)); // Recalculates the value of this node by its (possibly new) children. Tree[i] = Operand[i] * (Tree[Left(i)] + Tree[Right(i)]); } } ================================================ FILE: DataStructures/SegmentTrees/SegmentTreeUpdate.cs ================================================ namespace DataStructures.SegmentTrees; /// /// This is an extension of a segment tree, which allows the update of a single element. /// /// /// Initializes a new instance of the class. /// Runtime complexity: O(n) where n equals the array-length. /// /// Array on which the queries should be made. public class SegmentTreeUpdate(int[] arr) : SegmentTree(arr) { /// /// Updates a single element of the input array. /// Changes the leaf first and updates its parents afterwards. /// Runtime complexity: O(logN) where N equals the initial array-length. /// /// Index of the node that should be updated. /// New Value of the element. public void Update(int node, int value) { Tree[node + Tree.Length / 2] = value; Propagate(Parent(node + Tree.Length / 2)); } /// /// Recalculates the value of node by its children. /// Calls its parent to do the same. /// /// Index of current node. private void Propagate(int node) { if (node == 0) { // passed root return; } Tree[node] = Tree[Left(node)] + Tree[Right(node)]; Propagate(Parent(node)); } } ================================================ FILE: DataStructures/SortedList.cs ================================================ using System.Collections; namespace DataStructures; /// /// Implementation of SortedList using binary search. /// /// Generic Type. /// /// Initializes a new instance of the class. /// /// Comparer user for binary search. public class SortedList(IComparer comparer) : IEnumerable { private readonly IComparer comparer = comparer; private readonly List memory = []; /// /// Initializes a new instance of the class. Uses a Comparer.Default for type T. /// public SortedList() : this(Comparer.Default) { } /// /// Gets the number of elements containing in . /// public int Count => memory.Count; /// /// Adds new item to instance, maintaining the order. /// /// An element to insert. public void Add(T item) { var index = IndexFor(item, out _); memory.Insert(index, item); } /// /// Gets an element of at specified index. /// /// Index. public T this[int i] => memory[i]; /// /// Removes all elements from . /// public void Clear() => memory.Clear(); /// /// Indicates whether a contains a certain element. /// /// An element to search. /// true - contains an element, otherwise - false. public bool Contains(T item) { _ = IndexFor(item, out var found); return found; } /// /// Removes a certain element from . /// /// An element to remove. /// true - element is found and removed, otherwise false. public bool TryRemove(T item) { var index = IndexFor(item, out var found); if (found) { memory.RemoveAt(index); } return found; } /// /// Returns an enumerator that iterates through the . /// /// A Enumerator for the . public IEnumerator GetEnumerator() => memory.GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Binary search algorithm for finding element index in . /// /// Element. /// Indicates whether the equal value was found in . /// Index for the Element. private int IndexFor(T item, out bool found) { var left = 0; var right = memory.Count; while (right - left > 0) { var mid = (left + right) / 2; switch (comparer.Compare(item, memory[mid])) { case > 0: left = mid + 1; break; case < 0: right = mid; break; default: found = true; return mid; } } found = false; return left; } } ================================================ FILE: DataStructures/Stack/ArrayBasedStack.cs ================================================ namespace DataStructures.Stack; /// /// Implementation of an array-based stack. LIFO style. /// /// Generic Type. public class ArrayBasedStack { private const int DefaultCapacity = 10; private const string StackEmptyErrorMessage = "Stack is empty"; /// /// based stack. /// private T[] stack; /// /// How many items are in the stack right now. /// private int top; /// /// Initializes a new instance of the class. /// public ArrayBasedStack() { stack = new T[DefaultCapacity]; top = -1; } /// /// Initializes a new instance of the class. /// /// Item to push onto the . public ArrayBasedStack(T item) : this() => Push(item); /// /// Initializes a new instance of the class. /// /// Items to push onto the . public ArrayBasedStack(T[] items) { stack = items; top = items.Length - 1; } /// /// Gets the number of elements on the . /// public int Top => top; /// /// Gets or sets the Capacity of the . /// public int Capacity { get => stack.Length; set => Array.Resize(ref stack, value); } /// /// Removes all items from the . /// public void Clear() { top = -1; Capacity = DefaultCapacity; } /// /// Determines whether an element is in the . /// /// The item to locate in the . /// True, if the item is in the stack. public bool Contains(T item) => Array.IndexOf(stack, item, 0, top + 1) > -1; /// /// Returns the item at the top of the without removing it. /// /// The item at the top of the . public T Peek() { if (top == -1) { throw new InvalidOperationException(StackEmptyErrorMessage); } return stack[top]; } /// /// Removes and returns the item at the top of the . /// /// The item removed from the top of the . public T Pop() { if (top == -1) { throw new InvalidOperationException(StackEmptyErrorMessage); } return stack[top--]; } /// /// Inserts an item at the top of the . /// /// The item to push onto the . public void Push(T item) { if (top == Capacity - 1) { Capacity *= 2; } stack[++top] = item; } } ================================================ FILE: DataStructures/Stack/ListBasedStack.cs ================================================ namespace DataStructures.Stack; /// /// Implementation of a list based stack. FILO style. /// /// Generic Type. public class ListBasedStack { /// /// based stack. /// private readonly LinkedList stack; /// /// Initializes a new instance of the class. /// public ListBasedStack() => stack = new LinkedList(); /// /// Initializes a new instance of the class. /// /// Item to push onto the . public ListBasedStack(T item) : this() => Push(item); /// /// Initializes a new instance of the class. /// /// Items to push onto the . public ListBasedStack(IEnumerable items) : this() { foreach (var item in items) { Push(item); } } /// /// Gets the number of elements on the . /// public int Count => stack.Count; /// /// Removes all items from the . /// public void Clear() => stack.Clear(); /// /// Determines whether an element is in the . /// /// The item to locate in the . /// True, if the item is in the stack. public bool Contains(T item) => stack.Contains(item); /// /// Returns the item at the top of the without removing it. /// /// The item at the top of the . public T Peek() { if (stack.First is null) { throw new InvalidOperationException("Stack is empty"); } return stack.First.Value; } /// /// Removes and returns the item at the top of the . /// /// The item removed from the top of the . public T Pop() { if (stack.First is null) { throw new InvalidOperationException("Stack is empty"); } var item = stack.First.Value; stack.RemoveFirst(); return item; } /// /// Inserts an item at the top of the . /// /// The item to push onto the . public void Push(T item) => stack.AddFirst(item); } ================================================ FILE: DataStructures/Stack/QueueBasedStack.cs ================================================ namespace DataStructures.Stack; public class QueueBasedStack { private readonly Queue queue; public QueueBasedStack() => queue = new Queue(); /// /// Clears the stack. /// public void Clear() => queue.Clear(); public bool IsEmpty() => queue.Count == 0; /// /// Adds an item on top of the stack. /// /// Item to be added on top of stack. public void Push(T item) => queue.Enqueue(item); /// /// Removes an item from top of the stack and returns it. /// /// item on top of stack. /// Throw if stack is empty. public T Pop() { if (IsEmpty()) { throw new InvalidOperationException("The stack contains no items."); } for (int i = 0; i < queue.Count - 1; i++) { queue.Enqueue(queue.Dequeue()); } return queue.Dequeue(); } /// /// return an item from the top of the stack without removing it. /// /// item on top of the stack. /// Throw if stack is empty. public T Peek() { if (IsEmpty()) { throw new InvalidOperationException("The stack contains no items."); } for (int i = 0; i < queue.Count - 1; i++) { queue.Enqueue(queue.Dequeue()); } var item = queue.Peek(); queue.Enqueue(queue.Dequeue()); return item; } /// /// returns the count of items on the stack. /// /// number of items on the stack. public int Length() => queue.Count; } ================================================ FILE: DataStructures/Timeline.cs ================================================ /* Author: Lorenzo Lotti Name: Timeline (DataStructures.Timeline) Type: Data structure (class) Description: A collection of dates/times and values sorted by dates/times easy to query. Usage: this data structure can be used to represent an ordered series of dates or times with which to associate values. An example is a chronology of events: 306: Constantine is the new emperor, 312: Battle of the Milvian Bridge, 313: Edict of Milan, 330: Constantine move the capital to Constantinople. */ using System.Collections; namespace DataStructures; /// /// A collection of and /// sorted by field. /// /// Value associated with a . public class Timeline : ICollection<(DateTime Time, TValue Value)>, IEquatable> { /// /// Inner collection storing the timeline events as key-tuples. /// private readonly List<(DateTime Time, TValue Value)> timeline = []; /// /// Initializes a new instance of the class. /// public Timeline() { } /// /// Initializes a new instance of the class populated with an initial event. /// /// The time at which the given event occurred. /// The event's content. public Timeline(DateTime time, TValue value) => timeline = [ (time, value), ]; /// /// Initializes a new instance of the class containing the provided events /// ordered chronologically. /// /// The timeline to represent. public Timeline(params (DateTime, TValue)[] timeline) => this.timeline = timeline .OrderBy(pair => pair.Item1) .ToList(); /// /// Gets he number of unique times within this timeline. /// public int TimesCount => GetAllTimes().Length; /// /// Gets all events that has occurred in this timeline. /// public int ValuesCount => GetAllValues().Length; /// /// Get all values associated with . /// /// Time to get values for. /// Values associated with . public TValue[] this[DateTime time] { get => GetValuesByTime(time); set { timeline.RemoveAll(@event => @event.Time == time); foreach (var v in value) { Add(time, v); } } } /// bool ICollection<(DateTime Time, TValue Value)>.IsReadOnly => false; /// /// Gets the count of pairs. /// public int Count => timeline.Count; /// /// Clear the timeline, removing all events. /// public void Clear() => timeline.Clear(); /// /// Copy a value to an array. /// /// Destination array. /// The start index. public void CopyTo((DateTime, TValue)[] array, int arrayIndex) => timeline.CopyTo(array, arrayIndex); /// /// Add an event at a given time. /// /// The tuple containing the event date and value. void ICollection<(DateTime Time, TValue Value)>.Add((DateTime Time, TValue Value) item) => Add(item.Time, item.Value); /// /// Check whether or not a event exists at a specific date in the timeline. /// /// The tuple containing the event date and value. /// True if this event exists at the given date, false otherwise. bool ICollection<(DateTime Time, TValue Value)>.Contains((DateTime Time, TValue Value) item) => Contains(item.Time, item.Value); /// /// Remove an event at a specific date. /// /// The tuple containing the event date and value. /// True if the event was removed, false otherwise. bool ICollection<(DateTime Time, TValue Value)>.Remove((DateTime Time, TValue Value) item) => Remove(item.Time, item.Value); /// IEnumerator IEnumerable.GetEnumerator() => timeline.GetEnumerator(); /// IEnumerator<(DateTime Time, TValue Value)> IEnumerable<(DateTime Time, TValue Value)>.GetEnumerator() => timeline.GetEnumerator(); /// public bool Equals(Timeline? other) => other is not null && this == other; /// /// Checks whether or not two are equals. /// /// The first timeline. /// The other timeline to be checked against the one. /// True if both timelines are similar, false otherwise. public static bool operator ==(Timeline left, Timeline right) { var leftArray = left.ToArray(); var rightArray = right.ToArray(); if (left.Count != rightArray.Length) { return false; } for (var i = 0; i < leftArray.Length; i++) { if (leftArray[i].Time != rightArray[i].Time && !leftArray[i].Value!.Equals(rightArray[i].Value)) { return false; } } return true; } /// /// Checks whether or not two are not equals. /// /// The first timeline. /// The other timeline to be checked against the one. /// False if both timelines are similar, true otherwise. public static bool operator !=(Timeline left, Timeline right) => !(left == right); /// /// Get all of the timeline. /// public DateTime[] GetAllTimes() => timeline.Select(t => t.Time) .Distinct() .ToArray(); /// /// Get values of the timeline that have this . /// public DateTime[] GetTimesByValue(TValue value) => timeline.Where(pair => pair.Value!.Equals(value)) .Select(pair => pair.Time) .ToArray(); /// /// Get all before . /// public DateTime[] GetTimesBefore(DateTime time) => GetAllTimes() .Where(t => t < time) .OrderBy(t => t) .ToArray(); /// /// Get all after . /// public DateTime[] GetTimesAfter(DateTime time) => GetAllTimes() .Where(t => t > time) .OrderBy(t => t) .ToArray(); /// /// Get all of the timeline. /// public TValue[] GetAllValues() => timeline.Select(pair => pair.Value) .ToArray(); /// /// Get all associated with . /// public TValue[] GetValuesByTime(DateTime time) => timeline.Where(pair => pair.Time == time) .Select(pair => pair.Value) .ToArray(); /// /// Get all before . /// public Timeline GetValuesBefore(DateTime time) => new(this.Where(pair => pair.Time < time).ToArray()); /// /// Get all before . /// public Timeline GetValuesAfter(DateTime time) => new(this.Where(pair => pair.Time > time).ToArray()); /// /// Gets all values that happened at specified millisecond. /// /// Value to look for. /// Array of values. public Timeline GetValuesByMillisecond(int millisecond) => new(timeline.Where(pair => pair.Time.Millisecond == millisecond).ToArray()); /// /// Gets all values that happened at specified second. /// /// Value to look for. /// Array of values. public Timeline GetValuesBySecond(int second) => new(timeline.Where(pair => pair.Time.Second == second).ToArray()); /// /// Gets all values that happened at specified minute. /// /// Value to look for. /// Array of values. public Timeline GetValuesByMinute(int minute) => new(timeline.Where(pair => pair.Time.Minute == minute).ToArray()); /// /// Gets all values that happened at specified hour. /// /// Value to look for. /// Array of values. public Timeline GetValuesByHour(int hour) => new(timeline.Where(pair => pair.Time.Hour == hour).ToArray()); /// /// Gets all values that happened at specified day. /// /// Value to look for. /// Array of values. public Timeline GetValuesByDay(int day) => new(timeline.Where(pair => pair.Time.Day == day).ToArray()); /// /// Gets all values that happened at specified time of the day. /// /// Value to look for. /// Array of values. public Timeline GetValuesByTimeOfDay(TimeSpan timeOfDay) => new(timeline.Where(pair => pair.Time.TimeOfDay == timeOfDay).ToArray()); /// /// Gets all values that happened at specified day of the week. /// /// Value to look for. /// Array of values. public Timeline GetValuesByDayOfWeek(DayOfWeek dayOfWeek) => new(timeline.Where(pair => pair.Time.DayOfWeek == dayOfWeek).ToArray()); /// /// Gets all values that happened at specified day of the year. /// /// Value to look for. /// Array of values. public Timeline GetValuesByDayOfYear(int dayOfYear) => new(timeline.Where(pair => pair.Time.DayOfYear == dayOfYear).ToArray()); /// /// Gets all values that happened at specified month. /// /// Value to look for. /// Array of values. public Timeline GetValuesByMonth(int month) => new(timeline.Where(pair => pair.Time.Month == month).ToArray()); /// /// Gets all values that happened at specified year. /// /// Value to look for. /// Array of values. public Timeline GetValuesByYear(int year) => new(timeline.Where(pair => pair.Time.Year == year).ToArray()); /// /// Add an event at a given . /// /// The date at which the event occurred. /// The event value. public void Add(DateTime time, TValue value) { timeline.Add((time, value)); } /// /// Add a set of and to the timeline. /// public void Add(params (DateTime, TValue)[] timeline) { this.timeline.AddRange(timeline); } /// /// Append an existing timeline to this one. /// public void Add(Timeline timeline) => Add(timeline.ToArray()); /// /// Add a associated with to the timeline. /// public void AddNow(params TValue[] value) { var now = DateTime.Now; foreach (var v in value) { Add(now, v); } } /// /// Check whether or not a event exists at a specific date in the timeline. /// /// The date at which the event occurred. /// The event value. /// True if this event exists at the given date, false otherwise. public bool Contains(DateTime time, TValue value) => timeline.Contains((time, value)); /// /// Check if timeline contains this set of value pairs. /// /// The events to checks. /// True if any of the events has occurred in the timeline. public bool Contains(params (DateTime, TValue)[] timeline) => timeline.Any(@event => Contains(@event.Item1, @event.Item2)); /// /// Check if timeline contains any of the event of the provided . /// /// The events to checks. /// True if any of the events has occurred in the timeline. public bool Contains(Timeline timeline) => Contains(timeline.ToArray()); /// /// Check if timeline contains any of the time of the provided . /// /// The times to checks. /// True if any of the times is stored in the timeline. public bool ContainsTime(params DateTime[] times) { var storedTimes = GetAllTimes(); return times.Any(value => storedTimes.Contains(value)); } /// /// Check if timeline contains any of the event of the provided . /// /// The events to checks. /// True if any of the events has occurred in the timeline. public bool ContainsValue(params TValue[] values) { var storedValues = GetAllValues(); return values.Any(value => storedValues.Contains(value)); } /// /// Remove an event at a specific date. /// /// The date at which the event occurred. /// The event value. /// True if the event was removed, false otherwise. public bool Remove(DateTime time, TValue value) => timeline.Remove((time, value)); /// /// Remove a set of value pairs from the timeline. /// /// An collection of all events to remove. /// Returns true if the operation completed successfully. public bool Remove(params (DateTime, TValue)[] timeline) { var result = false; foreach (var (time, value) in timeline) { result |= this.timeline.Remove((time, value)); } return result; } /// /// Remove an existing timeline from this timeline. /// /// An collection of all events to remove. /// Returns true if the operation completed successfully. public bool Remove(Timeline timeline) => Remove(timeline.ToArray()); /// /// Remove a value pair from the timeline if the time is equal to . /// /// Returns true if the operation completed successfully. public bool RemoveTimes(params DateTime[] times) { var isTimeContainedInTheTimeline = times.Any(time => GetAllTimes().Contains(time)); if (!isTimeContainedInTheTimeline) { return false; } var eventsToRemove = times.SelectMany(time => timeline.Where(@event => @event.Time == time)) .ToList(); foreach (var @event in eventsToRemove) { timeline.Remove(@event); } return true; } /// /// Remove a value pair from the timeline if the value is equal to . /// /// Returns true if the operation completed successfully. public bool RemoveValues(params TValue[] values) { var isValueContainedInTheTimeline = values.Any(v => GetAllValues().Contains(v)); if (!isValueContainedInTheTimeline) { return false; } var eventsToRemove = values.SelectMany(value => timeline.Where(@event => EqualityComparer.Default.Equals(@event.Value, value))) .ToList(); foreach (var @event in eventsToRemove) { timeline.Remove(@event); } return true; } /// /// Convert the timeline to an array. /// /// /// The timeline as an array of tuples of (, ). /// public (DateTime Time, TValue Value)[] ToArray() => timeline.ToArray(); /// /// Convert the timeline to a list. /// /// /// The timeline as a list of tuples of (, ). /// public IList<(DateTime Time, TValue Value)> ToList() => timeline; /// /// Convert the timeline to a dictionary. /// /// /// The timeline as an dictionary of by . /// public IDictionary ToDictionary() => timeline.ToDictionary(@event => @event.Time, @event => @event.Value); /// public override bool Equals(object? obj) => obj is Timeline otherTimeline && this == otherTimeline; /// public override int GetHashCode() => timeline.GetHashCode(); } ================================================ FILE: DataStructures/Tries/Trie.cs ================================================ namespace DataStructures.Tries; /// /// A Trie is a data structure (particular case of m-ary tree) used to efficiently represent strings with common prefixes. /// Originally posed by E. Fredkin in 1960. /// Fredkin, Edward (Sept. 1960), "Trie Memory", Communications of the ACM 3 (9): 490-499. /// Its name is due to retrieval because its main application is in the field of "Information Retrieval" (information retrieval). /// public class Trie { /// /// This character marks the end of a string. /// private const char Mark = '$'; /// /// This property represents the root node of the trie. /// private readonly TrieNode root; /// /// Initializes a new instance of the class. This instances was created without text strings, generating the root node of the trie, without children. /// public Trie() { root = new TrieNode(Mark); } /// /// Initializes a new instance of the class. Given a set of text strings, each of those strings inserts them into the trie using the Insert (string) method. /// /// The array with text strings to insert in the trie. public Trie(IEnumerable words) : this() { foreach (string s in words) { Insert(s); } } /// /// Insert a string s to the trie. The $ mark is added to the end of the chain and then it is added, this in order to indicate the end of the chain in the trie. /// /// The string to insert into the trie. public void Insert(string s) { s += Mark; int index = 0; TrieNode match = PrefixQuery(s, ref index); for (int i = index; i < s.Length; i++) { TrieNode t = new(s[i], match); match[s[i]] = t; match = t; } } /// /// Remove a text string from the trie. /// /// The text string to be removed from the trie. public void Remove(string s) { s += Mark; int index = 0; TrieNode match = PrefixQuery(s, ref index); while (match.IsLeaf()) { char c = match.Value; if (match.Parent == null) { break; } match = match.Parent; match.Children.Remove(c); } } /// /// Know if a text string is in the trie. /// /// The string s that you want to know if it is in the trie. /// If the string is found, it returns true, otherwise false. public bool Find(string s) { int index = 0; return PrefixQuery(s + Mark, ref index).IsLeaf(); } /// /// This method analyzes which is the longest common prefix of a string s in the trie. If the string is in the trie then it is equivalent to doing Find (s). /// /// The string for which you want to know the longest common prefix. /// The index to which the longest common prefix goes. /// /// Returns the longest common prefix node found in the trie with the string s. /// private TrieNode PrefixQuery(string s, ref int index) { TrieNode current = root; for (int i = 0; i < s.Length && current != null; i++) { if (current[s[i]] != null) { current = current[s[i]] ?? throw new NullReferenceException(); index = i + 1; } else { break; } } return current ?? throw new NullReferenceException(); } } ================================================ FILE: DataStructures/Tries/TrieNode.cs ================================================ namespace DataStructures.Tries; /// /// This class represents the nodes of a trie. /// internal class TrieNode { /// /// Initializes a new instance of the class. This instance was created with a character from the alphabet, and its parent will be null. /// /// Character of the alphabet that represents the node. internal TrieNode(char value) : this(value, null) { } /// /// Initializes a new instance of the class. This instance was created with a character from the alphabet, and its parent. /// /// Character of the alphabet that represents the node. /// The parent or ancestor of the node in the trie structure. internal TrieNode(char value, TrieNode? parent) { Children = []; Parent = parent; Value = value; } /// /// Gets all the descendants of the current node. /// /// A sorted set with all the descendants. internal SortedList Children { get; private set; } /// /// Gets the parent or ancestor of the node in the trie structure. /// /// A TrieNode that represent a parent. internal TrieNode? Parent { get; private set; } /// /// Gets the character of the alphabet that represents the node. /// /// A character of the alphabet. internal char Value { get; private set; } /// /// Index the descendants of the current node given an alphabet character. /// /// A TrieNode with the character c in Children. public TrieNode? this[char c] { get => Children.ContainsKey(c) ? Children[c] : null; set => Children[c] = value ?? throw new NullReferenceException(); } /// /// Method that checks if the current node is a trie leaf. /// /// Returns true if the current node has no children, false otherwise. public bool IsLeaf() { return Children.Count == 0; } } ================================================ FILE: DataStructures/UnrolledList/UnrolledLinkedList.cs ================================================ namespace DataStructures.UnrolledList; /// /// Unrolled linked list is a linked list of small arrays, /// all of the same size where each is so small that the insertion /// or deletion is fast and quick, but large enough to fill the cache line. /// /// /// Initializes a new instance of the class. /// Create a unrolled list with start chunk size. /// /// The size of signe chunk. public class UnrolledLinkedList(int chunkSize) { private readonly int sizeNode = chunkSize + 1; private UnrolledLinkedListNode start = null!; private UnrolledLinkedListNode end = null!; /// /// Add value to list [O(n)]. /// /// The entered value. public void Insert(int value) { if (start == null) { start = new UnrolledLinkedListNode(sizeNode); start.Set(0, value); end = start; return; } if (end.Count + 1 < sizeNode) { end.Set(end.Count, value); } else { var pointer = new UnrolledLinkedListNode(sizeNode); var j = 0; for (var pos = end.Count / 2 + 1; pos < end.Count; pos++) { pointer.Set(j++, end.Get(pos)); } pointer.Set(j++, value); pointer.Count = j; end.Count = end.Count / 2 + 1; end.Next = pointer; end = pointer; } } /// /// Help method. Get all list inside to check the state. /// /// Items from all nodes. public IEnumerable GetRolledItems() { UnrolledLinkedListNode pointer = start; List result = []; while (pointer != null) { for (var i = 0; i < pointer.Count; i++) { result.Add(pointer.Get(i)); } pointer = pointer.Next; } return result; } } ================================================ FILE: DataStructures/UnrolledList/UnrolledLinkedListNode.cs ================================================ namespace DataStructures.UnrolledList; /// /// Single node with array buffer for unrolled list. /// public class UnrolledLinkedListNode(int nodeSize) { private readonly int[] array = new int[nodeSize]; public UnrolledLinkedListNode Next { get; set; } = null!; public int Count { get; set; } /// /// Set new item in array buffer. /// /// Index in array. /// The entered value. /// Index is out of scope. public void Set(int pos, int val) { if (pos < 0 || pos > array.Length - 1) { throw new ArgumentException("Position is out of size", nameof(pos)); } array[pos] = val; Count++; } /// /// Get item from array buffer. /// /// Index in array. /// Index is out of scope. public int Get(int pos) { if (pos < 0 || pos > array.Length - 1) { throw new ArgumentException("Position is out of size", nameof(pos)); } return array[pos]; } } ================================================ FILE: DataStructures.Tests/AATreeTests.cs ================================================ using DataStructures.AATree; namespace DataStructures.Tests; internal class AaTreeTests { [Test] public void Constructor_UseCustomComparer_FormsCorrectTree() { var tree = new AaTree(Comparer.Create((x, y) => y.CompareTo(x))); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetMax().Should().Be(1); tree.GetMin().Should().Be(10); tree.GetKeysInOrder().SequenceEqual(new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }).Should().BeTrue(); Validate(tree.Root); } [Test] public void Add_MultipleKeys_FormsCorrectTree() { var tree = new AaTree(); foreach (var elem in new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) { tree.Add(elem); tree.Count.Should().Be(elem); tree.Contains(elem).Should().BeTrue(); } tree.GetKeysInOrder().SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).Should().BeTrue(); tree.GetKeysPostOrder().SequenceEqual(new[] { 1, 3, 2, 5, 7, 10, 9, 8, 6, 4 }).Should().BeTrue(); Validate(tree.Root); } [Test] public void Add_KeyAlreadyInTree_ThrowsException() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); Assert.Throws(() => tree.Add(1)); } [Test] public void AddRange_MultipleKeys_FormsCorrectTree() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.Count.Should().Be(10); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).Should().BeTrue(); tree.GetKeysPostOrder().SequenceEqual(new[] { 1, 3, 2, 5, 7, 10, 9, 8, 6, 4 }).Should().BeTrue(); Validate(tree.Root); } [Test] public void Remove_MultipleKeys_TreeStillValid() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); Remove(4).Should().NotThrow(); tree.Contains(4).Should().BeFalse(); tree.Count.Should().Be(9); Remove(8).Should().NotThrow(); tree.Contains(8).Should().BeFalse(); tree.Count.Should().Be(8); Remove(1).Should().NotThrow(); tree.Contains(1).Should().BeFalse(); tree.Count.Should().Be(7); Validate(tree.Root); Action Remove(int x) => () => tree.Remove(x); } [Test] public void Remove_KeyNotInTree_Throws() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); Action act = () => tree.Remove(999); act.Should().Throw(); } [Test] public void Remove_EmptyTree_Throws() { var tree = new AaTree(); Action act = () => tree.Remove(999); act.Should().Throw(); } [Test] public void Contains_NonEmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.Contains(6).Should().BeTrue(); tree.Contains(999).Should().BeFalse(); } [Test] public void Contains_EmptyTree_ReturnsFalse() { var tree = new AaTree(); tree.Contains(999).Should().BeFalse(); } [Test] public void GetMax_NonEmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetMax().Should().Be(10); } [Test] public void GetMax_EmptyTree_ThrowsCorrectException() { var tree = new AaTree(); Assert.Throws(() => tree.GetMax()); } [Test] public void GetMin_NonEmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetMin().Should().Be(1); } [Test] public void GetMin_EmptyTree_ThrowsCorrectException() { var tree = new AaTree(); Assert.Throws(() => tree.GetMin()); } [Test] public void GetKeysInOrder_NonEmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).Should().BeTrue(); } [Test] public void GetKeysInOrder_EmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.GetKeysInOrder().ToList().Count.Should().Be(0); } [Test] public void GetKeysPreOrder_NonEmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 4, 2, 1, 3, 6, 5, 8, 7, 9, 10 }) .Should().BeTrue(); } [Test] public void GetKeysPreOrder_EmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.GetKeysPreOrder().ToList().Count.Should().Be(0); } [Test] public void GetKeysPostOrder_NonEmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetKeysPostOrder().SequenceEqual(new[] { 1, 3, 2, 5, 7, 10, 9, 8, 6, 4 }) .Should().BeTrue(); } [Test] public void GetKeysPostOrder_EmptyTree_ReturnsCorrectAnswer() { var tree = new AaTree(); tree.GetKeysPostOrder().ToList().Count.Should().Be(0); } /// /// Checks various properties to determine if the tree is a valid AA Tree. /// Throws exceptions if properties are violated. /// Useful for debugging. /// /// /// The properties that are checked are: /// /// The level of every leaf node is one. /// The level of every left child is exactly one less than that of its parent. /// The level of every right child is equal to or one less than that of its parent. /// The level of every right grandchild is strictly less than that of its grandparent. /// Every node of level greater than one has two children. /// /// More information: https://en.wikipedia.org/wiki/AA_tree . /// /// The node to check from. /// true if node passes all checks, false otherwise. private static bool Validate(AaTreeNode? node) { if (node is null) { return true; } // Check level == 1 if node if a leaf node. var leafNodeCheck = CheckLeafNode(node); // Check level of left child is exactly one less than parent. var leftCheck = CheckLeftSubtree(node); // Check level of right child is equal or one less than parent. var rightCheck = CheckRightSubtree(node); // Check right grandchild level is less than node. var grandchildCheck = CheckRightGrandChild(node); // Check if node has two children if not leaf. var nonLeafChildrenCheck = CheckNonLeafChildren(node); var thisNodeResult = leafNodeCheck && leftCheck && rightCheck; thisNodeResult = thisNodeResult && grandchildCheck && nonLeafChildrenCheck; return thisNodeResult && Validate(node.Left) && Validate(node.Right); } /// /// Checks if node is a leaf, and if so if its level is 1. /// /// The node to check. /// true if node passes check, false otherwise. private static bool CheckLeafNode(AaTreeNode node) { var condition = node.Left is null && node.Right is null && node.Level != 1; return !condition; } /// /// Checks if left node's level is exactly one less than node's level. /// /// The node to check. /// true if node passes check, false otherwise. private static bool CheckLeftSubtree(AaTreeNode node) { var condition = node.Left is not null && node.Level - node.Left.Level != 1; return !condition; } /// /// Checks if right node's level is either equal to or one less than node's level. /// /// The node to check. /// true if node passes check, false otherwise. private static bool CheckRightSubtree(AaTreeNode node) { var condition = node.Right is not null && node.Level - node.Right.Level != 1 && node.Level != node.Right.Level; return !condition; } /// /// Checks if right grandchild's (right node's right node) level is less than node. /// /// The node to check. /// true if node passes check, false otherwise. private static bool CheckRightGrandChild(AaTreeNode node) { var condition = node.Right?.Right is not null && node.Right.Level < node.Right.Right.Level; return !condition; } /// /// Checks if node is not a leaf, and if so if it has two children. /// /// The node to check. /// true if node passes check, false otherwise. private static bool CheckNonLeafChildren(AaTreeNode node) { var condition = node.Level > 1 && (node.Left is null || node.Right is null); return !condition; } } ================================================ FILE: DataStructures.Tests/AVLTreeTests.cs ================================================ using DataStructures.AVLTree; using static FluentAssertions.FluentActions; namespace DataStructures.Tests; internal class AvlTreeTests { private static readonly int[] Data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; private static readonly int[] PreOrder = [4, 2, 1, 3, 8, 6, 5, 7, 9, 10]; private static readonly int[] PostOrder = [1, 3, 2, 5, 7, 6, 10, 9, 8, 4]; [Test] public void Constructor_UseCustomComparer_FormsCorrectTree() { var tree = new AvlTree(Comparer.Create((x, y) => y.CompareTo(x))); tree.AddRange(Data); tree.GetMin().Should().Be(10); tree.GetMax().Should().Be(1); tree.GetKeysInOrder() .Should() .BeEquivalentTo( Data.Reverse(), config => config.WithStrictOrdering()); } [Test] public void Add_MultipleKeys_FormsCorrectTree() { var tree = new AvlTree(); for (var i = 0; i < Data.Length; ++i) { tree.Add(Data[i]); tree.Count.Should().Be(i + 1); } tree.GetKeysInOrder() .Should() .BeEquivalentTo( Data, config => config.WithStrictOrdering()); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( PreOrder, config => config.WithStrictOrdering()); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( PostOrder, config => config.WithStrictOrdering()); } [Test] public void Add_KeyAlreadyInTree_ThrowsException() { var tree = new AvlTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); Invoking(() => tree.Add(1)).Should().ThrowExactly(); } [Test] public void AddRange_MultipleKeys_FormsCorrectTree() { var tree = new AvlTree(); tree.AddRange(new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }); tree.Count.Should().Be(7); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }, config => config.WithStrictOrdering()); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( new[] { 'd', 'b', 'a', 'c', 'f', 'e', 'g' }, config => config.WithStrictOrdering()); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( new[] { 'a', 'c', 'b', 'e', 'g', 'f', 'd' }, config => config.WithStrictOrdering()); } [Test] public void Remove_MultipleKeys_TreeStillValid() { var tree = new AvlTree(); tree.AddRange(Data); tree.Remove(7); tree.Count.Should().Be(9); tree.Contains(7).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 5, 6, 8, 9, 10 }, config => config.WithStrictOrdering()); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( new[] { 4, 2, 1, 3, 8, 6, 5, 9, 10 }, config => config.WithStrictOrdering()); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( new[] { 1, 3, 2, 5, 6, 10, 9, 8, 4 }, config => config.WithStrictOrdering()); tree.Remove(2); tree.Count.Should().Be(8); tree.Contains(2).Should().BeFalse(); tree.Remove(1); tree.Count.Should().Be(7); tree.Contains(1).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 3, 4, 5, 6, 8, 9, 10 }, config => config.WithStrictOrdering()); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( new[] { 8, 4, 3, 6, 5, 9, 10 }, config => config.WithStrictOrdering()); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( new[] { 3, 5, 6, 4, 10, 9, 8 }, config => config.WithStrictOrdering()); tree.Remove(9); tree.Count.Should().Be(6); tree.Contains(9).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 3, 4, 5, 6, 8, 10 }, config => config.WithStrictOrdering()); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( new[] { 6, 4, 3, 5, 8, 10 }, config => config.WithStrictOrdering()); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( new[] { 3, 5, 4, 10, 8, 6 }, config => config.WithStrictOrdering()); tree.Remove(3); tree.Remove(4); tree.Remove(5); tree.Remove(6); tree.Remove(8); tree.Remove(10); tree.Count.Should().Be(0); tree.GetKeysInOrder().Should().BeEmpty(); } [Test] public void Remove_MultipleKeys_TreeStillValid_Variant2() { var tree = new AvlTree(); tree.AddRange(Data); tree.Remove(10); tree.Count.Should().Be(9); tree.Contains(10).Should().BeFalse(); tree.Remove(5); tree.Count.Should().Be(8); tree.Contains(5).Should().BeFalse(); tree.Remove(7); tree.Count.Should().Be(7); tree.Contains(7).Should().BeFalse(); tree.Remove(9); tree.Count.Should().Be(6); tree.Contains(9).Should().BeFalse(); tree.Remove(1); tree.Count.Should().Be(5); tree.Contains(1).Should().BeFalse(); tree.Remove(3); tree.Count.Should().Be(4); tree.Contains(3).Should().BeFalse(); tree.Remove(2); tree.Count.Should().Be(3); tree.Contains(2).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 4, 6, 8 }, config => config.WithStrictOrdering()); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( new[] { 6, 4, 8 }, config => config.WithStrictOrdering()); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( new[] { 4, 8, 6 }, config => config.WithStrictOrdering()); } [Test] public void Remove_EmptyTree_ThrowsException() { var tree = new AvlTree(); Invoking(() => tree.Remove(1)).Should().ThrowExactly(); } [Test] public void Remove_KeyNotInTree_ThrowsException() { var tree = new AvlTree(); tree.AddRange(Data); Invoking(() => tree.Remove(24)).Should().ThrowExactly(); } [Test] public void Contains_CorrectReturn() { var tree = new AvlTree(); tree.AddRange(Data); tree.Contains(3).Should().BeTrue(); tree.Contains(7).Should().BeTrue(); tree.Contains(24).Should().BeFalse(); tree.Contains(-1).Should().BeFalse(); } [Test] public void Contains_EmptyTree_ReturnsFalse() { var tree = new AvlTree(); tree.Contains(5).Should().BeFalse(); tree.Contains(-12).Should().BeFalse(); } [Test] public void GetMin_CorrectReturn() { var tree = new AvlTree(); tree.AddRange(Data); tree.GetMin().Should().Be(1); } [Test] public void GetMin_EmptyTree_ThrowsException() { var tree = new AvlTree(); Invoking(() => tree.GetMin()).Should().ThrowExactly(); } [Test] public void GetMax_CorrectReturn() { var tree = new AvlTree(); tree.AddRange(Data); tree.GetMax().Should().Be(10); } [Test] public void GetMax_EmptyTree_ThrowsException() { var tree = new AvlTree(); Invoking(() => tree.GetMax()).Should().ThrowExactly(); } [Test] public void GetKeysInOrder_CorrectReturn() { var tree = new AvlTree(); tree.AddRange(Data); tree.GetKeysInOrder() .Should() .BeEquivalentTo( Data, config => config.WithStrictOrdering()); } [Test] public void GetKeysInOrder_EmptyTree_CorrectReturn() { var tree = new AvlTree(); tree.GetKeysInOrder().Should().BeEmpty(); } [Test] public void GetKeysPreOrder_CorrectReturn() { var tree = new AvlTree(); tree.AddRange(Data); tree.GetKeysPreOrder() .Should() .BeEquivalentTo( PreOrder, config => config.WithStrictOrdering()); } [Test] public void GetKeysPreOrder_EmptyTree_CorrectReturn() { var tree = new AvlTree(); tree.GetKeysPreOrder().Should().BeEmpty(); } [Test] public void GetKeysPostOrder_CorrectReturn() { var tree = new AvlTree(); tree.AddRange(Data); tree.GetKeysPostOrder() .Should() .BeEquivalentTo( PostOrder, config => config.WithStrictOrdering()); } [Test] public void GetKeysPostOrder_EmptyTree_CorrectReturn() { var tree = new AvlTree(); tree.GetKeysPostOrder().Should().BeEmpty(); } } ================================================ FILE: DataStructures.Tests/BTreeTests.cs ================================================ using DataStructures.BTree; using static FluentAssertions.FluentActions; namespace DataStructures.Tests; internal class BTreeTests { private static readonly int[] Data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; [Test] public void Constructor_DefaultMinimumDegree_CreatesEmptyTree() { var tree = new BTree(); tree.Count.Should().Be(0); tree.MinimumDegree.Should().Be(2); } [Test] public void Constructor_CustomMinimumDegree_SetsCorrectDegree() { var tree = new BTree(3); tree.MinimumDegree.Should().Be(3); tree.Count.Should().Be(0); } [Test] public void Constructor_MinimumDegreeLessThan2_ThrowsException() { Invoking(() => new BTree(1)) .Should() .ThrowExactly() .WithMessage("Minimum degree must be at least 2.*"); } [Test] public void Constructor_CustomComparerMinimumDegreeLessThan2_ThrowsException() { var comparer = Comparer.Create((x, y) => y.CompareTo(x)); Invoking(() => new BTree(1, comparer)) .Should() .ThrowExactly() .WithMessage("Minimum degree must be at least 2.*"); } [Test] public void Constructor_UseCustomComparer_FormsCorrectTree() { var tree = new BTree(2, Comparer.Create((x, y) => y.CompareTo(x))); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); tree.GetMin().Should().Be(5); tree.GetMax().Should().Be(1); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 5, 4, 3, 2, 1 }, config => config.WithStrictOrdering()); } [Test] public void Add_SingleKey_IncreasesCount() { var tree = new BTree(); tree.Add(5); tree.Count.Should().Be(1); tree.Contains(5).Should().BeTrue(); } [Test] public void Add_MultipleKeys_FormsCorrectTree() { var tree = new BTree(2); for (var i = 0; i < Data.Length; i++) { tree.Add(Data[i]); tree.Count.Should().Be(i + 1); } tree.GetKeysInOrder() .Should() .BeEquivalentTo( Data, config => config.WithStrictOrdering()); } [Test] public void Add_DuplicateKey_ThrowsException() { var tree = new BTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); Invoking(() => tree.Add(3)) .Should() .ThrowExactly() .WithMessage("""Key "3" already exists in B-Tree."""); } [Test] public void Add_DuplicateKeyInLeaf_ThrowsException() { var tree = new BTree(3); tree.AddRange(new[] { 10, 20 }); Invoking(() => tree.Add(10)) .Should() .ThrowExactly() .WithMessage("""Key "10" already exists in B-Tree."""); } [Test] public void Add_CausesNodeSplit_TreeStillValid() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7 }); tree.Count.Should().Be(7); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 5, 6, 7 }, config => config.WithStrictOrdering()); } [Test] public void Add_LargeNumberOfKeys_TreeStillValid() { var tree = new BTree(3); var keys = Enumerable.Range(1, 100).ToArray(); tree.AddRange(keys); tree.Count.Should().Be(100); tree.GetKeysInOrder() .Should() .BeEquivalentTo( keys, config => config.WithStrictOrdering()); } [Test] public void AddRange_MultipleKeys_FormsCorrectTree() { var tree = new BTree(2); tree.AddRange(new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }); tree.Count.Should().Be(7); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }, config => config.WithStrictOrdering()); } [Test] public void AddRange_EmptyCollection_TreeRemainsEmpty() { var tree = new BTree(); tree.AddRange(Array.Empty()); tree.Count.Should().Be(0); tree.GetKeysInOrder().Should().BeEmpty(); } [Test] public void Contains_KeyExists_ReturnsTrue() { var tree = new BTree(); tree.AddRange(Data); tree.Contains(5).Should().BeTrue(); tree.Contains(1).Should().BeTrue(); tree.Contains(15).Should().BeTrue(); } [Test] public void Contains_KeyDoesNotExist_ReturnsFalse() { var tree = new BTree(); tree.AddRange(Data); tree.Contains(100).Should().BeFalse(); tree.Contains(-5).Should().BeFalse(); tree.Contains(0).Should().BeFalse(); } [Test] public void Contains_EmptyTree_ReturnsFalse() { var tree = new BTree(); tree.Contains(5).Should().BeFalse(); tree.Contains(-12).Should().BeFalse(); } [Test] public void Remove_SingleKey_DecreasesCount() { var tree = new BTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); tree.Remove(3); tree.Count.Should().Be(4); tree.Contains(3).Should().BeFalse(); } [Test] public void Remove_FromLeafNode_TreeStillValid() { var tree = new BTree(2); tree.AddRange(Data); tree.Remove(1); tree.Count.Should().Be(14); tree.Contains(1).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, config => config.WithStrictOrdering()); } [Test] public void Remove_FromNonLeafNode_TreeStillValid() { var tree = new BTree(2); tree.AddRange(Data); tree.Remove(8); tree.Count.Should().Be(14); tree.Contains(8).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15 }, config => config.WithStrictOrdering()); } [Test] public void Remove_CausesMerge_TreeStillValid() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7 }); tree.Remove(4); tree.Count.Should().Be(6); tree.Contains(4).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 5, 6, 7 }, config => config.WithStrictOrdering()); } [Test] public void Remove_MultipleKeys_TreeStillValid() { var tree = new BTree(2); tree.AddRange(Data); tree.Remove(5); tree.Remove(10); tree.Remove(15); tree.Count.Should().Be(12); tree.Contains(5).Should().BeFalse(); tree.Contains(10).Should().BeFalse(); tree.Contains(15).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14 }, config => config.WithStrictOrdering()); } [Test] public void Remove_AllKeys_TreeBecomesEmpty() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); tree.Remove(1); tree.Remove(2); tree.Remove(3); tree.Remove(4); tree.Remove(5); tree.Count.Should().Be(0); tree.GetKeysInOrder().Should().BeEmpty(); } [Test] public void Remove_EmptyTree_ThrowsException() { var tree = new BTree(); Invoking(() => tree.Remove(1)) .Should() .ThrowExactly() .WithMessage("""Key "1" is not in the B-Tree."""); } [Test] public void Remove_KeyNotInTree_ThrowsException() { var tree = new BTree(); tree.AddRange(Data); Invoking(() => tree.Remove(100)) .Should() .ThrowExactly() .WithMessage("""Key "100" is not in the B-Tree."""); } [Test] public void GetMin_NonEmptyTree_ReturnsMinimum() { var tree = new BTree(); tree.AddRange(new[] { 5, 2, 8, 1, 9, 3 }); tree.GetMin().Should().Be(1); } [Test] public void GetMin_SingleElement_ReturnsElement() { var tree = new BTree(); tree.Add(42); tree.GetMin().Should().Be(42); } [Test] public void GetMin_EmptyTree_ThrowsException() { var tree = new BTree(); Invoking(() => tree.GetMin()) .Should() .ThrowExactly() .WithMessage("B-Tree is empty."); } [Test] public void GetMax_NonEmptyTree_ReturnsMaximum() { var tree = new BTree(); tree.AddRange(new[] { 5, 2, 8, 1, 9, 3 }); tree.GetMax().Should().Be(9); } [Test] public void GetMax_SingleElement_ReturnsElement() { var tree = new BTree(); tree.Add(42); tree.GetMax().Should().Be(42); } [Test] public void GetMax_EmptyTree_ThrowsException() { var tree = new BTree(); Invoking(() => tree.GetMax()) .Should() .ThrowExactly() .WithMessage("B-Tree is empty."); } [Test] public void GetKeysInOrder_NonEmptyTree_ReturnsCorrectOrder() { var tree = new BTree(2); tree.AddRange(new[] { 5, 2, 8, 1, 9, 3, 7, 4, 6 }); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, config => config.WithStrictOrdering()); } [Test] public void GetKeysInOrder_EmptyTree_ReturnsEmpty() { var tree = new BTree(); tree.GetKeysInOrder().Should().BeEmpty(); } [Test] public void GetKeysInOrder_AfterRemoval_ReturnsCorrectOrder() { var tree = new BTree(2); tree.AddRange(Data); tree.Remove(5); tree.Remove(10); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15 }, config => config.WithStrictOrdering()); } [Test] public void GetKeysPreOrder_NonEmptyTree_ReturnsCorrectOrder() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7 }); var preOrder = tree.GetKeysPreOrder().ToArray(); preOrder.Should().HaveCount(7); preOrder.Should().Contain(new[] { 1, 2, 3, 4, 5, 6, 7 }); } [Test] public void GetKeysPreOrder_EmptyTree_ReturnsEmpty() { var tree = new BTree(); tree.GetKeysPreOrder().Should().BeEmpty(); } [Test] public void GetKeysPostOrder_NonEmptyTree_ReturnsCorrectOrder() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7 }); var postOrder = tree.GetKeysPostOrder().ToArray(); postOrder.Should().HaveCount(7); postOrder.Should().Contain(new[] { 1, 2, 3, 4, 5, 6, 7 }); } [Test] public void GetKeysPostOrder_EmptyTree_ReturnsEmpty() { var tree = new BTree(); tree.GetKeysPostOrder().Should().BeEmpty(); } [Test] public void BTree_LargeDataSet_MaintainsCorrectness() { var tree = new BTree(5); var keys = Enumerable.Range(1, 1000).ToArray(); tree.AddRange(keys); tree.Count.Should().Be(1000); tree.GetMin().Should().Be(1); tree.GetMax().Should().Be(1000); var inOrder = tree.GetKeysInOrder().ToArray(); inOrder.Should().BeEquivalentTo(keys, config => config.WithStrictOrdering()); for (var i = 1; i <= 1000; i++) { tree.Contains(i).Should().BeTrue(); } } [Test] public void BTree_RandomInsertion_MaintainsCorrectness() { var tree = new BTree(3); var random = new Random(42); var keys = Enumerable.Range(1, 100) .OrderBy(_ => random.Next()) .ToArray(); tree.AddRange(keys); tree.Count.Should().Be(100); var inOrder = tree.GetKeysInOrder().ToArray(); inOrder.Should().BeEquivalentTo( Enumerable.Range(1, 100), config => config.WithStrictOrdering()); } [Test] public void BTree_RandomDeletion_MaintainsCorrectness() { var tree = new BTree(3); tree.AddRange(Enumerable.Range(1, 50)); var random = new Random(42); var keysToRemove = Enumerable.Range(1, 50) .OrderBy(_ => random.Next()) .Take(25) .ToArray(); foreach (var key in keysToRemove) { tree.Remove(key); } tree.Count.Should().Be(25); var remainingKeys = Enumerable.Range(1, 50) .Except(keysToRemove) .OrderBy(x => x) .ToArray(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( remainingKeys, config => config.WithStrictOrdering()); } [Test] public void BTree_StringKeys_WorksCorrectly() { var tree = new BTree(2); var keys = new[] { "apple", "banana", "cherry", "date", "elderberry", "fig", "grape" }; tree.AddRange(keys); tree.Count.Should().Be(7); tree.GetMin().Should().Be("apple"); tree.GetMax().Should().Be("grape"); tree.GetKeysInOrder() .Should() .BeEquivalentTo( keys.OrderBy(x => x), config => config.WithStrictOrdering()); } [Test] public void BTree_DifferentMinimumDegrees_AllWorkCorrectly() { for (var degree = 2; degree <= 10; degree++) { var tree = new BTree(degree); tree.AddRange(Enumerable.Range(1, 50)); tree.Count.Should().Be(50); tree.GetKeysInOrder() .Should() .BeEquivalentTo( Enumerable.Range(1, 50), config => config.WithStrictOrdering()); } } [Test] public void BTree_InsertRemoveInsert_WorksCorrectly() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); tree.Remove(3); tree.Add(3); tree.Count.Should().Be(5); tree.Contains(3).Should().BeTrue(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 5 }, config => config.WithStrictOrdering()); } [Test] public void Remove_BorrowFromPreviousSibling_TreeStillValid() { var tree = new BTree(3); tree.AddRange(new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }); tree.Remove(100); tree.Count.Should().Be(9); tree.Contains(100).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90 }, config => config.WithStrictOrdering()); } [Test] public void Remove_BorrowFromNextSibling_TreeStillValid() { var tree = new BTree(3); tree.AddRange(new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }); tree.Remove(10); tree.Count.Should().Be(9); tree.Contains(10).Should().BeFalse(); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 20, 30, 40, 50, 60, 70, 80, 90, 100 }, config => config.WithStrictOrdering()); } [Test] public void Remove_UsesPredecessor_TreeStillValid() { var tree = new BTree(3); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); var rootKeys = tree.GetKeysPreOrder().Take(3).ToArray(); var keyToRemove = rootKeys[0]; tree.Remove(keyToRemove); tree.Contains(keyToRemove).Should().BeFalse(); tree.Count.Should().Be(14); var inOrder = tree.GetKeysInOrder().ToArray(); inOrder.Should().HaveCount(14); inOrder.Should().BeInAscendingOrder(); } [Test] public void Remove_UsesSuccessor_TreeStillValid() { var tree = new BTree(2); tree.AddRange(new[] { 10, 20, 30, 40, 50, 60, 70 }); tree.Remove(40); tree.Contains(40).Should().BeFalse(); tree.Count.Should().Be(6); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 10, 20, 30, 50, 60, 70 }, config => config.WithStrictOrdering()); } [Test] public void Remove_MergeWithSibling_TreeStillValid() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); tree.Remove(9); tree.Remove(8); tree.Remove(7); tree.Count.Should().Be(6); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 3, 4, 5, 6 }, config => config.WithStrictOrdering()); } [Test] public void Remove_ComplexRebalancing_TreeStillValid() { var tree = new BTree(3); tree.AddRange(Enumerable.Range(1, 30)); var keysToRemove = new[] { 5, 15, 25, 10, 20, 30, 1, 29 }; foreach (var key in keysToRemove) { tree.Remove(key); tree.Contains(key).Should().BeFalse(); } tree.Count.Should().Be(22); var expected = Enumerable.Range(1, 30).Except(keysToRemove).OrderBy(x => x); tree.GetKeysInOrder() .Should() .BeEquivalentTo( expected, config => config.WithStrictOrdering()); } [Test] public void Remove_FromMinimumDegree3_AllRebalancingPaths() { var tree = new BTree(3); tree.AddRange(Enumerable.Range(1, 50)); var keysToRemove = new[] { 25, 12, 37, 6, 44, 18, 31, 3, 47, 15 }; foreach (var key in keysToRemove) { var countBefore = tree.Count; tree.Remove(key); tree.Count.Should().Be(countBefore - 1); tree.Contains(key).Should().BeFalse(); var inOrder = tree.GetKeysInOrder().ToArray(); inOrder.Should().BeInAscendingOrder(); } tree.Count.Should().Be(40); } [Test] public void Remove_SequentialFromLargeTree_MaintainsStructure() { var tree = new BTree(4); tree.AddRange(Enumerable.Range(1, 100)); for (var i = 3; i <= 99; i += 3) { tree.Remove(i); } tree.Count.Should().Be(67); var inOrder = tree.GetKeysInOrder().ToArray(); inOrder.Should().HaveCount(67); inOrder.Should().BeInAscendingOrder(); tree.Contains(3).Should().BeFalse(); tree.Contains(6).Should().BeFalse(); tree.Contains(99).Should().BeFalse(); tree.Contains(1).Should().BeTrue(); tree.Contains(2).Should().BeTrue(); tree.Contains(100).Should().BeTrue(); } [Test] public void BTree_DegreeThree_CompleteInsertDeleteCycle() { var tree = new BTree(3); var keys = Enumerable.Range(1, 40).ToArray(); tree.AddRange(keys); tree.Count.Should().Be(40); for (var i = 2; i <= 40; i += 2) { tree.Remove(i); } tree.Count.Should().Be(20); var remaining = tree.GetKeysInOrder().ToArray(); remaining.Should().BeEquivalentTo( Enumerable.Range(1, 40).Where(x => x % 2 != 0), config => config.WithStrictOrdering()); tree.Add(2); tree.Add(4); tree.Count.Should().Be(22); tree.Contains(2).Should().BeTrue(); tree.Contains(4).Should().BeTrue(); } [Test] public void Remove_RootWithMultipleChildren_HandledCorrectly() { var tree = new BTree(2); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); tree.Remove(3); tree.Count.Should().Be(4); tree.GetKeysInOrder() .Should() .BeEquivalentTo( new[] { 1, 2, 4, 5 }, config => config.WithStrictOrdering()); } [Test] public void BTree_HighDegree_StressTest() { var tree = new BTree(10); tree.AddRange(Enumerable.Range(1, 200)); tree.Count.Should().Be(200); for (var i = 10; i <= 200; i += 10) { tree.Remove(i); } tree.Count.Should().Be(180); var inOrder = tree.GetKeysInOrder().ToArray(); inOrder.Should().HaveCount(180); inOrder.Should().BeInAscendingOrder(); tree.Contains(10).Should().BeFalse(); tree.Contains(100).Should().BeFalse(); tree.Contains(200).Should().BeFalse(); } } ================================================ FILE: DataStructures.Tests/BagTests.cs ================================================ using System.Collections.Generic; using System.Linq; using DataStructures.Bag; using FluentAssertions; using NUnit.Framework; namespace DataStructures.Tests; internal class BagTests { [Test] public void Add_ShouldIncreaseCount() { // Arrange & Act var bag = new Bag { 1, 2, 1 }; // Assert bag.Count.Should().Be(3); } [Test] public void Add_ShouldHandleDuplicates() { // Arrange & Act var bag = new Bag { "apple", "apple" }; // Assert bag.Count.Should().Be(2); bag.Should().Contain("apple"); } [Test] public void Clear_ShouldEmptyTheBag() { // Arrange var bag = new Bag { 1, 2 }; // Act bag.Clear(); // Assert bag.IsEmpty().Should().BeTrue(); bag.Count.Should().Be(0); } [Test] public void IsEmpty_ShouldReturnTrueForEmptyBag() { // Arrange var bag = new Bag(); // Act & Assert bag.IsEmpty().Should().BeTrue(); } [Test] public void IsEmpty_ShouldReturnFalseForNonEmptyBag() { // Arrange var bag = new Bag { 1 }; // Act & Assert bag.IsEmpty().Should().BeFalse(); } [Test] public void GetEnumerator_ShouldIterateAllItems() { // Arrange var bag = new Bag { 1, 2, 1 }; // Act var items = bag.ToList(); // Assert items.Count.Should().Be(3); items.Should().Contain(1); items.Should().Contain(2); } [Test] public void Count_ShouldReturnZeroForEmptyBag() { // Arrange var bag = new Bag(); // Act & Assert bag.Count.Should().Be(0); } [Test] public void Count_ShouldReturnCorrectCount() { // Arrange var bag = new Bag { 1, 2, 1 }; // Act & Assert bag.Count.Should().Be(3); } [Test] public void IEnumerableGetEnumerator_YieldsAllItemsWithCorrectMultiplicity() { // Arrange var bag = new Bag { "apple", "banana", "apple" }; var genericBag = bag as System.Collections.IEnumerable; // Act var enumerator = genericBag.GetEnumerator(); var items = new List(); while (enumerator.MoveNext()) { items.Add(enumerator.Current!); } // Assert items.Count(i => (string)i == "apple").Should().Be(2); items.Count(i => (string)i == "banana").Should().Be(1); items.Count.Should().Be(3); items.Should().BeEquivalentTo(["apple", "apple", "banana"]); } } ================================================ FILE: DataStructures.Tests/BinarySearchTreeTests.cs ================================================ using DataStructures.BinarySearchTree; namespace DataStructures.Tests; public static class BinarySearchTreeTests { [Test] public static void Constructor_UseCustomComparer_FormsCorrectTree() { var cmpFunc = Comparer.Create((x, y) => x.Length - y.Length); var tree = new BinarySearchTree(cmpFunc); var elems = new[] { "z", "yy", "vvv", "bbbb", "fffff", "pppppp" }; tree.AddRange(elems); Assert.That(tree.Search("vvv"), Is.Not.Null); Assert.That(tree.Search("vvv")!.Right, Is.Not.Null); Assert.That(tree.Search("vvv")!.Right!.Key, Is.EqualTo("bbbb")); } [Test] public static void Add_MultipleKeys_FormsCorrectBST() { var tree = new BinarySearchTree(); tree.Add(5); Assert.That(tree.Count, Is.EqualTo(1)); tree.Add(3); Assert.That(tree.Count, Is.EqualTo(2)); tree.Add(4); Assert.That(tree.Count, Is.EqualTo(3)); tree.Add(2); Assert.That(tree.Count, Is.EqualTo(4)); var rootNode = tree.Search(5); Assert.That(rootNode!.Key, Is.EqualTo(5)); Assert.That(rootNode!.Left!.Key, Is.EqualTo(3)); Assert.That(rootNode!.Right, Is.Null); var threeNode = tree.Search(3); Assert.That(threeNode!.Key, Is.EqualTo(3)); Assert.That(threeNode!.Left!.Key, Is.EqualTo(2)); Assert.That(threeNode!.Right!.Key, Is.EqualTo(4)); var twoNode = tree.Search(2); Assert.That(twoNode!.Left, Is.Null); Assert.That(twoNode!.Right, Is.Null); var fourNode = tree.Search(4); Assert.That(fourNode!.Left, Is.Null); Assert.That(fourNode!.Right, Is.Null); } [Test] public static void Add_KeyAlreadyInTree_ThrowsCorrectException() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2 }); _ = Assert.Throws(() => tree.Add(5)); } [Test] public static void AddRange_MultipleKeys_FormsCorrectBST() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2 }); var rootNode = tree.Search(5); Assert.That(rootNode!.Key, Is.EqualTo(5)); Assert.That(rootNode!.Left!.Key, Is.EqualTo(3)); Assert.That(rootNode!.Right, Is.Null); var threeNode = tree.Search(3); Assert.That(threeNode!.Key, Is.EqualTo(3)); Assert.That(threeNode!.Left!.Key, Is.EqualTo(2)); Assert.That(threeNode!.Right!.Key, Is.EqualTo(4)); var twoNode = tree.Search(2); Assert.That(twoNode!.Left, Is.Null); Assert.That(twoNode!.Right, Is.Null); var fourNode = tree.Search(4); Assert.That(fourNode!.Left, Is.Null); Assert.That(fourNode!.Right, Is.Null); } [Test] public static void Search_MultipleKeys_FindsAllKeys() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); Assert.That(tree.Search(2)!.Key, Is.EqualTo(2)); Assert.That(tree.Search(3)!.Key, Is.EqualTo(3)); Assert.That(tree.Search(4)!.Key, Is.EqualTo(4)); Assert.That(tree.Search(5)!.Key, Is.EqualTo(5)); Assert.That(tree.Search(6)!.Key, Is.EqualTo(6)); Assert.That(tree.Search(7)!.Key, Is.EqualTo(7)); Assert.That(tree.Search(8)!.Key, Is.EqualTo(8)); } [Test] public static void Contains_MultipleKeys_FindsAllKeys() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); Assert.That(tree.Contains(2), Is.True); Assert.That(tree.Contains(3), Is.True); Assert.That(tree.Contains(4), Is.True); Assert.That(tree.Contains(5), Is.True); Assert.That(tree.Contains(6), Is.True); Assert.That(tree.Contains(7), Is.True); Assert.That(tree.Contains(8), Is.True); } [Test] public static void Remove_LeafNodes_CorrectlyRemovesNodes() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); var twoRemoveResult = tree.Remove(2); Assert.That(twoRemoveResult, Is.True); Assert.That(tree.Search(2), Is.Null); Assert.That(tree.Search(3)!.Left, Is.Null); Assert.That(tree.Search(3)!.Right, Is.Not.Null); Assert.That(tree.Count, Is.EqualTo(6)); var fourRemoveResult = tree.Remove(4); Assert.That(fourRemoveResult, Is.True); Assert.That(tree.Search(4), Is.Null); Assert.That(tree.Search(3)!.Left, Is.Null); Assert.That(tree.Search(3)!.Right, Is.Null); Assert.That(tree.Count, Is.EqualTo(5)); } [Test] public static void Remove_NodesWithOneChild_CorrectlyRemovesNodes() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); tree.Remove(4); var threeRemoveResult = tree.Remove(3); Assert.That(threeRemoveResult, Is.True); Assert.That(tree.Search(3), Is.Null); Assert.That(tree.Search(2)!.Left, Is.Null); Assert.That(tree.Search(2)!.Right, Is.Null); Assert.That(tree.Count, Is.EqualTo(5)); tree.Remove(6); var sevenRemoveResult = tree.Remove(7); Assert.That(sevenRemoveResult, Is.True); Assert.That(tree.Search(7), Is.Null); Assert.That(tree.Search(8)!.Left, Is.Null); Assert.That(tree.Search(8)!.Right, Is.Null); Assert.That(tree.Count, Is.EqualTo(3)); } [Test] public static void Remove_NodesWithTwoChildren_CorrectlyRemovesNodes() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); var sevenRemoveResult = tree.Remove(7); Assert.That(sevenRemoveResult, Is.True); Assert.That(tree.Search(7), Is.Null); Assert.That(tree.Search(6)!.Left, Is.Null); Assert.That(tree.Search(6)!.Right, Is.Not.Null); Assert.That(tree.Count, Is.EqualTo(6)); } [Test] public static void Remove_NonExistentElement_ReturnsFalse() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); Assert.That(tree.Remove(999), Is.False); Assert.That(tree.Count, Is.EqualTo(7)); } [Test] public static void Remove_EmptyTree_ReturnsFalse() { var tree = new BinarySearchTree(); Assert.That(tree.Remove(8), Is.False); Assert.That(tree.Count, Is.EqualTo(0)); } [Test] public static void Remove_RemoveRoot_CorrectlyRemovesRoot() { var tree = new BinarySearchTree(); tree.Add(5); tree.Remove(5); Assert.That(tree.Count, Is.EqualTo(0)); Assert.That(tree.Search(5), Is.Null); tree.AddRange(new List { 5, 4, 6 }); tree.Remove(5); Assert.That(tree.Count, Is.EqualTo(2)); Assert.That(tree.Search(5), Is.Null); Assert.That(tree.Search(4), Is.Not.Null); Assert.That(tree.Search(6), Is.Not.Null); Assert.That(tree.Search(4)!.Right!.Key, Is.EqualTo(6)); } [Test] public static void GetMax_NonEmptyTree_ReturnsCorrectValue() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); Assert.That(tree.GetMax()!.Key, Is.EqualTo(8)); } [Test] public static void GetMax_EmptyTree_ReturnsDefaultValue() { var tree = new BinarySearchTree(); Assert.That(tree.GetMax(), Is.Null); } [Test] public static void GetMin_NonEmptyTree_ReturnsCorrectValue() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); Assert.That(tree.GetMin()!.Key, Is.EqualTo(2)); } [Test] public static void GetMin_EmptyTree_ReturnsDefaultValue() { var tree = new BinarySearchTree(); Assert.That(tree.GetMin(), Is.Null); } [Test] public static void GetKeysInOrder_MultipleKeys_ReturnsAllKeysInCorrectOrder() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); var keys = tree.GetKeysInOrder(); var expected = new List { 2, 3, 4, 5, 6, 7, 8 }; Assert.That(keys.SequenceEqual(expected), Is.True); } [Test] public static void GetKeysPreOrder_MultipleKeys_ReturnsAllKeysInCorrectOrder() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); var keys = tree.GetKeysPreOrder(); var expected = new List { 5, 3, 2, 4, 7, 6, 8 }; Assert.That(keys.SequenceEqual(expected), Is.True); } [Test] public static void GetKeysPostOrder_MultipleKeys_ReturnsAllKeysInCorrectOrder() { var tree = new BinarySearchTree(); tree.AddRange(new List { 5, 3, 4, 2, 7, 6, 8 }); var keys = tree.GetKeysPostOrder(); var expected = new List { 2, 4, 3, 6, 8, 7, 5 }; Assert.That(keys.SequenceEqual(expected), Is.True); } } ================================================ FILE: DataStructures.Tests/BitArrayTests.cs ================================================ namespace DataStructures.Tests; /// /// This class contains some tests for the class BitArray. /// public static class BitArrayTests { [Test] public static void TestIndexer() { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(24); // Assert Assert.That(testObj[0], Is.True); Assert.That(testObj[1], Is.True); Assert.That(testObj[3], Is.False); } [TestCase(19, 3)] public static void TestNumberOfOneBits(int number, int expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(number); // Assert Assert.That(expected, Is.EqualTo(testObj.NumberOfOneBits())); } [TestCase(26, 2)] public static void TestNumberOfZeroBits(int number, int expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(number); // Assert Assert.That(expected, Is.EqualTo(testObj.NumberOfZeroBits())); } [TestCase(33, 33)] public static void TestToInt64(int number, int expected) { // Arrange var testObj = new BitArray(6); // Act testObj.Compile(number); // Assert Assert.That(expected, Is.EqualTo(testObj.ToInt64())); } [Test] public static void TestToInt32MaxValue() { // Arrange var testObj = new BitArray(33); // Act // Assert _ = Assert.Throws(() => testObj.ToInt32()); } [Test] public static void TestToInt64MaxValue() { // Arrange var testObj = new BitArray(65); // Act // Assert _ = Assert.Throws(() => testObj.ToInt64()); } [TestCase("110")] public static void TestResetField(string sequence) { // Arrange var testObj = new BitArray(sequence); // Act testObj.ResetField(); // Assert Assert.That(0, Is.EqualTo(testObj.ToInt64())); } [TestCase("101001", 63)] public static void TestSetAll(string sequence, int expected) { // Arrange var testObj = new BitArray(sequence); // Act testObj.SetAll(true); // Assert Assert.That(expected, Is.EqualTo(testObj.ToInt64())); } [Test] public static void TestCloneEquals() { // Arrange var testObj1 = new BitArray("110"); // Act var testObj2 = (BitArray)testObj1.Clone(); // Assert Assert.That(testObj1.Equals(testObj2), Is.True); } [Test] public static void TestCloneNotEquals() { // Arrange var testObj1 = new BitArray("101"); var testObj2 = new BitArray(15); var testObj3 = new BitArray(3); // Act testObj3.Reset(); // Assert testObj1.Equals(testObj2).Should().BeFalse(); testObj1.Equals(testObj3).Should().BeFalse(); } [Test] public static void TestHasCode() { // Arrange const int num = 5; var testObj = new BitArray(3); // Act testObj.Compile(num); var result = testObj.GetHashCode(); // Assert Assert.That(result, Is.Not.Null); Assert.That(5, Is.EqualTo(result)); } [Test] public static void TestMoveNextCurrent() { var testObj1 = new BitArray("1111010"); var counterOnes = 0; var counterZeros = 0; foreach (var bit in testObj1) { if (bit) { counterOnes++; } else { counterZeros++; } } Assert.That(counterOnes, Is.EqualTo(5)); Assert.That(counterZeros, Is.EqualTo(2)); } [Test] public static void IEnumerable_IterationWorks() { var arr = new BitArray("010101010101010101"); var current = 0; foreach (var b in arr) { b.Should().Be(arr[current]); current++; } } [Test] public static void Equals_NullIsNotEqualToNotNull() { var arr1 = new BitArray("010101010101010101"); BitArray? arr2 = null; arr1.Equals(arr2).Should().BeFalse(); } #region COMPILE TESTS [TestCase("00100", "00100")] [TestCase("01101", "01101")] [TestCase("100", "00100")] public static void TestCompileToString(string sequence, string expectedSequence) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(sequence); // Assert Assert.That(expectedSequence, Is.EqualTo(testObj.ToString())); } [TestCase("klgml", 5)] [TestCase("klgml", 3)] public static void TestCompileToStringThorwsException(string sequence, int arrLen) { // Arrange var testObj = new BitArray(arrLen); // Act void Act() => testObj.Compile(sequence); // Assert Assert.Throws(Act); } [TestCase(15, "01111")] [TestCase(17, "10001")] [TestCase(4, "00100")] public static void TestCompileLong(int number, string expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile((long)number); // Assert Assert.That(expected, Is.EqualTo(testObj.ToString())); } [TestCase(46, 3)] [TestCase(-46, 5)] public static void TestCompileLongThrowsException(int number, int arrLen) { // Arrange var testObj = new BitArray(arrLen); // Act void Act() => testObj.Compile((long)number); // Assert Assert.Throws(Act); } [TestCase(17, "10001")] [TestCase(25, "11001")] [TestCase(4, "00100")] public static void TestCompileInteger(int number, string expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(number); // Assert Assert.That(expected, Is.EqualTo(testObj.ToString())); } [TestCase(-8, 5)] [TestCase(18, 3)] public static void TestCompileIntegerThrowsException(int number, int arrayLength) { // Arrange var testObj = new BitArray(arrayLength); // Act void Act() => testObj.Compile(number); // Assert Assert.Throws(Act); } #endregion COMPILE TESTS #region CONSTRUCTOR TESTS [TestCase("00100", 4)] public static void TestConstructor(string sequence, int expected) { // Arrange var testObj1 = new BitArray(sequence); // Act // Assert Assert.That(expected, Is.EqualTo(testObj1.ToInt64())); } [TestCase(new[] { true, false, true }, 5)] public static void TestConstructorBoolArray(bool[] sequence, int expected) { // Arrange var testObj3 = new BitArray(sequence); // Act // Assert Assert.That(expected, Is.EqualTo(testObj3.ToInt64())); } [TestCase("000120")] [TestCase("")] public static void TestConstructorThrowsException(string sequence) { // Arrange // Act Action act = () => new BitArray(sequence); // Assert act.Should().Throw(); } #endregion CONSTRUCTOR TESTS #region OPERATOR TESTS [TestCase(17, 17, "10001")] [TestCase(25, 31, "11001")] public static void TestOperatorAnd(int tObj1, int tObj2, string expected) { // Arrange var testObj1 = new BitArray(5); var testObj2 = new BitArray(5); // Act testObj1.Compile(tObj1); testObj2.Compile(tObj2); var result = testObj1 & testObj2; // Assert Assert.That(expected, Is.EqualTo(result.ToString())); } [TestCase(1, 1, 1, 1, "0")] [TestCase(5, 3, 8, 4, "1101")] [TestCase(9, 4, 4, 3, "1101")] public static void TestOperatorXorAndDiffSizes(int t1, int s1, int t2, int s2, string expected) { // Arrange var testObj1 = new BitArray(s1); var testObj2 = new BitArray(s2); // Act testObj1.Compile(t1); testObj2.Compile(t2); var result = testObj1 ^ testObj2; // Assert Assert.That(expected, Is.EqualTo(result.ToString())); } [TestCase(9, 4, 4, 3, "1101")] [TestCase(1, 1, 1, 1, "1")] [TestCase(5, 3, 8, 4, "1101")] public static void TestOperatorOrAndDiffSizes(int t1, int s1, int t2, int s2, string expected) { // Arrange var testObj1 = new BitArray(s1); var testObj2 = new BitArray(s2); // Act testObj1.Compile(t1); testObj2.Compile(t2); var result = testObj1 | testObj2; // Assert Assert.That(expected, Is.EqualTo(result.ToString())); } [TestCase(1, 1, 1, 1, "1")] [TestCase(5, 3, 8, 4, "0000")] [TestCase(9, 4, 4, 3, "0000")] public static void TestOperatorAndAndDiffSizes(int t1, int s1, int t2, int s2, string expected) { // Arrange var testObj1 = new BitArray(s1); var testObj2 = new BitArray(s2); // Act testObj1.Compile(t1); testObj2.Compile(t2); var result = testObj1 & testObj2; // Assert Assert.That(expected, Is.EqualTo(result.ToString())); } [TestCase(25, 30, "11111")] public static void TestOperatorOr(int tObj1, int tObj2, string expected) { // Arrange var testObj1 = new BitArray(5); var testObj2 = new BitArray(5); // Act testObj1.Compile(tObj1); testObj2.Compile(tObj2); var result = testObj1 | testObj2; // Assert Assert.That(expected, Is.EqualTo(result.ToString())); } [TestCase(16, "01111")] public static void TestOperatorNot(int number, string expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(number); testObj = ~testObj; // Assert Assert.That(expected, Is.EqualTo(testObj.ToString())); } [TestCase(25, 30, 7)] public static void TestOperatorXor(int testNum, int testNum2, int expected) { // Arrange var testObj1 = new BitArray(5); var testObj2 = new BitArray(5); // Act testObj1.Compile(testNum); testObj2.Compile(testNum2); var result = testObj1 ^ testObj2; // Assert Assert.That(expected, Is.EqualTo(result.ToInt32())); } [TestCase(16, "10000000")] public static void TestOperatorShiftLeft(int number, string expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(number); testObj <<= 3; // Assert Assert.That(expected, Is.EqualTo(testObj.ToString())); } [TestCase(24, "110")] public static void TestOperatorShiftRight(int number, string expected) { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(number); testObj >>= 2; // Assert Assert.That(expected, Is.EqualTo(testObj.ToString())); } #endregion OPERATOR TESTS #region COMPARE TESTS [Test] public static void TestParity() { // Arrange var testObj = new BitArray(5); // Act testObj.Compile(26); // Assert Assert.That(testObj.EvenParity(), Is.False); Assert.That(testObj.OddParity(), Is.True); } [Test] public static void TestCompare() { // Arrange var testObj1 = new BitArray("110"); var testObj2 = new BitArray("110"); var testObj3 = new BitArray("100"); // Act // Assert Assert.That(testObj1 == testObj2, Is.True); Assert.That(testObj1 != testObj3, Is.True); } [Test] public static void ArraysOfDifferentLengthsAreNotEqual() { // Arrange var testObj1 = new BitArray("110"); var testObj2 = new BitArray("10101"); // Act // Assert Assert.That(testObj1 == testObj2, Is.False); } #endregion COMPARE TESTS } ================================================ FILE: DataStructures.Tests/Cache/LfuCacheTests.cs ================================================ using DataStructures.Cache; namespace DataStructures.Tests.Cache; public static class LfuCacheTests { [Test] public static void TestPutGet() { var cache = new LfuCache(); cache.Put(1, "one"); cache.Contains(1).Should().BeTrue(); cache.Get(1).Should().Be("one"); } [Test] public static void TestCacheMiss() { var cache = new LfuCache(); cache.Put(1, "one"); cache.Contains(5).Should().BeFalse(); cache.Get(5).Should().BeNull(); } [Test] public static void Evict_ItemWasNotUsed() { var cache = new LfuCache(capacity: 1); cache.Put(1, "one"); // Add to the full cache, 1 will be removed cache.Put(2, "two"); cache.Get(1).Should().BeNull(); cache.Get(2).Should().Be("two"); } [Test] public static void Evict_OneItemWasUsed() { var cache = new LfuCache(capacity: 2); cache.Put(1, "one"); cache.Put(2, "two"); cache.Put(1, "ONE"); // Add to the full cache, 2 will be removed cache.Put(3, "three"); cache.Get(1).Should().Be("ONE"); cache.Get(2).Should().BeNull(); cache.Get(3).Should().Be("three"); } [Test] public static void Evict_LruOrder() { var cache = new LfuCache(capacity: 2); cache.Put(1, "one"); cache.Put(2, "two"); cache.Put(1, "ONE"); cache.Put(2, "TWO"); // Add to the full cache, 1 will be removed cache.Put(3, "three"); cache.Get(1).Should().BeNull(); cache.Get(2).Should().Be("TWO"); cache.Get(3).Should().Be("three"); } } ================================================ FILE: DataStructures.Tests/Cache/LruCacheTests.cs ================================================ using DataStructures.Cache; namespace DataStructures.Tests.Cache; public static class LruCacheTests { [Test] public static void TestPutGet() { var cache = new LruCache(); cache.Put(1, "one"); cache.Contains(1).Should().BeTrue(); cache.Get(1).Should().Be("one"); } [Test] public static void TestCacheMiss() { var cache = new LruCache(); cache.Put(1, "one"); cache.Contains(5).Should().BeFalse(); cache.Get(5).Should().BeNull(); } [Test] public static void TestCacheUpdate() { var cache = new LruCache(); cache.Put(1, "one"); cache.Put(1, "ONE"); cache.Get(1).Should().Be("ONE"); } [Test] public static void RemoveOldestItem_ItemWasNotUsed() { var cache = new LruCache(capacity: 2); cache.Put(1, "one"); cache.Put(2, "two"); // Add to the full cache, 1 will be removed cache.Put(3, "three"); cache.Get(1).Should().BeNull(); cache.Get(2).Should().Be("two"); cache.Get(3).Should().Be("three"); } [Test] public static void RemoveOldestItem_ItemWasRecentlyUsed() { var cache = new LruCache(capacity: 2); cache.Put(1, "one"); cache.Put(2, "two"); cache.Get(1); // Add to the full cache, 1 was used, 2 should be removed cache.Put(3, "three"); cache.Get(1).Should().Be("one"); cache.Get(2).Should().BeNull(); cache.Get(3).Should().Be("three"); } } ================================================ FILE: DataStructures.Tests/DataStructures.Tests.csproj ================================================  net8.0 false ..\stylecop.ruleset true enable runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: DataStructures.Tests/Deque/DequeTests.cs ================================================ using DataStructures.Deque; using NUnit.Framework; using System; using System.Linq; namespace DataStructures.Tests.Deque; /// /// Comprehensive test suite for the Deque (Double-Ended Queue) data structure. /// Tests cover: /// - Constructor validation and initialization /// - Basic operations (AddFront, AddRear, RemoveFront, RemoveRear) /// - Peek operations (non-destructive reads) /// - Edge cases (empty deque, single element, capacity overflow) /// - Type flexibility (int, string, tuples) /// - Circular array behavior and automatic resizing /// - Mixed operations maintaining correct order /// public static class DequeTests { [Test] public static void Constructor_WithDefaultCapacity_CreatesEmptyDeque() { // Arrange & Act: Create deque with default capacity (16) var deque = new Deque(); // Assert: Should be empty initially Assert.That(deque.Count, Is.EqualTo(0)); Assert.That(deque.IsEmpty, Is.True); } [Test] public static void Constructor_WithSpecifiedCapacity_CreatesEmptyDeque() { // Arrange & Act var deque = new Deque(10); // Assert Assert.That(deque.Count, Is.EqualTo(0)); Assert.That(deque.IsEmpty, Is.True); } [Test] public static void Constructor_WithInvalidCapacity_ThrowsArgumentException() { // Arrange, Act & Assert: Capacity must be at least 1 // Zero capacity should throw Assert.Throws(() => new Deque(0)); // Negative capacity should throw Assert.Throws(() => new Deque(-1)); } [Test] public static void AddFront_AddsElementToFront() { // Arrange var deque = new Deque(); // Act: Add elements to front (each becomes new front) deque.AddFront(1); // Deque: [1] deque.AddFront(2); // Deque: [2, 1] deque.AddFront(3); // Deque: [3, 2, 1] // Assert: Most recently added element should be at front Assert.That(deque.Count, Is.EqualTo(3)); Assert.That(deque.PeekFront(), Is.EqualTo(3)); } [Test] public static void AddRear_AddsElementToRear() { // Arrange var deque = new Deque(); // Act deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Assert Assert.That(deque.Count, Is.EqualTo(3)); Assert.That(deque.PeekRear(), Is.EqualTo(3)); } [Test] public static void RemoveFront_RemovesAndReturnsElementFromFront() { // Arrange: Build deque [1, 2, 3] var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Act: Remove front element int result = deque.RemoveFront(); // Assert: Should return 1 and deque becomes [2, 3] Assert.That(result, Is.EqualTo(1)); Assert.That(deque.Count, Is.EqualTo(2)); Assert.That(deque.PeekFront(), Is.EqualTo(2)); } [Test] public static void RemoveRear_RemovesAndReturnsElementFromRear() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Act int result = deque.RemoveRear(); // Assert Assert.That(result, Is.EqualTo(3)); Assert.That(deque.Count, Is.EqualTo(2)); Assert.That(deque.PeekRear(), Is.EqualTo(2)); } [Test] public static void RemoveFront_OnEmptyDeque_ThrowsInvalidOperationException() { // Arrange: Create empty deque var deque = new Deque(); // Act & Assert: Cannot remove from empty deque Assert.Throws(() => deque.RemoveFront()); } [Test] public static void RemoveRear_OnEmptyDeque_ThrowsInvalidOperationException() { // Arrange var deque = new Deque(); // Act & Assert Assert.Throws(() => deque.RemoveRear()); } [Test] public static void PeekFront_ReturnsElementWithoutRemoving() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); // Act int result = deque.PeekFront(); // Assert Assert.That(result, Is.EqualTo(1)); Assert.That(deque.Count, Is.EqualTo(2)); } [Test] public static void PeekRear_ReturnsElementWithoutRemoving() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); // Act int result = deque.PeekRear(); // Assert Assert.That(result, Is.EqualTo(2)); Assert.That(deque.Count, Is.EqualTo(2)); } [Test] public static void PeekFront_OnEmptyDeque_ThrowsInvalidOperationException() { // Arrange var deque = new Deque(); // Act & Assert Assert.Throws(() => deque.PeekFront()); } [Test] public static void PeekRear_OnEmptyDeque_ThrowsInvalidOperationException() { // Arrange var deque = new Deque(); // Act & Assert Assert.Throws(() => deque.PeekRear()); } [Test] public static void Clear_RemovesAllElements() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Act deque.Clear(); // Assert Assert.That(deque.Count, Is.EqualTo(0)); Assert.That(deque.IsEmpty, Is.True); } [Test] public static void ToArray_ReturnsElementsInCorrectOrder() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Act int[] result = deque.ToArray(); // Assert Assert.That(result, Is.EqualTo(new[] { 1, 2, 3 })); } [Test] public static void ToArray_WithMixedOperations_ReturnsCorrectOrder() { // Arrange: Build deque using both front and rear operations var deque = new Deque(); deque.AddFront(2); // Deque: [2] deque.AddFront(1); // Deque: [1, 2] deque.AddRear(3); // Deque: [1, 2, 3] deque.AddRear(4); // Deque: [1, 2, 3, 4] // Act int[] result = deque.ToArray(); // Assert: Array should maintain front-to-rear order Assert.That(result, Is.EqualTo(new[] { 1, 2, 3, 4 })); } [Test] public static void Contains_WithExistingElement_ReturnsTrue() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Act bool result = deque.Contains(2); // Assert Assert.That(result, Is.True); } [Test] public static void Contains_WithNonExistingElement_ReturnsFalse() { // Arrange var deque = new Deque(); deque.AddRear(1); deque.AddRear(2); deque.AddRear(3); // Act bool result = deque.Contains(5); // Assert Assert.That(result, Is.False); } [Test] public static void Deque_WithStringType_WorksCorrectly() { // Arrange var deque = new Deque(); // Act deque.AddRear("Hello"); deque.AddFront("World"); deque.AddRear("!"); // Assert Assert.That(deque.Count, Is.EqualTo(3)); Assert.That(deque.PeekFront(), Is.EqualTo("World")); Assert.That(deque.PeekRear(), Is.EqualTo("!")); } [Test] public static void Deque_AutomaticallyResizes_WhenCapacityExceeded() { // Arrange: Create deque with small capacity of 2 var deque = new Deque(2); // Act: Add more elements than initial capacity deque.AddRear(1); // Capacity: 2, Count: 1 deque.AddRear(2); // Capacity: 2, Count: 2 (full) deque.AddRear(3); // Should trigger resize to capacity 4, Count: 3 deque.AddRear(4); // Capacity: 4, Count: 4 // Assert: All elements should be present in correct order Assert.That(deque.Count, Is.EqualTo(4)); Assert.That(deque.ToArray(), Is.EqualTo(new[] { 1, 2, 3, 4 })); } [Test] public static void Deque_MixedOperations_MaintainsCorrectOrder() { // Arrange var deque = new Deque(); // Act: Perform complex sequence of operations deque.AddRear(3); // Deque: [3] deque.AddFront(2); // Deque: [2, 3] deque.AddFront(1); // Deque: [1, 2, 3] deque.AddRear(4); // Deque: [1, 2, 3, 4] deque.RemoveFront(); // Deque: [2, 3, 4] (removed 1) deque.RemoveRear(); // Deque: [2, 3] (removed 4) deque.AddRear(5); // Deque: [2, 3, 5] // Assert: Final order should be correct after all operations Assert.That(deque.ToArray(), Is.EqualTo(new[] { 2, 3, 5 })); } [Test] public static void Deque_WithComplexType_WorksCorrectly() { // Arrange var deque = new Deque<(int, string)>(); // Act deque.AddRear((1, "One")); deque.AddRear((2, "Two")); deque.AddFront((0, "Zero")); // Assert Assert.That(deque.Count, Is.EqualTo(3)); Assert.That(deque.PeekFront(), Is.EqualTo((0, "Zero"))); Assert.That(deque.PeekRear(), Is.EqualTo((2, "Two"))); } [Test] public static void Deque_AfterMultipleResizes_MaintainsIntegrity() { // Arrange: Start with very small capacity var deque = new Deque(2); // Act: Add many elements to trigger multiple resizes // Capacity progression: 2 -> 4 -> 8 -> 16 -> 32 -> 64 -> 128 for (int i = 0; i < 100; i++) { deque.AddRear(i); } // Assert: All elements should be intact after multiple resizes Assert.That(deque.Count, Is.EqualTo(100)); Assert.That(deque.PeekFront(), Is.EqualTo(0)); Assert.That(deque.PeekRear(), Is.EqualTo(99)); Assert.That(deque.ToArray(), Is.EqualTo(Enumerable.Range(0, 100).ToArray())); } [Test] public static void Deque_CircularBehavior_WorksCorrectly() { // Arrange: Create deque with capacity 4 var deque = new Deque(4); // Act: Test circular wrap-around behavior deque.AddRear(1); // Internal: [1, _, _, _], front=0, rear=1 deque.AddRear(2); // Internal: [1, 2, _, _], front=0, rear=2 deque.RemoveFront(); // Internal: [_, 2, _, _], front=1, rear=2 deque.RemoveFront(); // Internal: [_, _, _, _], front=2, rear=2 deque.AddRear(3); // Internal: [_, _, 3, _], front=2, rear=3 deque.AddRear(4); // Internal: [_, _, 3, 4], front=2, rear=0 (wrapped) deque.AddRear(5); // Internal: [5, _, 3, 4], front=2, rear=1 (wrapped) // Assert: Elements should be in correct logical order despite circular storage Assert.That(deque.ToArray(), Is.EqualTo(new[] { 3, 4, 5 })); } } ================================================ FILE: DataStructures.Tests/DisjointSet/DisjointSetTests.cs ================================================ using DataStructures.DisjointSet; namespace DataStructures.Tests.DisjointSet; [TestFixture] public class DisjointSetTests { [Test] public static void MakeSetDataInitializationTest() { DisjointSet ds = new(); var one = ds.MakeSet(1); var two = ds.MakeSet(2); one.Data.Should().Be(1); two.Data.Should().Be(2); } [Test] public static void UnionTest() { DisjointSet ds = new(); var one = ds.MakeSet(1); var two = ds.MakeSet(2); var three = ds.MakeSet(3); ds.UnionSet(one, two); ds.FindSet(one).Should().Be(ds.FindSet(two)); ds.UnionSet(one, three); ds.FindSet(two).Should().Be(ds.FindSet(three)); (one.Rank + two.Rank + three.Rank).Should().Be(1); } } ================================================ FILE: DataStructures.Tests/Fenwick/BinaryIndexedTreeTests.cs ================================================ using DataStructures.Fenwick; namespace DataStructures.Tests.Fenwick; [TestFixture] internal class BinaryIndexedTreeTests { [Test] public void GetSum_CreateBITAndRequestSum_ReturnCorrect() { int[] array = [2, 1, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9]; var tree = new BinaryIndexedTree(array); var expectedSum = 12; var resultedSum = tree.GetSum(5); resultedSum.Should().Be(expectedSum); } [Test] public void UpdateTree_UpdateTreeAndRequestSum_GetSum() { int[] array = [2, 1, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9]; var tree = new BinaryIndexedTree(array); var expectedSum = 18; array[3] += 6; tree.UpdateTree(3, 6); var resultedSum = tree.GetSum(5); resultedSum.Should().Be(expectedSum); } } ================================================ FILE: DataStructures.Tests/GlobalUsings.cs ================================================ // ----------------------------------------------------------------------------- // Global using directives for the C-Sharp solution. // These namespaces are imported globally so they don’t need to be repeatedly declared // in individual files, improving readability and reducing boilerplate. // // Guidelines: // - Keep only the most commonly used namespaces here. // - Add project-specific namespaces (e.g., Utilities.Extensions) only if they are // required across the majority of files in the project. // - Avoid placing rarely used namespaces here to maintain clarity. // ----------------------------------------------------------------------------- global using System; // Core base classes and fundamental types global using System.Collections.Generic; // Generic collection types (List, Dictionary, etc.) global using System.Linq; // LINQ query operators for collections global using System.Text; // Text encoding, StringBuilder, etc. global using NUnit.Framework; // Testing framework providing attributes and assertions for test cases global using FluentAssertions; // Assertion library for more readable and expressive unit tests ================================================ FILE: DataStructures.Tests/Graph/DirectedWeightedGraphTests.cs ================================================ using DataStructures.Graph; namespace DataStructures.Tests.Graph; [TestFixture] public class DirectedWeightedGraphTests { [TestCase(-1)] [TestCase(-2)] [TestCase(-3)] public void GraphInitializationTest_ShouldThrowOverflow(int capacity) { Func> createGraph = () => new DirectedWeightedGraph(capacity); createGraph.Should().Throw() .WithMessage("Graph capacity should always be a non-negative integer."); } [TestCase(1)] [TestCase(10)] [TestCase(20)] [TestCase(30)] public void GraphInitializationTest_Success(int capacity) { Func> createGraph = () => new DirectedWeightedGraph(capacity); createGraph.Should().NotThrow(); } [Test] public void GraphAddVertexTest_Success() { var graph = new DirectedWeightedGraph(10); graph.AddVertex('A'); graph.AddVertex('B'); graph.AddVertex('C'); graph.Count.Should().Be(3); } [Test] public void GraphAddVertexTest_ShouldThrowOverflow() { var graph = new DirectedWeightedGraph(10); for (var i = 0; i < 10; i++) { graph.AddVertex('A'); } Action addOverflow = () => graph.AddVertex('A'); graph.Count.Should().Be(10); graph.Vertices.Should().OnlyContain(x => x != null && x.Data == 'A'); addOverflow.Should().Throw() .WithMessage("Graph overflow."); } [Test] public void GraphRemoveVertexTest_Success() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); var vertexC = graph.AddVertex('C'); graph.AddEdge(vertexB, vertexA, 5); graph.AddEdge(vertexC, vertexA, 5); var neighborsB = graph.GetNeighbors(vertexB).ToList(); var neighborsC = graph.GetNeighbors(vertexC).ToList(); graph.RemoveVertex(vertexA); neighborsB.Should().HaveCount(1); neighborsB[0].Should().Be(vertexA); neighborsC.Should().HaveCount(1); neighborsC[0].Should().Be(vertexA); graph.GetNeighbors(vertexB).Should().HaveCount(0); graph.GetNeighbors(vertexC).Should().HaveCount(0); } [Test] public void GraphRemoveAndAddVertexTest_Success() { double weight_A_B = 1; double weight_A_C = 2; double weight_A_D = 3; double weight_B_A = 4; double weight_B_C = 5; double weight_C_A = 6; double weight_C_B = 7; double weight_C_D = 8; double weight_D_A = 9; double weight_D_C = 10; var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); var vertexC = graph.AddVertex('C'); graph.AddEdge(vertexA, vertexB, weight_A_B); graph.AddEdge(vertexA, vertexC, weight_A_C); graph.AddEdge(vertexB, vertexA, weight_B_A); graph.AddEdge(vertexB, vertexC, weight_B_C); graph.AddEdge(vertexC, vertexA, weight_C_A); graph.AddEdge(vertexC, vertexB, weight_C_B); var vertexA_Index_BeforeUpdate = vertexA.Index; vertexA_Index_BeforeUpdate.Should().Be(0); var neighborsA_BeforeUpdate = graph.GetNeighbors(vertexA).ToList(); neighborsA_BeforeUpdate.Should().HaveCount(2); neighborsA_BeforeUpdate[0].Should().Be(vertexB); neighborsA_BeforeUpdate[1].Should().Be(vertexC); var vertexB_Index_BeforeUpdate = vertexB.Index; vertexB_Index_BeforeUpdate.Should().Be(1); var neighborsB_BeforeUpdate = graph.GetNeighbors(vertexB).ToList(); neighborsB_BeforeUpdate.Should().HaveCount(2); neighborsB_BeforeUpdate[0].Should().Be(vertexA); neighborsB_BeforeUpdate[1].Should().Be(vertexC); var vertexC_Index_BeforeUpdate = vertexC.Index; vertexC_Index_BeforeUpdate.Should().Be(2); var neighborsC_BeforeUpdate = graph.GetNeighbors(vertexC).ToList(); neighborsC_BeforeUpdate.Should().HaveCount(2); neighborsC_BeforeUpdate[0].Should().Be(vertexA); neighborsC_BeforeUpdate[1].Should().Be(vertexB); var weight_A_B_BeforeUpdate = graph.AdjacentDistance(vertexA, vertexB); var weight_A_C_BeforeUpdate = graph.AdjacentDistance(vertexA, vertexC); var weight_B_A_BeforeUpdate = graph.AdjacentDistance(vertexB, vertexA); var weight_B_C_BeforeUpdate = graph.AdjacentDistance(vertexB, vertexC); var weight_C_A_BeforeUpdate = graph.AdjacentDistance(vertexC, vertexA); var weight_C_B_BeforeUpdate = graph.AdjacentDistance(vertexC, vertexB); weight_A_B_BeforeUpdate.Should().Be(weight_A_B); weight_A_C_BeforeUpdate.Should().Be(weight_A_C); weight_B_A_BeforeUpdate.Should().Be(weight_B_A); weight_B_C_BeforeUpdate.Should().Be(weight_B_C); weight_C_A_BeforeUpdate.Should().Be(weight_C_A); weight_C_B_BeforeUpdate.Should().Be(weight_C_B); graph.RemoveVertex(vertexB); var vertexD = graph.AddVertex('D'); graph.AddEdge(vertexA, vertexD, weight_A_D); graph.AddEdge(vertexC, vertexD, weight_C_D); graph.AddEdge(vertexD, vertexA, weight_D_A); graph.AddEdge(vertexD, vertexC, weight_D_C); var vertexA_Index_AfterUpdate = vertexA.Index; vertexA_Index_AfterUpdate.Should().Be(0); var neighborsA_AfterUpdate = graph.GetNeighbors(vertexA).ToList(); neighborsA_AfterUpdate.Should().HaveCount(2); neighborsA_AfterUpdate[0].Should().Be(vertexC); neighborsA_AfterUpdate[1].Should().Be(vertexD); var vertexC_Index_AfterUpdate = vertexC.Index; vertexC_Index_AfterUpdate.Should().Be(1); var neighborsC_AfterUpdate = graph.GetNeighbors(vertexC).ToList(); neighborsC_AfterUpdate.Should().HaveCount(2); neighborsC_AfterUpdate[0].Should().Be(vertexA); neighborsC_AfterUpdate[1].Should().Be(vertexD); var vertexD_Index_AfterUpdate = vertexD.Index; vertexD_Index_AfterUpdate.Should().Be(2); var neighborsD_AfterUpdate = graph.GetNeighbors(vertexD).ToList(); neighborsD_AfterUpdate.Should().HaveCount(2); neighborsD_AfterUpdate[0].Should().Be(vertexA); neighborsD_AfterUpdate[1].Should().Be(vertexC); var weight_A_C_AfterUpdate = graph.AdjacentDistance(vertexA, vertexC); var weight_A_D_AfterUpdate = graph.AdjacentDistance(vertexA, vertexD); var weight_C_A_AfterUpdate = graph.AdjacentDistance(vertexC, vertexA); var weight_C_D_AfterUpdate = graph.AdjacentDistance(vertexC, vertexD); var weight_D_A_AfterUpdate = graph.AdjacentDistance(vertexD, vertexA); var weight_D_C_AfterUpdate = graph.AdjacentDistance(vertexD, vertexC); weight_A_D_AfterUpdate.Should().Be(weight_A_D); weight_A_C_AfterUpdate.Should().Be(weight_A_C); weight_D_A_AfterUpdate.Should().Be(weight_D_A); weight_D_C_AfterUpdate.Should().Be(weight_D_C); weight_C_A_AfterUpdate.Should().Be(weight_C_A); weight_C_D_AfterUpdate.Should().Be(weight_C_D); } [Test] public void GraphRemoveVertexTest_ShouldThrowVertexNotInGraph() { var graph = new DirectedWeightedGraph(10); var vertexA = new Vertex('A', 0); Action removeVertex = () => graph.RemoveVertex(vertexA); removeVertex.Should().Throw() .WithMessage($"Vertex does not belong to graph: {vertexA}."); } [Test] public void GraphAddEdgeTest_Success() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); var vertexC = graph.AddVertex('C'); graph.AddEdge(vertexA, vertexB, 5); graph.AreAdjacent(vertexA, vertexB).Should().BeTrue(); graph.AreAdjacent(vertexA, vertexC).Should().BeFalse(); } [Test] public void GraphAddEdgeTest_ShouldThrowZeroWeight() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); Action addZeroEdge = () => graph.AddEdge(vertexA, vertexB, 0); addZeroEdge.Should().Throw() .WithMessage("Edge weight cannot be zero."); } [Test] public void GraphAddEdgeTest_ShouldThrowVertexNotInGraph() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = new Vertex('B', 1); Action addZeroEdge = () => graph.AddEdge(vertexA, vertexB, 0); addZeroEdge.Should().Throw() .WithMessage($"Vertex does not belong to graph: {vertexB}."); } [Test] public void GraphAddEdgeTest_ShouldThrowEdgeExists() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); const int currentEdgeWeight = 5; graph.AddEdge(vertexA, vertexB, currentEdgeWeight); Action addZeroEdge = () => graph.AddEdge(vertexA, vertexB, 10); addZeroEdge.Should().Throw() .WithMessage($"Vertex already exists: {currentEdgeWeight}"); } [Test] public void GraphRemoveEdgeTest_Success() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); graph.AddEdge(vertexA, vertexB, 5); graph.RemoveEdge(vertexA, vertexB); graph.AreAdjacent(vertexA, vertexB).Should().BeFalse(); } [Test] public void GraphRemoveEdgeTest_ShouldThrowVertexNotInGraph() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = new Vertex('B', 1); Action removeEdge = () => graph.RemoveEdge(vertexA, vertexB); removeEdge.Should().Throw() .WithMessage($"Vertex does not belong to graph: {vertexB}."); } [Test] public void GraphGetNeighborsTest_Success() { var graph = new DirectedWeightedGraph(10); var vertexA = graph.AddVertex('A'); var vertexB = graph.AddVertex('B'); var vertexC = graph.AddVertex('C'); var vertexD = graph.AddVertex('D'); graph.AddEdge(vertexA, vertexB, 5); graph.AddEdge(vertexA, vertexC, 5); graph.AddEdge(vertexA, vertexD, 5); var neighborsA = graph.GetNeighbors(vertexA).ToArray(); neighborsA.Should().HaveCount(3); neighborsA[0].Should().Be(vertexB); neighborsA[1].Should().Be(vertexC); neighborsA[2].Should().Be(vertexD); } [Test] public void GraphGetNeighborsTest_ShouldThrowVertexNotInGraph() { var graph = new DirectedWeightedGraph(10); var vertexA = new Vertex('A', 0); Func?>> getNeighbors = () => { var enumerable = graph.GetNeighbors(vertexA); return enumerable.ToList(); }; getNeighbors.Should().Throw() .WithMessage($"Vertex does not belong to graph: {vertexA}."); } } ================================================ FILE: DataStructures.Tests/Hashing/HashTableTests.cs ================================================ using DataStructures.Hashing; namespace DataStructures.Tests.Hashing; [TestFixture] public class HashTableTests { [Test] public void Add_ThrowsException_WhenKeyIsNull() { var hashTable = new HashTable(); Assert.Throws(() => hashTable.Add(null, 1)); } [Test] public void Add_ThrowsException_WhenKeyAlreadyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); Assert.Throws(() => hashTable.Add("a", 2)); } [Test] public void Add_IncreasesCount_WhenKeyDoesNotExist() { var hashTable = new HashTable(); hashTable.Add("a", 1); Assert.That(hashTable.Count, Is.EqualTo(1)); } [Test] public void Add_DoesNotIncreaseCount_WhenKeyAlreadyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); try { hashTable.Add("a", 2); } catch (ArgumentException) { Console.WriteLine("ArgumentException"); } Assert.That(hashTable.Count, Is.EqualTo(1)); } [Test] public void Add_ThrowsException_WhenValueIsNull() { var hashTable = new HashTable(); Assert.Throws(() => hashTable.Add("a", null)); } [Test] public void Add_IncreasesCount_WhenValueDoesNotExist() { var hashTable = new HashTable(); hashTable.Add("b", 1); Assert.That(hashTable.Count, Is.EqualTo(1)); } [Test] public void Add_DoesNotIncreaseCount_WhenValueAlreadyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); try { hashTable.Add("b", 1); } catch (ArgumentException) { Console.WriteLine("ArgumentException"); } Assert.That(hashTable.Count, Is.EqualTo(2)); } [Test] public void Add_IncreasesCount_WhenValueIsNull() { var hashTable = new HashTable(); try { hashTable.Add("a", null); } catch (ArgumentNullException) { Console.WriteLine("ArgumentNullException"); } Assert.That(hashTable.Count, Is.EqualTo(0)); } [Test] public void Add_IncreasesCount_WhenValueAlreadyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); hashTable.Add("b", 1); Assert.That(hashTable.Count, Is.EqualTo(2)); } [Test] public void Add_ThrowsException_OnCollision() { // Arrange var hashTable = new HashTable(); hashTable.Add(new Collider(1), 1); // Act & Assert Assert.Throws(() => hashTable.Add(new Collider(1), 2)); } [Test] public void Remove_ThrowsException_WhenKeyIsNull() { var hashTable = new HashTable(); Assert.Throws(() => hashTable.Remove(null)); } [Test] public void Remove_ReturnsFalse_WhenKeyDoesNotExist() { var hashTable = new HashTable(); Assert.That(hashTable.Remove("a"), Is.False); } [Test] public void Remove_ReturnsTrue_WhenKeyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); Assert.That(hashTable.Remove("a"), Is.True); } [Test] public void Remove_DecreasesCount_WhenKeyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); hashTable.Remove("a"); Assert.That(hashTable.Count, Is.EqualTo(0)); } [Test] public void Remove_DoesNotDecreaseCount_WhenKeyDoesNotExist() { var hashTable = new HashTable(); hashTable.Remove("a"); Assert.That(hashTable.Count, Is.EqualTo(0)); } [Test] public void Remove_TriggersResizeDown() { var hashTable = new HashTable(4); for (var i = 1; i <= 50; i++) { hashTable.Add(i, $"Value{i}"); } for (var i = 1; i <= 40; i++) { hashTable.Remove(i); } Assert.That(hashTable.Capacity, Is.EqualTo(40)); } [Test] public void Remove_TriggersResizeDown_MinimumOfDefaultCapacity() { var hashTable = new HashTable(4); for (var i = 1; i <= 50; i++) { hashTable.Add(i, $"Value{i}"); } for (var i = 1; i <= 48; i++) { hashTable.Remove(i); } Assert.That(hashTable.Capacity, Is.EqualTo(16)); } [Test] public void ContainsValue_ReturnsFalse_WhenValueDoesNotExist() { var hashTable = new HashTable(); Assert.That(hashTable.ContainsValue(1), Is.False); } [Test] public void ContainsValue_ReturnsTrue_WhenValueExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); Assert.That(hashTable.ContainsValue(1), Is.True); } [Test] public void ContainsValue_ReturnsFalse_WhenValueIsNull() { var hashTable = new HashTable(); Assert.Throws(() => hashTable.ContainsValue(null)); } [Test] public void ContainsKey_ReturnsFalse_WhenKeyDoesNotExist() { var hashTable = new HashTable(); Assert.That(hashTable.ContainsKey("a"), Is.False); } [Test] public void ContainsKey_ReturnsTrue_WhenKeyExists() { var hashTable = new HashTable(); hashTable.Add("a", 1); Assert.That(hashTable.ContainsKey("a"), Is.True); } [Test] public void ContainsKey_ReturnsFalse_WhenKeyIsNull() { var hashTable = new HashTable(); Assert.Throws(() => hashTable.ContainsKey(null)); } [Test] public void Clear_SetsCountToZero() { var hashTable = new HashTable(); hashTable.Add("a", 1); hashTable.Clear(); Assert.That(hashTable.Count, Is.EqualTo(0)); } [Test] public void Clear_RemovesAllElements() { var hashTable = new HashTable(); hashTable.Add("a", 1); hashTable.Clear(); Assert.That(hashTable.ContainsKey("a"), Is.False); } [Test] public void Clear_ResetsTable() { var hashTable = new HashTable(); hashTable.Add(1, "A"); hashTable.Clear(); hashTable.Add(2, "B"); Assert.That(hashTable.Count, Is.EqualTo(1)); Assert.That(hashTable[2], Is.EqualTo("B")); } [Test] public void Resize_IncreasesCapacity() { var hashTable = new HashTable(4); hashTable.Add("one", 1); hashTable.Add("two", 2); hashTable.Add("three", 3); hashTable.Add("four", 4); hashTable.Add("humour", 5); /// Next Prime number after 4 is 5 /// Capacity should be 5 /// After resizing, the capacity should be 10 Assert.That(hashTable.Capacity, Is.EqualTo(10)); } [Test] public void LoadFactor_ReturnsCorrectValue() { var hashTable = new HashTable(4); hashTable.Add("one", 1); hashTable.Add("two", 2); hashTable.Add("three", 3); hashTable.Add("four", 4); hashTable.Add("humour", 5); Assert.That(hashTable.LoadFactor, Is.EqualTo(0.75f)); } [Test] public void Keys_ReturnsCorrectKeys() { var hashTable = new HashTable(); hashTable.Add(1, "one"); hashTable.Add(2, "two"); hashTable.Add(3, "three"); var keys = new List { 1, 2, 3 }; Assert.That(keys, Is.EquivalentTo(hashTable.Keys)); } [Test] public void Values_ReturnsCorrectValues() { var hashTable = new HashTable(); hashTable.Add(1, "one"); hashTable.Add(2, "two"); hashTable.Add(3, "three"); var values = new List { "one", "two", "three" }; Assert.That(values, Is.EquivalentTo(hashTable.Values)); } [Test] public void Constructor_ThrowsException_WhenCapacityIsZero() { Assert.Throws(() => new HashTable(0)); } [Test] public void Constructor_ThrowsException_WhenLoadFactorIsZero() { Assert.Throws(() => new HashTable(4, 0)); } [Test] public void Constructor_ThrowsException_WhenLoadFactorIsLessThanZero() { Assert.Throws(() => new HashTable(4, -1)); } [Test] public void Constructor_ThrowsException_WhenLoadFactorIsGreaterThanOne() { Assert.Throws(() => new HashTable(4, 2)); } [Test] public void Constructor_RoundsCapacityToPrime() { var hashTable = new HashTable(17); Assert.That(hashTable.Capacity, Is.EqualTo(19)); } [Test] public void GetIndex_ThrowsException_WhenKeyIsNull() { var hashTable = new HashTable(4); Assert.Throws(() => hashTable.GetIndex(null)); } [Test] public void FindEntry_ThrowsException_WhenKeyIsNull() { var hashTable = new HashTable(4); Assert.Throws(() => hashTable.FindEntry(null)); } [Test] public void This_Get_ThrowsException_WhenKeyIsNull() { var hashTable = new HashTable(4); Assert.Throws(() => { var value = hashTable[null]; Console.WriteLine(value); }); } [Test] public void This_Set_ThrowsException_WhenKeyIsNull() { var hashTable = new HashTable(4); Assert.Throws(() => hashTable[null] = 1); } [Test] public void This_Get_ReturnsCorrectValue() { var hashTable = new HashTable(4); hashTable.Add("one", 1); Assert.That(hashTable["one"], Is.EqualTo(1)); } [Test] public void This_Set_UpdatesValue() { var hashTable = new HashTable(4); hashTable.Add("one", 1); hashTable["one"] = 2; Assert.That(hashTable["one"], Is.EqualTo(2)); } [Test] public void This_Set_KeyNotFoundException_WhenKeyDoesNotExist() { var hashTable = new HashTable(4); Assert.Throws(() => hashTable["one"] = 2); } [Test] public void This_Get_KeyNotFoundException_WhenKeyDoesNotExist() { var hashTable = new HashTable(4); Assert.Throws(() => { var value = hashTable["one"]; Console.WriteLine(value); }); } [Test] public void Test_NegativeHashKey_ReturnsCorrectValue() { var hashTable = new HashTable(4); hashTable.Add(new NegativeHashKey(1), 1); Assert.That(hashTable[new NegativeHashKey(1)], Is.EqualTo(1)); } [Test] public void Resize_HandlesNegativeHashCodeCorrectly() { // Arrange var hashTable = new HashTable(2); // Act hashTable.Add(new NegativeHashKey(1), "A"); hashTable.Add(new NegativeHashKey(2), "B"); hashTable.Add(new NegativeHashKey(3), "C"); // Assert Assert.That(hashTable[new NegativeHashKey(1)], Is.EqualTo("A")); Assert.That(hashTable[new NegativeHashKey(2)], Is.EqualTo("B")); Assert.That(hashTable[new NegativeHashKey(3)], Is.EqualTo("C")); } [Test] public void Resize_HandlesNegativeIndexCorrectly() { // Arrange var hashTable = new HashTable(2); var key = new NegativeHashKey(111); hashTable.Add(key, "Value"); // Act hashTable.Resize(); // Assert Assert.That(hashTable[key], Is.EqualTo("Value")); } [Test] public void Add_ShouldTriggerResize_WhenThresholdExceeded() { // Arrange var initialCapacity = 4; var hashTable = new HashTable(initialCapacity); // Act for (var i = 1; i <= 32; i++) { hashTable.Add(i, $"Value{i}"); } // Assert hashTable.Capacity.Should().BeGreaterThan(initialCapacity); hashTable.Count.Should().Be(32); } [Test] public void Add_ThrowsException_WhenKeyIsDefault() { // Arrange var hashTable = new HashTable(); // Act & Assert Action act = () => hashTable.Add(default, "Value"); act.Should().Throw().WithMessage("*key*"); } [Test] public void Add_ThrowsException_WhenValueIsDefault() { // Arrange var hashTable = new HashTable(); // Act & Assert Action act = () => hashTable.Add(1, default); act.Should().Throw().WithMessage("*value*"); } [Test] public void Add_StoresValueCorrectly() { // Arrange var hashTable = new HashTable(); // Act hashTable.Add(1, "Value1"); // Assert hashTable[1].Should().Be("Value1"); } [Test] public void Get_ReturnsCorrectValue_ForExistingKey() { // Arrange var hashTable = new HashTable(); hashTable.Add("key", 42); // Act var value = hashTable["key"]; // Assert value.Should().Be(42); } [Test] public void Get_ThrowsException_WhenKeyDoesNotExist() { // Arrange var hashTable = new HashTable(); // Act & Assert Action act = () => _ = hashTable["nonexistent"]; act.Should().Throw(); } [Test] public void Capacity_Increases_WhenResizeOccurs() { var initialCapacity = 4; var hashTable = new HashTable(initialCapacity); for (var i = 1; i <= 5; i++) { hashTable.Add(i, $"Value{i}"); } hashTable.Capacity.Should().BeGreaterThan(initialCapacity); } [Test] public void IndexerSet_Throws_KeyNotFound() { // Arrange var hashTable = new HashTable(); // Act & Assert Assert.Throws(() => hashTable[1] = "A"); } } public class NegativeHashKey(int id) { private readonly int id = id; public override int GetHashCode() { // Return a negative hash code return -id; } public override bool Equals(object? obj) { if (obj is NegativeHashKey other) { return id == other.id; } return false; } } /// /// Class to simulate hash collisions /// /// Id of this object public class Collider(int id) { private readonly int id = id; public override int GetHashCode() => 42; // Force all instances to collide public override bool Equals(object? obj) => obj is Collider other && other.id == id; } ================================================ FILE: DataStructures.Tests/Hashing/NumberTheory/PrimeNumberTests.cs ================================================ using DataStructures.Hashing.NumberTheory; namespace DataStructures.Tests.Hashing.NumberTheory; [TestFixture] public static class PrimeNumberTests { private static readonly object[] IsPrimeSource = [ new object[] { 0, false }, new object[] { 1, false }, new object[] { 2, true }, new object[] { 3, true }, new object[] { 4, false }, new object[] { 5, true }, new object[] { 6, false }, new object[] { 7, true }, new object[] { 8, false }, new object[] { 9, false }, new object[] { 10, false }, new object[] { 11, true }, new object[] { 12, false }, new object[] { 13, true }, new object[] { 14, false }, new object[] { 15, false }, new object[] { 16, false }, new object[] { 17, true }, new object[] { 18, false }, new object[] { 19, true }, new object[] { 20, false }, new object[] { 21, false }, new object[] { 22, false }, new object[] { 23, true }, new object[] { 24, false }, new object[] { 25, false }, new object[] { 26, false }, new object[] { 27, false }, new object[] { 28, false }, new object[] { 29, true }, new object[] { 30, false }, new object[] { 31, true }, new object[] { 32, false }, new object[] { 33, false }, new object[] { 34, false }, new object[] { 35, false }, new object[] { 36, false }, new object[] { 37, true }, new object[] { 38, false }, new object[] { 39, false }, new object[] { 40, false }, ]; private static readonly object[] NextPrimeSource = [ new object[] { 0, 1, false, 2 }, new object[] { 1, 1, false, 2 }, new object[] { 3, 1, false, 5 }, new object[] { 4, 1, false, 5 }, new object[] { 5, 1, false, 7 }, new object[] { 6, 1, false, 7 }, new object[] { 7, 1, false, 11 }, new object[] { 8, 1, false, 11 }, new object[] { 9, 1, false, 11 }, new object[] { 10, 1, false, 11 }, new object[] { 11, 1, false, 13 }, new object[] { 12, 1, false, 13 }, new object[] { 13, 1, false, 17 }, new object[] { 14, 1, false, 17 }, new object[] { 15, 1, false, 17 }, new object[] { 16, 1, false, 17 }, new object[] { 17, 1, false, 19 }, new object[] { 18, 1, false, 19 }, new object[] { 19, 1, false, 23 }, new object[] { 20, 1, false, 23 }, new object[] { 21, 1, false, 23 }, new object[] { 22, 1, false, 23 }, new object[] { 23, 1, false, 29 }, new object[] { 24, 1, false, 29 }, new object[] { 25, 1, false, 29 }, new object[] { 26, 1, false, 29 }, new object[] { 27, 1, false, 29 }, new object[] { 28, 1, false, 29 }, new object[] { 29, 1, false, 31 }, new object[] { 4, 1, true, 3 }, new object[] { 5, 1, true, 3 }, new object[] { 6, 1, true, 5 }, new object[] { 7, 1, true, 5 }, new object[] { 8, 1, true, 7 }, new object[] { 9, 1, true, 7 }, new object[] { 10, 1, true, 7 } ]; [TestCaseSource(nameof(IsPrimeSource))] public static void IsPrimeTest(int number, bool expected) { var actual = PrimeNumber.IsPrime(number); Assert.That(expected, Is.EqualTo(actual)); } [TestCaseSource(nameof(NextPrimeSource))] public static void NextPrimeTest(int number, int factor, bool desc, int expected) { var actual = PrimeNumber.NextPrime(number, factor, desc); Assert.That(expected, Is.EqualTo(actual)); } } ================================================ FILE: DataStructures.Tests/Heap/BinaryHeapTests.cs ================================================ using DataStructures.Heap; namespace DataStructures.Tests.Heap; internal static class BinaryHeapTests { private static BinaryHeap BuildTestHeap() { var heap = new BinaryHeap(); var elems = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; foreach (var i in elems) { heap.Push(i); } return heap; } [Test] public static void Constructor_UseCustomComparer_BuildCorrectHeap() { var revHeap = new BinaryHeap(Comparer.Create((x, y) => y.CompareTo(x))); foreach (var i in new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) { revHeap.Push(i); } Assert.That(revHeap.Count, Is.EqualTo(10)); Assert.That(revHeap.Peek(), Is.EqualTo(1)); Assert.That(revHeap.Pop(), Is.EqualTo(1)); Assert.That(revHeap.Peek(), Is.EqualTo(2)); } [Test] public static void Push_AddElements_BuildCorrectHeap() { var heap = BuildTestHeap(); Assert.That(heap.Peek(), Is.EqualTo(10)); Assert.That(heap.Count, Is.EqualTo(10)); } public static void Pop_RemoveElements_HeapStillValid() { var heap = BuildTestHeap(); Assert.That(heap.Peek(), Is.EqualTo(10)); Assert.That(heap.Count, Is.EqualTo(10)); Assert.That(heap.Pop(), Is.EqualTo(10)); Assert.That(heap.Count, Is.EqualTo(9)); Assert.That(heap.Contains(10), Is.False); Assert.That(heap.Pop(), Is.EqualTo(9)); Assert.That(heap.Count, Is.EqualTo(8)); Assert.That(heap.Contains(9), Is.False); } [Test] public static void Pop_EmptyHeap_ThrowsCorrectException() { var heap = new BinaryHeap(); Assert.Throws(() => heap.Pop()); } [Test] public static void Peek_NonEmptyHeap_ReturnsCorrectAnswer() { var heap = BuildTestHeap(); Assert.That(heap.Peek(), Is.EqualTo(10)); } [Test] public static void Peek_EmptyHeap_ThrowsCorrectException() { var heap = new BinaryHeap(); Assert.Throws(() => heap.Peek()); } [Test] public static void PushPop_EmptyHeap_ReturnsCorrectAnswer() { var heap = new BinaryHeap(); Assert.That(heap.PushPop(10), Is.EqualTo(10)); } [Test] public static void PushPop_NonEmptyHeap_ReturnsCorrectAnswer() { var heap = BuildTestHeap(); Assert.That(heap.PushPop(20), Is.EqualTo(20)); Assert.That(heap.PushPop(-10), Is.EqualTo(10)); } [Test] public static void Contains_NonEmptyHeap_ReturnsCorrectAnswer() { var heap = BuildTestHeap(); Assert.That(heap.Contains(1), Is.True); Assert.That(heap.Contains(5), Is.True); Assert.That(heap.Contains(10), Is.True); Assert.That(heap.Contains(11), Is.False); } [Test] public static void Contains_EmptyHeap_ReturnsCorrectAnswer() { var heap = new BinaryHeap(); Assert.That(heap.Contains(1), Is.False); Assert.That(heap.Contains(5), Is.False); Assert.That(heap.Contains(10), Is.False); Assert.That(heap.Contains(11), Is.False); } [Test] public static void Remove_NonEmptyHeap_HeapStillValid() { var heap = BuildTestHeap(); heap.Remove(2); Assert.That(heap.Contains(2), Is.False); Assert.That(heap.Peek(), Is.EqualTo(10)); Assert.That(heap.Count, Is.EqualTo(9)); heap.Remove(8); Assert.That(heap.Contains(8), Is.False); Assert.That(heap.Peek(), Is.EqualTo(10)); Assert.That(heap.Count, Is.EqualTo(8)); heap.Remove(5); Assert.That(heap.Contains(5), Is.False); Assert.That(heap.Peek(), Is.EqualTo(10)); Assert.That(heap.Count, Is.EqualTo(7)); Assert.Throws(() => heap.Remove(11)); } [Test] public static void Remove_EmptyHeap_ThrowsCorrectException() { var heap = new BinaryHeap(); Assert.Throws(() => heap.Remove(1)); } } ================================================ FILE: DataStructures.Tests/Heap/FibonacciHeaps/FibonacciHeapTests.cs ================================================ using DataStructures.Heap.FibonacciHeap; namespace DataStructures.Tests.Heap.FibonacciHeaps; internal class TestFHeap : FibonacciHeap { public void RawCut(FHeapNode x, FHeapNode y) { Cut(x, y); } public void RawCascadingCut(FHeapNode y) { CascadingCut(y); } public void RawConsolidate() { Consolidate(); } } internal static class FibonacciHeapTests { private static FibonacciHeap BuildTestHeap() { var heap = new FibonacciHeap(); var elems = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; foreach (var i in elems) { heap.Push(i); } return heap; } [Test] public static void Push_AddElements_BuildCorrectHeap() { var heap = BuildTestHeap(); Assert.That(heap.Peek(), Is.EqualTo(1)); Assert.That(heap.Count, Is.EqualTo(10)); } public static void Pop_RemoveElements_HeapStillValid() { var heap = BuildTestHeap(); Assert.That(heap.Peek(), Is.EqualTo(1)); Assert.That(heap.Count, Is.EqualTo(10)); Assert.That(heap.Pop(), Is.EqualTo(1)); Assert.That(heap.Count, Is.EqualTo(9)); Assert.That(heap.Pop(), Is.EqualTo(2)); Assert.That(heap.Count, Is.EqualTo(8)); } [Test] public static void Pop_EmptyHeap_ThrowsCorrectException() { var heap = new FibonacciHeap(); Assert.Throws(() => heap.Pop()); } [Test] public static void Pop_NonEmptyHeap_ReturnsInSortedOrder() { var heap = new FibonacciHeap(); var rand = new Random(); var heapSize = 100; for (var i = 0; i < heapSize; i++) { heap.Push(rand.Next(1000)); } var element = heap.Pop(); for (var i = 0; i < heapSize - 1; i++) { var newElement = heap.Pop(); Assert.That(element, Is.LessThanOrEqualTo(newElement)); element = newElement; } Assert.That(heap.Count, Is.Zero); } [Test] public static void Peek_EmptyHeap_ThrowsCorrectException() { var heap = new FibonacciHeap(); Assert.Throws(() => heap.Peek()); } [Test] public static void DecreaseKey_NonEmptyHeap_ReturnsCorrectAnswer() { var heap = BuildTestHeap(); var node = heap.Push(11); heap.DecreaseKey(node, -1); Assert.That(heap.Pop(), Is.EqualTo(-1)); Assert.That(heap.Pop(), Is.EqualTo(1)); node = heap.Push(5); heap.DecreaseKey(node, 1); Assert.That(heap.Pop(), Is.EqualTo(1)); Assert.That(heap.Pop(), Is.EqualTo(2)); Assert.That(heap.Pop(), Is.EqualTo(3)); } [Test] public static void Union_NonEmptyHeap_ReturnsSortedOrder() { var oddHeap = new FibonacciHeap(); for (var i = 1; i < 10; i += 2) { oddHeap.Push(i); } var evenHeap = new FibonacciHeap(); for (var i = 0; i < 10; i += 2) { evenHeap.Push(i); } oddHeap.Union(evenHeap); for (var i = 0; i < 10; i++) { Assert.That(oddHeap.Pop(), Is.EqualTo(i)); } Assert.That(oddHeap.Count, Is.Zero); Assert.That(evenHeap.Count, Is.Zero); } [Test] public static void Union_EmptyHeap_BecomesOtherHeap() { var thisHeap = new FibonacciHeap(); var otherHeap = BuildTestHeap(); var minNode = otherHeap.Peek(); var otherCount = otherHeap.Count; Assert.That(thisHeap.Count, Is.Zero); thisHeap.Union(otherHeap); Assert.That(otherHeap.Count, Is.Zero); Assert.That(minNode, Is.EqualTo(thisHeap.Peek())); Assert.Throws(() => otherHeap.Peek()); Assert.That(thisHeap.Count, Is.EqualTo(otherCount)); } [Test] public static void Union_FullHeapWithEmptyHeap_Unchanged() { var thisHeap = BuildTestHeap(); var otherHeap = new FibonacciHeap(); var previousCount = thisHeap.Count; var previousMin = thisHeap.Peek(); thisHeap.Union(otherHeap); Assert.That(previousCount, Is.EqualTo(thisHeap.Count)); Assert.That(previousMin, Is.EqualTo(thisHeap.Peek())); } [Test] public static void DecreaseKey_EmptyHeap_ThrowsCorrectException() { var heap = new FibonacciHeap(); var item = new FHeapNode(1); Assert.Throws(() => heap.DecreaseKey(item, 0)); } [Test] public static void DecreaseKey_TryIncreaseKey_ThrowsCorrectException() { var heap = new FibonacciHeap(); var item = heap.Push(1); Assert.Throws(() => heap.DecreaseKey(item, 2)); } [Test] public static void DecreaseKey_NonEmptyHeap_PreservesHeapStructure() { var heap = new FibonacciHeap(); for (var i = 11; i < 20; i++) { heap.Push(i); } var item = heap.Push(10); for (var i = 0; i < 10; i++) { heap.Push(i); } var bigItem = heap.Push(20); heap.DecreaseKey(item, -1); Assert.That(-1, Is.EqualTo(heap.Pop())); var currentVal = -1; for (var i = 0; i < 10; i++) { var newVal = heap.Pop(); Assert.That(currentVal < newVal, Is.True); currentVal = newVal; } heap.DecreaseKey(bigItem, -1); Assert.That(-1, Is.EqualTo(heap.Pop())); currentVal = -1; for (var i = 0; i < 9; i++) { var newVal = heap.Pop(); Assert.That(currentVal < newVal, Is.True); currentVal = newVal; } } [Test] public static void Cut_EmptyHeap_ThrowsCorrectExcpetion() { var heap = new TestFHeap(); var item1 = new FHeapNode(1); var item2 = new FHeapNode(2); Assert.Throws(() => heap.RawCut(item1, item2)); } [Test] public static void Cut_FilledHeap_AlteredItem() { var heap = new TestFHeap(); var item1 = heap.Push(1); var item2 = heap.Push(2); item2.Degree = -1; Assert.Throws(() => heap.RawCut(item1, item2)); } [Test] public static void Consolidate_EmptyHeap_DoesNothing() { var heap = new TestFHeap(); heap.RawConsolidate(); Assert.Throws(() => heap.Peek()); } } ================================================ FILE: DataStructures.Tests/Heap/MinMaxHeapTests.cs ================================================ using DataStructures.Heap; namespace DataStructures.Tests.Heap; [TestFixture] public static class MinMaxHeapTests { private static readonly object[] CollectionsSource = [ new[] { 5, 10, -2, 0, 3, 13, 5, -8, 41, -5, -7, -60, -12 }, new[] { 'e', '4', 'x', 'D', '!', '$', '-', '_', '2', ')', 'Z', 'q' }, new[] { "abc", "abc", "xyz", "bcd", "klm", "opq", "ijk" }, ]; [Test] public static void CustomComparerTest() { var arr = new[] { "aaaa", "c", "dd", "bbb" }; var comparer = Comparer.Create((a, b) => Comparer.Default.Compare(a.Length, b.Length)); var mmh = new MinMaxHeap(comparer: comparer); foreach (var s in arr) { mmh.Add(s); } Assert.That(comparer, Is.EqualTo(mmh.Comparer)); Assert.That("c", Is.EqualTo(mmh.GetMin())); Assert.That("aaaa", Is.EqualTo(mmh.GetMax())); } [TestCaseSource(nameof(CollectionsSource))] public static void AddTest(IEnumerable collection) { var mmh = new MinMaxHeap(); foreach (var item in collection) { mmh.Add(item); } var minValue = mmh.GetMin(); var maxValue = mmh.GetMax(); Assert.That(collection.Min(), Is.EqualTo(minValue)); Assert.That(collection.Max(), Is.EqualTo(maxValue)); Assert.That(collection.Count(), Is.EqualTo(mmh.Count)); } [TestCaseSource(nameof(CollectionsSource))] public static void ExtractMaxTest(IEnumerable collection) { var ordered = collection.OrderByDescending(x => x); var mmh = new MinMaxHeap(collection); var emptyHeap = new MinMaxHeap(); var first = mmh.ExtractMax(); var second = mmh.GetMax(); Assert.Throws(() => emptyHeap.ExtractMax()); Assert.That(ordered.ElementAt(0), Is.EqualTo(first)); Assert.That(ordered.ElementAt(1), Is.EqualTo(second)); Assert.That(collection.Count() - 1, Is.EqualTo(mmh.Count)); } [TestCaseSource(nameof(CollectionsSource))] public static void ExtractMinTest(IEnumerable collection) { var ordered = collection.OrderBy(x => x); var mmh = new MinMaxHeap(collection); var emptyHeap = new MinMaxHeap(); var first = mmh.ExtractMin(); var second = mmh.GetMin(); Assert.Throws(() => emptyHeap.ExtractMin()); Assert.That(ordered.ElementAt(0), Is.EqualTo(first)); Assert.That(ordered.ElementAt(1), Is.EqualTo(second)); Assert.That(collection.Count() - 1, Is.EqualTo(mmh.Count)); } [TestCaseSource(nameof(CollectionsSource))] public static void GetMaxTest(IEnumerable collection) { var emptyHeap = new MinMaxHeap(); var mmh = new MinMaxHeap(collection); var maxValue = mmh.GetMax(); Assert.Throws(() => emptyHeap.GetMax()); Assert.That(collection.Max(), Is.EqualTo(maxValue)); } [TestCaseSource(nameof(CollectionsSource))] public static void GetMinTest(IEnumerable collection) { var emptyHeap = new MinMaxHeap(); var mmh = new MinMaxHeap(collection); var minValue = mmh.GetMin(); Assert.Throws(() => emptyHeap.GetMin()); Assert.That(collection.Min(), Is.EqualTo(minValue)); } [Test] public static void HeapSortUsingGet( [ValueSource(nameof(CollectionsSource))] IEnumerable collection, [Values] bool ascending) { var ordered = ascending ? collection.OrderBy(x => x) : collection.OrderByDescending(x => x); var mmh = new MinMaxHeap(collection); var extracted = new List(); while (mmh.Count > 0) { T value; if (ascending) { value = mmh.GetMin(); _ = mmh.ExtractMin(); } else { value = mmh.GetMax(); _ = mmh.ExtractMax(); } extracted.Add(value); } Assert.That(ordered.SequenceEqual(extracted), Is.True); } [Test] public static void HeapSortUsingExtract( [ValueSource(nameof(CollectionsSource))] IEnumerable collection, [Values] bool ascending) { var ordered = ascending ? collection.OrderBy(x => x) : collection.OrderByDescending(x => x); var mmh = new MinMaxHeap(collection); var extracted = new List(); while (mmh.Count > 0) { var value = ascending ? mmh.ExtractMin() : mmh.ExtractMax(); extracted.Add(value); } Assert.That(ordered.SequenceEqual(extracted), Is.True); } } ================================================ FILE: DataStructures.Tests/Heap/PairingHeap/PairingHeapComparerTests.cs ================================================ using DataStructures.Heap.PairingHeap; namespace DataStructures.Tests.Heap.PairingHeap; internal class PairingHeapComparerTests { [Test] public void Compare_CheckAscending_ReturnNegative() { var minHeap = new PairingNodeComparer(Sorting.Ascending, Comparer.Default); var node1 = new PairingHeapNode(10); var node2 = new PairingHeapNode(20); var items = minHeap.Compare(node1.Value, node2.Value); items.Should().Be(-1); } [Test] public void Compare_CheckAscending_ReturnPositive() { var minHeap = new PairingNodeComparer(Sorting.Descending, Comparer.Default); var node1 = new PairingHeapNode(10); var node2 = new PairingHeapNode(20); var items = minHeap.Compare(node1.Value, node2.Value); items.Should().Be(1); } } ================================================ FILE: DataStructures.Tests/Heap/PairingHeap/PairingHeapTests.cs ================================================ using System.Collections; using DataStructures.Heap.PairingHeap; namespace DataStructures.Tests.Heap.PairingHeap; internal class PairingHeapTests { [Test] public void BuildMinHeap_CheckEnumerator_NotThrowOnEnumerate() { var minHeap = new PairingHeap(); minHeap.Insert(1); var items = minHeap.ToList(); items.Should().HaveCount(1); } [Test] public void BuildMinHeap_CheckEnumerable_NotThrowOnEnumerate() { var minHeap = new PairingHeap(); minHeap.Insert(1); foreach (var node in (IEnumerable)minHeap) { node.Should().NotBe(null); } } [Test] public void BuildMinHeap_UpdateNonExistingNode_ThrowException() { var minHeap = new PairingHeap(); minHeap.Insert(1); minHeap.Extract(); Action act = () => minHeap.UpdateKey(1, 10); act.Should().Throw(); } [Test] public void BuildMinHeap_UpdateBadNode_ThrowException() { var minHeap = new PairingHeap(); minHeap.Insert(10); Action act = () => minHeap.UpdateKey(10, 11); act.Should().Throw(); } [Test] public void BuildMinHeap_CreateHeap_HeapIsCheked() { var nodeCount = 1000 * 10; var minHeap = new PairingHeap(); for (var i = 0; i <= nodeCount; i++) { minHeap.Insert(i); } for (var i = 0; i <= nodeCount; i++) { minHeap.UpdateKey(i, i - 1); } var min = 0; for (var i = 0; i <= nodeCount; i++) { min = minHeap.Extract(); Assert.That(min, Is.EqualTo(i - 1)); } Assert.That(minHeap.Count, Is.EqualTo(minHeap.Count)); var rnd = new Random(); var testSeries = Enumerable.Range(0, nodeCount - 1).OrderBy(_ => rnd.Next()).ToList(); foreach (var item in testSeries) { minHeap.Insert(item); } for (var i = 0; i < testSeries.Count; i++) { var decremented = testSeries[i] - rnd.Next(0, 1000); minHeap.UpdateKey(testSeries[i], decremented); testSeries[i] = decremented; } testSeries.Sort(); for (var i = 0; i < nodeCount - 2; i++) { min = minHeap.Extract(); Assert.That(testSeries[i], Is.EqualTo(min)); } Assert.That(minHeap.Count, Is.EqualTo(minHeap.Count)); } [Test] public void BuildMaxHeap_CreateHeap_HeapIsCheked() { var nodeCount = 1000 * 10; var maxHeap = new PairingHeap(Sorting.Descending); for (var i = 0; i <= nodeCount; i++) { maxHeap.Insert(i); } for (var i = 0; i <= nodeCount; i++) { maxHeap.UpdateKey(i, i + 1); } Assert.That(maxHeap.Count, Is.EqualTo(maxHeap.Count)); var max = 0; for (var i = nodeCount; i >= 0; i--) { max = maxHeap.Extract(); Assert.That(max, Is.EqualTo(i + 1)); } var rnd = new Random(); var testSeries = Enumerable.Range(0, nodeCount - 1).OrderBy(_ => rnd.Next()).ToList(); foreach (var item in testSeries) { maxHeap.Insert(item); } for (var i = 0; i < testSeries.Count; i++) { var incremented = testSeries[i] + rnd.Next(0, 1000); maxHeap.UpdateKey(testSeries[i], incremented); testSeries[i] = incremented; } testSeries = testSeries.OrderByDescending(x => x).ToList(); for (var i = 0; i < nodeCount - 2; i++) { max = maxHeap.Extract(); Assert.That(testSeries[i], Is.EqualTo(max)); } Assert.That(maxHeap.Count, Is.EqualTo(maxHeap.Count)); } } ================================================ FILE: DataStructures.Tests/InvertedIndexTests.cs ================================================ namespace DataStructures.Tests; public class InvertedIndexTests { [Test] public void Or_GetSourcesWithAtLeastOneFromList_ReturnAllSources() { var index = new InvertedIndex(); var source1 = "one star is sparkling bright"; var source2 = "two stars are sparkling even brighter"; index.AddToIndex(nameof(source1), source1); index.AddToIndex(nameof(source2), source2); var or = index.Or(new List { "star", "sparkling" }); or.Should().BeEquivalentTo(nameof(source1), nameof(source2)); } [Test] public void And_GetSourcesWithAllInsideList_ReturnFirstSource() { var index = new InvertedIndex(); var source1 = "one star is sparkling bright"; var source2 = "two stars are sparkling even brighter"; index.AddToIndex(nameof(source1), source1); index.AddToIndex(nameof(source2), source2); var and = index.And(new List { "star", "sparkling" }); and.Should().BeEquivalentTo(nameof(source1)); } } ================================================ FILE: DataStructures.Tests/LinkedList/CircularLinkedListTests.cs ================================================ using DataStructures.LinkedList.CircularLinkedList; namespace DataStructures.Tests.LinkedList; [TestFixture] public static class CircularLinkedListTests { [Test] public static void TestInsertAtBeginning() { var cll = new CircularLinkedList(); cll.InsertAtBeginning(10); cll.InsertAtBeginning(20); cll.InsertAtBeginning(30); Assert.That("30 20 10", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestInsertAtEnd() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); Assert.That("10 20 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestInsertAfter() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); cll.InsertAfter(20, 25); Assert.That("10 20 25 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestInsertAtBeginningInEmptyList() { var cll = new CircularLinkedList(); cll.InsertAtBeginning(10); Assert.That("10", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestInsertAtEndInEmptyList() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); Assert.That("10", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestInsertAfterInEmptyList() { var cll = new CircularLinkedList(); var ex = Assert.Throws(() => cll.InsertAfter(10, 20)); Assert.That(ex!.Message, Is.EqualTo("List is empty.")); } [Test] public static void TestInsertAfterSpecificNode() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); cll.InsertAfter(20, 25); // Insert after node with value 20 Assert.That("10 20 25 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestInsertAfterOnNonExistingValue() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAfter(99, 25); // 99 does not exist Assert.That("10", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestDeleteNode() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); cll.DeleteNode(20); Assert.That("10 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestDeleteOnlyNode() { var cll = new CircularLinkedList(); cll.InsertAtBeginning(10); cll.DeleteNode(10); Assert.That(cll.IsEmpty(), Is.EqualTo(true)); } [Test] public static void TestDeleteHeadNode() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); cll.DeleteNode(10); Assert.That("20 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestDeleteTailNode() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); cll.DeleteNode(30); Assert.That("10 20", Is.EqualTo(GetDisplayOutput(cll).Trim())); } [Test] public static void TestDeleteFromEmptyList() { var cll = new CircularLinkedList(); var ex = Assert.Throws(() => cll.DeleteNode(10)); Assert.That(ex!.Message, Is.EqualTo("List is empty.")); } [Test] public static void TestDeleteNonExistentNode() { var cll = new CircularLinkedList(); cll.InsertAtEnd(10); cll.InsertAtEnd(20); cll.InsertAtEnd(30); cll.DeleteNode(40); // Attempting to delete a node that doesn't exist Assert.That("10 20 30", Is.EqualTo(GetDisplayOutput(cll).Trim())); } private static string GetDisplayOutput(CircularLinkedList list) { var head = list.GetHead(); if (head == null) { return string.Empty; } var current = head; var result = new System.Text.StringBuilder(); do { result.Append(current!.Data + " "); current = current.Next; } while (current != head); return result.ToString().Trim(); } } ================================================ FILE: DataStructures.Tests/LinkedList/DoublyLinkedListTests.cs ================================================ using DataStructures.LinkedList.DoublyLinkedList; namespace DataStructures.Tests.LinkedList; public static class DoublyLinkedListTests { [Test] public static void TestGetData() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); var arr = dll.GetData().ToArray(); Assert.That(dll.Count, Is.EqualTo(5)); Assert.That(new[] { 0, 1, 2, 3, 4 }, Is.EqualTo(arr)); } [Test] public static void TestGetAt() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); var one = dll.GetAt(1); var three = dll.GetAt(3); Assert.That(one.Data, Is.EqualTo(1)); Assert.That(three.Data, Is.EqualTo(3)); Assert.Throws( () => dll.GetAt(-1) ); Assert.Throws( () => dll.GetAt(5) ); } [Test] public static void TestAddtion() { var dll = new DoublyLinkedList(0); var one = dll.Add(1); dll.Add(3); dll.AddAfter(2, one); dll.Add(4); var arr = dll.GetData().ToArray(); var reversedArr = dll.GetDataReversed().ToArray(); Assert.That(dll.Count, Is.EqualTo(5)); Assert.That(new[] { 0, 1, 2, 3, 4 }, Is.EqualTo(arr)); Assert.That(new[] { 4, 3, 2, 1, 0 }, Is.EqualTo(reversedArr)); } [Test] public static void TestRemove() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); dll.RemoveNode(dll.Find(2)); dll.RemoveHead(); dll.Remove(); var arr = dll.GetData().ToArray(); var reversedArr = dll.GetDataReversed().ToArray(); Assert.That(dll.Count, Is.EqualTo(2)); Assert.That(new[] { 1, 3 }, Is.EqualTo(arr)); Assert.That(new[] { 3, 1 }, Is.EqualTo(reversedArr)); } [Test] public static void TestFind() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); var one = dll.Find(1); var three = dll.Find(3); Assert.That(one.Data, Is.EqualTo(1)); Assert.That(three.Data, Is.EqualTo(3)); } [Test] public static void TestIndexOf() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); var one = dll.IndexOf(1); var three = dll.IndexOf(3); Assert.That(one, Is.EqualTo(1)); Assert.That(three, Is.EqualTo(3)); } [Test] public static void TestContains() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); var one = dll.Contains(1); var six = dll.Contains(6); Assert.That(one, Is.True); Assert.That(six, Is.False); } [Test] public static void TestReverse() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); dll.Reverse(); var arr = dll.GetData().ToArray(); var empty = new DoublyLinkedList(new int[] { }); empty.Reverse(); var emptyArr = empty.GetData().ToArray(); Assert.That(arr, Is.EqualTo(new[] { 4, 3, 2, 1, 0 })); Assert.That(emptyArr, Is.EqualTo(new int[] { })); } [Test] public static void TestGetDataReversed() { var dll = new DoublyLinkedList(new[] { 0, 1, 2, 3, 4 }); var arr = dll.GetData().ToArray(); var reversedArr = dll.GetDataReversed().ToArray(); Assert.That(arr, Is.EqualTo(new[] { 0, 1, 2, 3, 4 })); Assert.That(reversedArr, Is.EqualTo(new[] { 4, 3, 2, 1, 0 })); } } ================================================ FILE: DataStructures.Tests/LinkedList/LinkedListTests.cs ================================================ using DataStructures.LinkedList.SinglyLinkedList; namespace DataStructures.Tests.LinkedList; public static class LinkedListTests { [Test] public static void LengthWorksCorrectly([Random(0, 1000, 100)] int quantity) { // Arrange var a = new SinglyLinkedList(); // Act var r = TestContext.CurrentContext.Random; for (var i = 0; i < quantity; i++) { _ = a.AddFirst(r.Next()); } // Assert Assert.That(quantity, Is.EqualTo(a.Length())); } [Test] public static void LengthOnEmptyListIsZero() { // Arrange var a = new SinglyLinkedList(); // Act // Assert Assert.That(0, Is.EqualTo(a.Length())); } [Test] public static void GetItemsFromLinkedList() { // Arrange var testObj = new SinglyLinkedList(); _ = testObj.AddLast("H"); _ = testObj.AddLast("E"); _ = testObj.AddLast("L"); _ = testObj.AddLast("L"); _ = testObj.AddLast("O"); // Act var items = testObj.GetListData(); // Assert Assert.That(5, Is.EqualTo(items.Count())); Assert.That("O", Is.EqualTo(testObj.GetElementByIndex(4))); } [Test] public static void GetElementByIndex_IndexOutOfRange_ArgumentOutOfRangeExceptionThrown() { // Arrange var list = new SinglyLinkedList(); // Act _ = list.AddFirst(1); _ = list.AddFirst(2); _ = list.AddFirst(3); // Assert _ = Assert.Throws(() => list.GetElementByIndex(-1)); _ = Assert.Throws(() => list.GetElementByIndex(3)); } [Test] public static void RemoveItemsFromList() { // Arrange var testObj = new SinglyLinkedList(); _ = testObj.AddLast("X"); _ = testObj.AddLast("H"); _ = testObj.AddLast("E"); _ = testObj.AddLast("L"); _ = testObj.AddLast("L"); _ = testObj.AddLast("I"); _ = testObj.AddLast("O"); // Act var xRemoveSucess = testObj.DeleteElement("X"); var oRemoveSucess = testObj.DeleteElement("O"); var eRemoveSucess = testObj.DeleteElement("E"); var lRemoveSucess = testObj.DeleteElement("L"); var l2RemoveSucess = testObj.DeleteElement("L"); var l3RemoveSucess = testObj.DeleteElement("L"); var nonExistantRemoveSucess = testObj.DeleteElement("F"); var resultString = testObj.GetElementByIndex(0) + testObj.GetElementByIndex(1); // Assert Assert.That("HI", Is.EqualTo(resultString)); Assert.That(xRemoveSucess, Is.True); Assert.That(oRemoveSucess, Is.True); Assert.That(eRemoveSucess, Is.True); Assert.That(lRemoveSucess, Is.True); Assert.That(l2RemoveSucess, Is.True); Assert.That(l3RemoveSucess, Is.False); Assert.That(nonExistantRemoveSucess, Is.False); } [Test] public static void DeleteFirstFromList() { // Arrange var testObj = new SinglyLinkedList(); _ = testObj.AddLast("H"); _ = testObj.AddLast("E"); _ = testObj.AddLast("L"); _ = testObj.AddLast("L"); _ = testObj.AddLast("O"); // Act var deleteSuccess = testObj.DeleteFirst(); // Assert Assert.That(deleteSuccess, Is.True); Assert.That(4, Is.EqualTo(testObj.Length())); Assert.That("E", Is.EqualTo(testObj.GetElementByIndex(0))); } [Test] public static void DeleteFirstFromEmptyList() { // Arrange var testObj = new SinglyLinkedList(); // Act var deleteSuccess = testObj.DeleteFirst(); // Assert Assert.That(deleteSuccess, Is.False); } [Test] public static void DeleteLastFromList() { // Arrange var testObj = new SinglyLinkedList(); _ = testObj.AddLast("H"); _ = testObj.AddLast("E"); _ = testObj.AddLast("L"); _ = testObj.AddLast("L"); _ = testObj.AddLast("O"); // Act var deleteSuccess = testObj.DeleteLast(); // Assert Assert.That(deleteSuccess, Is.True); Assert.That(4, Is.EqualTo(testObj.Length())); Assert.That("L", Is.EqualTo(testObj.GetElementByIndex(testObj.Length() - 1))); } [Test] public static void DeleteLastFromEmptyList() { // Arrange var testObj = new SinglyLinkedList(); // Act var deleteSuccess = testObj.DeleteLast(); // Assert Assert.That(deleteSuccess, Is.False); } } ================================================ FILE: DataStructures.Tests/LinkedList/SkipListTests.cs ================================================ using DataStructures.LinkedList.SkipList; namespace DataStructures.Tests.LinkedList; public static class SkipListTests { [Test] public static void TestAdd() { var list = new SkipList(); list.AddOrUpdate(1, 1); list[2] = 2; list[3] = 3; list.Count.Should().Be(3); list.GetValues().Should().ContainInOrder(1, 2, 3); } [Test] public static void TestUpdate() { var list = new SkipList(); // Add some elements. list[1] = "v1"; list[2] = "v2"; list[5] = "v5"; // Update list.AddOrUpdate(1, "v1-updated"); list[2] = "v2-updated"; list.Count.Should().Be(3); list.GetValues().Should().ContainInOrder("v1-updated", "v2-updated", "v5"); } [Test] public static void TestContains() { var list = new SkipList(); list.AddOrUpdate(1, 1); list.AddOrUpdate(3, 3); list.AddOrUpdate(5, 5); list.Contains(1).Should().BeTrue(); list.Contains(3).Should().BeTrue(); list.Contains(5).Should().BeTrue(); list.Contains(0).Should().BeFalse(); list.Contains(2).Should().BeFalse(); list.Contains(9).Should().BeFalse(); } [Test] public static void TestGetByKey_Success() { var list = new SkipList(); list[1] = "value1"; list[1].Should().Be("value1"); } [Test] public static void TestGetByKey_KeyNotFoundException() { var list = new SkipList(); list[1] = "value1"; string value; Action act = () => value = list[2]; act.Should().Throw(); } [Test] public static void TestRemove_ItemRemoved() { var list = new SkipList(); list.AddOrUpdate(1, 1); list.AddOrUpdate(2, 2); list.AddOrUpdate(3, 3); list.Count.Should().Be(3); list.Contains(2).Should().BeTrue(); var isRemoved = list.Remove(2); list.Count.Should().Be(2); list.Contains(2).Should().BeFalse(); isRemoved.Should().BeTrue(); } [Test] public static void TestRemove_ItemNotFound() { var list = new SkipList(); list.AddOrUpdate(1, 1); list.AddOrUpdate(2, 2); list.AddOrUpdate(3, 3); var isRemoved = list.Remove(222); list.Count.Should().Be(3); isRemoved.Should().BeFalse(); } [Test] public static void TestGetValues() { var list = new SkipList(); list[4] = "four"; list[2] = "two"; list[3] = "three"; list[1] = "one"; var valuesSortedByKey = list.GetValues(); valuesSortedByKey.Should().ContainInOrder("one", "two", "three", "four"); } } ================================================ FILE: DataStructures.Tests/Probabilistic/BloomFilterTests.cs ================================================ using DataStructures.Probabilistic; namespace DataStructures.Tests.Probabilistic; public class BloomFilterTests { static readonly string[] TestNames = ["kal;jsnfka", "alkjsdfn;lakm", "aljfopiawjf", "afowjeaofeij", "oajwsefoaiwje", "aoiwjfaoiejmf", "aoijfoawiejf"]; private class SimpleObject(string name, int number) { public string Name { get; set; } = name; public int Number { get; set; } = number; } private class SimpleObjectOverridenHash(string name, int number) { private const uint FnvPrime = 16777619; private const uint FnvOffsetBasis = 2166136261; public string Name { get; set; } = name; public int Number { get; set; } = number; public override int GetHashCode() { var bytes = Encoding.UTF8.GetBytes(Name).Concat(BitConverter.GetBytes(Number)); var hash = FnvOffsetBasis; foreach (var @byte in bytes) { hash = hash * FnvPrime; hash ^= @byte; } return (int)hash; } public override bool Equals(object? obj) { return obj is SimpleObjectOverridenHash asSimpleObj && asSimpleObj.Name == Name && asSimpleObj.Number == Number; } } [Test] public void TestBloomFilterInsertOptimalSize() { var filter = new BloomFilter(1000); var set = new HashSet(); var rand = new Random(124); var falsePositives = 0; for (var i = 0; i < 1000; i++) { var k = rand.Next(0, 1000); if (!set.Contains(k) && filter.Search(k)) { falsePositives++; } filter.Insert(k); set.Add(k); Assert.That(filter.Search(k), Is.True); } Assert.That(.05 > falsePositives / 1000.0, Is.True); // be a bit generous in our fault tolerance here } [Test] public void TestBloomFilterInsert() { var filter = new BloomFilter(100000, 3); var rand = new Random(); for (var i = 0; i < 1000; i++) { var simpleObject = new SimpleObject(TestNames[rand.Next(TestNames.Length)], rand.Next(15)); filter.Insert(simpleObject); Assert.That(filter.Search(simpleObject), Is.True); } } [Test] public void TestBloomFilterSearchOverridenHash() { var filter = new BloomFilter(100000, 3); var simpleObjectInserted = new SimpleObjectOverridenHash("foo", 1); var simpleObjectInserted2 = new SimpleObjectOverridenHash("foo", 1); var simpleObjectNotInserted = new SimpleObjectOverridenHash("bar", 2); filter.Insert(simpleObjectInserted); Assert.That(filter.Search(simpleObjectInserted), Is.True); Assert.That(filter.Search(simpleObjectInserted2), Is.True); Assert.That(filter.Search(simpleObjectNotInserted), Is.False); } [Test] public void TestBloomFilterSearch() { var filter = new BloomFilter(10000, 3); var simpleObjectInserted = new SimpleObject("foo", 1); var simpleObjectNotInserted = new SimpleObject("foo", 1); filter.Insert(simpleObjectInserted); Assert.That(filter.Search(simpleObjectNotInserted), Is.False); Assert.That(filter.Search(simpleObjectInserted), Is.True); } } ================================================ FILE: DataStructures.Tests/Probabilistic/CountMinSketchTests.cs ================================================ using DataStructures.Probabilistic; namespace DataStructures.Tests.Probabilistic; public class CountMinSketchTests { public class SimpleObject(string name, int number) { public string Name { get; set; } = name; public int Number { get; set; } = number; } [Test] public void TestInsertAndCount() { var obj1 = new SimpleObject("foo", 5); var obj2 = new SimpleObject("bar", 6); var sketch = new CountMinSketch(200, 5); for (var i = 0; i < 5000; i++) { sketch.Insert(obj1); sketch.Insert(obj2); } sketch.Query(obj1).Should().BeGreaterOrEqualTo(5000); sketch.Query(obj2).Should().BeGreaterOrEqualTo(5000); } [Test] public void TestOptimalInitializer() { var obj1 = new SimpleObject("foo", 5); var obj2 = new SimpleObject("bar", 6); var sketch = new CountMinSketch(.001, .05); for (var i = 0; i < 5000; i++) { sketch.Insert(obj1); sketch.Insert(obj2); } sketch.Query(obj1).Should().BeGreaterOrEqualTo(5000); sketch.Query(obj2).Should().BeGreaterOrEqualTo(5000); } [Test] public void TestProbabilities() { var sketch = new CountMinSketch(.01, .05); var random = new Random(); var insertedItems = new Dictionary(); for (var i = 0; i < 10000; i++) { var item = random.Next(0, 1000000); sketch.Insert(item); if (insertedItems.ContainsKey(item)) { insertedItems[item]++; } else { insertedItems.Add(item, 1); } } var numMisses = 0; foreach (var item in insertedItems) { if (sketch.Query(item.Key) - item.Value > .01 * 100000) { numMisses++; } } (numMisses / (double)insertedItems.Count).Should().BeLessOrEqualTo(.05); } } ================================================ FILE: DataStructures.Tests/Probabilistic/HyperLogLogTest.cs ================================================ using DataStructures.Probabilistic; namespace DataStructures.Tests.Probabilistic; public class HyperLogLogTest { [Test] public void TestHyperLogLog() { var hll = new HyperLogLog(); HashSet actual = []; var rand = new Random(); var tolerance = .05; for (var i = 0; i < 10000; i++) { var k = rand.Next(20000); hll.Add(k); actual.Add(k); } hll.Cardinality().Should() .BeGreaterOrEqualTo((int)(actual.Count * (1 - tolerance))) .And .BeLessOrEqualTo((int)(actual.Count * (1 + tolerance))); } [Test] public void TestHyperLogLogMerge() { var hll1 = new HyperLogLog(); var hll2 = new HyperLogLog(); var rand = new Random(); var tolerance = .05; HashSet actual = []; for (var i = 0; i < 5000; i++) { var k = rand.Next(20000); hll1.Add(k); actual.Add(k); } for (var i = 0; i < 5000; i++) { var k = rand.Next(20000); hll2.Add(k); actual.Add(k); } var hll = HyperLogLog.Merge(hll1, hll2); hll.Cardinality().Should() .BeGreaterOrEqualTo((int)(actual.Count * (1 - tolerance))) .And .BeLessOrEqualTo((int)(actual.Count * (1 + tolerance))); } } ================================================ FILE: DataStructures.Tests/Queue/ArrayBasedQueueTests.cs ================================================ using DataStructures.Queue; namespace DataStructures.Tests.Queue; public static class ArrayBasedQueueTests { [Test] public static void DequeueWorksCorrectly() { // Arrange var q = new ArrayBasedQueue(3); q.Enqueue('A'); q.Enqueue('B'); q.Enqueue('C'); var result = new StringBuilder(); // Act for (var i = 0; i < 3; i++) { result.Append(q.Dequeue()); } // Assert Assert.That("ABC", Is.EqualTo(result.ToString())); Assert.That(q.IsEmpty(), Is.True, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } [Test] public static void PeekWorksCorrectly() { // Arrange var q = new ArrayBasedQueue(2); q.Enqueue(1); q.Enqueue(2); var peeked = 0; // Act for (var i = 0; i < 3; i++) { peeked = q.Peek(); } // Assert Assert.That(1, Is.EqualTo(peeked)); Assert.That(q.IsEmpty(), Is.False, "Queue is empty"); Assert.That(q.IsFull(), Is.True, "Queue is full"); } [Test] public static void DequeueEmptyQueueThrowsInvalidOperationException() { // Arrange var q = new ArrayBasedQueue(1); Exception? exception = null; // Act try { q.Dequeue(); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void EnqueueFullQueueThrowsInvalidOperationException() { // Arrange var q = new ArrayBasedQueue(1); q.Enqueue(0); Exception? exception = null; // Act try { q.Enqueue(1); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void PeekEmptyQueueThrowsInvalidOperationException() { // Arrange var q = new ArrayBasedQueue(1); Exception? exception = null; // Act try { q.Peek(); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void ClearWorksCorrectly() { // Arrange var q = new ArrayBasedQueue(2); q.Enqueue(1); q.Enqueue(2); // Act q.Clear(); // Assert Assert.That(q.IsEmpty(), Is.True, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } } ================================================ FILE: DataStructures.Tests/Queue/ListBasedQueueTests.cs ================================================ using DataStructures.Queue; namespace DataStructures.Tests.Queue; public static class ListBasedQueueTests { [Test] public static void DequeueWorksCorrectly() { // Arrange var q = new ListBasedQueue(); q.Enqueue('A'); q.Enqueue('B'); q.Enqueue('C'); var result = new StringBuilder(); // Act for (var i = 0; i < 3; i++) { result.Append(q.Dequeue()); } // Assert Assert.That("ABC", Is.EqualTo(result.ToString())); Assert.That(q.IsEmpty(), Is.True, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } [Test] public static void PeekWorksCorrectly() { // Arrange var q = new ListBasedQueue(); q.Enqueue(1); q.Enqueue(2); var peeked = 0; // Act for (var i = 0; i < 3; i++) { peeked = q.Peek(); } // Assert Assert.That(1, Is.EqualTo(peeked)); Assert.That(q.IsEmpty(), Is.False, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } [Test] public static void DequeueEmptyQueueThrowsInvalidOperationException() { // Arrange var q = new ListBasedQueue(); Exception? exception = null; // Act try { q.Dequeue(); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void PeekEmptyQueueThrowsInvalidOperationException() { // Arrange var q = new ListBasedQueue(); Exception? exception = null; // Act try { q.Peek(); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void ClearWorksCorrectly() { // Arrange var q = new ListBasedQueue(); q.Enqueue(1); q.Enqueue(2); // Act q.Clear(); // Assert Assert.That(q.IsEmpty(), Is.True, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } } ================================================ FILE: DataStructures.Tests/Queue/StackBasedQueueTests.cs ================================================ using DataStructures.Queue; namespace DataStructures.Tests.Queue; public static class StackBasedQueueTests { [Test] public static void DequeueWorksCorrectly() { // Arrange var q = new StackBasedQueue(); q.Enqueue('A'); q.Enqueue('B'); q.Enqueue('C'); var result = new StringBuilder(); // Act for (var i = 0; i < 3; i++) { result.Append(q.Dequeue()); } // Assert Assert.That("ABC", Is.EqualTo(result.ToString())); Assert.That(q.IsEmpty(), Is.True, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } [Test] public static void PeekWorksCorrectly() { // Arrange var q = new StackBasedQueue(); q.Enqueue(1); q.Enqueue(2); var peeked = 0; // Act for (var i = 0; i < 3; i++) { peeked = q.Peek(); } // Assert Assert.That(1, Is.EqualTo(peeked)); Assert.That(q.IsEmpty(), Is.False, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } [Test] public static void DequeueEmptyQueueThrowsInvalidOperationException() { // Arrange var q = new StackBasedQueue(); Exception? exception = null; // Act try { q.Dequeue(); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void PeekEmptyQueueThrowsInvalidOperationException() { // Arrange var q = new StackBasedQueue(); Exception? exception = null; // Act try { q.Peek(); } catch (Exception ex) { exception = ex; } // Assert Assert.That(typeof(InvalidOperationException), Is.EqualTo(exception?.GetType())); } [Test] public static void ClearWorksCorrectly() { // Arrange var q = new StackBasedQueue(); q.Enqueue(1); q.Enqueue(2); // Act q.Clear(); // Assert Assert.That(q.IsEmpty(), Is.True, "Queue is empty"); Assert.That(q.IsFull(), Is.False, "Queue is full"); } } ================================================ FILE: DataStructures.Tests/RedBlackTreeTests.cs ================================================ using DataStructures.RedBlackTree; namespace DataStructures.Tests; internal class RedBlackTreeTests { [Test] public void Constructor_UseCustomComparer_FormsCorrect_Tree() { var tree = new RedBlackTree(Comparer.Create((x, y) => y.CompareTo(x))); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetMin().Should().Be(10); tree.GetMax().Should().Be(1); tree.GetKeysInOrder().SequenceEqual(new[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }).Should().BeTrue(); } [Test] public void Add_Case3_FormsCorrectTree() { var tree = new RedBlackTree(); tree.Add(5); tree.Count.Should().Be(1); } [Test] public void Add_Case24_FormsCorrectTree() { var tree = new RedBlackTree(); tree.AddRange(new[] { 5, 4, 6, 3 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 4, 3, 6 }).Should().BeTrue(); } [Test] public void Add_Case1_FormsCorrectTree() { var tree = new RedBlackTree(); tree.AddRange(new[] { 5, 4, 6, 3, 7 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 4, 3, 6, 7 }).Should().BeTrue(); } [Test] public void Add_Case6_FormsCorrectTree() { // Right rotation var tree = new RedBlackTree(); tree.AddRange(new[] { 5, 4, 6, 3, 2 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 3, 2, 4, 6 }).Should().BeTrue(); // Left rotation tree = new RedBlackTree(); tree.AddRange(new[] { 5, 4, 6, 7, 8 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 4, 7, 6, 8 }).Should().BeTrue(); } [Test] public void Add_Case5_FormsCorrectTree() { // Left-right rotation var tree = new RedBlackTree(); tree.AddRange(new[] { 5, 4, 6, 2, 3 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 3, 2, 4, 6 }).Should().BeTrue(); // Right-left rotation tree = new RedBlackTree(); tree.AddRange(new[] { 5, 4, 6, 8, 7 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 4, 7, 6, 8 }).Should().BeTrue(); } [Test] public void Add_MultipleKeys_FormsCorrectTree() { var tree = new RedBlackTree(); foreach (var value in new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) { tree.Add(value); tree.Count.Should().Be(value); } tree.GetKeysInOrder().SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 4, 2, 1, 3, 6, 5, 8, 7, 9, 10 }).Should().BeTrue(); } [Test] public void Add_KeyAlreadyInTree_ThrowsException() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5 }); Assert.Throws(() => tree.Add(1)); } [Test] public void AddRange_MultipleKeys_FormsCorrectTree() { var tree = new RedBlackTree(); tree.AddRange(new[] { 9, 0, 1, 6, 7, 5, 2, 8, 4, 3 }); tree.Count.Should().Be(10); tree.GetKeysInOrder().SequenceEqual(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 1, 0, 3, 2, 4, 7, 6, 9, 8 }).Should().BeTrue(); } [Test] public void Remove_SimpleCases_TreeStillValid() { var tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 }); tree.Remove(6); tree.Count.Should().Be(9); tree.Contains(6).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 8, 11, 13, 15, 17, 22, 25, 27 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 13, 8, 1, 11, 17, 15, 25, 22, 27 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 }); tree.Remove(1); tree.Count.Should().Be(9); tree.Contains(1).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 6, 8, 11, 13, 15, 17, 22, 25, 27 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 13, 8, 6, 11, 17, 15, 25, 22, 27 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 }); tree.Remove(17); tree.Count.Should().Be(9); tree.Contains(17).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 6, 8, 11, 13, 15, 22, 25, 27 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 13, 8, 1, 6, 11, 22, 15, 25, 27 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 }); tree.Remove(25); tree.Count.Should().Be(9); tree.Contains(25).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 6, 8, 11, 13, 15, 17, 22, 27 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 13, 8, 1, 6, 11, 17, 15, 27, 22 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 7, 3, 18, 10, 22, 8, 11, 26 }); tree.Remove(18); tree.Count.Should().Be(7); tree.Contains(18).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 3, 7, 8, 10, 11, 22, 26 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 7, 3, 22, 10, 8, 11, 26 }).Should().BeTrue(); tree = new RedBlackTree(); tree.Add(1); tree.Add(2); tree.Remove(1); tree.Count.Should().Be(1); tree.GetKeysInOrder().SequenceEqual(new[] { 2 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 2 }).Should().BeTrue(); } [Test] public void Remove_Case1_TreeStillValid() { var tree = new RedBlackTree(); tree.AddRange(new[] { 5, 2, 8, 1 }); tree.Remove(1); tree.Remove(2); tree.Contains(2).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 5, 8 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 8 }).Should().BeTrue(); } [Test] public void Remove_Case3_TreeStillValid() { // Move to case 6 var tree = new RedBlackTree(); tree.AddRange(new[] { 7, 3, 18, 1, 10, 22, 8, 11, 26 }); tree.Remove(1); tree.Remove(3); tree.Count.Should().Be(7); tree.Contains(3).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 7, 8, 10, 11, 18, 22, 26 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 18, 10, 7, 8, 11, 22, 26 }).Should().BeTrue(); // Move to case 5 tree = new RedBlackTree(); tree.AddRange(new[] { 8, 3, 2, 0, 9, 4, 7, 6, 1, 5 }); tree.Remove(8); tree.Remove(6); tree.Remove(9); tree.Count.Should().Be(7); tree.GetKeysInOrder().SequenceEqual(new[] { 0, 1, 2, 3, 4, 5, 7 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 3, 1, 0, 2, 5, 4, 7 }).Should().BeTrue(); // Move to case 4 tree = new RedBlackTree(); tree.AddRange(new[] { 7, 5, 8, 4, 6, 3, 2, 9, 0, 1 }); tree.Remove(9); tree.Remove(6); tree.Remove(5); tree.Remove(8); tree.Count.Should().Be(6); tree.GetKeysInOrder().SequenceEqual(new[] { 0, 1, 2, 3, 4, 7 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 3, 1, 0, 2, 7, 4 }).Should().BeTrue(); } [Test] public void Remove_Case4_TreeStillValid() { var tree = new RedBlackTree(); tree.AddRange(new[] { 5, 2, 8, 1, 4, 7, 9, 0, 3 }); tree.Remove(0); tree.Remove(3); tree.Remove(2); tree.Count.Should().Be(6); tree.Contains(2).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 4, 5, 7, 8, 9 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 5, 4, 1, 8, 7, 9 }).Should().BeTrue(); } [Test] public void Remove_Case5_TreeStillValid() { var tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 }); tree.Remove(8); tree.Count.Should().Be(9); tree.Contains(8).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 6, 11, 13, 15, 17, 22, 25, 27 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 13, 6, 1, 11, 17, 15, 25, 22, 27 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 0, 6, 22 }); tree.Remove(13); tree.Count.Should().Be(9); tree.Contains(13).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 0, 1, 6, 8, 11, 15, 17, 22, 25 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 15, 8, 1, 0, 6, 11, 22, 17, 25 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 7, 0, 1, 4, 8, 2, 3, 6, 5, 9 }); tree.Remove(7); tree.Remove(0); tree.Remove(1); tree.Remove(4); tree.Remove(8); tree.GetKeysInOrder().SequenceEqual(new[] { 2, 3, 5, 6, 9 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 3, 2, 6, 5, 9 }).Should().BeTrue(); } [Test] public void Remove_Case6_TreeStillValid() { var tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 6, 22, 27 }); tree.Remove(13); tree.Count.Should().Be(9); tree.Contains(13).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 6, 8, 11, 15, 17, 22, 25, 27 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 15, 8, 1, 6, 11, 25, 17, 22, 27 }).Should().BeTrue(); tree = new RedBlackTree(); tree.AddRange(new[] { 13, 8, 17, 1, 11, 15, 25, 0, 6, 22 }); tree.Remove(8); tree.Count.Should().Be(9); tree.Contains(8).Should().BeFalse(); tree.GetKeysInOrder().SequenceEqual(new[] { 0, 1, 6, 11, 13, 15, 17, 22, 25 }).Should().BeTrue(); tree.GetKeysPreOrder().SequenceEqual(new[] { 13, 1, 0, 11, 6, 17, 15, 25, 22 }).Should().BeTrue(); } [Test] public void Remove_EmptyTree_ThrowsException() { var tree = new RedBlackTree(); Assert.Throws(() => tree.Remove(1)); } [Test] public void Remove_KeyNotInTree_ThrowsException() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); Assert.Throws(() => tree.Remove(24)); } [Test] public void Contains_CorrectReturn() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.Contains(3).Should().BeTrue(); tree.Contains(7).Should().BeTrue(); tree.Contains(24).Should().BeFalse(); tree.Contains(-1).Should().BeFalse(); } [Test] public void Contains_EmptyTree_ReturnsFalse() { var tree = new RedBlackTree(); tree.Contains(5).Should().BeFalse(); tree.Contains(-12).Should().BeFalse(); } [Test] public void GetMin_CorrectReturn() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetMin().Should().Be(1); } [Test] public void GetMin_EmptyTree_ThrowsException() { var tree = new RedBlackTree(); Assert.Throws(() => tree.GetMin()); } [Test] public void GetMax_CorrectReturn() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetMax().Should().Be(10); } [Test] public void GetMax_EmptyTree_ThrowsException() { var tree = new RedBlackTree(); Assert.Throws(() => tree.GetMax()); } [Test] public void GetKeysInOrder_CorrectReturn() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetKeysInOrder().SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).Should().BeTrue(); } [Test] public void GetKeysInOrder_EmptyTree_CorrectReturn() { var tree = new RedBlackTree(); tree.GetKeysInOrder().SequenceEqual(Array.Empty()).Should().BeTrue(); } [Test] public void GetKeysPreOrder_CorrectReturn() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetKeysPreOrder().SequenceEqual(new[] { 4, 2, 1, 3, 6, 5, 8, 7, 9, 10 }).Should().BeTrue(); } [Test] public void GetKeysPreOrder_EmptyTree_CorrectReturn() { var tree = new RedBlackTree(); tree.GetKeysPreOrder().SequenceEqual(Array.Empty()).Should().BeTrue(); } [Test] public void GetKeysPostOrder_CorrectReturn() { var tree = new RedBlackTree(); tree.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); tree.GetKeysPostOrder().SequenceEqual(new[] { 1, 3, 2, 5, 7, 10, 9, 8, 6, 4 }).Should().BeTrue(); } [Test] public void GetKeysPostOrder_EmptyTree_CorrectReturn() { var tree = new RedBlackTree(); tree.GetKeysPostOrder().SequenceEqual(Array.Empty()).Should().BeTrue(); } } ================================================ FILE: DataStructures.Tests/ScapegoatTree/ExtensionsTests.cs ================================================ using DataStructures.ScapegoatTree; namespace DataStructures.Tests.ScapegoatTree; public class ExtensionsTests { [Test] public void RebuildFlatTree_ValidFlatTree_RebuildsTree() { var expected = new Node(3) { Left = new Node(1) { Left = new Node(-1), Right = new Node(2), }, Right = new Node(6) { Left = new Node(5), }, }; var list = new List> { new(-1), new(1), new(2), new(3), new(5), new(6), }; var tree = Extensions.RebuildFromList(list, 0, list.Count - 1); Assert.That(list.Count, Is.EqualTo(tree.GetSize())); Assert.That(expected.Key, Is.EqualTo(tree.Key)); Assert.That(tree.Left, Is.Not.Null); Assert.That(tree.Right, Is.Not.Null); Assert.That(expected.Left.Key, Is.EqualTo(tree.Left!.Key)); Assert.That(expected.Right.Key, Is.EqualTo(tree.Right!.Key)); Assert.That(tree.Left.Left, Is.Not.Null); Assert.That(tree.Left.Right, Is.Not.Null); Assert.That(expected.Left.Left.Key, Is.EqualTo(tree.Left!.Left!.Key)); Assert.That(expected.Left.Right.Key, Is.EqualTo(tree.Left!.Right!.Key)); Assert.That(tree.Right.Left, Is.Not.Null); Assert.That(expected.Right.Left.Key, Is.EqualTo(tree.Right!.Left!.Key)); } [Test] public void RebuildFromList_RangeIsInvalid_ThrowsException() { Assert.Throws(() => Extensions.RebuildFromList(new List>(), 1, 0)); } } ================================================ FILE: DataStructures.Tests/ScapegoatTree/ScapegoatTreeNodeTests.cs ================================================ using DataStructures.ScapegoatTree; namespace DataStructures.Tests.ScapegoatTree; [TestFixture] public class ScapegoatTreeNodeTests { [TestCase(2, 1)] [TestCase("B", "A")] public void RightSetter_OtherKeyPrecedesRightKey_ThrowsException(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); Assert.Throws(() => instance.Right = other); } [TestCase(1, 2)] [TestCase("A", "B")] public void RightSetter_OtherKeyFollowsRightKey_AddsChild(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); Assert.DoesNotThrow(() => instance.Right = other); } [TestCase(1, 2)] [TestCase("A", "B")] public void LeftSetter_OtherKeyFollowsLeftKey_ThrowsException(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); Assert.Throws(() => instance.Left = other); } [TestCase(2, 1)] [TestCase("B", "A")] public void LeftSetter_OtherKeyPrecedesLeftKey_AddsChild(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); Assert.DoesNotThrow(() => instance.Left = other); } [TestCase(1, 2)] [TestCase("A", "B")] public void CompareTo_InstanceKeyPrecedesOtherKey_ReturnsMinusOne(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); var result = instance.Key.CompareTo(other.Key); Assert.That(result, Is.EqualTo(-1)); } [TestCase(2, 1)] [TestCase("B", "A")] public void CompareTo_InstanceKeyFollowsOtherKey_ReturnsOne(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); var result = instance.Key.CompareTo(other.Key); Assert.That(1, Is.EqualTo(result)); } [TestCase(1, 1)] [TestCase("A", "A")] public void CompareTo_InstanceKeyEqualsOtherKey_ReturnsZero(TKey a, TKey b) where TKey : IComparable { var instance = new Node(a); var other = new Node(b); var result = instance.Key.CompareTo(other.Key); Assert.That(0, Is.EqualTo(result)); } [Test] public void GetSize_NodeHasNoChildren_ReturnsOne() { var node = new Node(1); Assert.That(1, Is.EqualTo(node.GetSize())); } [Test] public void GetSize_NodeHasChildren_ReturnsCorrectSize() { var node = new Node(1, new Node(2), new Node(0)); Assert.That(3, Is.EqualTo(node.GetSize())); } [Test] public void GetSmallestKeyNode_NodeHasNoLeftChildren_ReturnsNode() { var node = new Node(1); Assert.That(node, Is.EqualTo(node.GetSmallestKeyNode())); } [Test] public void GetSmallestKeyNode_NodeHasSmallestChild_ReturnsChild() { var node = new Node(1); var smaller = new Node(0); var smallest = new Node(-1); node.Left = smaller; smaller.Left = smallest; Assert.That(smallest, Is.EqualTo(node.GetSmallestKeyNode())); } [Test] public void GetLargestKeyNode_NodeHasNoRightChildren_ReturnsNode() { var node = new Node(1); Assert.That(node, Is.EqualTo(node.GetLargestKeyNode())); } [Test] public void GetLargestKeyNode_NodeHasLargestChild_ReturnsChild() { var node = new Node(1); var larger = new Node(2); var largest = new Node(3); node.Right = larger; larger.Right = largest; Assert.That(largest, Is.EqualTo(node.GetLargestKeyNode())); } [Test] public void IsAlphaWeightBalanced_TreeIsUnbalanced_ReturnsFalse() { var root = new Node(0); var a = new Node(-1); var b = new Node(-2); var c = new Node(-3); var d = new Node(1); root.Left = a; a.Left = b; b.Left = c; root.Right = d; Assert.That(root.IsAlphaWeightBalanced(0.5), Is.False); } [Test] public void IsAlphaWeightBalanced_TreeIsBalanced_ReturnsTrue() { var root = new Node(0); var a = new Node(-1); var b = new Node(-2); var d = new Node(1); root.Left = a; a.Left = b; root.Right = d; Assert.That(root.IsAlphaWeightBalanced(0.5), Is.True); } } ================================================ FILE: DataStructures.Tests/ScapegoatTree/ScapegoatTreeTests.cs ================================================ using DataStructures.ScapegoatTree; namespace DataStructures.Tests.ScapegoatTree; public class ScapegoatTreeTests { [Test] public void Constructor_NoParameters_InstanceIsValid() { var tree = new ScapegoatTree(); Assert.That(tree.Root, Is.Null); Assert.That(tree.Size == 0, Is.True); Assert.That(tree.MaxSize == 0, Is.True); Assert.That(tree.Alpha, Is.EqualTo(0.5)); } [Test] public void Constructor_AlphaParameter_InstanceIsValid() { var expected = 0.6; var tree = new ScapegoatTree(expected); Assert.That(tree.Root, Is.Null); Assert.That(tree.Size == 0, Is.True); Assert.That(tree.MaxSize == 0, Is.True); Assert.That(tree.Alpha, Is.EqualTo(expected)); } [TestCase(1.1)] [TestCase(0.4)] public void Constructor_AlphaParameterIsInvalid_ThrowsException(double alpha) { Assert.Throws(() => new ScapegoatTree(alpha)); Assert.Throws(() => new ScapegoatTree(1, alpha)); } [Test] public void Constructor_KeyParameter_InstanceIsValid() { var expected = 10; var tree = new ScapegoatTree(expected); Assert.That(tree.Root, Is.Not.Null); Assert.That(tree.Root!.Key == expected, Is.True); Assert.That(tree.Size == 1, Is.True); Assert.That(tree.MaxSize == 1, Is.True); Assert.That(tree.Alpha, Is.EqualTo(0.5)); } [Test] public void Constructor_KeyAndAlphaParameters_InstanceIsValid() { var key = 10; var alpha = 0.8; var tree = new ScapegoatTree(key, alpha); Assert.That(tree.Root, Is.Not.Null); Assert.That(tree.Size == 1, Is.True); Assert.That(tree.MaxSize == 1, Is.True); Assert.That(tree.Alpha, Is.EqualTo(alpha)); } [Test] public void Constructor_NodeAndAlphaParameters_InstanceIsValid() { var node = new Node(10, new Node(11), new Node(1)); var alpha = 0.8; var tree = new ScapegoatTree(node, alpha); Assert.That(tree.Root, Is.Not.Null); Assert.That(tree.Size == 3, Is.True); Assert.That(tree.MaxSize == 3, Is.True); Assert.That(tree.Alpha, Is.EqualTo(alpha)); } [Test] public void IsAlphaWeightBalanced_RootIsNull_ReturnsTrue() { var tree = new ScapegoatTree(); var result = tree.IsAlphaWeightBalanced(); Assert.That(result, Is.True); } [Test] public void Search_RootIsNull_ReturnsNull() { var tree = new ScapegoatTree(); var result = tree.Search(1); Assert.That(result, Is.Null); } [Test] public void Search_KeyIsPresent_ReturnsKey() { var tree = new ScapegoatTree(key: 1); var result = tree.Search(1); Assert.That(result, Is.Not.Null); Assert.That(result!.Key, Is.EqualTo(1)); } [TestCase(-2)] [TestCase(3)] public void Search_KeyIsNotPresent_ReturnsNull(int key) { var root = new Node(1, new Node(2), new Node(-1)); var tree = new ScapegoatTree(root, 0.5); var result = tree.Search(key); Assert.That(result, Is.Null); } [Test] public void Insert_RootIsNull_InsertsRoot() { var tree = new ScapegoatTree(); var inserted = tree.Insert(1); Assert.That(inserted, Is.True); Assert.That(tree.Root, Is.Not.Null); Assert.That(tree.Root!.Key, Is.EqualTo(1)); Assert.That(tree.Size, Is.EqualTo(1)); Assert.That(tree.MaxSize, Is.EqualTo(1)); } [Test] public void Delete_RootIsNull_ReturnsFalse() { var tree = new ScapegoatTree(); var deleted = tree.Delete(1); Assert.That(deleted, Is.False); } [Test] public void Delete_KeyIsNotPresent_ReturnsFalse() { var tree = new ScapegoatTree(1); var deleted = tree.Delete(2); Assert.That(deleted, Is.False); Assert.That(tree.Size, Is.EqualTo(1)); } [Test] public void Insert_KeyIsPresent_ReturnsFalse() { var tree = new ScapegoatTree(1); var inserted = tree.Insert(1); Assert.That(inserted, Is.False); Assert.That(tree.Size, Is.EqualTo(1)); Assert.That(tree.MaxSize, Is.EqualTo(1)); } [Test] public void Remove_KeyIsPresent_RemovesKey() { var tree = new ScapegoatTree(1); var inserted = tree.Insert(2); Assert.That(inserted, Is.True); var deleted = tree.Delete(2); Assert.That(deleted, Is.True); Assert.That(tree.Size, Is.EqualTo(1)); } [Test] public void Remove_KeyIsRootWithNoChildren_RemovesKey() { var tree = new ScapegoatTree(1); var deleted = tree.Delete(1); Assert.That(deleted, Is.True); Assert.That(tree.Root, Is.Null); Assert.That(tree.Size, Is.EqualTo(0)); } [Test] public void Remove_KeyIsRootWithOneLeftChild_RemovesKey() { var tree = new ScapegoatTree(1); var inserted = tree.Insert(-1); Assert.That(inserted, Is.True); var deleted = tree.Delete(1); Assert.That(deleted, Is.True); Assert.That(tree.Size, Is.EqualTo(1)); } [Test] public void Remove_KeyIsRootWithOneRightChild_RemovesKey() { var tree = new ScapegoatTree(1); var inserted = tree.Insert(2); Assert.That(inserted, Is.True); var deleted = tree.Delete(1); Assert.That(deleted, Is.True); Assert.That(tree.Size, Is.EqualTo(1)); } [Test] public void Remove_KeyIsRootWithTwoChildren_RemovesKey() { var tree = new ScapegoatTree(1); var inserted = tree.Insert(-1); Assert.That(inserted, Is.True); inserted = tree.Insert(2); Assert.That(inserted, Is.True); var deleted = tree.Delete(1); Assert.That(deleted, Is.True); Assert.That(tree.Size, Is.EqualTo(2)); } [Test] public void Insert_KeyIsNotPresent_KeyIsInserted() { var tree = new ScapegoatTree(1); var inserted = tree.Insert(2); Assert.That(inserted, Is.True); Assert.That(tree.Size, Is.EqualTo(2)); Assert.That(tree.MaxSize, Is.EqualTo(2)); } [TestCase(3, new[] { 2, 5, 1, 6 }, -1, 0.5)] public void Insert_TreeIsUnbalanced_RebuildsTree(int root, int[] keys, int candidate, double alpha) { var tree = new ScapegoatTree(root, alpha); tree.TreeIsUnbalanced += FailTreeIsUnbalanced; foreach (var item in keys) { Assert.DoesNotThrow(() => tree.Insert(item)); } tree.TreeIsUnbalanced -= FailTreeIsUnbalanced; tree.TreeIsUnbalanced += PassTreeIsUnbalanced; Assert.Throws(() => tree.Insert(candidate)); } [TestCase(20, new[] { 10, 30, 5, 11, 29, 40, 50, 1, 12 }, new[] { 50, 40, 30, 29 }, 0.7)] public void Delete_TreeIsUnbalanced_BalancesTree(int root, int[] keys, int[] candidates, double alpha) { var tree = new ScapegoatTree(root, alpha); tree.TreeIsUnbalanced += FailTreeIsUnbalanced; foreach (var item in keys) { Assert.DoesNotThrow(() => tree.Insert(item)); } tree.TreeIsUnbalanced -= FailTreeIsUnbalanced; tree.TreeIsUnbalanced += PassTreeIsUnbalanced; Assert.Throws(() => { foreach (var item in candidates) { tree.Delete(item); } }); } [TestCase(20, new[] { 10, 30, 5, 11, 29, 40, 50 }, 10, 1)] public void Delete_TreeIsUnbalanced_MaxSizeEqualsSize(int root, int[] keys, int candidate, double alpha) { var tree = new ScapegoatTree(root, alpha); tree.TreeIsUnbalanced += FailTreeIsUnbalanced; foreach (var item in keys) { Assert.DoesNotThrow(() => tree.Insert(item)); } tree.TreeIsUnbalanced -= FailTreeIsUnbalanced; tree.Delete(candidate); Assert.That(tree.MaxSize, Is.EqualTo(tree.Size)); } [TestCase(3, new[] { 2, 5, 1, 6 }, -1, 0.5)] [TestCase(3, new[] { 2, 5, 1, 6 }, 7, 0.5)] public void Insert_TreeIsUnbalanced_BalancesTree(int root, int[] keys, int candidate, double alpha) { var tree = new ScapegoatTree(root, alpha); tree.TreeIsUnbalanced += FailTreeIsUnbalanced; foreach (var item in keys) { Assert.DoesNotThrow(() => tree.Insert(item)); } tree.TreeIsUnbalanced -= FailTreeIsUnbalanced; var inserted = tree.Insert(candidate); Assert.That(inserted, Is.True); Assert.That(tree.Size == 6, Is.True); Assert.That(tree.IsAlphaWeightBalanced(), Is.True); } [TestCase(3, 5, 0.5)] public void Insert_TreeIsUnbalanced_BalancesTree2(int root, int candidate, double alpha) { var tree = new ScapegoatTree(root, alpha); var inserted = tree.Insert(candidate); Assert.That(inserted, Is.True); Assert.That(tree.Size == 2, Is.True); Assert.That(tree.IsAlphaWeightBalanced(), Is.True); } [Test] public void Contains_RootIsNull_ReturnsFalse() { var tree = new ScapegoatTree(); Assert.That(tree.Contains(1), Is.False); } [Test] public void Contains_RootHasKey_ReturnsTrue() { var tree = new ScapegoatTree(1); Assert.That(tree.Contains(1), Is.True); } [Test] public void Contains_TreeHasKey_ReturnsTrue() { var tree = new ScapegoatTree(1); tree.Insert(2); Assert.That(tree.Contains(2), Is.True); } [Test] public void Contains_TreeDoesNotContainKey_ReturnsFalse() { var tree = new ScapegoatTree(1); tree.Insert(2); Assert.That(tree.Contains(-1), Is.False); } [Test] public void Clear_TreeHasKeys_ClearsTree() { var tree = new ScapegoatTree(1); tree.Clear(); Assert.That(tree.Size == 0, Is.True); Assert.That(tree.MaxSize == 0, Is.True); Assert.That(tree.Root, Is.Null); } [Test] public void Tune_AlphaIsValid_ChangesAlpha() { var expected = 0.7; var tree = new ScapegoatTree(); tree.Tune(expected); Assert.That(tree.Alpha, Is.EqualTo(expected)); } [Test] public void Tune_AlphaIsNotValid_ThrowsException() { var expected = 9.9; var tree = new ScapegoatTree(); Assert.Throws(() => tree.Tune(expected)); } [Test] public void FindScapegoatInPath_PathIsEmpty_ThrowsAnException() { var tree = new ScapegoatTree(); Assert.Throws(() => tree.FindScapegoatInPath(new Stack>())); } [Test] public void FindScapegoatInPath_ScapegoatIsNotPresent_ThrowsAnException() { var tree = new ScapegoatTree(1, 1); var path = new Stack>(); path.Push(tree.Root!); Assert.Throws(() => tree.FindScapegoatInPath(path)); } private static void FailTreeIsUnbalanced(object? sender, EventArgs? e) { Assert.Fail(); } private static void PassTreeIsUnbalanced(object? sender, EventArgs? e) { Assert.Pass(); } } ================================================ FILE: DataStructures.Tests/SegmentTrees/SegmentTreeApplyTests.cs ================================================ using DataStructures.SegmentTrees; namespace DataStructures.Tests.SegmentTrees; [TestFixture] public class SegmentTreeApplyTests { private readonly SegmentTreeApply testTree = new([8, 9, 1, 4, 8, 7, 2]); [Test] public void Apply_Query_Update_Query_Test() { Assert.That(testTree.Query(1, 4), Is.EqualTo(22)); testTree.Apply(0, 3, 2); Assert.That(testTree.Operand, Is.EqualTo(new[] { 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 })); Assert.That(testTree.Query(1, 4), Is.EqualTo(36)); } } ================================================ FILE: DataStructures.Tests/SegmentTrees/SegmentTreeTests.cs ================================================ using DataStructures.SegmentTrees; namespace DataStructures.Tests.SegmentTrees; [TestFixture] public class SegmentTreeTests { private readonly SegmentTree testTree = new([8, 9, 1, 4, 8, 7, 2]); [Test] public void TreeArray_Test() { int[] expectedArray = [0, 39, 22, 17, 17, 5, 15, 2, 8, 9, 1, 4, 8, 7, 2, 0]; Assert.That(testTree.Tree, Is.EqualTo(expectedArray)); } [TestCase(1, 4, 22)] [TestCase(2, 2, 1)] public void Query_Test(int left, int right, int expectedValue) { Assert.That(testTree.Query(left, right), Is.EqualTo(expectedValue)); } } ================================================ FILE: DataStructures.Tests/SegmentTrees/SegmentTreeUpdateTest.cs ================================================ using DataStructures.SegmentTrees; namespace DataStructures.Tests.SegmentTrees; [TestFixture] public class SegmentTreeUpdateTests { [SetUp] public void Init() { testTree = new SegmentTreeUpdate([8, 9, 1, 4, 8, 7, 2]); } private SegmentTreeUpdate testTree = new([8, 9, 1, 4, 8, 7, 2]); [TestCase(2, 3, 1, 4, 24)] [TestCase(0, 3, 1, 4, 22)] public void Update_Test(int node, int value, int left, int right, int aftQuery) { testTree.Update(node, value); Assert.That(aftQuery, Is.EqualTo(testTree.Query(left, right))); } } ================================================ FILE: DataStructures.Tests/SortedListTests.cs ================================================ namespace DataStructures.Tests; [TestFixture] public class SortedListTests { [Test] public void Add_AddMultipleValues_SortingCorrectly( [Random(1, 1000, 100, Distinct = true)] int count) { var values = GetValues(count); var list = new SortedList(); foreach (var value in values) { list.Add(value); } Assert.That(list, Is.EqualTo(values.OrderBy(i => i))); } [Test] public void Contains_PositiveArrayAdded_NegativeNumberAsked_FalseReturned( [Random(1, 200, 10, Distinct = true)] int count) { var values = GetValues(count); const int value = -1; var list = new SortedList(); foreach (var i in values) { list.Add(i); } Assert.That(list.Contains(value), Is.False); } [Test] public void Contains_PositiveArrayAdded_ContainingValueAsked_TrueReturned( [Random(1, 200, 10, Distinct = true)] int count) { var values = GetValues(count); var value = values[TestContext.CurrentContext.Random.Next(count - 1)]; var list = new SortedList(); foreach (var i in values) { list.Add(i); } Assert.That(list.Contains(value), Is.True); } [Test] public void Remove_PositiveArrayAdded_NegativeNumberAsked_FalseReturned( [Random(1, 200, 10, Distinct = true)] int count) { var values = GetValues(count); const int value = -1; var list = new SortedList(); foreach (var i in values) { list.Add(i); } Assert.That(list.TryRemove(value), Is.False); } [Test] public void Remove_PositiveArrayAdded_ContainingValueAsked_TrueReturned( [Random(1, 200, 10, Distinct = true)] int count) { var values = GetValues(count); var value = values[TestContext.CurrentContext.Random.Next(count - 1)]; var list = new SortedList(); foreach (var i in values) { list.Add(i); } var expectingValues = values .OrderBy(i => i) .ToList(); expectingValues.Remove(value); Assert.That(list.TryRemove(value), Is.True); Assert.That(list, Is.EqualTo(expectingValues)); } [Test] public void Clear_ArrayAdded_ListCleaned_ListIsEmpty( [Random(1, 20, 1, Distinct = true)] int count) { var values = GetValues(count); var list = new SortedList(); foreach (var i in values) { list.Add(i); } list.Clear(); Assert.That(list, Is.Empty); } private static List GetValues(int count) => Enumerable .Range(0, count) .Select(_ => TestContext.CurrentContext.Random.Next(1_000_000)) .ToList(); } ================================================ FILE: DataStructures.Tests/Stack/ArrayBasedStackTests.cs ================================================ using DataStructures.Stack; namespace DataStructures.Tests.Stack; public static class ArrayBasedStackTests { private const string StackEmptyErrorMessage = "Stack is empty"; [Test] public static void CountTest() { var stack = new ArrayBasedStack([0, 1, 2, 3, 4]); stack.Top.Should().Be(4); } [Test] public static void ClearTest() { var stack = new ArrayBasedStack([0, 1, 2, 3, 4]); stack.Clear(); stack.Top.Should().Be(-1); } [Test] public static void ContainsTest() { var stack = new ArrayBasedStack([0, 1, 2, 3, 4]); Assert.Multiple(() => { stack.Contains(0).Should().BeTrue(); stack.Contains(1).Should().BeTrue(); stack.Contains(2).Should().BeTrue(); stack.Contains(3).Should().BeTrue(); stack.Contains(4).Should().BeTrue(); }); } [Test] public static void PeekTest() { var stack = new ArrayBasedStack([0, 1, 2, 3, 4]); Assert.Multiple(() => { stack.Peek().Should().Be(4); stack.Peek().Should().Be(4); stack.Peek().Should().Be(4); }); } [Test] public static void PopTest() { var stack = new ArrayBasedStack([0, 1, 2, 3, 4]); Assert.Multiple(() => { stack.Pop().Should().Be(4); stack.Pop().Should().Be(3); stack.Pop().Should().Be(2); stack.Pop().Should().Be(1); stack.Pop().Should().Be(0); }); } [Test] public static void PushTest() { var stack = new ArrayBasedStack(); Assert.Multiple(() => Enumerable.Range(0, 5) .ToList() .ForEach(number => { stack.Push(number); stack.Peek().Should().Be(number); })); } [Test] public static void AutomaticResizesTest() { const int initialCapacity = 2; var stack = new ArrayBasedStack { Capacity = initialCapacity, }; stack.Push(0); stack.Push(1); stack.Push(2); stack.Push(3); stack.Push(4); stack.Capacity.Should().BeGreaterThan(initialCapacity); } [Test] public static void ShouldThrowStackEmptyExceptionOnEmptyPopTest() { var stack = new ArrayBasedStack(); Action poppingAnEmptyStack = () => stack.Pop(); poppingAnEmptyStack.Should() .Throw() .WithMessage(StackEmptyErrorMessage); } [Test] public static void ShouldThrowStackEmptyExceptionOnEmptyPeekTest() { var stack = new ArrayBasedStack(); Action peekingAnEmptyStack = () => stack.Peek(); peekingAnEmptyStack.Should() .Throw() .WithMessage(StackEmptyErrorMessage); } } ================================================ FILE: DataStructures.Tests/Stack/ListBasedStackTests.cs ================================================ using DataStructures.Stack; namespace DataStructures.Tests.Stack; public static class ListBasedStackTests { [Test] public static void CountTest() { var stack = new ListBasedStack([0, 1, 2, 3, 4]); stack.Count.Should().Be(5); } [Test] public static void ClearTest() { var stack = new ListBasedStack([0, 1, 2, 3, 4]); stack.Clear(); stack.Count.Should().Be(0); } [Test] public static void ContainsTest() { var stack = new ListBasedStack([0, 1, 2, 3, 4]); Assert.Multiple(() => { stack.Contains(0).Should().BeTrue(); stack.Contains(1).Should().BeTrue(); stack.Contains(2).Should().BeTrue(); stack.Contains(3).Should().BeTrue(); stack.Contains(4).Should().BeTrue(); }); } [Test] public static void PeekTest() { var stack = new ListBasedStack([0, 1, 2, 3, 4]); Assert.Multiple(() => { stack.Peek().Should().Be(4); stack.Peek().Should().Be(4); stack.Peek().Should().Be(4); }); } [Test] public static void PopTest() { var stack = new ListBasedStack([0, 1, 2, 3, 4]); Assert.Multiple(() => { stack.Pop().Should().Be(4); stack.Pop().Should().Be(3); stack.Pop().Should().Be(2); stack.Pop().Should().Be(1); stack.Pop().Should().Be(0); }); } [Test] public static void PushTest() { var stack = new ListBasedStack(); Assert.Multiple(() => Enumerable.Range(0, 5) .ToList() .ForEach(number => { stack.Push(number); stack.Peek().Should().Be(number); })); } } ================================================ FILE: DataStructures.Tests/Stack/QueueBasedStackTests.cs ================================================ using DataStructures.Stack; namespace DataStructures.Tests.Stack; public static class QueueBasedStackTests { [Test] public static void PopWorksCorrectly() { //Arrange QueueBasedStack s = new QueueBasedStack(); s.Push('A'); s.Push('B'); s.Push('C'); var result = new StringBuilder(); //Act for (int i = 0; i < 3; i++) { result.Append(s.Pop()); } //Assert Assert.That("CBA", Is.EqualTo(result.ToString())); Assert.That(s.IsEmpty(), Is.True, "Stack is Empty"); } [Test] public static void PeekWorksCorrectly() { //Arrange QueueBasedStack s = new QueueBasedStack(); s.Push(1); s.Push(2); s.Push(3); var peeked = 0; //Act for (int i = 0; i < 3; i++) { peeked = s.Peek(); } //Assert Assert.That(3, Is.EqualTo(peeked)); Assert.That(s.IsEmpty(), Is.False, "Stack is Empty"); } [Test] public static void PopEmptyStackThrowsInvalidOperationException() { //Arrange var s = new QueueBasedStack(); Exception? exception = null; //Act try { s.Pop(); } catch (Exception ex) { exception = ex; } //Assert Assert.That(exception?.GetType(), Is.EqualTo(typeof(InvalidOperationException))); } [Test] public static void PeekEmptyStackThrowsInvalidOperationException() { //Arrange var s = new QueueBasedStack(); Exception? exception = null; //Act try { s.Peek(); } catch (Exception ex) { exception = ex; } //Assert Assert.That(exception?.GetType(), Is.EqualTo(typeof(InvalidOperationException))); } [Test] public static void ClearWorksCorrectly() { // Arrange var s = new QueueBasedStack(); s.Push(1); s.Push(2); // Act s.Clear(); // Assert Assert.That(s.IsEmpty(), Is.True, "Queue is empty"); } [Test] public static void LengthWorksCorrectly() { // Arrange var s = new QueueBasedStack(); s.Push(1); s.Push(2); var length = 0; // Act length = s.Length(); // Assert Assert.That(2, Is.EqualTo(length)); } } ================================================ FILE: DataStructures.Tests/TimelineTests.cs ================================================ using FluentAssertions.Execution; namespace DataStructures.Tests; public static class TimelineTests { [Test] public static void CountTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Count .Should() .Be(5); } [Test] public static void TimesCountTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.TimesCount .Should() .Be(timeline.GetAllTimes().Length); } [Test] public static void ValuesCountTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.ValuesCount .Should() .Be(timeline.GetAllValues().Length); } [Test] public static void IndexerGetTest() { const string eventName = "TestTime2"; var eventDate = new DateTime(2000, 1, 1); var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { eventDate, eventName }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline[eventDate][0] .Should() .Be(eventName); } [Test] public static void IndexerSetTest() { var eventDate = new DateTime(2000, 1, 1); const string formerEventName = "TestTime2"; const string eventName = "TestTime2Modified"; var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { eventDate, formerEventName }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline[new DateTime(2000, 1, 1)] = [eventName]; timeline[new DateTime(2000, 1, 1)][0] .Should() .Be(eventName); } [Test] public static void EqualsTest() { var timeline1 = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var timeline2 = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; (timeline1 == timeline2) .Should() .BeTrue(); } [Test] public static void ClearTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Clear(); timeline.Count .Should() .Be(0); } [Test] public static void CopyToTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var array = new (DateTime Time, string Value)[timeline.Count]; timeline.CopyTo(array, 0); timeline.Count .Should() .Be(array.Length); var i = 0; using (new AssertionScope()) { foreach (var (time, value) in timeline) { array[i].Time .Should() .Be(time); array[i].Value .Should() .Be(value); ++i; } } } [Test] public static void GetAllTimesTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var times = timeline.GetAllTimes(); var i = 0; using (new AssertionScope()) { foreach (var (time, _) in timeline) { times[i++] .Should() .Be(time); } } } [Test] public static void GetTimesByValueTest() { var eventDate = new DateTime(2000, 1, 1); const string eventName = "TestTime2"; var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { eventDate, eventName }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.GetTimesByValue(eventName)[0] .Should() .Be(eventDate); } [Test] public static void GetTimesBeforeTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var times = timeline.GetTimesBefore(new DateTime(2003, 1, 1)); using (new AssertionScope()) { times.Length .Should() .Be(2); times[0] .Should() .Be(new DateTime(1995, 1, 1)); times[1] .Should() .Be(new DateTime(2000, 1, 1)); } } [Test] public static void GetTimesAfterTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var times = timeline.GetTimesAfter(new DateTime(2003, 1, 1)); using (new AssertionScope()) { times.Length .Should() .Be(3); times[0] .Should() .Be(new DateTime(2005, 1, 1)); times[1] .Should() .Be(new DateTime(2010, 1, 1)); times[2] .Should() .Be(new DateTime(2015, 1, 1)); } } [Test] public static void GetAllValuesTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var values = timeline.GetAllValues(); var i = 0; using (new AssertionScope()) { foreach (var (_, value) in timeline) { values[i++] .Should() .Be(value); } } } [Test] public static void GetValuesByTimeTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.GetValuesByTime(new DateTime(2000, 1, 1))[0] .Should() .Be("TestTime2"); } [Test] public static void GetValuesBeforeTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var array = timeline.GetValuesBefore(new DateTime(2003, 1, 1)).ToArray(); using (new AssertionScope()) { array.Length .Should() .Be(2); array[0].Time .Should() .Be(new DateTime(1995, 1, 1)); array[1].Time .Should() .Be(new DateTime(2000, 1, 1)); } } [Test] public static void GetValuesAfterTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var array = timeline.GetValuesAfter(new DateTime(2003, 1, 1)).ToArray(); using (new AssertionScope()) { array.Length .Should() .Be(3); array[0].Time .Should() .Be(new DateTime(2005, 1, 1)); array[1].Time .Should() .Be(new DateTime(2010, 1, 1)); array[2].Time .Should() .Be(new DateTime(2015, 1, 1)); } } [Test] public static void GetValuesByMillisecondTest() { var timeline = new Timeline { { new DateTime(1985, 1, 1, 10, 0, 0, 250), "TestTime1" }, { new DateTime(1990, 1, 1, 10, 0, 0, 250), "TestTime2" }, { new DateTime(1995, 1, 1, 10, 0, 0, 250), "TestTime3" }, { new DateTime(2005, 1, 1, 10, 0, 0, 750), "TestTime4" }, { new DateTime(2015, 1, 1, 10, 0, 0, 750), "TestTime5" }, }; var query = timeline.GetValuesByMillisecond(750); query.Count .Should() .Be(2); } [Test] public static void GetValuesBySecondTest() { var timeline = new Timeline { { new DateTime(1985, 1, 1, 10, 0, 5), "TestTime1" }, { new DateTime(1990, 1, 1, 10, 0, 5), "TestTime2" }, { new DateTime(1995, 1, 1, 10, 0, 5), "TestTime3" }, { new DateTime(2005, 1, 1, 10, 0, 20), "TestTime4" }, { new DateTime(2015, 1, 1, 10, 0, 20), "TestTime5" }, }; var query = timeline.GetValuesBySecond(20); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByMinuteTest() { var timeline = new Timeline { { new DateTime(1985, 1, 1, 10, 15, 0), "TestTime1" }, { new DateTime(1990, 1, 1, 10, 15, 0), "TestTime2" }, { new DateTime(1995, 1, 1, 10, 15, 0), "TestTime3" }, { new DateTime(2005, 1, 1, 10, 40, 0), "TestTime4" }, { new DateTime(2015, 1, 1, 10, 40, 0), "TestTime5" }, }; var query = timeline.GetValuesByMinute(40); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByHourTest() { var timeline = new Timeline { { new DateTime(1985, 1, 1, 7, 0, 0), "TestTime1" }, { new DateTime(1990, 1, 1, 7, 0, 0), "TestTime2" }, { new DateTime(1995, 1, 1, 7, 0, 0), "TestTime3" }, { new DateTime(2005, 1, 1, 16, 0, 0), "TestTime4" }, { new DateTime(2015, 1, 1, 16, 0, 0), "TestTime5" }, }; var query = timeline.GetValuesByHour(16); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByDayTest() { var timeline = new Timeline { { new DateTime(1985, 1, 10), "TestTime1" }, { new DateTime(1990, 1, 10), "TestTime2" }, { new DateTime(1995, 1, 10), "TestTime3" }, { new DateTime(2005, 1, 20), "TestTime4" }, { new DateTime(2015, 1, 20), "TestTime5" }, }; var query = timeline.GetValuesByDay(20); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByTimeOfDayTest() { var timeline = new Timeline { { new DateTime(1985, 1, 1, 10, 30, 15, 500), "TestTime1" }, { new DateTime(1990, 1, 1, 10, 30, 15, 500), "TestTime2" }, { new DateTime(1995, 1, 1, 10, 30, 15, 500), "TestTime3" }, { new DateTime(2005, 1, 1, 21, 15, 40, 600), "TestTime4" }, { new DateTime(2015, 1, 1, 21, 15, 40, 600), "TestTime5" }, }; var query = timeline.GetValuesByTimeOfDay(new TimeSpan(0, 21, 15, 40, 600)); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByDayOfWeekTest() { var timeline = new Timeline { { new DateTime(2015, 1, 5), "TestTime1" }, //Monday { new DateTime(2015, 2, 2), "TestTime2" }, //Monday { new DateTime(2015, 1, 6), "TestTime3" }, //Tuesday { new DateTime(2015, 1, 7), "TestTime4" }, //Wednesday { new DateTime(2015, 1, 8), "TestTime5" }, //Thursday }; var query = timeline.GetValuesByDayOfWeek(DayOfWeek.Monday); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByDayOfYearTest() { var timeline = new Timeline { { new DateTime(1985, 1, 3), "TestTime1" }, //3rd day of year { new DateTime(1990, 1, 7), "TestTime2" }, //7th day of year { new DateTime(1995, 1, 22), "TestTime3" }, //22th day of year { new DateTime(2000, 2, 1), "TestTime4" }, //32th day of year { new DateTime(2005, 2, 1), "TestTime5" }, //32th day of year }; var query = timeline.GetValuesByDayOfYear(32); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByMonthTest() { var timeline = new Timeline { { new DateTime(1985, 1, 1), "TestTime1" }, { new DateTime(1990, 2, 1), "TestTime2" }, { new DateTime(1995, 3, 1), "TestTime3" }, { new DateTime(2005, 4, 1), "TestTime4" }, { new DateTime(2015, 4, 1), "TestTime5" }, }; var query = timeline.GetValuesByMonth(4); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void GetValuesByYearTest() { var timeline = new Timeline { { new DateTime(1985, 1, 2), "TestTime1" }, { new DateTime(1990, 2, 1), "TestTime2" }, { new DateTime(1995, 1, 2), "TestTime3" }, { new DateTime(2005, 2, 1), "TestTime4" }, { new DateTime(2005, 1, 2), "TestTime5" }, }; var query = timeline.GetValuesByYear(2005); using (new AssertionScope()) { query.Count .Should() .Be(2); timeline .Should() .Contain(query); } } [Test] public static void AddDateTimeAndTValueTest() //void Add(DateTime time, TValue value) { var eventDate = new DateTime(2015, 1, 1); const string eventName = "TestTime"; var timeline = new Timeline { { eventDate, eventName } }; timeline.Count .Should() .Be(1); timeline[eventDate][0] .Should() .Be(eventName); } [Test] public static void AddDateTimeAndTValueArrayTest() //void Add(params (DateTime, TValue)[] timeline) { var eventDate1 = new DateTime(2015, 1, 1); const string eventName1 = "TestTime1"; var eventDate2 = new DateTime(1750, 1, 1); const string eventName2 = "TestTime2"; var timeline = new Timeline { { (eventDate1, eventName1), (eventDate2, eventName2) } }; using (new AssertionScope()) { timeline.Count .Should() .Be(2); timeline[eventDate1][0] .Should() .Be(eventName1); timeline[eventDate2][0] .Should() .Be(eventName2); } } [Test] public static void AddTimelineTest() //void Add(Timeline timeline) { var eventDate = new DateTime(2015, 1, 1); const string eventName = "TestTime"; var timeline = new Timeline { new Timeline(eventDate, eventName) }; using (new AssertionScope()) { timeline.Count .Should() .Be(1); timeline[eventDate][0] .Should() .Be(eventName); } } [Test] public static void AddNowTest() { var timeline = new Timeline(); timeline.AddNow("Now"); using (new AssertionScope()) { timeline.Count .Should() .Be(1); timeline.ContainsValue("Now") .Should() .BeTrue(); } } [Test] public static void ContainsDateTimeAndTValueTest() //bool Contains(DateTime time, TValue value) { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Contains(new DateTime(2000, 1, 1), "TestTime2") .Should() .BeTrue(); } [Test] public static void ContainsDateTimeAndTValueArrayTest() //bool Contains(params (DateTime, TValue)[] timeline) { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Contains( (new DateTime(1995, 1, 1), "TestTime1"), (new DateTime(2000, 1, 1), "TestTime2")) .Should() .BeTrue(); } [Test] public static void ContainsTimelineTest() //bool Contains(Timeline timeline) { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Contains(new Timeline(new DateTime(2000, 1, 1), "TestTime2")) .Should() .BeTrue(); } [Test] public static void ContainsTimeTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.ContainsTime(new DateTime(2000, 1, 1)) .Should() .BeTrue(); } [Test] public static void ContainsValueTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.ContainsValue("TestTime1") .Should() .BeTrue(); } [Test] public static void RemoveDateTimeAndTValueTest() //bool Remove(DateTime time, TValue value) { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Remove(new DateTime(2000, 1, 1), "TestTime2"); using (new AssertionScope()) { timeline.Count .Should() .Be(4); timeline.Contains(new DateTime(2000, 1, 1), "TestTime2") .Should() .BeFalse(); } } [Test] public static void RemoveDateTimeAndTValueArrayTest() //bool Remove(params (DateTime, TValue)[] timeline) { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Remove( (new DateTime(1995, 1, 1), "TestTime1"), (new DateTime(2000, 1, 1), "TestTime2")); using (new AssertionScope()) { timeline.Count .Should() .Be(3); timeline.Contains( (new DateTime(1995, 1, 1), "TestTime1"), (new DateTime(2000, 1, 1), "TestTime2")) .Should() .BeFalse(); } } [Test] public static void RemoveTimelineTest() //bool Remove(Timeline timeline) { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.Remove(new Timeline(new DateTime(2000, 1, 1), "TestTime2")); using (new AssertionScope()) { timeline.Count .Should() .Be(4); timeline.Contains(new DateTime(2000, 1, 1), "TestTime2") .Should() .BeFalse(); } } [Test] public static void RemoveTimeTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.RemoveTimes(new DateTime(2000, 1, 1)); using (new AssertionScope()) { timeline.Count .Should() .Be(4); timeline.ContainsTime(new DateTime(2000, 1, 1)) .Should() .BeFalse(); } } [Test] public static void RemoveValueTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; timeline.RemoveValues("TestTime1"); using (new AssertionScope()) { timeline.Count .Should() .Be(4); timeline.ContainsValue("TestTime1") .Should() .BeFalse(); } } [Test] public static void ToArrayTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var array = timeline.ToArray(); timeline.Count .Should() .Be(array.Length); using (new AssertionScope()) { var i = 0; foreach (var (time, value) in timeline) { time .Should() .Be(array[i].Time); value .Should() .Be(array[i].Value); ++i; } } } [Test] public static void ToListTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var list = timeline.ToList(); timeline.Count .Should() .Be(list.Count); using (new AssertionScope()) { var i = 0; foreach (var (time, value) in timeline) { time .Should() .Be(list[i].Time); value .Should() .Be(list[i].Value); ++i; } } } [Test] public static void ToDictionaryTest() { var timeline = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var dictionary = timeline.ToDictionary(); var timelineList = new List<(DateTime Time, string Value)>(); foreach (var pair in timeline) { timelineList.Add(pair); } var dictionaryList = new List<(DateTime Time, string Value)>(); foreach (var (key, value) in dictionary) { dictionaryList.Add((key, value)); } timelineList.OrderBy(pair => pair.Time); dictionaryList.OrderBy(pair => pair.Time); timelineList.Count .Should() .Be(dictionaryList.Count); using (new AssertionScope()) { for (var i = 0; i < timelineList.Count; ++i) { timelineList[i].Time .Should() .Be(dictionaryList[i].Time); timelineList[i].Value .Should() .Be(dictionaryList[i].Value); } } } [Test] public static void EqualityOperatorTest() { var timeline1 = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var timeline2 = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; (timeline1 == timeline2) .Should() .BeTrue(); } [Test] public static void InequalityOperatorTest() { var timeline1 = new Timeline { { new DateTime(1995, 1, 1), "TestTime1" }, { new DateTime(2000, 1, 1), "TestTime2" }, { new DateTime(2005, 1, 1), "TestTime3" }, { new DateTime(2010, 1, 1), "TestTime4" }, { new DateTime(2015, 1, 1), "TestTime5" }, }; var timeline2 = new Timeline { { new DateTime(1895, 1, 1), "TestTime6" }, { new DateTime(1900, 1, 1), "TestTime7" }, { new DateTime(1905, 1, 1), "TestTime8" }, { new DateTime(1910, 1, 1), "TestTime9" }, { new DateTime(1915, 1, 1), "TestTime10" }, }; (timeline1 == timeline2) .Should() .BeFalse(); } } ================================================ FILE: DataStructures.Tests/Tries/TrieTests.cs ================================================ using DataStructures.Tries; namespace DataStructures.Tests.Tries; public static class TrieTests { [Test] public static void FindWordInTrie() { // Arrange string[] words = [ "trie", "node", "none", "treatment", ]; // Act Trie trie = new(words); // Assert Assert.That(trie.Find("trie"), Is.True, "The word 'trie' isn't in Trie structure"); Assert.That(trie.Find("node"), Is.True, "The word 'node' isn't in Trie structure"); Assert.That(trie.Find("none"), Is.True, "The word 'none' isn't in Trie structure"); Assert.That(trie.Find("treatment"), Is.True, "The word 'treatment' isn't in Trie structure"); Assert.That(trie.Find("nodes"), Is.False, "The word 'nodes' is in Trie sturcture"); Assert.That(trie.Find(""), Is.False, "The word empty is in Trie structure"); Assert.That(trie.Find("tri"), Is.False, "The word 'tri' is in Trie structure"); } [Test] public static void InsertInTrie() { // Arrange string[] words = [ "trie", "node", "none", "treatment", ]; Trie trie = new(); // Act foreach (var t in words) { trie.Insert(t); } // Assert Assert.That(trie.Find("trie"), Is.True, "The word 'trie' isn't in Trie structure"); Assert.That(trie.Find("node"), Is.True, "The word 'node' isn't in Trie structure"); Assert.That(trie.Find("none"), Is.True, "The word 'none' isn't in Trie structure"); Assert.That(trie.Find("treatment"), Is.True, "The word 'treatment' isn't in Trie structure"); } [Test] public static void RemoveFromTrie() { // Arrange string[] words = [ "trie", "node", "none", "treatment", ]; Trie trie = new(); // Act foreach (var t in words) { trie.Insert(t); } trie.Remove("trie"); // Assert Assert.That(trie.Find("trie"), Is.False, "The word 'trie' is in Trie structure"); Assert.That(trie.Find("treatment"), Is.True, "The word 'treament' isn't in Trie structure"); Assert.That(trie.Find("node"), Is.True, "The word 'node' isn't in Trie structure"); Assert.That(trie.Find("none"), Is.True, "The word 'none' isn't in Trie structure"); } [Test] public static void MultipleInsert() { // Arrange string w = "trie"; Trie trie = new(); // Act trie.Insert(w); trie.Insert(w); // Assert Assert.That(trie.Find("trie"), Is.True, "The word 'trie' isn't in Trie structure"); Assert.That(trie.Find("nodes"), Is.False, "The word 'nodes' is in Trie sturcture"); } [Test] public static void RemoveAWordThatIsNtInTrie() { // Arrange const string w = "trie"; Trie trie = new(); // Act trie.Insert(w); trie.Remove("tri"); trie.Remove("none"); // Assert Assert.That(trie.Find("trie"), Is.True, "The word 'trie' isn't in Trie structure"); } } ================================================ FILE: DataStructures.Tests/UnrolledList/UnrolledLinkedListNodeTests.cs ================================================ using DataStructures.UnrolledList; namespace DataStructures.Tests.UnrolledList; public class UnrolledLinkedListNodeTests { [Test] public void GetAndSet_SetItemNodeAndGetIt_ReturnExpectedItem() { var node = new UnrolledLinkedListNode(6); node.Set(0, 1); var result = node.Get(0); result.Should().Be(1); } [Test] public void Get_GetLowIndex_ThrowArgumentException() { var node = new UnrolledLinkedListNode(6); Action action = () => node.Get(-1); action.Should().Throw(); } [Test] public void Get_GetHighIndex_ThrowArgumentException() { var node = new UnrolledLinkedListNode(6); Action action = () => node.Get(7); action.Should().Throw(); } [Test] public void Set_SetLowIndex_ThrowArgumentException() { var node = new UnrolledLinkedListNode(6); Action action = () => node.Set(-1, 0); action.Should().Throw(); } [Test] public void Set_SetHighIndex_ThrowArgumentException() { var node = new UnrolledLinkedListNode(6); Action action = () => node.Set(7, 0); action.Should().Throw(); } } ================================================ FILE: DataStructures.Tests/UnrolledList/UnrolledLinkedListTests.cs ================================================ using DataStructures.UnrolledList; namespace DataStructures.Tests.UnrolledList; public class UnrolledLinkedListTests { [Test] public void Insert_LinkArrayToLinkedList_ReturnArrayHaveSameItems() { var linkedList = new UnrolledLinkedList(6); var contest = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; foreach (var number in contest) { linkedList.Insert(number); } var result = linkedList.GetRolledItems(); result.Should().BeEquivalentTo(contest); } } ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================
# The Algorithms - C# [![Discord chat](https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA)](https://discord.gg/c7MnfGFGa6) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/58895a2795bd48a8b3b7eb6ebe22d576)](https://www.codacy.com/gh/TheAlgorithms/C-Sharp/dashboard?utm_source=github.com&utm_medium=referral&utm_content=TheAlgorithms/C-Sharp&utm_campaign=Badge_Grade) [![codecov](https://codecov.io/gh/TheAlgorithms/C-Sharp/branch/master/graph/badge.svg)](https://codecov.io/gh/TheAlgorithms/C-Sharp) [![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/TheAlgorithms/donate) ## All Algorithms implemented in C# - for education purposes The repository is a collection of a variety of algorithms implemented in C#. The algorithms span over a variety of topics from computer science, mathematics and statistics, data science, machine learning, engineering, etc. The implementations and their associated documentations are meant to provide a learning resource for educators and students. Hence, one may find more than one implementation for the same objective but using different algorithm strategies and optimizations.
## List of Algorithms * [Algorithms](./Algorithms) * [Crypto](./Algorithms/Crypto/) * [Paddings](./Algorithms/Crypto/Paddings/) * [ISO 10125-2 Padding](./Algorithms/Crypto/Paddings/Iso10126D2Padding.cs) * [ISO 7816-4 Padding](./Algorithms/Crypto/Paddings/Iso7816D4Padding.cs) * [X9.32 Padding](./Algorithms/Crypto/Paddings/X932Padding.cs) * [TBC Padding](./Algorithms/Crypto/Paddings/TbcPadding.cs) * [PKCS7 Padding](./Algorithms/Crypto/Paddings/Pkcs7Padding.cs) * [Digests](./Algorithms/Crypto/Digests/) * [Ascon Hash Digest](./Algorithms/Crypto/Digests/AsconDigest.cs) * [MD2 Digest](./Algorithms/Crypto/Digests/Md2Digest.cs) * [Data Compression](./Algorithms/DataCompression) * [Burrows-Wheeler transform](./Algorithms/DataCompression/BurrowsWheelerTransform.cs) * [Huffman Compressor](./Algorithms/DataCompression/HuffmanCompressor.cs) * [Shannon-Fano Compressor](./Algorithms/DataCompression/ShannonFanoCompressor.cs) * [Encoders](./Algorithms/Encoders) * [Caesar](./Algorithms/Encoders/CaesarEncoder.cs) * [Vigenere](./Algorithms/Encoders/VigenereEncoder.cs) * [Hill](./Algorithms/Encoders/HillEncoder.cs) * [NYSIIS](./Algorithms/Encoders/NysiisEncoder.cs) * [Soundex](./Algorithms/Encoders/SoundexEncoder.cs) * [Feistel](./Algorithms/Encoders/FeistelCipher.cs) * [Blowfish](./Algorithms/Encoders/BlowfishEncoder.cs) * [Autokey](./Algorithms/Encoders/AutokeyEncoder.cs) * [Graph](./Algorithms/Graph) * [Minimum Spanning Tree](./Algorithms/Graph/MinimumSpanningTree) * [Prim's Algorithm (Adjacency Matrix)](./Algorithms/Graph/MinimumSpanningTree/PrimMatrix.cs) * [Kruskal's Algorithm](./Algorithms/Graph/MinimumSpanningTree/Kruskal.cs) * [BreadthFirstTreeTraversal](./Algorithms/Graph/BreadthFirstTreeTraversal.cs) * [BreadthFirstSearch](./Algorithms/Graph/BreadthFirstSearch.cs) * [DepthFirstSearch](./Algorithms/Graph/DepthFirstSearch.cs) * [Dijkstra Shortest Path](./Algorithms/Graph/Dijkstra/DijkstraAlgorithm.cs) * [FloydWarshall](./Algorithms/Graph/FloydWarshall.cs) * [Kosaraju](./Algorithms/Graph/Kosaraju.cs) * [Topological Sort](./Algorithms/Graph/TopologicalSort.cs) * [Knapsack problem](./Algorithms/Knapsack) * [Naive solver](./Algorithms/Knapsack/NaiveKnapsackSolver.cs) * [Dynamic Programming solver](./Algorithms/Knapsack/DynamicProgrammingKnapsackSolver.cs) * [Branch and bound solver](./Algorithms/Knapsack/BranchAndBoundKnapsackSolver.cs) * [IHeuristicKnapsackSolver](./Algorithms/Knapsack/IHeuristicKnapsackSolver.cs) * [Linear Algebra](./Algorithms/LinearAlgebra) * [Distances](./Algorithms/LinearAlgebra/Distances) * [Chebyshev](./Algorithms/LinearAlgebra/Distances/Chebyshev.cs) * [Euclidean](./Algorithms/LinearAlgebra/Distances/Euclidean.cs) * [Manhattan](./Algorithms/LinearAlgebra/Distances/Manhattan.cs) * [Minkowski](./Algorithms/LinearAlgebra/Distances/Minkowski.cs) * [Eigenvalue](./Algorithms/LinearAlgebra/Eigenvalue) * [Power Iteration](./Algorithms/LinearAlgebra/Eigenvalue/PowerIteration.cs) * [Modular Arithmetic](./Algorithms/ModularArithmetic) * [Chinese Remainder Theorem](./Algorithms/ModularArithmetic/ChineseRemainderTheorem.cs) * [Extended Euclidean Algorithm](./Algorithms/ModularArithmetic/ExtendedEuclideanAlgorithm.cs) * [Modular Multiplicative Inverse](./Algorithms/ModularArithmetic/ModularMultiplicativeInverse.cs) * [Numeric](./Algorithms/Numeric) * [Absolute](./Algorithms/Numeric/Abs.cs) * [Addition Without Arithmetic](./Algorithms/Numeric/AdditionWithoutArithmetic.cs) * [Aliquot Sum Calculator](./Algorithms/Numeric/AliquotSumCalculator.cs) * [Amicable Numbers Checker](./Algorithms/Numeric/AmicableNumbersChecker.cs) * [Ceil](./Algorithms/Numeric/Ceil.cs) * [Decomposition](./Algorithms/Numeric/Decomposition) * [LU Decomposition](./Algorithms/Numeric/Decomposition/LU.cs) * [Thin Singular Vector Decomposition](./Algorithms/Numeric/Decomposition/ThinSVD.cs) * [Floor](./Algorithms/Floor.cs) * [Greatest Common Divisor](./Algorithms/Numeric/GreatestCommonDivisor) * [Euclidean GCD](./Algorithms/Numeric/GreatestCommonDivisor/EuclideanGreatestCommonDivisorFinder.cs) * [Binary GCD](./Algorithms/Numeric/GreatestCommonDivisor/BinaryGreatestCommonDivisorFinder.cs) * [Factorization](./Algorithms/Numeric/Factorization) * [Trial division Factorization](./Algorithms/Numeric/Factorization/TrialDivisionFactorizer.cs) * [Modular Exponentiation](./Algorithms/Numeric/ModularExponentiation.cs) * [Series](./Algorithms/Numeric/Series) * [Maclaurin Series](./Algorithms/Numeric/Series/Maclaurin.cs) * [Gauss-Jordan Elimination](./Algorithms/Numeric/GaussJordanElimination.cs) * [BinomialCoefficient](./Algorithms/Numeric/BinomialCoefficient.cs) * [Factorial](./Algorithms/Numeric/Factorial.cs) * [Keith Number Checker](./Algorithms/Numeric/KeithNumberChecker.cs) * [Pseudo-Inverse](./Algorithms/Numeric/Pseudoinverse/PseudoInverse.cs) * [Narcissistic Number Checker](./Algorithms/Numeric/NarcissisticNumberChecker.cs) * [Perfect Cube Checker](./Algorithms/Numeric/PerfectCubeChecker.cs) * [Perfect Number Checker](./Algorithms/Numeric/PerfectNumberChecker.cs) * [Perfect Square Checker](./Algorithms/Numeric/PerfectSquareChecker.cs) * [Euler Method](./Algorithms/Numeric/EulerMethod.cs) * [Classic Runge-Kutta Method](./Algorithms/Numeric/RungeKuttaMethod.cs) * [Miller-Rabin primality check](./Algorithms/Numeric/MillerRabinPrimalityChecker.cs) * [KrishnamurthyNumberChecker](./Algorithms/Numeric/KrishnamurthyNumberChecker.cs) * [Automorphic Number](./Algorithms/Numeric/AutomorphicNumber.cs) * [Josephus Problem](./Algorithms/Numeric/JosephusProblem.cs) * [Newton's Square Root Calculation](./Algorithms/NewtonSquareRoot.cs) * [SoftMax Function](./Algorithms/Numeric/SoftMax.cs) * [RecommenderSystem](./Algorithms/RecommenderSystem) * [CollaborativeFiltering](./Algorithms/RecommenderSystem/CollaborativeFiltering) * [Machine Learning](./Algorithms/MachineLearning) * [Linear Regression](./Algorithms/MachineLearning/LinearRegression.cs) * [K-Nearest Neighbors](./Algorithms/MachineLearning/KNearestNeighbors.cs) * [Logistic Regression](./Algorithms/MachineLearning/LogisticRegression.cs) * [Searches](./Algorithms/Search) * [A-Star](./Algorithms/Search/AStar/) * [Binary Search](./Algorithms/Search/BinarySearcher.cs) * [BoyerMoore Search](./Algorithms/Search/BoyerMoore.cs) * [Fast Search](./Algorithms/Search/FastSearcher.cs) * [Fibonacci Search](./Algorithms/Search/FibonacciSearcher.cs) * [Interpolation Search](./Algorithms/Search/InterpolationSearch.cs) * [Jump Search](./Algorithms/Search/JumpSearcher.cs) * [Linear Search](./Algorithms/Search/LinearSearcher.cs) * [Recursive Binary Search](./Algorithms/Search/RecursiveBinarySearcher.cs) * [Sorts](./Algorithms/Sorters) * [Comparison](./Algorithms/Sorters/Comparison) * [Binary Insertion Sort](./Algorithms/Sorters/Comparison/BinaryInsertionSorter.cs) * [Bogo Sort](./Algorithms/Sorters/Comparison/BogoSorter.cs) * [Bubble Sort](./Algorithms/Sorters/Comparison/BubbleSorter.cs) * [Cocktail Sort](./Algorithms/Sorters/Comparison/CocktailSorter.cs) * [Comb Sort](./Algorithms/Sorters/Comparison/CombSorter.cs) * [Cycle Sort](./Algorithms/Sorters/Comparison/CycleSorter.cs) * [Exchange Sort](./Algorithms/Sorters/Comparison/ExchangeSorter.cs) * [Gnome Sort](./Algorithms/Sorters/Comparison/GnomeSorter.cs) * [Heap Sort](./Algorithms/Sorters/Comparison/HeapSorter.cs) * [Insertion Sort](./Algorithms/Sorters/Comparison/InsertionSorter.cs) * [Merge Sort](./Algorithms/Sorters/Comparison/MergeSorter.cs) * [Pancake Sort](./Algorithms/Sorters/Comparison/PancakeSorter.cs) * [Quick Sort](./Algorithms/Sorters/Comparison/QuickSorter.cs) * [Median of three pivot](./Algorithms/Sorters/Comparison/MedianOfThreeQuickSorter.cs) * [Middle point pivot](./Algorithms/Sorters/Comparison/MiddlePointQuickSorter.cs) * [Random pivot](./Algorithms/Sorters/Comparison/RandomPivotQuickSorter.cs) * [Selection Sort](./Algorithms/Sorters/Comparison/SelectionSorter.cs) * [Shell Sort](./Algorithms/Sorters/Comparison/ShellSorter.cs) * [Tim Sort](./Algorithms/Sorters/Comparison/TimSorter.cs) * [Simplified Tim Sort](./Algorithms/Sorters/Comparison/BasicTimSorter.cs) * [External](./Algorithms/Sorters/External) * [Merge Sort](./Algorithms/Sorters/External/ExternalMergeSorter.cs) * [Integer](./Algorithms/Sorters/Integer) * [Counting Sort](./Algorithms/Sorters/Integer/CountingSorter.cs) * [Bucket Sort](./Algorithms/Sorters/Integer/BucketSorter.cs) * [Radix Sort](./Algorithms/Sorters/Integer/RadixSorter.cs) * [String](./Algorithms/Sorters/String) * [MSD Radix Sort](./Algorithms/Sorters/String/MsdRadixStringSorter.cs) * [Shufflers](./Algorithms/Shufflers) * [Fisher-Yates Shuffler](./Algorithms/Shufflers/FisherYatesShuffler.cs) * [LINQ Shuffler](./Algorithms/Shufflers/LinqShuffler.cs) * [Naive Shuffler](./Algorithms/Shufflers/NaiveShuffler.cs) * [Recursive Shuffler](./Algorithms/Shufflers/RecursiveShuffler.cs) * [Sequences](./Algorithms/Sequences) * [A000002 Kolakoski](./Algorithms/Sequences/KolakoskiSequence.cs) * [A000004 Zero](./Algorithms/Sequences/ZeroSequence.cs) * [A000005 Count of Divisors](./Algorithms/Sequences/DivisorsCountSequence.cs) * [A000008 Make Change](./Algorithms/Sequences/MakeChangeSequence.cs) * [A000010 Euler's Totient](./Algorithms/Sequences/EulerTotientSequence.cs) * [A000012 All Ones](./Algorithms/Sequences/AllOnesSequence.cs) * [A000027 Natural](./Algorithms/Sequences/NaturalSequence.cs) * [A000032 Lucas Numbers](./Algorithms/Sequences/LucasNumbersBeginningAt2Sequence.cs) * [A000040 Primes](./Algorithms/Sequences/PrimesSequence.cs) * [A000045 Fibonacci](./Algorithms/Sequences/FibonacciSequence.cs) * [A000079 Powers of 2](./Algorithms/Sequences/PowersOf2Sequence.cs) * [A000108 Catalan](./Algorithms/Sequences/CatalanSequence.cs) * [A000120 1's Counting](./Algorithms/Sequences/OnesCountingSequence.cs) * [A000124 Central Polygonal Numbers](./Algorithms/Sequences/CentralPolygonalNumbersSequence.cs) * [A000125 Cake Numbers](./Algorithms/Sequences/CakeNumbersSequence.cs) * [A000142 Factorial](./Algorithms/Sequences/FactorialSequence.cs) * [A000213 Tribonacci Numbers](./Algorithms/Sequences/TribonacciNumbersSequence.cs) * [A000215 Fermat Numbers](./Algorithms/Sequences/FermatNumbersSequence.cs) * [A000288 Tetranacci Numbers](./Algorithms/Sequences/TetranacciNumbersSequence.cs) * [A000290 Squares](./Algorithms/Sequences/SquaresSequence.cs) * [A000292 Tetrahedral numbers](./Algorithms/Sequences/TetrahedralSequence.cs) * [A000578 Cubes](./Algorithms/Sequences/CubesSequence.cs) * [A000720 PrimePi](./Algorithms/Sequences/PrimePiSequence.cs) * [A001146 Number of Boolean Functions](./Algorithms/Sequences/NumberOfBooleanFunctionsSequence.cs) * [A001462 Golomb's](./Algorithms/Sequences/GolombsSequence.cs) * [A001478 Negative Integers](./Algorithms/Sequences/NegativeIntegersSequence.cs) * [A002110 Primorial Numbers](./Algorithms/Sequences/PrimorialNumbersSequence.cs) * [A002717 Matchstick Triangle Arrangement](./Algorithms/Sequences/MatchstickTriangleSequence.cs) * [A005132 Recaman's](./Algorithms/Sequences/RecamansSequence.cs) * [A006577 Number of '3n+1' steps to reach 1](./Algorithms/Sequences/ThreeNPlusOneStepsSequence.cs) * [A006862 Euclid Numbers](./Algorithms/Sequences/EuclidNumbersSequence.cs) * [A006879 Number of Primes by Number of Digits](./Algorithms/Sequences/NumberOfPrimesByNumberOfDigitsSequence.cs) * [A006880 Number of Primes by Powers of 10](./Algorithms/Sequences/NumberOfPrimesByPowersOf10Sequence.cs) * [A007318 Binomial](./Algorithms/Sequences/BinomialSequence.cs) * [A007395 All Twos](./Algorithms/Sequences/AllTwosSequence.cs) * [A010051 Binary Prime Constant](./Algorithms/Sequences/BinaryPrimeConstantSequence.cs) * [A010701 All Threes](./Algorithms/Sequences/BinaryPrimeConstantSequence.cs) * [A011557 Powers of 10](./Algorithms/Sequences/PowersOf10Sequence.cs) * [A057588 Kummer Numbers](./Algorithms/Sequences/KummerNumbersSequence.cs) * [A019434 Fermat Primes](./Algorithms/Sequences/FermatPrimesSequence.cs) * [A181391 Van Eck's](./Algorithms/Sequences/VanEcksSequence.cs) * [Stack](./Algorithms/Stack) * [Next Greater Element](./Algorithms/Stack/NextGreaterElement.cs) * [Balanced Parentheses Checker](./Algorithms/Stack/BalancedParenthesesChecker.cs) * [Reverse Stack](./Algorithms/Stack/ReverseStack.cs) * [String](./Algorithms/Strings) * [Similarity](./Algorithms/Strings/Similarity/) * [Cosine Similarity](./Algorithms/Strings/Similarity/CosineSimilarity.cs) * [Damerau-Levenshtein Distance](./Algorithms/Strings/Similarity/DamerauLevenshteinDistance.cs) * [Hamming Distance](./Algorithms/Strings/Similarity/HammingDistance.cs) * [Jaro Similarity](./Algorithms/Strings/Similarity/JaroSimilarity.cs) * [Jaro-Winkler Distance](./Algorithms/Strings/Similarity/JaroWinklerDistance.cs) * [Optimal String Alignment](./Algorithms/Strings/Similarity/OptimalStringAlignment.cs) * [Pattern Matching](./Algorithms/Strings/PatternMatching/) * [Bitop Pattern Matching](./Algorithms/Strings/PatternMatching/Bitap.cs) * [Naive String Search](./Algorithms/Strings/PatternMatching/NaiveStringSearch.cs) * [Rabin Karp](./Algorithms/Strings/PatternMatching/RabinKarp.cs) * [Boyer Moore](./Algorithms/Strings/PatternMatching/BoyerMoore.cs) * [Knuth–Morris–Pratt Search](./Algorithms/Strings/PatternMatching/KnuthMorrisPrattSearcher.cs) * [WildCard Pattern Matching](./Algorithms/Strings/PatternMatching/WildCardMatcher.cs) * [Z-block substring search](./Algorithms/Strings/PatternMatching/ZblockSubstringSearch.cs) * [Longest Consecutive Character](./Algorithms/Strings/GeneralStringAlgorithms.cs) * [Manacher's Algorithm](./Algorithms/Strings/ManachersAlgorithm.cs) * [Palindrome Checker](./Algorithms/Strings/Palindrome.cs) * [Get all permutations of a string](./Algorithms/Strings/Permutation.cs) * [Other](./Algorithms/Other) * [Fermat Prime Checker](./Algorithms/Other/FermatPrimeChecker.cs) * [Sieve of Eratosthenes](./Algorithms/Other/SieveOfEratosthenes.cs) * [Kadane's Algorithm](./Algorithms/Other/KadanesAlgorithm.cs) * [Luhn](./Algorithms/Other/Luhn.cs) * [Int2Binary](./Algorithms/Other/Int2Binary.cs) * [GeoLocation](./Algorithms/Other/GeoLocation.cs) * [Mandelbrot](./Algorithms/Other/Mandelbrot.cs) * [Koch Snowflake](./Algorithms/Other/KochSnowflake.cs) * [RGB-HSV Conversion](./Algorithms/Other/RGBHSVConversion.cs) * [Flood Fill](./Algorithms/Other/FloodFill.cs) * [Pareto Optimization](./Algorithms/Other/ParetoOptimization.cs) * [Gauss Optimization](./Algorithms/Other/GaussOptimization.cs) * [Decisions Convolutions](./Algorithms/Other/DecisionsConvolutions.cs) * [Welford's Variance](./Algorithms/Other/WelfordsVariance.cs) * [Julian Easter](./Algorithms/Other/JulianEaster.cs) * [Pollard's Rho](./Algorithms/Other/PollardsRhoFactorizing.cs) * [GeoLocation Hash](./Algorithms/Other/Geohash.cs) * [Geofencing](./Algorithms/Other/Geofence.cs) * [Triangulation Algorithm](./Algorithms/Other/Triangulator.cs) * [Problems](./Algorithms/Problems) * [Stable Marriage](./Algorithms/Problems/StableMarriage) * [Gale-Shapley](./Algorithms/Problems/StableMarriage/GaleShapley.cs) * [Accepter](./Algorithms/Problems/StableMarriage/Accepter.cs) * [Proposer](./Algorithms/Problems/StableMarriage/Proposer.cs) * [N-Queens](./Algorithms/Problems/NQueens) * [Backtracking](./Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs) * [Knight Tour](./Algorithms/Problems/KnightTour/) * [Open Knight Tour](./Algorithms/Problems/KnightTour/OpenKnightTour.cs) * [Graph Coloring](./Algorithms/Problems/GraphColoring) * [Backtracking Graph Coloring Solver](./Algorithms/Problems/GraphColoring/GraphColoringSolver.cs) * [Dynamic Programming](./Algorithms/Problems/DynamicProgramming) * [Coin Change](./Algorithms/Problems/DynamicProgramming/CoinChange/DynamicCoinChangeSolver.cs) * [Levenshtein Distance](./Algorithms/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistance.cs) * [Traveling Salesman Problem (TSP)](./Algorithms/Problems/TravelingSalesman/TravelingSalesmanSolver.cs) * [Brute-force and Nearest Neighbor algorithms](./Algorithms/Problems/TravelingSalesman/TravelingSalesmanSolver.cs) * [Job Scheduling](./Algorithms/Problems/JobScheduling) * [Interval Scheduling (Greedy)](./Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs) * [Data Structures](./DataStructures) * [Bag](./DataStructures/Bag) * [Bit Array](./DataStructures/BitArray.cs) * [Deque (Double-Ended Queue)](./DataStructures/Deque/Deque.cs) * [Timeline](./DataStructures/Timeline.cs) * [Segment Trees](./DataStructures/SegmentTrees) * [Segment Tree](./DataStructures/SegmentTrees/SegmentTree.cs) * [Segment Tree Multiplication](./DataStructures/SegmentTrees/SegmentTreeApply.cs) * [Segment Tree Update](./DataStructures/SegmentTrees/SegmentTreeUpdate.cs) * [Binary Search Tree](./DataStructures/BinarySearchTree) * [Scapegoat Tree](./DataStructures/ScapegoatTree) * [Fenwick tree (or Binary Indexed Tree)](./DataStructures/Fenwick/BinaryIndexedTree.cs) * [AA Tree](./DataStructures/AATree) * [AVL Tree](./DataStructures/AVLTree) * [B-Tree](./DataStructures/BTree) * [Red-Black Tree](./DataStructures/RedBlackTree) * [Stack](./DataStructures/Stack) * [Array-based Stack](./DataStructures/Stack/ArrayBasedStack.cs) * [List-based Stack](./DataStructures/Stack/ListBasedStack.cs) * [Queue-based Stack](./DataStructures/Stack/QueueBasedStack.cs) * [Heap](./DataStructures/Heap) * [Min-Max Heap](./DataStructures/Heap/MinMaxHeap.cs) * [Binary Heap](./DataStructures/Heap/BinaryHeap.cs) * [Fibonacci Heap](./DataStructures/Heap/FibonacciHeap/FibonacciHeap.cs) * [Pairing Heap](./DataStructures/Heap/PairingHeap/PairingHeap.cs) * [Probabilistic](./DataStructures/Probabilistic) * [BloomFilter](./DataStructures/Probabilistic/BloomFilter.cs) * [Count-Min Sketch](./DataStructures/Probabilistic/CountMinSketch.cs) * [HyperLogLog](./DataStructures/Probabilistic/HyperLogLog.cs) * [Queue](./DataStructures/Queue) * [Array-based Queue](./DataStructures/Queue/ArrayBasedQueue.cs) * [List-based Queue](./DataStructures/Queue/ListBasedQueue.cs) * [Stack-based Queue](./DataStructures/Queue/StackBasedQueue.cs) * [Linked List](./DataStructures/LinkedList) * [Singly Linked List](./DataStructures/LinkedList/SinglyLinkedList/SinglyLinkedList.cs) * [Doubly Linked List](./DataStructures/LinkedList/DoublyLinkedList/DoublyLinkedList.cs) * [Skip List](./DataStructures/LinkedList/SkipList/SkipList.cs) * [Circular Linked List](./DataStructures/LinkedList/CircularLinkedList/CircularLinkedList.cs) * [Graph](./DataStructures/Graph) * [Directed Weighted Graph Via Adjacency Matrix](./DataStructures/Graph/DirectedWeightedGraph.cs) * [Disjoint Set](./DataStructures/DisjointSet) * [SortedList](./DataStructures/SortedList.cs) * [Inverted index](./DataStructures/InvertedIndex.cs) * [Unrolled linked list](./DataStructures/UnrolledList/UnrolledLinkedList.cs) * [Tries](./DataStructures/Tries/Trie.cs) * [HashTable](./DataStructures/Hashing/HashTable.cs) * [Cache](./DataStructures/Cache) * [Least Frequently Used (LFU) Cache](./DataStructures/Cache/LfuCache.cs) * [Least Recently Used (LRU) Cache](./DataStructures/Cache/LruCache.cs) ## Project Update: .NET 8 Migration As part of our continuous effort to stay up-to-date with the latest technologies, we have migrated our project to .NET 8. This upgrade enhances our project with the latest features and improvements from the .NET ecosystem. ### New Requirements * To build and run this project, **.NET 8 SDK** is now required. * Ensure your development tools are compatible with .NET 8. ### Building the Project * With .NET 8 SDK installed, you can build the project using the standard `dotnet build` command. * All existing build scripts have been updated to accommodate the .NET 8 SDK. ### Running Tests * Our comprehensive suite of unit tests ensures compatibility with .NET 8. * Run tests using the `dotnet test` command as usual. ## Contributing You can contribute with pleasure to this repository. Please orient on the directory structure and overall code style of this repository and refer to [our contributing guidelines](./CONTRIBUTING.md) for more details. If you want to ask a question or suggest something, please open an issue. ================================================ FILE: Utilities/Exceptions/ItemNotFoundException.cs ================================================ namespace Utilities.Exceptions; /// /// Signs that sequence doesn't contain any items that one was looking for. /// public class ItemNotFoundException : Exception { } ================================================ FILE: Utilities/Extensions/DictionaryExtensions.cs ================================================ namespace Utilities.Extensions; public static class DictionaryExtensions { /// /// Adds the specified key value tuples to the dictionary. /// /// The dictionary. /// The collection of key value tuples to add. /// The type of the keys in the dictionary. /// The type of the values in the dictionary. /// /// A key from the already exists in . /// public static void AddMany( this Dictionary keys, IEnumerable<(TKey Key, TValue Value)> enumerable) where TKey : notnull { foreach (var (key, value) in enumerable) { keys.Add(key, value); } } } ================================================ FILE: Utilities/Extensions/MatrixExtensions.cs ================================================ namespace Utilities.Extensions; public static class MatrixExtensions { /// /// Performs immutable dot product multiplication on source matrix to operand. /// /// Source left matrix. /// Operand right matrix. /// Dot product result. /// The width of a first operand should match the height of a second. public static double[,] Multiply(this double[,] source, double[,] operand) { if (source.GetLength(1) != operand.GetLength(0)) { throw new InvalidOperationException( "The width of a first operand should match the height of a second."); } var result = new double[source.GetLength(0), operand.GetLength(1)]; for (var i = 0; i < result.GetLength(0); i++) { for (var j = 0; j < result.GetLength(1); j++) { double elementProduct = 0; for (var k = 0; k < source.GetLength(1); k++) { elementProduct += source[i, k] * operand[k, j]; } result[i, j] = elementProduct; } } return result; } /// /// Makes a copy of a matrix. Changes to the copy should not affect the original. /// /// The matrix. /// A copy of the matrix. public static double[,] Copy(this double[,] matrix) { var result = new double[matrix.GetLength(0), matrix.GetLength(1)]; for (var i = 0; i < matrix.GetLength(0); i++) { for (var j = 0; j < matrix.GetLength(1); j++) { result[i, j] = matrix[i, j]; } } return result; } /// /// Transposes a matrix. /// /// The matrix. /// The transposed matrix. public static double[,] Transpose(this double[,] matrix) { var result = new double[matrix.GetLength(1), matrix.GetLength(0)]; for (var i = 0; i < matrix.GetLength(0); i++) { for (var j = 0; j < matrix.GetLength(1); j++) { result[j, i] = matrix[i, j]; } } return result; } /// /// Multiplies a matrix by a vector. /// /// The matrix. /// The vector. /// The product of the matrix and the vector, which is a vector. /// Dimensions of matrix and vector do not match. public static double[] MultiplyVector(this double[,] matrix, double[] vector) { var vectorReshaped = new double[vector.Length, 1]; for (var i = 0; i < vector.Length; i++) { vectorReshaped[i, 0] = vector[i]; } var resultMatrix = matrix.Multiply(vectorReshaped); var result = new double[resultMatrix.GetLength(0)]; for (var i = 0; i < result.Length; i++) { result[i] = resultMatrix[i, 0]; } return result; } /// /// Performs matrix subtraction. /// /// The LHS matrix. /// The RHS matrix. /// The difference of the two matrices. /// Dimensions of matrices do not match. public static double[,] Subtract(this double[,] lhs, double[,] rhs) { if (lhs.GetLength(0) != rhs.GetLength(0) || lhs.GetLength(1) != rhs.GetLength(1)) { throw new ArgumentException("Dimensions of matrices must be the same"); } var result = new double[lhs.GetLength(0), lhs.GetLength(1)]; for (var i = 0; i < lhs.GetLength(0); i++) { for (var j = 0; j < lhs.GetLength(1); j++) { result[i, j] = lhs[i, j] - rhs[i, j]; } } return result; } /// /// Performs an element by element comparison on both matrices. /// /// Source left matrix. /// Openrand right matrix. /// true: if all elements are the same; false otherwise. public static bool IsEqual(this double[,] source, double[,] operand) { if (source.Length != operand.Length || source.GetLength(0) != operand.GetLength(0) || source.GetLength(1) != operand.GetLength(1)) { return false; } for (var i = 0; i < source.GetLength(0); i++) { for (var j = 0; j < source.GetLength(0); j++) { if (Math.Abs(source[i, j] - operand[i, j]) >= 0.0001) { return false; } } } return true; } /// /// Performs a round operation on every element of the input matrix up to the neareast integer. /// /// Input matrix. /// Matrix with rounded elements. public static double[,] RoundToNextInt(this double[,] source) { var rows = source.GetLength(0); var cols = source.GetLength(1); var result = new double[rows, cols]; for (var i = 0; i < rows; i++) { for (var j = 0; j < cols; j++) { result[i, j] = Math.Round(source[i, j]); } } return result; } } ================================================ FILE: Utilities/Extensions/RandomExtensions.cs ================================================ namespace Utilities.Extensions; public static class RandomExtensions { /// /// Returns a random normalized vector of the specified size. /// /// The random number generator. /// The size of the vector to return. /// A random normalized vector. public static double[] NextVector(this Random rand, int size) { var vector = Enumerable.Range(0, size) .Select(_ => rand.NextDouble()).ToArray(); var norm = vector.Magnitude(); return vector.Select(x => x / norm).ToArray(); } } ================================================ FILE: Utilities/Extensions/VectorExtensions.cs ================================================ namespace Utilities.Extensions; public static class VectorExtensions { /// /// Makes a copy of a vector. Changes to the copy should not affect the original. /// /// The vector. /// The copy. public static double[] Copy(this double[] vector) { var result = new double[vector.Length]; for (var i = 0; i < vector.Length; i++) { result[i] = vector[i]; } return result; } /// /// Computes the outer product of two vectors. /// /// The LHS vector. /// The RHS vector. /// The outer product of the two vector. public static double[,] OuterProduct(this double[] lhs, double[] rhs) { var result = new double[lhs.Length, rhs.Length]; for (var i = 0; i < lhs.Length; i++) { for (var j = 0; j < rhs.Length; j++) { result[i, j] = lhs[i] * rhs[j]; } } return result; } /// /// Computes the dot product of two vectors. /// /// The LHS vector. /// The RHS vector. /// The dot product of the two vector. /// Dimensions of vectors do not match. public static double Dot(this double[] lhs, double[] rhs) { if (lhs.Length != rhs.Length) { throw new ArgumentException("Dot product arguments must have same dimension"); } double result = 0; for (var i = 0; i < lhs.Length; i++) { result += lhs[i] * rhs[i]; } return result; } /// /// Computes the magnitude of a vector. /// /// The vector. /// The magnitude. public static double Magnitude(this double[] vector) { return Math.Sqrt(Dot(vector, vector)); } /// /// Returns the scaled vector. /// /// The vector. /// Scale factor. /// The unit vector. public static double[] Scale(this double[] vector, double factor) { var result = new double[vector.Length]; for (var i = 0; i < vector.Length; i++) { result[i] = vector[i] * factor; } return result; } /// /// Transpose 1d row vector to column vector. /// /// Input 1d vector. /// Column vector. public static double[,] ToColumnVector(this double[] source) { var columnVector = new double[source.Length, 1]; for (var i = 0; i < source.Length; i++) { columnVector[i, 0] = source[i]; } return columnVector; } /// /// Transpose column vector to 1d row vector. /// /// Input column vector. /// Row vector. /// The column vector should have only 1 element in width. public static double[] ToRowVector(this double[,] source) { if (source.GetLength(1) != 1) { throw new InvalidOperationException("The column vector should have only 1 element in width."); } var rowVector = new double[source.Length]; for (var i = 0; i < rowVector.Length; i++) { rowVector[i] = source[i, 0]; } return rowVector; } /// /// Generates a diagonal matrix from an specified vector. /// /// The input vector. /// A Diagonal matrix. public static double[,] ToDiagonalMatrix(this double[] vector) { var len = vector.Length; var result = new double[len, len]; for (var i = 0; i < len; i++) { result[i, i] = vector[i]; } return result; } } ================================================ FILE: Utilities/GlobalUsings.cs ================================================ // ----------------------------------------------------------------------------- // Global using directives for the C-Sharp solution. // These namespaces are imported globally so they don’t need to be repeatedly declared // in individual files, improving readability and reducing boilerplate. // // Guidelines: // - Keep only the most commonly used namespaces here. // - Add project-specific namespaces (e.g., Utilities.Extensions) only if they are // required across the majority of files in the project. // - Avoid placing rarely used namespaces here to maintain clarity. // ----------------------------------------------------------------------------- global using System; // Core base classes and fundamental types global using System.Collections.Generic; // Generic collection types (List, Dictionary, etc.) global using System.Linq; // LINQ query operators for collections ================================================ FILE: Utilities/Utilities.csproj ================================================ net8.0 ..\stylecop.ruleset true enable ./bin/Utilities.xml all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: Utilities.Tests/Extensions/DictionaryExtensionsTests.cs ================================================ namespace Utilities.Tests.Extensions; public class DictionaryExtensionsTests { [Test] public void AddMany_ShouldThrowArgumentException_WhenKeyAlreadyExists() { var dictionary = new Dictionary { ["one"] = 1 }; var enumerable = new[] { ("one", 1), ("two", 2) }; var action = () => dictionary.AddMany(enumerable); action.Should().Throw(); } [Test] public void AddMany_ShouldAddAllKeyValuePairs() { var dictionary = new Dictionary { ["one"] = 1 }; var enumerable = new[] { ("two", 2), ("three", 3) }; dictionary.AddMany(enumerable); dictionary.Should().HaveCount(3); dictionary.Should().ContainKey("one").WhoseValue.Should().Be(1); dictionary.Should().ContainKey("two").WhoseValue.Should().Be(2); dictionary.Should().ContainKey("three").WhoseValue.Should().Be(3); } [Test] public void AddMany_ShouldNotChangeDictionary_WhenEnumerableIsEmpty() { var dictionary = new Dictionary { ["one"] = 1 }; var enumerable = Array.Empty<(string, int)>(); dictionary.AddMany(enumerable); dictionary.Should().HaveCount(1); dictionary.Should().ContainKey("one").WhoseValue.Should().Be(1); } [Test] public void AddMany_ShouldThrowArgumentNullException_WhenDictionaryIsNull() { Dictionary dictionary = null!; var enumerable = new[] { ("one", 1) }; var action = () => dictionary.AddMany(enumerable); action.Should().Throw(); } [Test] public void AddMany_ShouldThrowArgumentNullException_WhenEnumerableIsNull() { var dictionary = new Dictionary { ["one"] = 1 }; IEnumerable<(string, int)> enumerable = null!; var action = () => dictionary.AddMany(enumerable); action.Should().Throw(); } [Test] public void AddMany_ShouldAllowNullValues_WhenValueTypeIsNullable() { var dictionary = new Dictionary { ["one"] = 1 }; var enumerable = new[] { ("two", (int?)null) }; dictionary.AddMany(enumerable); dictionary.Should().HaveCount(2); dictionary.Should().ContainKey("two").WhoseValue.Should().Be(null); } [Test] public void AddMany_ShouldAllowNullValue_WhenValueIsNullable() { var dictionary = new Dictionary(); // Key type is int, value type is nullable string var enumerable = new[] { (1, null), // null value (2, "banana") }; dictionary.AddMany(enumerable); dictionary.Should().ContainKey(1).WhoseValue.Should().BeNull(); dictionary.Should().ContainKey(2).WhoseValue.Should().Be("banana"); } [Test] public void AddMany_ShouldThrowArgumentException_WhenAddingDuplicateKey() { var dictionary = new Dictionary(); // Key type is int, value type is nullable string var enumerable = new[] { (1, "Things"), // First entry (2, "Stuff"), (1, "That Thing") // Duplicate key (should throw exception) }; var action = () => dictionary.AddMany(enumerable); action.Should().Throw(); // Adding a duplicate key should throw ArgumentException } [Test] public void AddMany_ShouldAddManyKeyValuePairs_WhenAddingLargeEnumerable() { var dictionary = new Dictionary(); var enumerable = new List<(int, string)>(); // Create a large enumerable for (int i = 0; i < 10000; i++) { enumerable.Add((i, "Value" + i)); } dictionary.AddMany(enumerable); dictionary.Should().HaveCount(10000); dictionary[9999].Should().Be("Value9999"); } } ================================================ FILE: Utilities.Tests/Extensions/MatrixExtensionsTests.cs ================================================ namespace Utilities.Tests.Extensions; public class MatrixExtensionsTests { private static readonly object[] MatrixMultiplyTestCases = [ new object[] { new double[,] { { 2, 2, -1 }, { 0, -2, -1 }, { 0, 0, 5 } }, new double[,] { { 2 }, { 2 }, { 3 } }, new double[,] { { 5 }, { -7 }, { 15 } }, }, new object[] { new double[,] { { 5, 8, -4 }, { 6, 9, -5 }, { 4, 7, -3 } }, new double[,] { { 3, 2, 5 }, { 4, -1, 3 }, { 9, 6, 5 } }, new double[,] { { 11, -22, 29 }, { 9, -27, 32 }, { 13, -17, 26 } }, }, ]; private static readonly object[] MatrixTransposeTestCases = [ new object[] { new double[,] { { 2, 2, 3 } }, new double[,] { { 2 }, { 2 }, { 3 } }, }, new object[] { new double[,] { { 5, 8 }, { 6, 9 } }, new double[,] { { 5, 6 }, { 8, 9 } }, }, ]; private static readonly object[] MatrixSubtractTestCases = [ new object[] { new double[,] { { 0, 0 }, { 0, 0 } }, new double[,] { { 1, 1 }, { 1, 1 } }, new double[,] { { -1, -1 }, { -1, -1 } }, }, new object[] { new double[,] { { 1, 2 }, { 2, 3 }, { 3, 4 } }, new double[,] { { 1, 1 }, { 1, 1 }, { 1, 1 } }, new double[,] { { 0, 1 }, { 1, 2 }, { 2, 3 } }, }, new object[] { new double[,] { { -1, -2, 0 }, { 2, -3, 2 }, { 3, 4, 1 } }, new double[,] { { 2, 5, 12 }, { 0, 5, 1 }, { 1, 1, 4 } }, new double[,] { { -3, -7, -12 }, { 2, -8, 1 }, { 2, 3, -3 } }, }, ]; [Test] public void Multiply_ShouldThrowInvalidOperationException_WhenOperandsAreNotCompatible() { // Arrange var source = new double[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } }; var operand = new double[,] { { 1 }, { 1 } }; // Act Action action = () => source.Multiply(operand); // Assert action.Should().Throw() .WithMessage("The width of a first operand should match the height of a second."); } [TestCaseSource(nameof(MatrixMultiplyTestCases))] public void Multiply_ShouldCalculateDotProductMultiplicationResult( double[,] source, double[,] operand, double[,] result) => source.Multiply(operand).Should().BeEquivalentTo(result); [Test] public void Copy_ShouldReturnImmutableCopyOfMatrix() { // Arrange var sutMatrix = new double[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } }; // Act var actualMatrix = sutMatrix.Copy(); // Assert actualMatrix.Should().NotBeSameAs(sutMatrix); actualMatrix.Should().BeEquivalentTo(sutMatrix); } [TestCaseSource(nameof(MatrixTransposeTestCases))] public void Transpose_ShouldReturnTransposedMatrix( double[,] source, double[,] target) => source.Transpose().Should().BeEquivalentTo(target); [Test] public void MultiplyVector_ShouldCalculateDotProductMultiplicationResult() { // Arrange var source = new double[,] { { 2, 2, -1 }, { 0, -2, -1 }, { 0, 0, 5 } }; var operand = new double[] { 2, 2, 3 }; var result = new double[] { 5, -7, 15 }; // Act var actualMatrix = source.MultiplyVector(operand); // Assert actualMatrix.Should().BeEquivalentTo(result); } [Test] public void Subtract_ShouldThrowArgumentException_WhenOperandsAreNotCompatible() { // Arrange var source = new double[,] { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } }; var operand = new double[,] { { 1 }, { 1 } }; // Act Action action = () => source.Subtract(operand); // Assert action.Should().Throw() .WithMessage("Dimensions of matrices must be the same"); } [Test] public static void EqualMatricesShouldReturnTrue() { // Arrange var a = new double[,] { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } }; var b = new double[,] { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } }; // Act var result = a.IsEqual(b); // Assert Assert.That(result, Is.True); } [Test] public static void NonEqualMatricesShouldReturnFalse() { // Arrange var a = new double[,] { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } }; var b = new double[,] { { 1, 2, 3 }, { 1, 2, 6 }, { 1, 2, 3 } }; // Act var result = a.IsEqual(b); // Assert Assert.That(result, Is.False); } [Test] public static void DifferentSizeMatricesShouldReturnFalse() { // Arrange var a = new double[,] { { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 } }; var b = new double[,] { { 1, 2, 3 }, { 1, 2, 3 } }; // Act var result = a.IsEqual(b); // Assert Assert.That(result, Is.False); } [TestCaseSource(nameof(MatrixSubtractTestCases))] public void Subtract_ShouldCalculateSubtractionResult( double[,] source, double[,] operand, double[,] result) => source.Subtract(operand).Should().BeEquivalentTo(result); [Test] public void RoundToNextInt_ShouldReturnRoundedMatrix() { var source = new[,] { { -1.9, 1.9 }, { -1.5, 1.5 }, { -1.1, 1.1 }, { -0.9, 0.9 }, { -0.5, 0.5 }, { -0.1, 0.1 }, }; var result = new double[,] { { -2, 2 }, { -2, 2 }, { -1, 1 }, { -1, 1 }, { 0, 0 }, { 0, 0 }, }; var actualResult = source.RoundToNextInt(); actualResult.Should().BeEquivalentTo(result); } } ================================================ FILE: Utilities.Tests/Extensions/RandomExtensionsTests.cs ================================================ namespace Utilities.Tests.Extensions; public class RandomExtensionsTests { [Test] public void NextVector_ShouldReturnNormalizedVector() { var random = new Random(0); var result = random.NextVector(10); result.Length.Should().Be(10); result.Magnitude().Should().BeApproximately(1.0, 1e-6); } } ================================================ FILE: Utilities.Tests/Extensions/VectorExtensionsTests.cs ================================================ namespace Utilities.Tests.Extensions; public class VectorExtensionsTests { [Test] public void Copy_ShouldReturnCopyOfVector() { var vector = new double[] { 0, 1, 2, 3 }; var vectorCopy = vector.Copy(); vectorCopy.Should().BeEquivalentTo(vector); vectorCopy.Should().NotBeSameAs(vector); } [Test] public void OuterProduct_ShouldCalculateOuterProduct() { var lhs = new double[] { -2, -1, 0, 1, 2 }; var rhs = new double[] { 1, 2, 3 }; var result = new double[,] { { -2, -4, -6 }, { -1, -2, -3 }, { 0, 0, 0 }, { 1, 2, 3 }, { 2, 4, 6 }, }; var actualResult = lhs.OuterProduct(rhs); actualResult.Should().BeEquivalentTo(result); } [Test] public void Dot_ShouldThrowArgumentException_WhenDimensionsDoNotMatch() { var lhs = new double[] { 1, 2, 3 }; var rhs = new double[] { 1, 2, 3, 4 }; var func = () => lhs.Dot(rhs); func.Should().Throw() .WithMessage("Dot product arguments must have same dimension"); } [Test] public void Dot_ShouldCalculateDotProduct() { var lhs = new double[] { 1, 2, 3 }; var rhs = new double[] { 4, 5, 6 }; var actualResult = lhs.Dot(rhs); actualResult.Should().Be(32); } [Test] public void Magnitude_ShouldCalculateMagnitude() { var vector = new double[] { -3, 4 }; var actualResult = vector.Magnitude(); actualResult.Should().BeApproximately(5.0, 0.0001); } [Test] public void Scale_ShouldCalculateScale() { var vector = new double[] { -1, 0, 1 }; var factor = 2; var result = new double[] { -2, 0, 2 }; var actualResult = vector.Scale(factor); actualResult.Should().BeEquivalentTo(result); } [Test] public void ToColumnVector_ShouldReturnColumnVector() { var vector = new double[] { 1, 2, 3, 4 }; var result = new double[,] { { 1 }, { 2 }, { 3 }, { 4 } }; var actualResult = vector.ToColumnVector(); actualResult.Should().BeEquivalentTo(result); } [Test] public void ToRowVector_ShouldThrowInvalidOperationException_WhenSourceIsNotAColumnVector() { var source = new double[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } }; var func = () => source.ToRowVector(); func.Should().Throw() .WithMessage("The column vector should have only 1 element in width."); } [Test] public void ToRowVector_ShouldReturnRowVector() { var source = new double[,] { { 1 }, { 2 }, { 3 }, { 4 } }; var result = new double[] { 1, 2, 3, 4 }; var actualResult = source.ToRowVector(); actualResult.Should().BeEquivalentTo(result); } [Test] public void ToDiagonalMatrix_ShouldReturnDiagonalMatrix() { var source = new double[] { 1, 2, 3, 4 }; var result = new double[,] { { 1, 0, 0, 0 }, { 0, 2, 0, 0 }, { 0, 0, 3, 0 }, { 0, 0, 0, 4 }, }; var actualResult = source.ToDiagonalMatrix(); actualResult.Should().BeEquivalentTo(result); } } ================================================ FILE: Utilities.Tests/GlobalUsings.cs ================================================ // ----------------------------------------------------------------------------- // Global using directives for the C-Sharp solution. // These namespaces are imported globally so they don’t need to be repeatedly declared // in individual files, improving readability and reducing boilerplate. // // Guidelines: // - Keep only the most commonly used namespaces here. // - Add project-specific namespaces (e.g., Utilities.Extensions) only if they are // required across the majority of files in the project. // - Avoid placing rarely used namespaces here to maintain clarity. // ----------------------------------------------------------------------------- global using System; // Core base classes and fundamental types global using System.Collections.Generic; // Generic collection types (List, Dictionary, etc.) global using FluentAssertions; // Assertion library for more readable and expressive unit tests global using NUnit.Framework; // Testing framework providing attributes and assertions for test cases global using Utilities.Extensions; // Common project-specific extension methods reused across multiple files ================================================ FILE: Utilities.Tests/Utilities.Tests.csproj ================================================ net8.0 false ..\stylecop.ruleset true enable runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: stylecop.json ================================================ { "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "indentation": { "useTabs": false }, "layoutRules": { "newlineAtEndOfFile": "require" }, "orderingRules": { "blankLinesBetweenUsingGroups": "omit", "usingDirectivesPlacement": "outsideNamespace" } } } ================================================ FILE: stylecop.ruleset ================================================