diff --git a/HW1/HW1.sln b/HW1/HW1.sln new file mode 100644 index 0000000..291d9f0 --- /dev/null +++ b/HW1/HW1.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trie", "Trie\Trie.csproj", "{A87E7018-FB5B-47AB-8323-213DC3A49658}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trie.Test", "Trie.Test\Trie.Test.csproj", "{A2F4F365-7EDC-4ECD-9B8F-6D184B1F9CA3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A87E7018-FB5B-47AB-8323-213DC3A49658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A87E7018-FB5B-47AB-8323-213DC3A49658}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A87E7018-FB5B-47AB-8323-213DC3A49658}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A87E7018-FB5B-47AB-8323-213DC3A49658}.Release|Any CPU.Build.0 = Release|Any CPU + {A2F4F365-7EDC-4ECD-9B8F-6D184B1F9CA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2F4F365-7EDC-4ECD-9B8F-6D184B1F9CA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2F4F365-7EDC-4ECD-9B8F-6D184B1F9CA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2F4F365-7EDC-4ECD-9B8F-6D184B1F9CA3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/HW1/Trie.Test/Trie.Test.csproj b/HW1/Trie.Test/Trie.Test.csproj new file mode 100644 index 0000000..7cefcd9 --- /dev/null +++ b/HW1/Trie.Test/Trie.Test.csproj @@ -0,0 +1,40 @@ + + + + net9.0 + enable + enable + + false + true + Trie.Tests + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/HW1/Trie.Test/TrieTest.cs b/HW1/Trie.Test/TrieTest.cs new file mode 100644 index 0000000..8906613 --- /dev/null +++ b/HW1/Trie.Test/TrieTest.cs @@ -0,0 +1,172 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace Trie.Tests; + +/// +/// Unit tests for Trie data structure. +/// +public class TrieTest +{ + private Trie trie; + + /// + /// Initializes a new trie before each test. + /// + [SetUp] + public void Setup() + => this.trie = new(); + + /// + /// Tests Add method when inserting fresh words. + /// + [Test] + public void Trie_Add_NewWords_ShouldReturnTrue() + { + List words = ["apple", "app", "application"]; + + foreach (var word in words) + { + Assert.That(this.trie.Add(word), Is.True); + } + } + + /// + /// Tests WordCount after adding multiple words. + /// + [Test] + public void Trie_WordCount_AfterAddingSeveralWords() + { + List words = ["alpha", "beta", "gamma", "delta"]; + + foreach (var word in words) + { + this.trie.Add(word); + } + + Assert.That(this.trie.WordCount, Is.EqualTo(words.Count)); + } + + /// + /// Tests Add method when the same word is inserted twice. + /// + [Test] + public void Trie_Add_SameWordTwice_ShouldReturnFalse() + { + const string word = "matrix"; + + this.trie.Add(word); + + Assert.That(this.trie.Add(word), Is.False); + } + + /// + /// Tests Add method after removing a word. + /// + [Test] + public void Trie_Add_AfterRemovingWord_ShouldReturnTrue() + { + const string word = "sun"; + + this.trie.Add(word); + this.trie.Remove(word); + + Assert.That(this.trie.Add(word), Is.True); + } + + /// + /// Tests WordCount after removing words. + /// + [Test] + public void Trie_WordCount_AfterRemovingSomeWords() + { + List words = ["dog", "cat", "bird", "fish"]; + + foreach (var word in words) + { + this.trie.Add(word); + } + + this.trie.Remove("dog"); + this.trie.Remove("bird"); + + var expectedCount = words.Count - 2; + + Assert.That(this.trie.WordCount, Is.EqualTo(expectedCount)); + } + + /// + /// Tests WordCount of an empty trie. + /// + [Test] + public void Trie_WordCount_OfEmptyTrie_ShouldBeZero() + { + const int expected = 0; + + Assert.That(this.trie.WordCount, Is.EqualTo(expected)); + } + + /// + /// Tests Contains method after adding a word. + /// + [Test] + public void Trie_Contains_AfterAddingWord_ShouldReturnTrue() + { + const string word = "cloud"; + + this.trie.Add(word); + + Assert.That(this.trie.Contains(word), Is.True); + } + + /// + /// Tests Contains method with a word that was never added. + /// + [Test] + public void Trie_Contains_NonExistingWord_ShouldReturnFalse() + { + const string word = "universe"; + + Assert.That(this.trie.Contains(word), Is.False); + } + + /// + /// Tests Contains method after removing a word. + /// + [Test] + public void Trie_Contains_AfterRemovingWord_ShouldReturnFalse() + { + const string word = "planet"; + + this.trie.Add(word); + this.trie.Remove(word); + + Assert.That(this.trie.Contains(word), Is.False); + } + + /// + /// Tests Remove method with a word that exists. + /// + [Test] + public void Trie_Remove_ExistingWord_ShouldRemoveSuccessfully() + { + const string word = "river"; + + this.trie.Add(word); + this.trie.Remove(word); + + Assert.That(this.trie.Contains(word), Is.False); + } + + /// + /// Tests Remove method with a word that does not exist. + /// + [Test] + public void Trie_Remove_NonExistingWord_ShouldReturnFalse() + { + const string word = "mountain"; + + Assert.That(this.trie.Remove(word), Is.False); + } +} \ No newline at end of file diff --git a/HW1/Trie.Test/stylecop.json b/HW1/Trie.Test/stylecop.json new file mode 100644 index 0000000..76c8e76 --- /dev/null +++ b/HW1/Trie.Test/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "khusainovilas", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} \ No newline at end of file diff --git a/HW1/Trie/Trie.cs b/HW1/Trie/Trie.cs new file mode 100644 index 0000000..3071921 --- /dev/null +++ b/HW1/Trie/Trie.cs @@ -0,0 +1,161 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace Trie; + +/// +/// realization of Trie. +/// +public class Trie +{ + private readonly TrieNode root = new(); + + /// + /// Gets the number of words added to the tree. + /// + public int WordCount { get; private set; } + + /// + /// Add a new word into the trie. + /// + /// The word to be inserted. + /// Returns false if the word is already present. + public bool Add(string? word) + { + if (word is null) + { + return false; + } + + var current = this.root; + + foreach (var letter in word) + { + if (!current.Children.TryGetValue(letter, out var value)) + { + value = new TrieNode(); + current.Children[letter] = value; + } + + current = value; + current.PrefixCount++; + } + + if (current.IsEnd) + { + return false; + } + + current.IsEnd = true; + this.WordCount++; + return true; + } + + /// + /// Determines whether the trie contains a specific word. + /// + /// The word to look for. + /// Returns true if the word exists in the trie. + public bool Contains(string? word) + { + if (word is null) + { + return false; + } + + var current = this.root; + + foreach (var letter in word) + { + if (!current.Children.TryGetValue(letter, out var value)) + { + return false; + } + + current = value; + } + + return current.IsEnd; + } + + /// + /// Deletes a word from the trie. + /// + /// The word to delete. + /// Returns true if the word was present in the trie. + public bool Remove(string? word) + { + if (word is null) + { + return false; + } + + var current = this.root; + var path = new Stack(); + + foreach (var letter in word) + { + if (!current.Children.TryGetValue(letter, out var value)) + { + return false; + } + + path.Push(current); + current = value; + } + + if (!current.IsEnd) + { + return false; + } + + current.IsEnd = false; + this.WordCount--; + + foreach (var trieNode in path) + { + trieNode.PrefixCount--; + } + + current.PrefixCount--; + + return true; + } + + /// + /// Returns the number of words in the trie that start with a given prefix. + /// + /// The prefix to search for. + /// The count of words that begin with the specified prefix. + public int HowManyStartsWithPrefix(string? prefix) + { + if (prefix is null) + { + return this.WordCount; + } + + var current = this.root; + + foreach (var letter in prefix) + { + if (!current.Children.TryGetValue(letter, out var value)) + { + return 0; + } + + current = value; + } + + return current.PrefixCount; + } + + private class TrieNode + { + public Dictionary Children { get; } = new(); + + public bool IsEnd { get; set; } + + public int PrefixCount { get; set; } + } +} diff --git a/HW1/Trie/Trie.csproj b/HW1/Trie/Trie.csproj new file mode 100644 index 0000000..b158c23 --- /dev/null +++ b/HW1/Trie/Trie.csproj @@ -0,0 +1,21 @@ + + + + Library + net9.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/HW1/Trie/stylecop.json b/HW1/Trie/stylecop.json new file mode 100644 index 0000000..76c8e76 --- /dev/null +++ b/HW1/Trie/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "khusainovilas", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} \ No newline at end of file