From 9e17d21b8f79f4ceb44116cdc79b20c07ea8f74f Mon Sep 17 00:00:00 2001 From: Downtown-54 Date: Sat, 28 Feb 2026 12:16:45 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TestTask/IReadOnlyStream.cs | 7 +- TestTask/Program.cs | 125 +++++++++++++++++++++++++----------- TestTask/ReadOnlyStream.cs | 42 +++++++----- 3 files changed, 118 insertions(+), 56 deletions(-) diff --git a/TestTask/IReadOnlyStream.cs b/TestTask/IReadOnlyStream.cs index 6946745..db8764d 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/Program.cs b/TestTask/Program.cs index fdf048e..51c9081 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; namespace TestTask { public class Program { + private static readonly HashSet Vowels = new HashSet( + "аеёиоуыэюяАЕЁИОУЫЭЮЯaeiouAEIOU" + ); /// /// Программа принимает на входе 2 пути до файлов. @@ -16,19 +20,38 @@ public class Program /// Второй параметр - путь до второго файла. static void Main(string[] args) { - IReadOnlyStream inputStream1 = GetInputStream(args[0]); - IReadOnlyStream inputStream2 = GetInputStream(args[1]); - - IList singleLetterStats = FillSingleLetterStats(inputStream1); - IList doubleLetterStats = FillDoubleLetterStats(inputStream2); + if (args == null || args.Length < 2) + { + Console.WriteLine("Ошибка: Недостаточно аргументов или файлы не найдены"); - RemoveCharStatsByType(singleLetterStats, CharType.Vowel); - RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); + Console.WriteLine("\nНажмите любую клавишу для закрытия консоли..."); + Console.ReadKey(); + return; + } - PrintStatistic(singleLetterStats); - PrintStatistic(doubleLetterStats); + try + { + // Используем блоки using для автоматического закрытия файлов + using (IReadOnlyStream inputStream1 = GetInputStream(args[0])) + using (IReadOnlyStream inputStream2 = GetInputStream(args[1])) + { + IList singleLetterStats = FillSingleLetterStats(inputStream1); + IList doubleLetterStats = FillDoubleLetterStats(inputStream2); + + RemoveCharStatsByType(singleLetterStats, CharType.Vowel); + RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); + + PrintStatistic(singleLetterStats); + PrintStatistic(doubleLetterStats); + } + } + catch (Exception ex) + { + Console.WriteLine($"Произошла ошибка при обработке файлов: {ex.Message}"); + } - // TODO : Необжодимо дождаться нажатия клавиши, прежде чем завершать выполнение программы. + Console.WriteLine("\nНажмите любую клавишу для закрытия консоли..."); + Console.ReadKey(); } /// @@ -49,16 +72,21 @@ private static IReadOnlyStream GetInputStream(string fileFullPath) /// Коллекция статистик по каждой букве, что была прочитана из стрима. private static IList FillSingleLetterStats(IReadOnlyStream stream) { + var statsMap = new Dictionary(); stream.ResetPositionToStart(); + while (!stream.IsEof) { char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - регистрозависимый. + if (char.IsLetter(c)) + { + string key = c.ToString(); + if (!statsMap.ContainsKey(key)) statsMap[key] = 0; + statsMap[key]++; + } } - //return ???; - - throw new NotImplementedException(); + return statsMap.Select(kvp => new LetterStats { Letter = kvp.Key, Count = kvp.Value }).ToList(); } /// @@ -70,16 +98,36 @@ private static IList FillSingleLetterStats(IReadOnlyStream stream) /// Коллекция статистик по каждой букве, что была прочитана из стрима. private static IList FillDoubleLetterStats(IReadOnlyStream stream) { + var statsMap = new Dictionary(); stream.ResetPositionToStart(); + + char? prevChar = null; + while (!stream.IsEof) { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - НЕ регистрозависимый. + char current = stream.ReadNextChar(); + + if (char.IsLetter(current)) + { + if (prevChar.HasValue && char.ToLower(prevChar.Value) == char.ToLower(current)) + { + string key = (current.ToString() + current.ToString()).ToLower(); + if (!statsMap.ContainsKey(key)) statsMap[key] = 0; + statsMap[key]++; + + // После нахождения пары сбрасываем, чтобы "aaa" считалось как одна пара "aa" + prevChar = null; + continue; + } + prevChar = current; + } + else + { + prevChar = null; + } } - //return ???; - - throw new NotImplementedException(); + return statsMap.Select(kvp => new LetterStats { Letter = kvp.Key, Count = kvp.Value }).ToList(); } /// @@ -91,15 +139,19 @@ 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; + bool isVowel = Vowels.Contains(letters[i].Letter.ToLower()[0]); + + if (charType == CharType.Vowel && isVowel) + { + letters.RemoveAt(i); + } + else if (charType == CharType.Consonants && !isVowel) + { + letters.RemoveAt(i); + } } - } /// @@ -111,19 +163,16 @@ private static void RemoveCharStatsByType(IList letters, CharType c /// Коллекция со статистикой private static void PrintStatistic(IEnumerable letters) { - // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! - throw new NotImplementedException(); - } - - /// - /// Метод увеличивает счётчик вхождений по переданной структуре. - /// - /// - private static void IncStatistic(LetterStats letterStats) - { - letterStats.Count++; - } + var sorted = letters.OrderBy(l => l.Letter); + int total = 0; + foreach (var stat in sorted) + { + Console.WriteLine($"{stat.Letter} : {stat.Count}"); + total += stat.Count; + } + Console.WriteLine($"ИТОГО : {total}"); + } } } diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index a51a61e..b760172 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -1,11 +1,13 @@ using System; using System.IO; +using System.Runtime.Remoting.Messaging; namespace TestTask { public class ReadOnlyStream : IReadOnlyStream { - private Stream _localStream; + private StreamReader _localStream; + private readonly string _filePath; /// /// Конструктор класса. @@ -15,10 +17,9 @@ public class ReadOnlyStream : IReadOnlyStream /// Полный путь до файла для чтения public ReadOnlyStream(string fileFullPath) { - IsEof = true; - - // TODO : Заменить на создание реального стрима для чтения файла! - _localStream = null; + _filePath = fileFullPath; + // Инициализируем стрим сразу + ResetPositionToStart(); } /// @@ -26,7 +27,7 @@ public ReadOnlyStream(string fileFullPath) /// public bool IsEof { - get; // TODO : Заполнять данный флаг при достижении конца файла/стрима при чтении + get; private set; } @@ -38,8 +39,16 @@ public bool IsEof /// Считанный символ. public char ReadNextChar() { - // TODO : Необходимо считать очередной символ из _localStream - throw new NotImplementedException(); + if (IsEof) throw new EndOfStreamException("Достигнут конец файла"); + + int charCode = _localStream.Read(); + if (charCode == -1) + { + IsEof = true; + return '\0'; + } + + return (char)charCode; } /// @@ -47,14 +56,17 @@ public char ReadNextChar() /// public void ResetPositionToStart() { - if (_localStream == null) - { - IsEof = true; - return; - } - - _localStream.Position = 0; + _localStream?.Dispose(); + _localStream = new StreamReader(_filePath); IsEof = false; } + + /// + /// Освобождаем память + /// + public void Dispose() + { + _localStream?.Dispose(); + } } }