Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 16 additions & 20 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -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~~

---

Expand Down
53 changes: 53 additions & 0 deletions src/Csdsa/Algorithms/String/StringUtils.AreAnagrams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Csdsa.Algorithms.Strings;

public static partial class StringUtils
{
/// <summary>
/// Determines whether two strings are anagrams of each other.
/// The comparison is case-sensitive and includes all characters.
/// </summary>
/// <param name="first">The first string.</param>
/// <param name="second">The second string.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="first"/> and <paramref name="second"/>
/// are anagrams; otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="first"/> or <paramref name="second"/> is <see langword="null"/>.
/// </exception>
public static bool AreAnagrams(string first, string second)
{
ArgumentNullException.ThrowIfNull(first);
ArgumentNullException.ThrowIfNull(second);

if (first.Length != second.Length)
{
return false;
}

Dictionary<char, int> counts = new Dictionary<char, int>();

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;
}
}
42 changes: 42 additions & 0 deletions src/Csdsa/Algorithms/String/StringUtils.FirstUniqueChar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Csdsa.Algorithms.Strings;

public static partial class StringUtils
{
/// <summary>
/// Finds the first non-repeating character in the specified string.
/// </summary>
/// <param name="input">The string to inspect.</param>
/// <returns>
/// The first character that appears exactly once in <paramref name="input"/>,
/// or <see langword="null"/> if no such character exists or the string is empty.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="input"/> is <see langword="null"/>.
/// </exception>
public static char? FirstUniqueChar(string input)
{
ArgumentNullException.ThrowIfNull(input);

if (input.Length == 0)
{
return null;
}

Dictionary<char, int> frequency = new Dictionary<char, int>();

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;
}
}
51 changes: 51 additions & 0 deletions src/Csdsa/Algorithms/String/StringUtils.IndexOfSubstring.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Csdsa.Algorithms.Strings;

public static partial class StringUtils
{
/// <summary>
/// Searches for the first occurrence of <paramref name="target"/> within <paramref name="source"/>
/// using a naive substring scan.
/// </summary>
/// <param name="source">The string to search.</param>
/// <param name="target">The substring to locate.</param>
/// <returns>
/// The zero-based index of the first occurrence of <paramref name="target"/> in
/// <paramref name="source"/>, or -1 if the substring is not found or if either
/// string is empty.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="source"/> or <paramref name="target"/> is <see langword="null"/>.
/// </exception>
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;
}
}
44 changes: 44 additions & 0 deletions src/Csdsa/Algorithms/String/StringUtils.IsPalindrome.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace Csdsa.Algorithms.Strings;

public static partial class StringUtils
{
/// <summary>
/// Determines whether the specified string is a palindrome, ignoring
/// case and non-alphanumeric characters.
/// </summary>
/// <param name="input">The string to inspect.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="input"/> is a palindrome
/// under the given normalization; otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="input"/> is <see langword="null"/>.
/// </exception>
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;
}
}
31 changes: 31 additions & 0 deletions src/Csdsa/Algorithms/String/StringUtils.IsRotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Csdsa.Algorithms.Strings;

public static partial class StringUtils
{
/// <summary>
/// Determines whether <paramref name="rotated"/> is a rotation of <paramref name="original"/>.
/// For example, <c>"erbottlewat"</c> is a rotation of <c>"waterbottle"</c>.
/// </summary>
/// <param name="original">The original string.</param>
/// <param name="rotated">The candidate rotated string.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="rotated"/> is a rotation of
/// <paramref name="original"/>; otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="original"/> or <paramref name="rotated"/> is <see langword="null"/>.
/// </exception>
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);
}
}
100 changes: 100 additions & 0 deletions src/Csdsa/Algorithms/String/StringUtils.KmpSearch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
namespace Csdsa.Algorithms.Strings;

public static partial class StringUtils
{
/// <summary>
/// Finds all occurrences of <paramref name="pattern"/> in <paramref name="text"/>
/// using the Knuth–Morris–Pratt (KMP) algorithm.
/// </summary>
/// <param name="pattern">The pattern to search for.</param>
/// <param name="text">The text to search within.</param>
/// <returns>
/// A read-only list of zero-based indices where <paramref name="pattern"/> occurs in
/// <paramref name="text"/>. Returns an empty list if <paramref name="pattern"/> or
/// <paramref name="text"/> is empty.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="pattern"/> or <paramref name="text"/> is <see langword="null"/>.
/// </exception>
public static IReadOnlyList<int> KmpSearch(string pattern, string text)
{
ArgumentNullException.ThrowIfNull(pattern);
ArgumentNullException.ThrowIfNull(text);

if (pattern.Length == 0 || text.Length == 0)
{
return Array.Empty<int>();
}

int m = pattern.Length;
int n = text.Length;
List<int> result = new List<int>();

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;
}
}
Loading