diff --git a/LZW/LZW.sln b/LZW/LZW.sln new file mode 100644 index 0000000..bee34d3 --- /dev/null +++ b/LZW/LZW.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33403.182 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LZW", "LZW\LZW.csproj", "{B99BFF18-B071-406A-989E-994845F53D3B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestsBor", "TestsBor\TestsBor.csproj", "{019B956A-4284-4708-9B93-8D6FC218A62E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestLZW", "TestLZW\TestLZW.csproj", "{C3A8A289-5E8E-4D79-8263-3E4726F4A4CD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B99BFF18-B071-406A-989E-994845F53D3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B99BFF18-B071-406A-989E-994845F53D3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B99BFF18-B071-406A-989E-994845F53D3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B99BFF18-B071-406A-989E-994845F53D3B}.Release|Any CPU.Build.0 = Release|Any CPU + {019B956A-4284-4708-9B93-8D6FC218A62E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {019B956A-4284-4708-9B93-8D6FC218A62E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {019B956A-4284-4708-9B93-8D6FC218A62E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {019B956A-4284-4708-9B93-8D6FC218A62E}.Release|Any CPU.Build.0 = Release|Any CPU + {C3A8A289-5E8E-4D79-8263-3E4726F4A4CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3A8A289-5E8E-4D79-8263-3E4726F4A4CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3A8A289-5E8E-4D79-8263-3E4726F4A4CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3A8A289-5E8E-4D79-8263-3E4726F4A4CD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {40032E74-4213-49E7-9C40-632CA5D1FA92} + EndGlobalSection +EndGlobal diff --git a/LZW/LZW/Bor.cs b/LZW/LZW/Bor.cs new file mode 100644 index 0000000..c4c1338 --- /dev/null +++ b/LZW/LZW/Bor.cs @@ -0,0 +1,143 @@ +namespace Bor; + +/// +/// String Parsing Tree +/// +public class Bor +{ + private BorElement root = new(); + + /// + /// Adding an element to a Trie + /// + public (bool, int) Add(char[] buffer, int from = 0, int to = 0) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (root == null) + { + throw new InvalidOperationException(); + } + var walker = root; + int i = 0; + int pointer = from; + int toSymbolCode = -1; + bool isStringInBor = Contains(buffer, from, to); + while (i < to - from + 1) + { + int number = (int)buffer[pointer]; + if (!walker.Next.ContainsKey(number)) + { + walker.Next.Add(number, new BorElement()); + ++walker.SizeDictionary; + } + if (!isStringInBor) + { + ++walker.Next[number].HowManyStringInDictionary; + } + toSymbolCode = walker.SymbolCode; + walker = walker.Next[number]; + pointer++; + i++; + } + if (!walker.IsTerminal) + { + walker.SymbolCode = root.HowManyStringInDictionary; + root.HowManyStringInDictionary++; + } + + if (!walker.IsTerminal) + { + walker.IsTerminal = true; + return (true, toSymbolCode); + } + else + { + return (true, toSymbolCode); + } + + } + + /// + /// Returns a stream by letter + /// + public int ReturnSymbolCodeByCharArray(char[] buffer, int to = 0, int from = 0) + { + if (buffer.Length == 0) + { + return -1; + } + var walker = root; + int i = 0; + int pointer = from; + while(i < to - from + 1) + { + int number = (int)buffer[pointer]; + if (!walker.Next.ContainsKey(number)) + { + return -1; + } + walker = walker.Next[number]; + ++i; + } + return walker.SymbolCode; + } + + /// + /// Returns how many strings in a Trie + /// + public int HowManyStringsInBor() + { + if (root == null) + { + return -1; + } + return root.HowManyStringInDictionary; + } + + /// + /// Checks whether the string in the Trie contains + /// + public bool Contains(char[] buffer, int from = 0 , int to = 0) + { + if (root == null) + { + return false; + } + var walker = root; + int i = 0; + int pointer = from; + while (i < to - from + 1) + { + if (!walker.Next.ContainsKey(buffer[pointer])) + { + return false; + } + walker = walker.Next[buffer[pointer]]; + pointer++; + ++i; + } + return true; + } + + private class BorElement + { + public Dictionary Next { get; set; } + public bool IsTerminal { get; set; } + + public int SizeDictionary { get; set; } + + public int HowManyStringInDictionary { get; set; } + + public int SymbolCode { get; set; } + + public BorElement() + { + Next = new Dictionary(); + SymbolCode = -1; + } + } +} diff --git a/LZW/LZW/LZW.cs b/LZW/LZW/LZW.cs new file mode 100644 index 0000000..a1e4345 --- /dev/null +++ b/LZW/LZW/LZW.cs @@ -0,0 +1,189 @@ +namespace LZW; + +using Bor; + +/// +/// A class for compressing and decompressing data using the LZW algorithm +/// +public static class LZWAlgorithm +{ + private static void AddAlphabetToBor(Bor bor) + { + var letter = new char[1]; + for (int i = 0; i < 256; ++i) + { + letter[0] = (char)i; + bor.Add(letter, 0, 0); + } + } + + private static bool CodeFile(string fileName, ref double compressionRatio) + { + double sizeForCompressionRatio = 0; + try + { + FileInfo fileFromMain = new(fileName); + sizeForCompressionRatio = fileFromMain.Length; + } + catch (FileNotFoundException) + { + return false; + } + string newFile = fileName + ".zipped"; + + File.WriteAllText(newFile, string.Empty); + + var bor = new Bor(); + AddAlphabetToBor(bor); + + byte[] bufferIn = File.ReadAllBytes(fileName); + if (bufferIn.Length == 0) + { + compressionRatio = 1; + return true; + } + int j = 0; + var textFromFile = new char[bufferIn.Length]; + for (int i = 0; i < bufferIn.Length; ++i) + { + textFromFile[j] = Convert.ToChar(bufferIn[i]); + ++j; + } + + // Добавление потоков в файл + int walker = 0; + using var archivedFile = new FileStream(newFile, FileMode.Append); + for (int i = 0; i < textFromFile.Length; i++) + { + if (!bor.Contains(textFromFile, walker, i)) + { + var (_, flow) = bor.Add(textFromFile, walker, i); + walker = i; + byte[] bytes = BitConverter.GetBytes(flow); + archivedFile.WriteAsync(bytes, 0, bytes.Length); + } + } + + // Добавление последнего потока в файл + var flowLast = bor.ReturnSymbolCodeByCharArray(textFromFile, bufferIn.Length - 1, bufferIn.Length - 1); + byte[] bytesLast = BitConverter.GetBytes(flowLast); + archivedFile.WriteAsync(bytesLast, 0, bytesLast.Length); + archivedFile.Close(); + var file = new FileInfo(newFile.ToString()); + double newSizeForCompressionRatio = file.Length; + compressionRatio = newSizeForCompressionRatio / sizeForCompressionRatio; + return true; + } + + private static bool DecodeFile(string fileName) + { + byte[]? bufferIn = null; + try + { + bufferIn = File.ReadAllBytes(fileName); + } + catch (FileNotFoundException) + { + return false; + } + int i = 0; + string newFile = fileName.Remove(fileName.Length - 7); + + File.WriteAllText(newFile, string.Empty); + + if (fileName.Length == 0) + { + return true; + } + + var dictionaryForDecode = new Dictionary(); + + for (int j = 0; j < 256; ++j) + { + var stringLetter = new char[1]; + stringLetter[0] = (char)j; + dictionaryForDecode.Add(j, stringLetter); + } + + int index = 256; + int input = 0; + char[]? previousString = null; + bool isFirst = true; + while (i < bufferIn.Length) + { + var symbolFromArray = new byte[4]; + int pointerForSymbolsFromOneArray = 0; + int size = i + 4; + for (; i < size; ++i) + { + symbolFromArray[pointerForSymbolsFromOneArray] = bufferIn[i]; + ++pointerForSymbolsFromOneArray; + } + + input = BitConverter.ToInt32(symbolFromArray, 0); + + if (!isFirst) + { + if (!dictionaryForDecode.ContainsKey(input)) + { + ArgumentNullException.ThrowIfNull(previousString); + Array.Resize(ref previousString, previousString.Length + 1); + previousString[previousString.Length - 1] = previousString[0]; + dictionaryForDecode.Add(index, previousString); + } + var stringByIndex = dictionaryForDecode[input]; + if (index != input) + { + ArgumentNullException.ThrowIfNull(previousString); + Array.Resize(ref previousString, previousString.Length + 1); + previousString[previousString.Length - 1] = stringByIndex[0]; + dictionaryForDecode.Add(index, previousString); + } + FileStream file = File.Open(newFile, FileMode.Append); + foreach (var symbol in stringByIndex) + { + file.WriteByte((byte)symbol); + } + file.Close(); + previousString = stringByIndex; + ++index; + } + else + { + var stringByIndex = dictionaryForDecode[input]; + previousString = stringByIndex; + isFirst = false; + using FileStream file = File.Open(newFile, FileMode.Append); + foreach (var symbol in stringByIndex) + { + file.WriteByte((byte)symbol); + } + file.Close(); + } + } + return true; + } + + /// + /// Function for accepting data + /// + /// Shows decompression or compression + /// Returns whether it was executed correctly and what is the compression percentage + public static (bool, double) LzwAlgorithm(string fileName, string parameter) + { + if (parameter.Length == 2 && parameter[0] == '-' && parameter[1] == 'c') + { + double compressionRatio = 0; + var isCorrect = CodeFile(fileName, ref compressionRatio); + return (isCorrect, compressionRatio); + } + else if (parameter.Length == 2 && parameter[0] == '-' && parameter[1] == 'u') + { + return (DecodeFile(fileName), 0); + } + else + { + return (false, 0); + } + } +} \ No newline at end of file diff --git a/LZW/LZW/LZW.csproj b/LZW/LZW/LZW.csproj new file mode 100644 index 0000000..f02677b --- /dev/null +++ b/LZW/LZW/LZW.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/LZW/LZW/Program.cs b/LZW/LZW/Program.cs new file mode 100644 index 0000000..c3c8a86 --- /dev/null +++ b/LZW/LZW/Program.cs @@ -0,0 +1,10 @@ +using LZW; + + +var (isCorrect, compressionRatio) = LZWAlgorithm.LzwAlgorithm(args[0], args[1]); +if (!isCorrect) +{ + Console.WriteLine("Problems..."); + return; +} +Console.WriteLine(compressionRatio); \ No newline at end of file diff --git a/LZW/TestLZW/TestLZW.cs b/LZW/TestLZW/TestLZW.cs new file mode 100644 index 0000000..242673f --- /dev/null +++ b/LZW/TestLZW/TestLZW.cs @@ -0,0 +1,51 @@ +namespace TestLZW; + +using LZW; + +public class Tests +{ + [Test] + public void TheLZWShouldWorkCorrectlyToReturnTheCorrectValueOnASimpleExample() + { + var (isCorrect, _) = LZWAlgorithm.LzwAlgorithm(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "testAnElementaryExample.txt"), "-c"); + Assert.That(!isCorrect, Is.EqualTo(false)); + (isCorrect, var _) = LZWAlgorithm.LzwAlgorithm(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "testAnElementaryExample.txt.zipped"), "-u"); + Assert.That(!isCorrect, Is.EqualTo(false)); + string correctText = File.ReadAllText(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "correctTestAnElementaryExample.txt")); + string fromLZWText = File.ReadAllText(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "testAnElementaryExample.txt")); + Assert.That(correctText, Is.EqualTo(fromLZWText)); + } + + [Test] + public void LZWCodeingShouldReturnFalseWhenReceivingAFileWithAnIncorrectName() + { + var (isCorrect, _) = LZWAlgorithm.LzwAlgorithm("ds", "-c"); + Assert.False(isCorrect); + } + + [Test] + public void LZWDecodeingShouldReturnFalseWhenReceivingAFileWithAnIncorrectName() + { + var (isCorrect, _) = LZWAlgorithm.LzwAlgorithm("ds", "-u"); + Assert.False(isCorrect); + } + + [Test] + public void LZWShouldReturnFalseWhenReceivingAFileWithAnIncorrectParametr() + { + var (isCorrect, _) = LZWAlgorithm.LzwAlgorithm("ds", "-e"); + Assert.False(isCorrect); + } + + [Test] + public void TheLZWShouldReturnAnEmptyFileEmpty() + { + var (isCorrect, _) = LZWAlgorithm.LzwAlgorithm(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "emptyFile.txt"), "-c"); + Assert.That(!isCorrect, Is.EqualTo(false)); + (isCorrect, var _) = LZWAlgorithm.LzwAlgorithm(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "emptyFile.txt.zipped"), "-u"); + Assert.That(!isCorrect, Is.EqualTo(false)); + string fromLZWText = File.ReadAllText(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "emptyFile.txt")); + string correctText = File.ReadAllText(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestsForLZW", "correctEmptyFile.txt")); + Assert.That(correctText, Is.EqualTo(fromLZWText)); + } +} \ No newline at end of file diff --git a/LZW/TestLZW/TestLZW.csproj b/LZW/TestLZW/TestLZW.csproj new file mode 100644 index 0000000..cfd3a15 --- /dev/null +++ b/LZW/TestLZW/TestLZW.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/LZW/TestLZW/Usings.cs b/LZW/TestLZW/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/LZW/TestLZW/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/correctEmptyFile.txt b/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/correctEmptyFile.txt new file mode 100644 index 0000000..e69de29 diff --git a/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/correctTestAnElementaryExample.txt b/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/correctTestAnElementaryExample.txt new file mode 100644 index 0000000..1acd2c7 --- /dev/null +++ b/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/correctTestAnElementaryExample.txt @@ -0,0 +1 @@ +abacabadabae \ No newline at end of file diff --git a/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/emptyFile.txt b/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/emptyFile.txt new file mode 100644 index 0000000..e69de29 diff --git a/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/testAnElementaryExample.txt b/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/testAnElementaryExample.txt new file mode 100644 index 0000000..1acd2c7 --- /dev/null +++ b/LZW/TestLZW/bin/Debug/net7.0/TestsForLZW/testAnElementaryExample.txt @@ -0,0 +1 @@ +abacabadabae \ No newline at end of file diff --git a/LZW/TestsBor/TestsBor.cs b/LZW/TestsBor/TestsBor.cs new file mode 100644 index 0000000..becd865 --- /dev/null +++ b/LZW/TestsBor/TestsBor.cs @@ -0,0 +1,54 @@ +namespace TestsForBor; + +using Bor; + +public class TestsForBor +{ + private Bor bor; + + [SetUp] + public void Setup() + { + bor = new Bor(); + } + + [Test] + public void TheAddedElementShouldbeFoundInBor() + { + Setup(); + bor.Add("end".ToCharArray(), 0, 2); + Assert.True(bor.Contains("end".ToCharArray(), 0, 2), "Problems with the addition test!\n"); + } + + [Test] + public void WhenAddingTwoLinesToBorTheBorShouldIncludeTwoLines() + { + Setup(); + bor.Add("endProgram".ToCharArray(), 0, 9); + bor.Add("endFunction".ToCharArray(), 0, 10); + Assert.True(bor.HowManyStringsInBor() == 2, "Problems with the prefix test!"); + } + + [Test] + public void WhenAddingTheSymbolFlowInTheBorShouldBeCorrect() + { + Setup(); + bor.Add("a".ToCharArray(), 0, 0); + bor.Add("b".ToCharArray(), 0, 0); + Assert.True(bor.ReturnSymbolCodeByCharArray("b".ToCharArray(), 0, 0) == 1, "Problems with the prefix test!"); + } + + [Test] + public void TheInitializedBorShouldBeEmpty() + { + Setup(); + Assert.False(bor.Contains("end".ToCharArray(), 0, 2), "Problems with the prefix test!"); + } + + [Test] + public void ThereShouldBeNoThreadsInTheCreatedBor() + { + Assert.True(bor.ReturnSymbolCodeByCharArray("".ToCharArray(), 0, 0) == -1, "Problems with the prefix test!"); + } + +} \ No newline at end of file diff --git a/LZW/TestsBor/TestsBor.csproj b/LZW/TestsBor/TestsBor.csproj new file mode 100644 index 0000000..cfd3a15 --- /dev/null +++ b/LZW/TestsBor/TestsBor.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/LZW/TestsBor/Usings.cs b/LZW/TestsBor/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/LZW/TestsBor/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file