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~~
---
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
+{
+}
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
+{
+}
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);
+ }
+}
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);
+ }
+}