diff --git a/TestTask/IReadOnlyStream.cs b/TestTask/IReadOnlyStream.cs index 6946745..0ddbaf0 100644 --- a/TestTask/IReadOnlyStream.cs +++ b/TestTask/IReadOnlyStream.cs @@ -1,12 +1,13 @@ -namespace TestTask +using System; + +namespace TestTask { /// /// Интерфейс для работы с файлом в сильно урезаном виде. /// Умеет всего 2 вещи: прочитать символ, и перемотать стрим на начало. /// - internal interface IReadOnlyStream + internal interface IReadOnlyStream: IDisposable { - // TODO : Необходимо доработать данный интерфейс для обеспечения гарантированного закрытия файла, по окончанию работы с таковым! char ReadNextChar(); void ResetPositionToStart(); diff --git a/TestTask/LetterStats.cs b/TestTask/LetterStats.cs index aa10728..2919bc0 100644 --- a/TestTask/LetterStats.cs +++ b/TestTask/LetterStats.cs @@ -3,16 +3,16 @@ /// /// Статистика вхождения буквы/пары букв /// - public struct LetterStats + public class LetterStats { /// /// Буква/Пара букв для учёта статистики. /// - public string Letter; + public string Letter { get; set; } /// /// Кол-во вхождений буквы/пары. /// - public int Count; + public int Count{get;set;} } } diff --git a/TestTask/Program.cs b/TestTask/Program.cs index fdf048e..68be70a 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; namespace TestTask { public class Program { + private static readonly HashSet _vowels = new HashSet("ЁУЕЫАОЭЯИЮ"); /// /// Программа принимает на входе 2 пути до файлов. @@ -16,19 +19,44 @@ public class Program /// Второй параметр - путь до второго файла. static void Main(string[] args) { - IReadOnlyStream inputStream1 = GetInputStream(args[0]); - IReadOnlyStream inputStream2 = GetInputStream(args[1]); + if (args == null|| args.Length <2) + { + Console.WriteLine("Нужно указать пути к двум файлам"); + Console.ReadKey(true); + return; + } + + try + { + using (IReadOnlyStream inputStream1 = GetInputStream(args[0])) + using (IReadOnlyStream inputStream2 = GetInputStream(args[1])) + { + IList singleLetterStats = FillSingleLetterStats(inputStream1); + IList doubleLetterStats = FillDoubleLetterStats(inputStream2); - IList singleLetterStats = FillSingleLetterStats(inputStream1); - IList doubleLetterStats = FillDoubleLetterStats(inputStream2); + RemoveCharStatsByType(singleLetterStats, CharType.Vowel); + RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); - RemoveCharStatsByType(singleLetterStats, CharType.Vowel); - RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); + PrintStatistic(singleLetterStats); + PrintStatistic(doubleLetterStats); + } - PrintStatistic(singleLetterStats); - PrintStatistic(doubleLetterStats); + } + catch (FileNotFoundException ex) + { + Console.WriteLine("Ошибка.Файл не найден"); + } + catch (Exception ex) + { + Console.WriteLine($"Ошибка.{ex.Message}"); + } + finally + { + Console.WriteLine("Нажмите любую клавишу, чтобы выйти"); + Console.ReadKey(true); + } + - // TODO : Необжодимо дождаться нажатия клавиши, прежде чем завершать выполнение программы. } /// @@ -48,17 +76,37 @@ private static IReadOnlyStream GetInputStream(string fileFullPath) /// Стрим для считывания символов для последующего анализа /// Коллекция статистик по каждой букве, что была прочитана из стрима. private static IList FillSingleLetterStats(IReadOnlyStream stream) - { + { + var statDict=new Dictionary(); stream.ResetPositionToStart(); while (!stream.IsEof) { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - регистрозависимый. + char c; + try + { + c = stream.ReadNextChar(); + } + catch (EndOfStreamException) + { + break; + } + + if (!char.IsLetter(c)) + { + continue; + } + + string key = c.ToString(); + if (statDict.TryGetValue(key, out var stat)) + { + IncStatistic(stat); + } + else + { + statDict[key] = new LetterStats { Letter = key, Count = 1 }; + } } - - //return ???; - - throw new NotImplementedException(); + return statDict.Values.ToList(); } /// @@ -70,16 +118,50 @@ private static IList FillSingleLetterStats(IReadOnlyStream stream) /// Коллекция статистик по каждой букве, что была прочитана из стрима. private static IList FillDoubleLetterStats(IReadOnlyStream stream) { + var statDict=new Dictionary(); + char? prevC = null; stream.ResetPositionToStart(); while (!stream.IsEof) { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - НЕ регистрозависимый. + char c; + try + { + c = stream.ReadNextChar(); + } + catch (EndOfStreamException) + { + break; + } + + if(!char.IsLetter(c)) + { + prevC=null; + continue; + } + + char upperC=char.ToUpper(c); + if (prevC.HasValue && prevC.Value == upperC) + { + string pair = new string(new char[] { upperC, upperC }); + if (statDict.TryGetValue(pair, out var stat)) + { + IncStatistic(stat); + } + else + { + statDict[pair] = new LetterStats { Letter = pair, Count = 1 }; + } + prevC=null; + } + else + { + prevC=upperC; + } + + } - //return ???; - - throw new NotImplementedException(); + return statDict.Values.ToList(); } /// @@ -91,15 +173,40 @@ private static IList FillDoubleLetterStats(IReadOnlyStream stream) /// Тип букв для анализа private static void RemoveCharStatsByType(IList letters, CharType charType) { - // TODO : Удалить статистику по запрошенному типу букв. - switch (charType) + for(int i=letters.Count-1; i >= 0; i--) { - case CharType.Consonants: - break; - case CharType.Vowel: - break; + var charStat = letters[i]; + if (string.IsNullOrEmpty(charStat.Letter)) + { + continue; + } + + bool isVowel = IsVowel(charStat.Letter[0] ); + bool isRemove = true; + + switch (charType) + { + case CharType.Consonants: + isRemove = !isVowel; + break; + case CharType.Vowel: + isRemove = isVowel; + break; + } + + if (isRemove) + { + letters.RemoveAt(i); + } + } - + + + } + + private static bool IsVowel(char c) + { + return _vowels.Contains(char.ToUpper(c)); } /// @@ -111,8 +218,25 @@ private static void RemoveCharStatsByType(IList letters, CharType c /// Коллекция со статистикой private static void PrintStatistic(IEnumerable letters) { - // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! - throw new NotImplementedException(); + if (letters == null) + { + return; + } + + var sortLet=letters.OrderBy(x => x.Letter,StringComparer.Ordinal).ToList(); + if (sortLet.Count == 0) + { + Console.WriteLine("нет данных"); + return; + } + + int allCount = 0; + foreach (var c in sortLet) + { + Console.WriteLine($"{c.Letter} : {c.Count}"); + allCount+=c.Count; + } + Console.WriteLine($"ИТОГО: {allCount}"); } /// diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index a51a61e..4f45b71 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -1,11 +1,13 @@ using System; using System.IO; +using System.Runtime.Remoting.Channels; +using System.Text; namespace TestTask { public class ReadOnlyStream : IReadOnlyStream { - private Stream _localStream; + private StreamReader _localStream; /// /// Конструктор класса. @@ -13,12 +15,21 @@ public class ReadOnlyStream : IReadOnlyStream /// обеспечить ГАРАНТИРОВАННОЕ закрытие файла после окончания работы с таковым! /// /// Полный путь до файла для чтения - public ReadOnlyStream(string fileFullPath) + /// + public ReadOnlyStream(string fileFullPath): this (fileFullPath, Encoding.UTF8) { } + public ReadOnlyStream(string fileFullPath, Encoding enc) { - IsEof = true; + if (string.IsNullOrWhiteSpace(fileFullPath)) + { + throw new ArgumentNullException(nameof(fileFullPath),"Ошибка. Пустой путь до файла"); + } + if(!File.Exists(fileFullPath)) + { + throw new FileNotFoundException("Ошибка. Файл не найден",fileFullPath); + } - // TODO : Заменить на создание реального стрима для чтения файла! - _localStream = null; + _localStream = new StreamReader(fileFullPath, enc); + IsEof=_localStream.EndOfStream; } /// @@ -26,10 +37,16 @@ public ReadOnlyStream(string fileFullPath) /// public bool IsEof { - get; // TODO : Заполнять данный флаг при достижении конца файла/стрима при чтении + get; private set; } + public void Dispose() + { + _localStream?.Dispose(); + _localStream= null; + } + /// /// Ф-ция чтения следующего символа из потока. /// Если произведена попытка прочитать символ после достижения конца файла, метод @@ -38,8 +55,24 @@ public bool IsEof /// Считанный символ. public char ReadNextChar() { - // TODO : Необходимо считать очередной символ из _localStream - throw new NotImplementedException(); + if (_localStream == null) + { + throw new ObjectDisposedException(nameof(ReadOnlyStream)); + } + if (IsEof) + { + throw new EndOfStreamException("Коней файла"); + } + + int data = _localStream.Read(); + if (data == -1) + { + IsEof = true; + throw new EndOfStreamException("Коней файла"); + } + + IsEof = _localStream.EndOfStream; + return (char)data; } /// @@ -47,14 +80,14 @@ public char ReadNextChar() /// public void ResetPositionToStart() { - if (_localStream == null) + if(_localStream == null) { - IsEof = true; + IsEof=true; return; } - - _localStream.Position = 0; - IsEof = false; + _localStream.DiscardBufferedData(); + _localStream.BaseStream.Seek(0,SeekOrigin.Begin); + IsEof = _localStream.EndOfStream; } } } diff --git a/TestTask/TestTask.csproj b/TestTask/TestTask.csproj index dc470b0..9903e02 100644 --- a/TestTask/TestTask.csproj +++ b/TestTask/TestTask.csproj @@ -8,9 +8,10 @@ Exe TestTask TestTask - v4.6.1 + v4.8 512 true + AnyCPU @@ -49,5 +50,8 @@ + + + \ No newline at end of file diff --git a/TestTask/app.config b/TestTask/app.config new file mode 100644 index 0000000..3e0e37c --- /dev/null +++ b/TestTask/app.config @@ -0,0 +1,3 @@ + + + diff --git a/testdata/test1.txt b/testdata/test1.txt new file mode 100644 index 0000000..bf9afa8 --- /dev/null +++ b/testdata/test1.txt @@ -0,0 +1,2 @@ +АаБбВвАБВ555пп77атТ +ШШ5 \ No newline at end of file diff --git a/testdata/test2.txt b/testdata/test2.txt new file mode 100644 index 0000000..0aaaaef --- /dev/null +++ b/testdata/test2.txt @@ -0,0 +1,2 @@ +ААааОоБбССеееСссС676 +*/]рр И ии ООООоО \ No newline at end of file