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