Task3 solution implementation
This commit is contained in:
parent
375ed55be3
commit
f7ab102def
6 changed files with 428 additions and 0 deletions
31
Task3/Task3.sln
Normal file
31
Task3/Task3.sln
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32407.343
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Task3", "Task3\Task3.csproj", "{39C4AE67-6CF5-4E99-8FAD-6889450F73F7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Task3Test", "Task3Test\Task3Test.csproj", "{E440F0AF-635C-4BA8-BC05-B068C1E29B91}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{39C4AE67-6CF5-4E99-8FAD-6889450F73F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{39C4AE67-6CF5-4E99-8FAD-6889450F73F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{39C4AE67-6CF5-4E99-8FAD-6889450F73F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{39C4AE67-6CF5-4E99-8FAD-6889450F73F7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E440F0AF-635C-4BA8-BC05-B068C1E29B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E440F0AF-635C-4BA8-BC05-B068C1E29B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E440F0AF-635C-4BA8-BC05-B068C1E29B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E440F0AF-635C-4BA8-BC05-B068C1E29B91}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {CEB485EA-C695-4C15-A390-6249B91DA744}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
26
Task3/Task3/Program.cs
Normal file
26
Task3/Task3/Program.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using Task3;
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
string path = args[0];
|
||||
bool doParallel = args.Length > 1 && args[1] == "-p";
|
||||
try
|
||||
{
|
||||
WordCounter wordCounter = new WordCounter(path);
|
||||
wordCounter.ProcessText(doParallel: doParallel);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WordCounter wordCounter = new WordCounter();
|
||||
wordCounter.ProcessTextConsole();
|
||||
}
|
||||
|
10
Task3/Task3/Task3.csproj
Normal file
10
Task3/Task3/Task3.csproj
Normal file
|
@ -0,0 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
265
Task3/Task3/WordCounter.cs
Normal file
265
Task3/Task3/WordCounter.cs
Normal file
|
@ -0,0 +1,265 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Task3
|
||||
{
|
||||
public class WordCounter
|
||||
{
|
||||
protected const int DEFAULT_MEMORY_LIMIT = 1_048_576; // 1 Mb
|
||||
|
||||
/// <summary>
|
||||
/// Memory limit for reading data in bytes, set to 1_048_576 bytes
|
||||
/// </summary>
|
||||
public int MemoryLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// StreamReader for line by line data input, used by the TextProcessor to read data
|
||||
/// </summary>
|
||||
public StreamReader? Input { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// StreamWriter for line by line data output, used by the TextProcessor to write data
|
||||
/// </summary>
|
||||
public StreamWriter Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encoding for reading and writing data
|
||||
/// </summary>
|
||||
public Encoding TextEncoding { get; set; } = Encoding.UTF8;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of WordCounter with the specified memory limit for reading data, input and output streams
|
||||
/// </summary>
|
||||
/// <param name="memoryLimit">Memory limit for reading data in bytes, set to 1_048_576 by default</param>
|
||||
public WordCounter(StreamReader input, StreamWriter output, int memoryLimit = DEFAULT_MEMORY_LIMIT)
|
||||
{
|
||||
Input = input;
|
||||
Output = output;
|
||||
MemoryLimit = memoryLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of WordCounter with the specified memory limit for reading data and an input stream. Sets console as an output
|
||||
/// </summary>
|
||||
/// <param name="input">StreamWriter supplying data to the WordCounter</param>
|
||||
/// <param name="memoryLimit">Memory limit for reading data in bytes, set to 1_048_576 by default</param>
|
||||
public WordCounter(StreamReader input, int memoryLimit = DEFAULT_MEMORY_LIMIT)
|
||||
{
|
||||
Input = input;
|
||||
Output = new StreamWriter(Console.OpenStandardOutput());
|
||||
MemoryLimit = memoryLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of WordCounter with the specified memory limit for reading data, an input and an output from specified files
|
||||
/// </summary>
|
||||
/// <param name="inputFile">Name of the file used for input</param>
|
||||
/// <param name="outputFile">Name of the file used for output</param>
|
||||
/// <param name="memoryLimit">Memory limit for reading data in bytes, set to 1_048_576 by default</param>
|
||||
public WordCounter(string inputFile, string outputFile, int memoryLimit = DEFAULT_MEMORY_LIMIT)
|
||||
{
|
||||
SetFileInput(inputFile);
|
||||
SetFileOutput(outputFile);
|
||||
MemoryLimit = memoryLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of WordCounter with the specified memory limit for reading data and an input from specified file. Sets console as an output
|
||||
/// </summary>
|
||||
/// <param name="inputFile">Name of the file used for input</param>
|
||||
/// <param name="memoryLimit">Memory limit for reading data in bytes, set to 1_048_576 by default</param>
|
||||
public WordCounter(string inputFile, int memoryLimit = DEFAULT_MEMORY_LIMIT)
|
||||
{
|
||||
SetFileInput(inputFile);
|
||||
Output = new StreamWriter(Console.OpenStandardOutput());
|
||||
MemoryLimit = memoryLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of WordCounter with the specified memory limit for reading data.
|
||||
/// Input is set to null, should be specified somewhere later in code before calling the processing methods.
|
||||
/// Sets console as an output
|
||||
/// </summary>
|
||||
/// <param name="memoryLimit">Memory limit for reading data in bytes, set to 1_048_576 by default</param>
|
||||
public WordCounter(int memoryLimit = DEFAULT_MEMORY_LIMIT)
|
||||
{
|
||||
Output = new StreamWriter(Console.OpenStandardOutput());
|
||||
MemoryLimit = memoryLimit;
|
||||
}
|
||||
|
||||
// simple write to output line by line
|
||||
protected void WriteData(string result)
|
||||
{
|
||||
string[] lines = result.Split(' ');
|
||||
foreach (string line in lines) Output.WriteLine(line);
|
||||
}
|
||||
|
||||
// helper method for parallel counting
|
||||
protected long CountWordsParallel(char[] block) => CountWords(block, block[0], block.Length, 1);
|
||||
|
||||
// main counting logic, gets a char from previous block and a current block of chars
|
||||
// can be overriden to implement own logic of counting
|
||||
protected virtual long CountWords(char[] currentBlock, char prevChar, int blockLength, int startingIndex = 0)
|
||||
{
|
||||
long amount = 0;
|
||||
char currentChar;
|
||||
// look for the occurences of "SX", where X is any non whitespace charachter and S is a whitespace
|
||||
for (int i = startingIndex; i < blockLength; i++)
|
||||
{
|
||||
currentChar = currentBlock[i];
|
||||
if (char.IsWhiteSpace(prevChar) && !char.IsWhiteSpace(currentChar)) amount++;
|
||||
prevChar = currentChar;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
// helper method for non parallel counting
|
||||
protected virtual long CountWordsNonParallel(char[] currentBlock, char prevChar, int blockLength) => CountWords(currentBlock, prevChar, blockLength);
|
||||
|
||||
// main parallel counting logic, gets a char from previous block and a current block of chars
|
||||
// can be overriden to implement own logic of counting
|
||||
protected virtual long CountWordsParallel(char[] currentBlock, char prevChar, int blockLength)
|
||||
{
|
||||
int parts = Environment.ProcessorCount;
|
||||
if (parts > blockLength / 4) parts = blockLength / 4; // get the amount of parts to partition the array into
|
||||
// if amount of parts is greater than initial block length, reduce it to 1/4 of block length
|
||||
var partitionedBlock = PartitionBlock(currentBlock, prevChar, blockLength, parts); // partition the block
|
||||
long result = partitionedBlock.AsParallel().Sum(x => CountWordsParallel(x)); // count words in parallel
|
||||
return result;
|
||||
}
|
||||
|
||||
// supplier of block parts, used in a parallel counting methods
|
||||
protected static IEnumerable<char[]> PartitionBlock(char[] currentBlock, char prevChar, int blockLength, int parts)
|
||||
{
|
||||
int partSize = blockLength / parts; // calculate size of a part
|
||||
for (int i = 0; i < parts; i++)
|
||||
{
|
||||
char[] part = new char[partSize + 1]; // create a part with 1 extra space for a previous char from other block
|
||||
part[0] = prevChar; // set first char to that previous char
|
||||
Array.Copy(currentBlock, partSize * i, part, 1, partSize); // copy the needed elements of initial block into the part
|
||||
prevChar = part[^1]; // set the previous char to the last char of the current part
|
||||
yield return part;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the WordCounter. Reads and processes data, then writes (if doWrite is true) and returns it
|
||||
/// </summary>
|
||||
/// <param name="doWrite">If true, writes the result to specified Output, true by default</param>
|
||||
/// <param name="doParallel">If true, processes data in parallel by partitioning the blocks of data. Recommended to use with a memory limit of at least 100 Kb, true by default</param>
|
||||
/// <returns>The result of data processing</returns>
|
||||
public long ProcessText(bool doWrite = true, bool doParallel = true)
|
||||
{
|
||||
if (Input == null) throw new ArgumentNullException("No input was found");
|
||||
Func<char[], char, int, long> countingFunc = doParallel ? CountWordsParallel : CountWordsNonParallel; // determine the processing function to use (no if statememnt on every read)
|
||||
char prevChar = ' ';
|
||||
char[] currentBlock = new char[MemoryLimit]; // init buffer for the data
|
||||
int charsRead = Input.ReadBlock(currentBlock, 0, MemoryLimit); // first read
|
||||
long wordsAmount = 0;
|
||||
// while there are chars being read, continue reading
|
||||
while (charsRead > 0)
|
||||
{
|
||||
wordsAmount += countingFunc(currentBlock, prevChar, charsRead); // process data
|
||||
prevChar = currentBlock[^1]; // set the prev char to the last char of the current block
|
||||
charsRead = Input.ReadBlock(currentBlock, 0, MemoryLimit); // read data
|
||||
}
|
||||
string result = wordsAmount.ToString();
|
||||
if (doWrite) WriteData(result); // write data to output if needed
|
||||
// release resources
|
||||
Input.Dispose();
|
||||
Output.Dispose();
|
||||
return wordsAmount;
|
||||
}
|
||||
|
||||
private static FileInfo FindFile(string filePath)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(filePath);
|
||||
if (!fileInfo.Exists) throw new FileNotFoundException($"File {filePath} was not found");
|
||||
if (fileInfo.Attributes.HasFlag(FileAttributes.Directory)) throw new ArgumentException($"Expected a file, got a directory from path {filePath}");
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a file as a WordCounter input
|
||||
/// </summary>
|
||||
/// <param name="filePath">Name of the file used for input</param>
|
||||
public void SetFileInput(string filePath)
|
||||
{
|
||||
FileInfo fileInfo = FindFile(filePath);
|
||||
FileStream fileStream = fileInfo.OpenRead();
|
||||
StreamReader reader = new StreamReader(fileStream, TextEncoding);
|
||||
Input = reader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a file as a WordCounter output
|
||||
/// </summary>
|
||||
/// <param name="filePath">Name of the file used for output</param>
|
||||
public void SetFileOutput(string filePath)
|
||||
{
|
||||
FileInfo fileInfo = FindFile(filePath);
|
||||
FileStream fileStream = fileInfo.OpenWrite();
|
||||
StreamWriter writer = new StreamWriter(fileStream, TextEncoding);
|
||||
Output = writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the WordCounter using console as input. Reads and processes data, then writes (if doWrite is true) and returns it
|
||||
/// </summary>
|
||||
/// <param name="doWrite">If true, writes the result to specified Output, true by default</param>
|
||||
/// <param name="doParallel">If true, processes data in parallel by partitioning the blocks of data. Recommended to use with a memory limit of at least 100 Kb, false by default</param>
|
||||
/// <returns>The result of data processing</returns>
|
||||
public long ProcessTextConsole(bool doWrite = true, bool doParallel = false)
|
||||
{
|
||||
Console.WriteLine($"Подсчёт слов в тексте, нажмите Ctrl-Z для окончания ввода");
|
||||
string? line;
|
||||
MemoryStream stream = new MemoryStream();
|
||||
StreamWriter sw = new StreamWriter(stream);
|
||||
while (true)
|
||||
{
|
||||
line = Console.ReadLine();
|
||||
if (line == null || line.Contains('\u001A')) break;
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
sw.Flush();
|
||||
stream.Position = 0;
|
||||
Input = new StreamReader(stream);
|
||||
return ProcessText(doWrite, doParallel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the WordCounter using string value as input. Reads and processes data, then writes (if doWrite is true) and returns it
|
||||
/// </summary>
|
||||
/// <param name="data">If true, writes the result to specified Output, true by default</param>
|
||||
/// <param name="doWrite">If true, writes the result to specified Output, false by default</param>
|
||||
/// <param name="doParallel">If true, processes data in parallel by partitioning the blocks of data. Recommended to use with a memory limit of at least 100 Kb, false by default</param>
|
||||
/// <returns>The result of data processing</returns>
|
||||
public long ProcessString(string data, bool doWrite = false, bool doParallel = false)
|
||||
{
|
||||
SetStringInput(data);
|
||||
return ProcessText(doWrite, doParallel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a string value as a WordCounter input
|
||||
/// </summary>
|
||||
/// <param name="data">A string value</param>
|
||||
public void SetStringInput(string data)
|
||||
{
|
||||
MemoryStream stream = new MemoryStream();
|
||||
StreamWriter sw = new StreamWriter(stream);
|
||||
sw.Write(data);
|
||||
sw.Flush();
|
||||
stream.Position = 0;
|
||||
Input = new StreamReader(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets console as WordCounter output
|
||||
/// </summary>
|
||||
public void SetConsoleOutput() => Output = new StreamWriter(Console.OpenStandardOutput());
|
||||
}
|
||||
}
|
21
Task3/Task3Test/Task3Test.csproj
Normal file
21
Task3/Task3Test/Task3Test.csproj
Normal file
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Task3\Task3.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
75
Task3/Task3Test/WordCounterTests.cs
Normal file
75
Task3/Task3Test/WordCounterTests.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Task3;
|
||||
|
||||
namespace Task3Test
|
||||
{
|
||||
[TestClass]
|
||||
public class WordCounterTests
|
||||
{
|
||||
private readonly WordCounter wordCounter = new WordCounter();
|
||||
|
||||
[TestMethod]
|
||||
public void CountingTest()
|
||||
{
|
||||
long expected = 5;
|
||||
long actual = wordCounter.ProcessString("one two three four five");
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleSpacesTest()
|
||||
{
|
||||
long expected = 5;
|
||||
long actual = wordCounter.ProcessString("one two three four five");
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TrailingAndLeadingSpacesTest()
|
||||
{
|
||||
long expected = 5;
|
||||
long actual = wordCounter.ProcessString(" one two three four five ");
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MixedTest()
|
||||
{
|
||||
long expected = 7;
|
||||
long actual = wordCounter.ProcessString(" one two three four five \n six \n \n seven ");
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParallelCountingTest()
|
||||
{
|
||||
long expected = 5;
|
||||
long actual = wordCounter.ProcessString("one two three four five", doParallel: true);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParallelMultipleSpacesTest()
|
||||
{
|
||||
long expected = 5;
|
||||
long actual = wordCounter.ProcessString("one two three four five", doParallel: true);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParallelTrailingAndLeadingSpacesTest()
|
||||
{
|
||||
long expected = 5;
|
||||
long actual = wordCounter.ProcessString(" one two three four five ", doParallel: true);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ParallelMixedTest()
|
||||
{
|
||||
long expected = 7;
|
||||
long actual = wordCounter.ProcessString(" one two three four five \n six \n \n seven ", doParallel: true);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue