From bc82e75a312d6d3e90443374b9e3bdd9f80fca0c Mon Sep 17 00:00:00 2001 From: mavantgarderc Date: Sun, 7 Dec 2025 13:32:12 +0330 Subject: [PATCH 1/5] Algorithms.Tests(String) --- .../String/StringUtils.AreAnagrams.cs | 53 ++++++++++ .../String/StringUtils.FirstUniqueChar.cs | 42 ++++++++ .../String/StringUtils.IndexOfSubstring.cs | 51 +++++++++ .../String/StringUtils.IsPalindrome.cs | 44 ++++++++ .../String/StringUtils.IsRotation.cs | 31 ++++++ .../String/StringUtils.KmpSearch.cs | 100 ++++++++++++++++++ .../String/StringUtils.RabinKarpSearch.cs | 99 +++++++++++++++++ .../Algorithms/String/StringUtils.Reverse.cs | 28 +++++ .../String/StringUtils.ZAlgorithm.cs | 67 ++++++++++++ src/Csdsa/Algorithms/String/StringUtils.cs | 49 +++++++++ 10 files changed, 564 insertions(+) create mode 100644 src/Csdsa/Algorithms/String/StringUtils.AreAnagrams.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.FirstUniqueChar.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.IndexOfSubstring.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.IsPalindrome.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.IsRotation.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.KmpSearch.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.RabinKarpSearch.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.Reverse.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.ZAlgorithm.cs create mode 100644 src/Csdsa/Algorithms/String/StringUtils.cs diff --git a/src/Csdsa/Algorithms/String/StringUtils.AreAnagrams.cs b/src/Csdsa/Algorithms/String/StringUtils.AreAnagrams.cs new file mode 100644 index 0000000..f3b5783 --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.AreAnagrams.cs @@ -0,0 +1,53 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Determines whether two strings are anagrams of each other. + /// The comparison is case-sensitive and includes all characters. + /// + /// The first string. + /// The second string. + /// + /// if and + /// are anagrams; otherwise, . + /// + /// + /// Thrown when or is . + /// + public static bool AreAnagrams(string first, string second) + { + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + if (first.Length != second.Length) + { + return false; + } + + Dictionary counts = new Dictionary(); + + foreach (char ch in first) + { + counts[ch] = counts.TryGetValue(ch, out int value) ? value + 1 : 1; + } + + foreach (char ch in second) + { + if (!counts.TryGetValue(ch, out int value)) + { + return false; + } + + value--; + counts[ch] = value; + + if (value < 0) + { + return false; + } + } + + return true; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.FirstUniqueChar.cs b/src/Csdsa/Algorithms/String/StringUtils.FirstUniqueChar.cs new file mode 100644 index 0000000..5a4c479 --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.FirstUniqueChar.cs @@ -0,0 +1,42 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Finds the first non-repeating character in the specified string. + /// + /// The string to inspect. + /// + /// The first character that appears exactly once in , + /// or if no such character exists or the string is empty. + /// + /// + /// Thrown when is . + /// + public static char? FirstUniqueChar(string input) + { + ArgumentNullException.ThrowIfNull(input); + + if (input.Length == 0) + { + return null; + } + + Dictionary frequency = new Dictionary(); + + foreach (char ch in input) + { + frequency[ch] = frequency.TryGetValue(ch, out int value) ? value + 1 : 1; + } + + foreach (char ch in input) + { + if (frequency[ch] == 1) + { + return ch; + } + } + + return null; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.IndexOfSubstring.cs b/src/Csdsa/Algorithms/String/StringUtils.IndexOfSubstring.cs new file mode 100644 index 0000000..9a3099a --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.IndexOfSubstring.cs @@ -0,0 +1,51 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Searches for the first occurrence of within + /// using a naive substring scan. + /// + /// The string to search. + /// The substring to locate. + /// + /// The zero-based index of the first occurrence of in + /// , or -1 if the substring is not found or if either + /// string is empty. + /// + /// + /// Thrown when or is . + /// + public static int IndexOfSubstring(string source, string target) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(target); + + if (source.Length == 0 || target.Length == 0) + { + return -1; + } + + if (target.Length > source.Length) + { + return -1; + } + + for (int i = 0; i <= source.Length - target.Length; i++) + { + int j = 0; + + while (j < target.Length && source[i + j] == target[j]) + { + j++; + } + + if (j == target.Length) + { + return i; + } + } + + return -1; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.IsPalindrome.cs b/src/Csdsa/Algorithms/String/StringUtils.IsPalindrome.cs new file mode 100644 index 0000000..62d9390 --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.IsPalindrome.cs @@ -0,0 +1,44 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Determines whether the specified string is a palindrome, ignoring + /// case and non-alphanumeric characters. + /// + /// The string to inspect. + /// + /// if is a palindrome + /// under the given normalization; otherwise, . + /// + /// + /// Thrown when is . + /// + public static bool IsPalindrome(string input) + { + ArgumentNullException.ThrowIfNull(input); + + if (input.Length == 0) + { + return true; + } + + string normalized = new string( + input + .Where(char.IsLetterOrDigit) + .Select(char.ToLowerInvariant) + .ToArray()); + + int mid = normalized.Length / 2; + + for (int i = 0; i < mid; i++) + { + if (normalized[i] != normalized[normalized.Length - 1 - i]) + { + return false; + } + } + + return true; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.IsRotation.cs b/src/Csdsa/Algorithms/String/StringUtils.IsRotation.cs new file mode 100644 index 0000000..ad20a7f --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.IsRotation.cs @@ -0,0 +1,31 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Determines whether is a rotation of . + /// For example, "erbottlewat" is a rotation of "waterbottle". + /// + /// The original string. + /// The candidate rotated string. + /// + /// if is a rotation of + /// ; otherwise, . + /// + /// + /// Thrown when or is . + /// + public static bool IsRotation(string original, string rotated) + { + ArgumentNullException.ThrowIfNull(original); + ArgumentNullException.ThrowIfNull(rotated); + + if (original.Length != rotated.Length) + { + return false; + } + + string doubled = original + original; + return doubled.Contains(rotated, StringComparison.Ordinal); + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.KmpSearch.cs b/src/Csdsa/Algorithms/String/StringUtils.KmpSearch.cs new file mode 100644 index 0000000..ca6b337 --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.KmpSearch.cs @@ -0,0 +1,100 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Finds all occurrences of in + /// using the Knuth–Morris–Pratt (KMP) algorithm. + /// + /// The pattern to search for. + /// The text to search within. + /// + /// A read-only list of zero-based indices where occurs in + /// . Returns an empty list if or + /// is empty. + /// + /// + /// Thrown when or is . + /// + public static IReadOnlyList KmpSearch(string pattern, string text) + { + ArgumentNullException.ThrowIfNull(pattern); + ArgumentNullException.ThrowIfNull(text); + + if (pattern.Length == 0 || text.Length == 0) + { + return Array.Empty(); + } + + int m = pattern.Length; + int n = text.Length; + List result = new List(); + + int[] lps = ComputeLpsArray(pattern); + int i = 0; // index for text + int j = 0; // index for pattern + + while (i < n) + { + if (pattern[j] == text[i]) + { + j++; + i++; + } + + if (j == m) + { + result.Add(i - j); + j = lps[j - 1]; + } + else if (i < n && pattern[j] != text[i]) + { + if (j != 0) + { + j = lps[j - 1]; + } + else + { + i++; + } + } + } + + return result; + } + + private static int[] ComputeLpsArray(string pattern) + { + int m = pattern.Length; + int[] lps = new int[m]; + + int length = 0; + int i = 1; + + lps[0] = 0; + + while (i < m) + { + if (pattern[i] == pattern[length]) + { + length++; + lps[i] = length; + i++; + } + else + { + if (length != 0) + { + length = lps[length - 1]; + } + else + { + lps[i] = 0; + i++; + } + } + } + + return lps; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.RabinKarpSearch.cs b/src/Csdsa/Algorithms/String/StringUtils.RabinKarpSearch.cs new file mode 100644 index 0000000..c7c9d1d --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.RabinKarpSearch.cs @@ -0,0 +1,99 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Finds all occurrences of in + /// using the Rabin–Karp rolling-hash algorithm. + /// + /// The text to search. + /// The pattern to find. + /// + /// A prime modulus used in the rolling hash computation. Defaults to 101. + /// + /// + /// A read-only list of zero-based indices where occurs in + /// . Returns an empty list if or + /// is empty. + /// + /// + /// Thrown when or is . + /// + public static IReadOnlyList RabinKarpSearch( + string text, + string pattern, + int prime = 101) + { + ArgumentNullException.ThrowIfNull(text); + ArgumentNullException.ThrowIfNull(pattern); + + List result = new List(); + + if (text.Length == 0 || pattern.Length == 0) + { + return result; + } + + int m = pattern.Length; + int n = text.Length; + + if (m > n) + { + return result; + } + + long patternHash = 0; + long textHash = 0; + long h = 1; + int d = 256; + + for (int i = 0; i < m - 1; i++) + { + h = (h * d) % prime; + } + + for (int i = 0; i < m; i++) + { + patternHash = ((d * patternHash) + pattern[i]) % prime; + textHash = ((d * textHash) + text[i]) % prime; + } + + for (int i = 0; i <= n - m; i++) + { + if (patternHash == textHash) + { + bool match = true; + + for (int j = 0; j < m; j++) + { + if (text[i + j] != pattern[j]) + { + match = false; + break; + } + } + + if (match) + { + result.Add(i); + } + } + + if (i < n - m) + { + long removed = text[i] * h; + long temp = textHash - removed; + long shifted = d * temp; + long added = shifted + text[i + m]; + textHash = added % prime; + + if (textHash < 0) + { + textHash += prime; + } + } + } + + return result; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.Reverse.cs b/src/Csdsa/Algorithms/String/StringUtils.Reverse.cs new file mode 100644 index 0000000..2402fb7 --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.Reverse.cs @@ -0,0 +1,28 @@ +using System.Text; + +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Reverses the specified string. + /// + /// The string to reverse. + /// A new string whose characters are in reverse order. + /// + /// Thrown when is . + /// + public static string Reverse(string input) + { + ArgumentNullException.ThrowIfNull(input); + + StringBuilder builder = new StringBuilder(input.Length); + + for (int i = input.Length - 1; i >= 0; i--) + { + builder.Append(input[i]); + } + + return builder.ToString(); + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.ZAlgorithm.cs b/src/Csdsa/Algorithms/String/StringUtils.ZAlgorithm.cs new file mode 100644 index 0000000..b091d92 --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.ZAlgorithm.cs @@ -0,0 +1,67 @@ +namespace Csdsa.Algorithms.Strings; + +public static partial class StringUtils +{ + /// + /// Finds all occurrences of in + /// using the Z-Algorithm in linear time. + /// + /// The text to search. + /// The pattern to find. + /// + /// A read-only list of zero-based indices where occurs in + /// . Returns an empty list if or + /// is empty. + /// + /// + /// Thrown when or is . + /// + public static IReadOnlyList ZAlgorithm(string text, string pattern) + { + ArgumentNullException.ThrowIfNull(text); + ArgumentNullException.ThrowIfNull(pattern); + + if (text.Length == 0 || pattern.Length == 0) + { + return Array.Empty(); + } + + string combined = pattern + "$" + text; + int n = combined.Length; + int[] z = new int[n]; + + int left = 0; + int right = 0; + + for (int i = 1; i < n; i++) + { + if (i <= right) + { + z[i] = Math.Min(right - i + 1, z[i - left]); + } + + while (i + z[i] < n && combined[z[i]] == combined[i + z[i]]) + { + z[i]++; + } + + if (i + z[i] - 1 > right) + { + left = i; + right = i + z[i] - 1; + } + } + + List result = new List(); + + for (int i = pattern.Length + 1; i < n; i++) + { + if (z[i] == pattern.Length) + { + result.Add(i - pattern.Length - 1); + } + } + + return result; + } +} diff --git a/src/Csdsa/Algorithms/String/StringUtils.cs b/src/Csdsa/Algorithms/String/StringUtils.cs new file mode 100644 index 0000000..cc1c3ff --- /dev/null +++ b/src/Csdsa/Algorithms/String/StringUtils.cs @@ -0,0 +1,49 @@ +namespace Csdsa.Algorithms.Strings; + +/// +/// Provides classic string algorithms and utilities, including reversal, palindromes, +/// anagram checks, substring search, and linear-time pattern matching algorithms. +/// +/// Concepts: +/// +/// +/// +/// String reversal and palindrome detection. +/// +/// +/// Anagram verification via character frequency counting. +/// +/// +/// Naive and rolling-hash-based substring search. +/// +/// +/// Z-Algorithm and Rabin–Karp for linear-time pattern matching. +/// +/// +/// KMP (Knuth–Morris–Pratt) prefix-function-based pattern search. +/// +/// +/// +/// Key practices: +/// +/// +/// +/// Case and whitespace normalization for robust comparisons. +/// +/// +/// Dictionary-based character counting for anagram and uniqueness checks. +/// +/// +/// Manual string scanning with explicit time-complexity considerations. +/// +/// +/// Use of for efficient concatenation. +/// +/// +/// Rolling-hash and prefix-function construction for linear-time search. +/// +/// +/// +public static partial class StringUtils +{ +} From 97e31a5003b67cc0949b870a220cd0ad87d7ce11 Mon Sep 17 00:00:00 2001 From: mavantgarderc Date: Sun, 7 Dec 2025 13:32:24 +0330 Subject: [PATCH 2/5] Algorithms.Tests(String) --- .../String/StringUtils.AreAnagrams.Tests.cs | 19 ++++++++++++++++ .../StringUtils.FirstUniqueChar.Tests.cs | 19 ++++++++++++++++ .../StringUtils.IndexOfSubstring.Tests.cs | 18 +++++++++++++++ .../String/StringUtils.IsPalindrome.Tests.cs | 18 +++++++++++++++ .../String/StringUtils.IsRotation.Tests.cs | 22 +++++++++++++++++++ .../String/StringUtils.KmpSearch.Tests.cs | 21 ++++++++++++++++++ .../StringUtils.RabinKarpSearch.Tests.cs | 18 +++++++++++++++ .../String/StringUtils.Reverse.Tests.cs | 19 ++++++++++++++++ .../String/StringUtils.ZAlgorithms.Tests.cs | 18 +++++++++++++++ 9 files changed, 172 insertions(+) create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.AreAnagrams.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.FirstUniqueChar.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.IndexOfSubstring.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.IsPalindrome.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.IsRotation.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.KmpSearch.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.RabinKarpSearch.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.Reverse.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/String/StringUtils.ZAlgorithms.Tests.cs diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.AreAnagrams.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.AreAnagrams.Tests.cs new file mode 100644 index 0000000..8ae5689 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.AreAnagrams.Tests.cs @@ -0,0 +1,19 @@ +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("listen", "silent", true)] + [InlineData("hello", "world", false)] + [InlineData("triangle", "integral", true)] + [InlineData("rat", "car", false)] + public void AreAnagrams_ShouldDetectCorrectly(string first, string second, bool expected) + { + bool result = StringUtils.AreAnagrams(first, second); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.FirstUniqueChar.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.FirstUniqueChar.Tests.cs new file mode 100644 index 0000000..95dd0ea --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.FirstUniqueChar.Tests.cs @@ -0,0 +1,19 @@ +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("swiss", 'w')] + [InlineData("redivider", 'v')] + [InlineData("aabbcc", null)] + [InlineData("", null)] + public void FirstUniqueChar_ShouldReturnCorrectChar(string input, char? expected) + { + char? result = StringUtils.FirstUniqueChar(input); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.IndexOfSubstring.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.IndexOfSubstring.Tests.cs new file mode 100644 index 0000000..e64a2f4 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.IndexOfSubstring.Tests.cs @@ -0,0 +1,18 @@ +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("hello world", "world", 6)] + [InlineData("abcde", "f", -1)] + [InlineData("banana", "na", 2)] + public void IndexOfSubstring_ShouldReturnCorrectIndex(string source, string target, int expected) + { + int result = StringUtils.IndexOfSubstring(source, target); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.IsPalindrome.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.IsPalindrome.Tests.cs new file mode 100644 index 0000000..2e957a7 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.IsPalindrome.Tests.cs @@ -0,0 +1,18 @@ +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("RaceCar", true)] + [InlineData("A man a plan a canal Panama", true)] + [InlineData("NotAPalindrome", false)] + public void IsPalindrome_ShouldValidateCorrectly(string input, bool expected) + { + bool result = StringUtils.IsPalindrome(input); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.IsRotation.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.IsRotation.Tests.cs new file mode 100644 index 0000000..0dd0c45 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.IsRotation.Tests.cs @@ -0,0 +1,22 @@ +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("waterbottle", "erbottlewat", true)] + [InlineData("rotation", "tationro", true)] + [InlineData("hello", "elloh", true)] + [InlineData("hello", "olelh", false)] + [InlineData("abc", "abcd", false)] + [InlineData("", "abc", false)] + [InlineData("abc", "", false)] + public void IsRotation_ShouldValidateRotation(string original, string rotated, bool expected) + { + bool result = StringUtils.IsRotation(original, rotated); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.KmpSearch.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.KmpSearch.Tests.cs new file mode 100644 index 0000000..64d9f90 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.KmpSearch.Tests.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("ABABDABACDABABCABAB", "ABABCABAB", new[] { 10 })] + [InlineData("aaaaa", "aa", new[] { 0, 1, 2, 3 })] + [InlineData("abcabcabc", "abc", new[] { 0, 3, 6 })] + [InlineData("abcdef", "gh", new int[0])] + public void KmpSearch_ShouldReturnExpectedIndices(string text, string pattern, int[] expected) + { + IReadOnlyList result = StringUtils.KmpSearch(pattern, text); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.RabinKarpSearch.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.RabinKarpSearch.Tests.cs new file mode 100644 index 0000000..4c5408e --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.RabinKarpSearch.Tests.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Fact] + public void RabinKarpSearch_ShouldReturnCorrectMatchPositions() + { + IReadOnlyList result = StringUtils.RabinKarpSearch("xyzxyzxyz", "xyz"); + + Assert.Equal(new[] { 0, 3, 6 }, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.Reverse.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.Reverse.Tests.cs new file mode 100644 index 0000000..83ea0a7 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.Reverse.Tests.cs @@ -0,0 +1,19 @@ +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Theory] + [InlineData("abc", "cba")] + [InlineData("racecar", "racecar")] + [InlineData("", "")] + [InlineData("a", "a")] + public void Reverse_ShouldReturnReversedString(string input, string expected) + { + string result = StringUtils.Reverse(input); + Assert.Equal(expected, result); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/String/StringUtils.ZAlgorithms.Tests.cs b/tests/Csdsa.Tests/Algorithms/String/StringUtils.ZAlgorithms.Tests.cs new file mode 100644 index 0000000..84a5962 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/String/StringUtils.ZAlgorithms.Tests.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +using Csdsa.Algorithms.Strings; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.String; + +public sealed partial class StringUtilsTests +{ + [Fact] + public void ZAlgorithm_ShouldFindAllOccurrences() + { + IReadOnlyList result = StringUtils.ZAlgorithm("ababcababcababc", "abc"); + + Assert.Equal(new[] { 2, 7, 12 }, result); + } +} From f501b11fa52e2cbea4604c44313ca2614753ea16 Mon Sep 17 00:00:00 2001 From: mavantgarderc Date: Sun, 7 Dec 2025 14:05:04 +0330 Subject: [PATCH 3/5] Algorithms(Tuple) --- .../Tuple/TupleUtils.ApplySelect.cs | 67 +++++++++++++++++++ .../Tuple/TupleUtils.ContainsIndexOf.cs | 48 +++++++++++++ .../Tuple/TupleUtils.Conversions.cs | 35 ++++++++++ .../Algorithms/Tuple/TupleUtils.Create.cs | 30 +++++++++ .../Algorithms/Tuple/TupleUtils.Duplicate.cs | 21 ++++++ .../Algorithms/Tuple/TupleUtils.Flatten.cs | 38 +++++++++++ src/Csdsa/Algorithms/Tuple/TupleUtils.Map.cs | 65 ++++++++++++++++++ .../Algorithms/Tuple/TupleUtils.Rotate.cs | 34 ++++++++++ .../Tuple/TupleUtils.StructuralEquals.cs | 46 +++++++++++++ src/Csdsa/Algorithms/Tuple/TupleUtils.Swap.cs | 34 ++++++++++ .../Tuple/TupleUtils.ToDictionary.cs | 44 ++++++++++++ src/Csdsa/Algorithms/Tuple/TupleUtils.Zip.cs | 52 ++++++++++++++ src/Csdsa/Algorithms/Tuple/TupleUtils.cs | 47 +++++++++++++ 13 files changed, 561 insertions(+) create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.ApplySelect.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.ContainsIndexOf.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Conversions.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Create.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Duplicate.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Flatten.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Map.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Rotate.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.StructuralEquals.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Swap.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.ToDictionary.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.Zip.cs create mode 100644 src/Csdsa/Algorithms/Tuple/TupleUtils.cs diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.ApplySelect.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.ApplySelect.cs new file mode 100644 index 0000000..4d72a84 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.ApplySelect.cs @@ -0,0 +1,67 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Applies the specified action to the elements of a value tuple. + /// + /// The type of the first element. + /// The type of the second element. + /// The value tuple whose elements to pass to the action. + /// The action to apply to the elements. + /// + /// Thrown when is . + /// + public static void Apply((T1 First, T2 Second) tuple, Action action) + { + ArgumentNullException.ThrowIfNull(action); + + action(tuple.First, tuple.Second); + } + + /// + /// Projects the elements of a value tuple into a single result value using + /// the specified selector function. + /// + /// The type of the first element. + /// The type of the second element. + /// The result type. + /// The value tuple whose elements to project. + /// A function that produces the result value from the tuple elements. + /// The result of invoking on the tuple elements. + /// + /// Thrown when is . + /// + public static TResult Select( + (T1 First, T2 Second) tuple, + Func selector) + { + ArgumentNullException.ThrowIfNull(selector); + + return selector(tuple.First, tuple.Second); + } + + /// + /// Applies the same mapping function to both elements of a 2-tuple + /// (T1, T1) to produce a new value tuple (TResult, TResult). + /// + /// The source element type. + /// The result element type. + /// The source value tuple. + /// The mapping function to apply to both elements. + /// + /// A new value tuple whose elements are the results of applying + /// to the elements of . + /// + /// + /// Thrown when is . + /// + public static (TResult First, TResult Second) TransformAll( + (T1 First, T1 Second) tuple, + Func map) + { + ArgumentNullException.ThrowIfNull(map); + + return (map(tuple.First), map(tuple.Second)); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.ContainsIndexOf.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.ContainsIndexOf.cs new file mode 100644 index 0000000..0ad4c0f --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.ContainsIndexOf.cs @@ -0,0 +1,48 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Determines whether the specified value is equal to either element of the value tuple. + /// + /// The type of the first element. + /// The type of the second element. + /// The value tuple to inspect. + /// The value to compare with the tuple elements. + /// + /// if equals First or Second; + /// otherwise, . + /// + public static bool Contains(this (T1 First, T2 Second) tuple, object value) + { + return Equals(tuple.First, value) || Equals(tuple.Second, value); + } + + /// + /// Returns the zero-based index of the first element in the value tuple that is equal + /// to the specified value, or -1 if neither element is equal. + /// + /// The type of the first element. + /// The type of the second element. + /// The value tuple to inspect. + /// The value to compare with the tuple elements. + /// + /// 0 if equals First; + /// 1 if it equals Second; + /// otherwise, -1. + /// + public static int IndexOf(this (T1 First, T2 Second) tuple, object value) + { + if (Equals(tuple.First, value)) + { + return 0; + } + + if (Equals(tuple.Second, value)) + { + return 1; + } + + return -1; + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Conversions.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Conversions.cs new file mode 100644 index 0000000..4c163a4 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Conversions.cs @@ -0,0 +1,35 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Converts the specified to a value tuple. + /// + /// The type of the first element. + /// The type of the second element. + /// The reference tuple to convert. + /// A value tuple containing the same elements as . + /// + /// Thrown when is . + /// + public static (T1 First, T2 Second) ToValueTuple(this Tuple tuple) + { + ArgumentNullException.ThrowIfNull(tuple); + + return (tuple.Item1, tuple.Item2); + } + + /// + /// Converts the specified value tuple to a . + /// + /// The type of the first element. + /// The type of the second element. + /// The value tuple to convert. + /// + /// A new containing the same elements as . + /// + public static Tuple ToTuple(this (T1 First, T2 Second) valueTuple) + { + return new Tuple(valueTuple.First, valueTuple.Second); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Create.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Create.cs new file mode 100644 index 0000000..ca286a0 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Create.cs @@ -0,0 +1,30 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Creates a new instance with the specified elements. + /// + /// The type of the first element. + /// The type of the second element. + /// The first element. + /// The second element. + /// A new containing the specified elements. + public static Tuple Create(T1 item1, T2 item2) + { + return new Tuple(item1, item2); + } + + /// + /// Creates a new value tuple with the specified elements. + /// + /// The type of the first element. + /// The type of the second element. + /// The first element. + /// The second element. + /// A value tuple containing the specified elements. + public static (T1 First, T2 Second) CreateValueTuple(T1 item1, T2 item2) + { + return (item1, item2); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Duplicate.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Duplicate.cs new file mode 100644 index 0000000..fe8dab8 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Duplicate.cs @@ -0,0 +1,21 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Duplicates a 2-tuple (T1, T2) into a 4-tuple + /// (T1, T2, T1, T2). + /// + /// The type of the first element. + /// The type of the second element. + /// The value tuple to duplicate. + /// + /// A new value tuple whose first two elements are the elements of + /// followed by those elements again. + /// + public static (T1 First, T2 Second, T1 Third, T2 Fourth) Duplicate( + (T1 First, T2 Second) tuple) + { + return (tuple.First, tuple.Second, tuple.First, tuple.Second); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Flatten.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Flatten.cs new file mode 100644 index 0000000..eab0f7f --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Flatten.cs @@ -0,0 +1,38 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Flattens a nested value tuple ((T1, T2), T3) into a 3-tuple (T1, T2, T3). + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The nested value tuple to flatten. + /// A flattened value tuple (T1, T2, T3). + public static (T1 First, T2 Second, T3 Third) Flatten( + this ((T1 First, T2 Second) Inner, T3 Third) tuple) + { + return (tuple.Inner.First, tuple.Inner.Second, tuple.Third); + } + + /// + /// Flattens a nested reference tuple Tuple<Tuple<T1, T2>, T3> + /// into a 3-tuple . + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The nested reference tuple to flatten. + /// A flattened . + /// + /// Thrown when or is . + /// + public static Tuple Flatten(this Tuple, T3> tuple) + { + ArgumentNullException.ThrowIfNull(tuple); + ArgumentNullException.ThrowIfNull(tuple.Item1); + + return new Tuple(tuple.Item1.Item1, tuple.Item1.Item2, tuple.Item2); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Map.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Map.cs new file mode 100644 index 0000000..36f7ff9 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Map.cs @@ -0,0 +1,65 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Projects the elements of a value tuple into a new value tuple by applying + /// separate mapping functions to each element. + /// + /// The type of the first source element. + /// The type of the second source element. + /// The type of the first result element. + /// The type of the second result element. + /// The source value tuple. + /// The mapping function for the first element. + /// The mapping function for the second element. + /// + /// A value tuple whose elements are the results of applying + /// and to the elements of . + /// + /// + /// Thrown when or is . + /// + public static (TResult1 First, TResult2 Second) Map( + this (T1 First, T2 Second) tuple, + Func map1, + Func map2) + { + ArgumentNullException.ThrowIfNull(map1); + ArgumentNullException.ThrowIfNull(map2); + + return (map1(tuple.First), map2(tuple.Second)); + } + + /// + /// Projects the elements of a into a new + /// by applying separate mapping functions to each element. + /// + /// The type of the first source element. + /// The type of the second source element. + /// The type of the first result element. + /// The type of the second result element. + /// The source reference tuple. + /// The mapping function for the first element. + /// The mapping function for the second element. + /// + /// A new whose elements are the results of applying + /// and to the elements of + /// . + /// + /// + /// Thrown when , , or + /// is . + /// + public static Tuple Map( + this Tuple tuple, + Func map1, + Func map2) + { + ArgumentNullException.ThrowIfNull(tuple); + ArgumentNullException.ThrowIfNull(map1); + ArgumentNullException.ThrowIfNull(map2); + + return new Tuple(map1(tuple.Item1), map2(tuple.Item2)); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Rotate.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Rotate.cs new file mode 100644 index 0000000..0a5486a --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Rotate.cs @@ -0,0 +1,34 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Rotates the elements of a 3-tuple to the left: + /// (T1, T2, T3) → (T2, T3, T1). + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The value tuple whose elements to rotate. + /// A new value tuple with elements rotated left. + public static (T2 Second, T3 Third, T1 First) RotateLeft( + (T1 First, T2 Second, T3 Third) tuple) + { + return (tuple.Second, tuple.Third, tuple.First); + } + + /// + /// Rotates the elements of a 3-tuple to the right: + /// (T1, T2, T3) → (T3, T1, T2). + /// + /// The type of the first element. + /// The type of the second element. + /// The type of the third element. + /// The value tuple whose elements to rotate. + /// A new value tuple with elements rotated right. + public static (T3 Third, T1 First, T2 Second) RotateRight( + (T1 First, T2 Second, T3 Third) tuple) + { + return (tuple.Third, tuple.First, tuple.Second); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.StructuralEquals.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.StructuralEquals.cs new file mode 100644 index 0000000..6cafd71 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.StructuralEquals.cs @@ -0,0 +1,46 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Determines whether two value tuples are structurally equal using + /// the default equality comparers for their element types. + /// + /// The type of the first element. + /// The type of the second element. + /// The first value tuple. + /// The second value tuple. + /// + /// if both tuples' elements are equal; otherwise, . + /// + public static bool StructuralEquals( + (T1 First, T2 Second) a, + (T1 First, T2 Second) b) + { + return EqualityComparer.Default.Equals(a.First, b.First) + && EqualityComparer.Default.Equals(a.Second, b.Second); + } + + /// + /// Determines whether two instances are structurally equal + /// using the default equality comparers for their element types. + /// + /// The type of the first element. + /// The type of the second element. + /// The first reference tuple. + /// The second reference tuple. + /// + /// if both tuples' elements are equal; otherwise, . + /// + /// + /// Thrown when or is . + /// + public static bool StructuralEquals(Tuple a, Tuple b) + { + ArgumentNullException.ThrowIfNull(a); + ArgumentNullException.ThrowIfNull(b); + + return EqualityComparer.Default.Equals(a.Item1, b.Item1) + && EqualityComparer.Default.Equals(a.Item2, b.Item2); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Swap.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Swap.cs new file mode 100644 index 0000000..2f6576b --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Swap.cs @@ -0,0 +1,34 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Swaps the elements of a value tuple (T1, T2) to produce (T2, T1). + /// + /// The type of the first element. + /// The type of the second element. + /// The value tuple whose elements to swap. + /// A new value tuple with the elements swapped. + public static (T2 Second, T1 First) Swap(this (T1 First, T2 Second) tuple) + { + return (tuple.Second, tuple.First); + } + + /// + /// Swaps the elements of a to produce a + /// with reversed element order. + /// + /// The type of the first element. + /// The type of the second element. + /// The reference tuple whose elements to swap. + /// A new with reversed element order. + /// + /// Thrown when is . + /// + public static Tuple Swap(this Tuple tuple) + { + ArgumentNullException.ThrowIfNull(tuple); + + return new Tuple(tuple.Item2, tuple.Item1); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.ToDictionary.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.ToDictionary.cs new file mode 100644 index 0000000..68d3368 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.ToDictionary.cs @@ -0,0 +1,44 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Projects a sequence of value tuples into a . + /// + /// + /// The type of the key element. Must be non-nullable to be used as a dictionary key. + /// + /// The type of the value element. + /// The sequence of value tuples to project. + /// + /// If , throws when a value tuple contains a value element; + /// otherwise allows values in the resulting dictionary. + /// + /// + /// A populated from . + /// + /// + /// Thrown when is . + /// + /// + /// Thrown when is and a tuple's value element is null. + /// + public static Dictionary ToDictionary( + this IEnumerable<(T1 Key, T2 Value)> tuples, + bool throwIfNull = false) + where T1 : notnull + { + ArgumentNullException.ThrowIfNull(tuples); + + if (throwIfNull) + { + return tuples.ToDictionary( + t => t.Key, + t => t.Value is null + ? throw new InvalidOperationException("Tuple.Item2 is null.") + : t.Value); + } + + return tuples.ToDictionary(t => t.Key, t => t.Value); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.Zip.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.Zip.cs new file mode 100644 index 0000000..debf102 --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.Zip.cs @@ -0,0 +1,52 @@ +namespace Csdsa.Algorithms.Tuples; + +public static partial class TupleUtils +{ + /// + /// Zips two sequences into a sequence of value tuples. + /// + /// The element type of the first sequence. + /// The element type of the second sequence. + /// The first sequence to zip. + /// The second sequence to zip. + /// + /// A sequence of value tuples, where each value tuple contains the elements from the input + /// sequences at the corresponding position. + /// + /// + /// Thrown when or is . + /// + public static IEnumerable<(T1 First, T2 Second)> ZipToValueTuples( + IEnumerable first, + IEnumerable second) + { + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + return first.Zip(second, (a, b) => (a, b)); + } + + /// + /// Zips two sequences into a sequence of instances. + /// + /// The element type of the first sequence. + /// The element type of the second sequence. + /// The first sequence to zip. + /// The second sequence to zip. + /// + /// A sequence of , where each tuple contains the elements + /// from the input sequences at the corresponding position. + /// + /// + /// Thrown when or is . + /// + public static IEnumerable> ZipToTuples( + IEnumerable first, + IEnumerable second) + { + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + return first.Zip(second, (a, b) => new Tuple(a, b)); + } +} diff --git a/src/Csdsa/Algorithms/Tuple/TupleUtils.cs b/src/Csdsa/Algorithms/Tuple/TupleUtils.cs new file mode 100644 index 0000000..fed5e7e --- /dev/null +++ b/src/Csdsa/Algorithms/Tuple/TupleUtils.cs @@ -0,0 +1,47 @@ +namespace Csdsa.Algorithms.Tuples; + +/// +/// Provides helper and extension methods for working with tuples, +/// supporting conversions, mapping, structural comparisons, rotation, +/// and dictionary projection. +/// +/// Concepts: +/// +/// +/// +/// Creation of reference and value tuples. +/// +/// +/// Mapping and transformation of tuple elements. +/// +/// +/// Flattening and rotating nested tuples. +/// +/// +/// Structural equality and membership checks. +/// +/// +/// Safe dictionary projection from sequences of tuples. +/// +/// +/// +/// Key practices: +/// +/// +/// +/// Method chaining for fluent tuple transformations. +/// +/// +/// Preserving data integrity and type-safety. +/// +/// +/// Use of functional idioms for mapping and selection. +/// +/// +/// Leveraging compile-time checks via generic constraints. +/// +/// +/// +public static partial class TupleUtils +{ +} From be93abb45f73d4048a0ea3a0936c9a4c6c890959 Mon Sep 17 00:00:00 2001 From: mavantgarderc Date: Sun, 7 Dec 2025 14:05:15 +0330 Subject: [PATCH 4/5] Algorithms.Tests(Tuple) --- .../Tuple/TupleUtils.ApplySelect.Tests.cs | 36 +++++++++++++++++ .../Tuple/TupleUtils.ContainsIndexOf.Tests.cs | 27 +++++++++++++ .../Tuple/TupleUtils.Conversions.Tests.cs | 32 +++++++++++++++ .../Tuple/TupleUtils.Create.Tests.cs | 25 ++++++++++++ .../Tuple/TupleUtils.Duplicate.Tests.cs | 17 ++++++++ .../Tuple/TupleUtils.Flatten.Tests.cs | 32 +++++++++++++++ .../Algorithms/Tuple/TupleUtils.Map.Tests.cs | 31 ++++++++++++++ .../Tuple/TupleUtils.Rotate.Tests.cs | 28 +++++++++++++ .../TupleUtils.StructuralEquals.Tests.cs | 32 +++++++++++++++ .../Algorithms/Tuple/TupleUtils.Swap.Tests.cs | 31 ++++++++++++++ .../Tuple/TupleUtils.ToDictionary.Tests.cs | 40 +++++++++++++++++++ .../Algorithms/Tuple/TupleUtils.Zip.Tests.cs | 38 ++++++++++++++++++ 12 files changed, 369 insertions(+) create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ApplySelect.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ContainsIndexOf.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Conversions.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Create.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Duplicate.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Flatten.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Map.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Rotate.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.StructuralEquals.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Swap.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ToDictionary.Tests.cs create mode 100644 tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Zip.Tests.cs diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ApplySelect.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ApplySelect.Tests.cs new file mode 100644 index 0000000..0e9a8f2 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ApplySelect.Tests.cs @@ -0,0 +1,36 @@ +using System; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Apply_ExecutesActionOnTuple() + { + int result = 0; + + TupleUtils.Apply((2, 3), (x, y) => result = x + y); + + Assert.Equal(5, result); + } + + [Fact] + public void Select_ProjectsTupleToResult() + { + int result = TupleUtils.Select((2, 3), (x, y) => x * y); + + Assert.Equal(6, result); + } + + [Fact] + public void TransformAll_MapsBothItems() + { + (int First, int Second) result = TupleUtils.TransformAll((3, 4), x => x * 10); + + Assert.Equal((30, 40), (result.First, result.Second)); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ContainsIndexOf.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ContainsIndexOf.Tests.cs new file mode 100644 index 0000000..3d3c674 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ContainsIndexOf.Tests.cs @@ -0,0 +1,27 @@ +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Contains_IdentifiesPresence() + { + (int First, string Second) tuple = (1, "x"); + + Assert.True(tuple.Contains("x")); + Assert.False(tuple.Contains("y")); + } + + [Fact] + public void IndexOf_ReturnsCorrectIndex() + { + (int First, string Second) tuple = (1, "a"); + + Assert.Equal(0, tuple.IndexOf(1)); + Assert.Equal(1, tuple.IndexOf("a")); + Assert.Equal(-1, tuple.IndexOf("z")); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Conversions.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Conversions.Tests.cs new file mode 100644 index 0000000..288aca9 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Conversions.Tests.cs @@ -0,0 +1,32 @@ +using System; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void ToValueTuple_ConvertsFromTuple() + { + Tuple tuple = new Tuple(1, "a"); + + (int First, string Second) result = tuple.ToValueTuple(); + + Assert.Equal(1, result.First); + Assert.Equal("a", result.Second); + } + + [Fact] + public void ToTuple_ConvertsValueTupleToTuple() + { + (int First, string Second) input = (1, "a"); + + Tuple result = input.ToTuple(); + + Assert.Equal(1, result.Item1); + Assert.Equal("a", result.Item2); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Create.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Create.Tests.cs new file mode 100644 index 0000000..1f608c6 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Create.Tests.cs @@ -0,0 +1,25 @@ +using System; +using Csdsa.Algorithms.Tuples; +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Create_ReturnsCorrectTuple() + { + Tuple tuple = TupleUtils.Create(1, "a"); + + Assert.Equal(1, tuple.Item1); + Assert.Equal("a", tuple.Item2); + } + + [Fact] + public void CreateValueTuple_ReturnsCorrectValueTuple() + { + (int, string) tuple = TupleUtils.CreateValueTuple(1, "a"); + + Assert.Equal((1, "a"), tuple); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Duplicate.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Duplicate.Tests.cs new file mode 100644 index 0000000..3d6945e --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Duplicate.Tests.cs @@ -0,0 +1,17 @@ +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Duplicate_CreatesRepeatedTuple() + { + (int First, int Second, int Third, int Fourth) result = + TupleUtils.Duplicate((1, 2)); + + Assert.Equal((1, 2, 1, 2), (result.First, result.Second, result.Third, result.Fourth)); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Flatten.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Flatten.Tests.cs new file mode 100644 index 0000000..343e28f --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Flatten.Tests.cs @@ -0,0 +1,32 @@ +using System; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Flatten_ValueTuple_FlattensProperly() + { + ((int First, int Second), int Third) nested = ((1, 2), 3); + + (int First, int Second, int Third) flat = nested.Flatten(); + + Assert.Equal((1, 2, 3), (flat.First, flat.Second, flat.Third)); + } + + [Fact] + public void Flatten_ReferenceTuple_FlattensProperly() + { + Tuple, int> nested = Tuple.Create(Tuple.Create(1, 2), 3); + + Tuple flat = nested.Flatten(); + + Assert.Equal(1, flat.Item1); + Assert.Equal(2, flat.Item2); + Assert.Equal(3, flat.Item3); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Map.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Map.Tests.cs new file mode 100644 index 0000000..b555bdc --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Map.Tests.cs @@ -0,0 +1,31 @@ +using System; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Map_ValueTuple_TransformsBothItems() + { + (int First, int Second) input = (2, 3); + + (int First, int Second) result = input.Map(x => x + 1, y => y * 2); + + Assert.Equal((3, 6), (result.First, result.Second)); + } + + [Fact] + public void Map_ReferenceTuple_TransformsBothItems() + { + Tuple input = Tuple.Create(2, 3); + + Tuple result = input.Map(x => x + 1, y => y * 2); + + Assert.Equal(3, result.Item1); + Assert.Equal(6, result.Item2); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Rotate.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Rotate.Tests.cs new file mode 100644 index 0000000..fa56847 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Rotate.Tests.cs @@ -0,0 +1,28 @@ +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void RotateLeft_ShiftsElementsLeft() + { + (int First, int Second, int Third) input = (1, 2, 3); + + (int Second, int Third, int First) result = TupleUtils.RotateLeft(input); + + Assert.Equal((2, 3, 1), (result.Second, result.Third, result.First)); + } + + [Fact] + public void RotateRight_ShiftsElementsRight() + { + (int First, int Second, int Third) input = (1, 2, 3); + + (int Third, int First, int Second) result = TupleUtils.RotateRight(input); + + Assert.Equal((3, 1, 2), (result.Third, result.First, result.Second)); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.StructuralEquals.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.StructuralEquals.Tests.cs new file mode 100644 index 0000000..faee3d6 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.StructuralEquals.Tests.cs @@ -0,0 +1,32 @@ +using System; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void StructuralEquals_ValueTuple_TrueForEqual() + { + (int First, string Second) a = (1, "x"); + (int First, string Second) b = (1, "x"); + + bool equal = TupleUtils.StructuralEquals(a, b); + + Assert.True(equal); + } + + [Fact] + public void StructuralEquals_ReferenceTuple_FalseForMismatch() + { + Tuple a = Tuple.Create(1, "x"); + Tuple b = Tuple.Create(2, "x"); + + bool equal = TupleUtils.StructuralEquals(a, b); + + Assert.False(equal); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Swap.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Swap.Tests.cs new file mode 100644 index 0000000..fe6892a --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Swap.Tests.cs @@ -0,0 +1,31 @@ +using System; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void Swap_ValueTuple_SwapsElements() + { + (int First, string Second) input = (1, "a"); + + (string Second, int First) swapped = input.Swap(); + + Assert.Equal(("a", 1), (swapped.Second, swapped.First)); + } + + [Fact] + public void Swap_ReferenceTuple_SwapsElements() + { + Tuple input = Tuple.Create(1, "a"); + + Tuple swapped = input.Swap(); + + Assert.Equal("a", swapped.Item1); + Assert.Equal(1, swapped.Item2); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ToDictionary.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ToDictionary.Tests.cs new file mode 100644 index 0000000..69775e9 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.ToDictionary.Tests.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void ToDictionary_ConvertsToDict_WhenNotThrowing() + { + List<(int Key, string Value)> data = new List<(int, string)> + { + (1, "a"), + (2, "b"), + }; + + Dictionary dict = data.ToDictionary(); + + Assert.Equal("a", dict[1]); + Assert.Equal("b", dict[2]); + } + + [Fact] + public void ToDictionary_ThrowsWhenValueIsNull_IfThrowIfNullTrue() + { + List<(int Key, string? Value)> data = new List<(int, string?)> + { + (1, null), + }; + + InvalidOperationException ex = + Assert.Throws(() => data.ToDictionary(true)); + + Assert.Equal("Tuple.Item2 is null.", ex.Message); + } +} diff --git a/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Zip.Tests.cs b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Zip.Tests.cs new file mode 100644 index 0000000..fa36401 --- /dev/null +++ b/tests/Csdsa.Tests/Algorithms/Tuple/TupleUtils.Zip.Tests.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Csdsa.Algorithms.Tuples; + +using Xunit; + +namespace Csdsa.Tests.Algorithms.Tuples; + +public sealed partial class TupleUtilsTests +{ + [Fact] + public void ZipToValueTuples_ZipsCorrectly() + { + int[] first = { 1, 2 }; + string[] second = { "a", "b" }; + + List<(int First, string Second)> result = + TupleUtils.ZipToValueTuples(first, second).ToList(); + + Assert.Equal((1, "a"), (result[0].First, result[0].Second)); + Assert.Equal((2, "b"), (result[1].First, result[1].Second)); + } + + [Fact] + public void ZipToTuples_ZipsCorrectly() + { + int[] first = { 1, 2 }; + string[] second = { "a", "b" }; + + List> result = + TupleUtils.ZipToTuples(first, second).ToList(); + + Assert.Equal(1, result[0].Item1); + Assert.Equal("a", result[0].Item2); + } +} From 180efd81c28e3c03610e376af7b6ca3e46b87f8e Mon Sep 17 00:00:00 2001 From: mavantgarderc Date: Sun, 7 Dec 2025 14:05:24 +0330 Subject: [PATCH 5/5] chore(README): Update README.md --- ROADMAP.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 4774b8a..f1d3c12 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,41 +1,37 @@ # Roadmap -- [x] Linear Structures & Sequential Access Patterns +- [X] Linear Structures & Sequential Access Patterns - Linear Data Structures - 1. Arrays + ~~1. Arrays~~ - 2. Linked Lists (Singly, Doubly, Circular) + ~~2. Linked Lists (Singly, Doubly, Circular)~~ - 3. Stacks + ~~3. Strings~~ - 4. Queues + ~~4. Vectors/Dynamic Arrays~~ - 5. Strings + ~~5. Array Lists~~ - 6. Vectors/Dynamic Arrays + ~~6. Deques (Double-ended Queues)~~ - 7. Array Lists - - 8. Deques (Double-ended Queues) - - 9. Tuples - Immutable ordered sequences + ~~7. Tuples - Immutable ordered sequences~~ - Sequential Access Patterns - 1. Sequential Traversal + ~~1. Sequential Traversal~~ - 2. Stacks (LIFO) + ~~2. Stacks (LIFO)~~ - 3. Queue (FIFO) + ~~3. Queue (FIFO)~~ - 4. Linear Search + ~~4. Linear Search~~ - 5. Stream Processing + ~~5. Stream Processing~~ - 6. BFS (Breadth-First Search) + ~~6. BFS (Breadth-First Search)~~ - 7. DFS (Depth-First Search) + ~~7. DFS (Depth-First Search)~~ - 8. Serialization/Deserialization + ~~8. Serialization/Deserialization~~ ---