From 28162cc1ac29ceb64b25e6190b8a28901f27bcca Mon Sep 17 00:00:00 2001 From: nkl Date: Wed, 25 Feb 2026 16:36:04 +0700 Subject: [PATCH 01/19] chore: clean up .gitignore and ignore .cp-images --- .gitignore | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 42d3208..ad54655 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,9 @@ -## .gitignore -#.gitignore - -### -## *.html -### -## !special.html -### .. -## *.[ao] - - *.suo *.user .vs/ bin/ obj/ -packages/ \ No newline at end of file +packages/ + +# Terminal Paste Image folder +.cp-images/ From 72028232b0a719c13ad7199030767512a83ddbad Mon Sep 17 00:00:00 2001 From: nkl Date: Wed, 25 Feb 2026 16:39:58 +0700 Subject: [PATCH 02/19] style: apply auto-formatting --- README.md | 27 ++++++++++++++------------- TestTask/Program.cs | 2 +- TestTask/ReadOnlyStream.cs | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1036790..205754e 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,23 @@ # Тестовое задание. -Доброго дня. + +Доброго дня. Данный репозиторий представляет из себя тестовое задание для выполнения перед собеседованием на вакансию разработчика C# в компанию Катрен. -Репозиторий уже содержит основные классы и методы для последующей разработки. -Большая часть уже описаных методов - _заглушки_, требующие реализации. +Репозиторий уже содержит основные классы и методы для последующей разработки. +Большая часть уже описаных методов - _заглушки_, требующие реализации. -Все методы/классы документированы и описывают своё предназначение и требуемое поведение. -Таким образом, чтобы понять что именно данный метод должен делать - достаточно внимательно прочитать его документацию. +Все методы/классы документированы и описывают своё предназначение и требуемое поведение. +Таким образом, чтобы понять что именно данный метод должен делать - достаточно внимательно прочитать его документацию. -Все места, требующие реализации, помечены _TODO_ маркером. Всего таких мест 9 шт. -Все _TODO_ маркеры можно найти нажатием _"Ctrl+W, T"_ (Visual Studio 2017) или любым другим удобным для Вас способом. +Все места, требующие реализации, помечены _TODO_ маркером. Всего таких мест 9 шт. +Все _TODO_ маркеры можно найти нажатием _"Ctrl+W, T"_ (Visual Studio 2017) или любым другим удобным для Вас способом. -Исходный код содержит ошибки/недоработки, которые необходимо исправить каким-либо способом процессе разработки. +Исходный код содержит ошибки/недоработки, которые необходимо исправить каким-либо способом процессе разработки. -В рамках разработки не запрещается как-либо модифицировать уже существующий код. -А именно допускается: создание/удаление/модификация методов/классов/структур и т.д., если этого требует ваша реализация. +В рамках разработки не запрещается как-либо модифицировать уже существующий код. +А именно допускается: создание/удаление/модификация методов/классов/структур и т.д., если этого требует ваша реализация. -__Для разработки настоятельно рекомендуется сделать Fork данного проекта. -И по готовности результат отдать на ревью методом Pull Request'a.__ +**Для разработки настоятельно рекомендуется сделать Fork данного проекта. +И по готовности результат отдать на ревью методом Pull Request'a.** -Если данный способ по каким-то причинам Вам не представляется реализуемым, можете выслать результат в виде архива на почту кадровому агенту. +Если данный способ по каким-то причинам Вам не представляется реализуемым, можете выслать результат в виде архива на почту кадровому агенту. diff --git a/TestTask/Program.cs b/TestTask/Program.cs index fdf048e..514b3ac 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -99,7 +99,7 @@ private static void RemoveCharStatsByType(IList letters, CharType c case CharType.Vowel: break; } - + } /// diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index a51a61e..9f65c69 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -20,7 +20,7 @@ public ReadOnlyStream(string fileFullPath) // TODO : Заменить на создание реального стрима для чтения файла! _localStream = null; } - + /// /// Флаг окончания файла. /// From 6404732a2de6694856970fc3f7d980905965be5b Mon Sep 17 00:00:00 2001 From: nkl Date: Wed, 25 Feb 2026 16:49:07 +0700 Subject: [PATCH 03/19] chore: migrate project to SDK-style .csproj for WSL compatibility --- TestTask/TestTask.csproj | 54 +++------------------------------------- 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/TestTask/TestTask.csproj b/TestTask/TestTask.csproj index dc470b0..22f5812 100644 --- a/TestTask/TestTask.csproj +++ b/TestTask/TestTask.csproj @@ -1,53 +1,7 @@ - - - + - Debug - AnyCPU - {6E9922F2-AF75-4807-B6E4-F74499317C39} Exe - TestTask - TestTask - v4.6.1 - 512 - true + net8.0 + false - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + From 1656ac41314309e214c08c80a61e70f323017e5e Mon Sep 17 00:00:00 2001 From: nkl Date: Wed, 25 Feb 2026 17:16:21 +0700 Subject: [PATCH 04/19] refactor: rename stream variables and clarify args doc in Main --- TestTask/Program.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/TestTask/Program.cs b/TestTask/Program.cs index 514b3ac..d6d04a0 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -12,15 +12,16 @@ public class Program /// Анализирует во втором файле кол-во вхождений парных букв (не регистрозависимо). Например АА, Оо, еЕ, тт и т.д. /// По окончанию работы - выводит данную статистику на экран. /// - /// Первый параметр - путь до первого файла. - /// Второй параметр - путь до второго файла. + /// Массив аргументов командной строки: + /// args[0] - путь до первого файла, + /// args[1] - путь до второго файла. static void Main(string[] args) { - IReadOnlyStream inputStream1 = GetInputStream(args[0]); - IReadOnlyStream inputStream2 = GetInputStream(args[1]); + IReadOnlyStream singleLetterStream = GetInputStream(args[0]); + IReadOnlyStream doubleLetterStream = GetInputStream(args[1]); - IList singleLetterStats = FillSingleLetterStats(inputStream1); - IList doubleLetterStats = FillDoubleLetterStats(inputStream2); + IList singleLetterStats = FillSingleLetterStats(singleLetterStream); + IList doubleLetterStats = FillDoubleLetterStats(doubleLetterStream); RemoveCharStatsByType(singleLetterStats, CharType.Vowel); RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); From a27f1c61e4ca3fbaef35ad1637f4721aff097171 Mon Sep 17 00:00:00 2001 From: nkl Date: Wed, 25 Feb 2026 17:19:29 +0700 Subject: [PATCH 05/19] fix: add Console.ReadKey to prevent immediate exit --- TestTask/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestTask/Program.cs b/TestTask/Program.cs index d6d04a0..898ee33 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -29,7 +29,7 @@ static void Main(string[] args) PrintStatistic(singleLetterStats); PrintStatistic(doubleLetterStats); - // TODO : Необжодимо дождаться нажатия клавиши, прежде чем завершать выполнение программы. + Console.ReadKey(); } /// From 064e438a75dca83a36713b049294188602cfbbf9 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 10:57:44 +0700 Subject: [PATCH 06/19] refactor: rename _localStream to _fileStream for clarity --- TestTask/ReadOnlyStream.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index 9f65c69..166c66d 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -5,7 +5,7 @@ namespace TestTask { public class ReadOnlyStream : IReadOnlyStream { - private Stream _localStream; + private Stream _fileStream; /// /// Конструктор класса. @@ -18,7 +18,7 @@ public ReadOnlyStream(string fileFullPath) IsEof = true; // TODO : Заменить на создание реального стрима для чтения файла! - _localStream = null; + _fileStream = null; } /// @@ -47,13 +47,13 @@ public char ReadNextChar() /// public void ResetPositionToStart() { - if (_localStream == null) + if (_fileStream == null) { IsEof = true; return; } - _localStream.Position = 0; + _fileStream.Position = 0; IsEof = false; } } From 37d5d34af2f4b1a68f1877956dee0147af68b464 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 11:22:50 +0700 Subject: [PATCH 07/19] fix: throw InvalidOperationException when stream is not initialized --- TestTask/ReadOnlyStream.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index 166c66d..5579d37 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -49,8 +49,7 @@ public void ResetPositionToStart() { if (_fileStream == null) { - IsEof = true; - return; + throw new InvalidOperationException("Stream is not initialized."); } _fileStream.Position = 0; From 5e79e800fb68bb25974d3836770d05ff90c7c34a Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 11:51:29 +0700 Subject: [PATCH 08/19] fix: replace Stream with StreamReader to support character encoding --- TestTask/ReadOnlyStream.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index 5579d37..6b2f684 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -5,7 +5,7 @@ namespace TestTask { public class ReadOnlyStream : IReadOnlyStream { - private Stream _fileStream; + private readonly StreamReader _fileStream; /// /// Конструктор класса. @@ -52,7 +52,8 @@ public void ResetPositionToStart() throw new InvalidOperationException("Stream is not initialized."); } - _fileStream.Position = 0; + _fileStream.BaseStream.Position = 0; + _fileStream.DiscardBufferedData(); IsEof = false; } } From 2b8877ff5f80c9bef51d465f3990facc869a2c5b Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 13:33:08 +0700 Subject: [PATCH 09/19] feat: implement ReadOnlyStream constructor and ReadNextChar --- TestTask/ReadOnlyStream.cs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index 6b2f684..37263a4 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -8,17 +8,15 @@ public class ReadOnlyStream : IReadOnlyStream private readonly StreamReader _fileStream; /// - /// Конструктор класса. - /// Т.к. происходит прямая работа с файлом, необходимо + /// Конструктор класса. + /// Т.к. происходит прямая работа с файлом, необходимо /// обеспечить ГАРАНТИРОВАННОЕ закрытие файла после окончания работы с таковым! /// /// Полный путь до файла для чтения public ReadOnlyStream(string fileFullPath) { - IsEof = true; - - // TODO : Заменить на создание реального стрима для чтения файла! - _fileStream = null; + _fileStream = new StreamReader(fileFullPath); + IsEof = false; } /// @@ -26,20 +24,31 @@ public ReadOnlyStream(string fileFullPath) /// public bool IsEof { - get; // TODO : Заполнять данный флаг при достижении конца файла/стрима при чтении + get; private set; } /// /// Ф-ция чтения следующего символа из потока. - /// Если произведена попытка прочитать символ после достижения конца файла, метод - /// должен бросать соответствующее исключение + /// Если произведена попытка прочитать символ после достижения конца файла, метод + /// бросит соответствующее исключение /// /// Считанный символ. public char ReadNextChar() { - // TODO : Необходимо считать очередной символ из _localStream - throw new NotImplementedException(); + if (IsEof) + { + throw new InvalidOperationException("Stream is already at end of file"); + } + + var nextChar = (char)_fileStream.Read(); + + if (_fileStream.EndOfStream) + { + IsEof = true; + } + + return nextChar; } /// From fb92fb9f632c67e2d4946d7acdd69b57bb2a667b Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 14:45:09 +0700 Subject: [PATCH 10/19] feat: implement IDisposable to ensure guaranteed file closure --- TestTask/IReadOnlyStream.cs | 7 ++++--- TestTask/ReadOnlyStream.cs | 12 ++++++++++-- 2 files changed, 14 insertions(+), 5 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/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index 37263a4..cafb076 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -9,8 +9,7 @@ public class ReadOnlyStream : IReadOnlyStream /// /// Конструктор класса. - /// Т.к. происходит прямая работа с файлом, необходимо - /// обеспечить ГАРАНТИРОВАННОЕ закрытие файла после окончания работы с таковым! + /// Класс реализует IDisposable — используйте using/Dispose для гарантированного закрытия файла. /// /// Полный путь до файла для чтения public ReadOnlyStream(string fileFullPath) @@ -65,5 +64,14 @@ public void ResetPositionToStart() _fileStream.DiscardBufferedData(); IsEof = false; } + + /// + /// Освобождает ресурсы и закрывает файл. + /// + public void Dispose() + { + _fileStream.Dispose(); + GC.SuppressFinalize(this); + } } } From 539dac49ad7a28031d12c45f029bffd983f03288 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 14:48:38 +0700 Subject: [PATCH 11/19] chore: add .gitattributes and normalize line endings to LF --- .gitattributes | 1 + .gitignore | 18 +- TestTask/CharType.cs | 36 ++-- TestTask/IReadOnlyStream.cs | 34 ++-- TestTask/LetterStats.cs | 36 ++-- TestTask/Program.cs | 260 ++++++++++++++-------------- TestTask/Properties/AssemblyInfo.cs | 72 ++++---- TestTask/ReadOnlyStream.cs | 154 ++++++++-------- TestTask/TestTask.csproj | 14 +- TestTask/TestTask.sln | 50 +++--- 10 files changed, 338 insertions(+), 337 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore index ad54655..504dc97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ -*.suo -*.user -.vs/ -bin/ -obj/ -packages/ - -# Terminal Paste Image folder -.cp-images/ +*.suo +*.user +.vs/ +bin/ +obj/ +packages/ + +# Terminal Paste Image folder +.cp-images/ diff --git a/TestTask/CharType.cs b/TestTask/CharType.cs index 899564d..587a031 100644 --- a/TestTask/CharType.cs +++ b/TestTask/CharType.cs @@ -1,18 +1,18 @@ -namespace TestTask -{ - /// - /// Тип букв - /// - public enum CharType - { - /// - /// Гласные - /// - Vowel, - - /// - /// Согласные - /// - Consonants - } -} +namespace TestTask +{ + /// + /// Тип букв + /// + public enum CharType + { + /// + /// Гласные + /// + Vowel, + + /// + /// Согласные + /// + Consonants + } +} diff --git a/TestTask/IReadOnlyStream.cs b/TestTask/IReadOnlyStream.cs index db8764d..e615414 100644 --- a/TestTask/IReadOnlyStream.cs +++ b/TestTask/IReadOnlyStream.cs @@ -1,17 +1,17 @@ -using System; - -namespace TestTask -{ - /// - /// Интерфейс для работы с файлом в сильно урезаном виде. - /// Умеет всего 2 вещи: прочитать символ, и перемотать стрим на начало. - /// - internal interface IReadOnlyStream : IDisposable - { - char ReadNextChar(); - - void ResetPositionToStart(); - - bool IsEof { get; } - } -} +using System; + +namespace TestTask +{ + /// + /// Интерфейс для работы с файлом в сильно урезаном виде. + /// Умеет всего 2 вещи: прочитать символ, и перемотать стрим на начало. + /// + internal interface IReadOnlyStream : IDisposable + { + char ReadNextChar(); + + void ResetPositionToStart(); + + bool IsEof { get; } + } +} diff --git a/TestTask/LetterStats.cs b/TestTask/LetterStats.cs index aa10728..90611af 100644 --- a/TestTask/LetterStats.cs +++ b/TestTask/LetterStats.cs @@ -1,18 +1,18 @@ -namespace TestTask -{ - /// - /// Статистика вхождения буквы/пары букв - /// - public struct LetterStats - { - /// - /// Буква/Пара букв для учёта статистики. - /// - public string Letter; - - /// - /// Кол-во вхождений буквы/пары. - /// - public int Count; - } -} +namespace TestTask +{ + /// + /// Статистика вхождения буквы/пары букв + /// + public struct LetterStats + { + /// + /// Буква/Пара букв для учёта статистики. + /// + public string Letter; + + /// + /// Кол-во вхождений буквы/пары. + /// + public int Count; + } +} diff --git a/TestTask/Program.cs b/TestTask/Program.cs index 898ee33..b29e20b 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -1,130 +1,130 @@ -using System; -using System.Collections.Generic; - -namespace TestTask -{ - public class Program - { - - /// - /// Программа принимает на входе 2 пути до файлов. - /// Анализирует в первом файле кол-во вхождений каждой буквы (регистрозависимо). Например А, б, Б, Г и т.д. - /// Анализирует во втором файле кол-во вхождений парных букв (не регистрозависимо). Например АА, Оо, еЕ, тт и т.д. - /// По окончанию работы - выводит данную статистику на экран. - /// - /// Массив аргументов командной строки: - /// args[0] - путь до первого файла, - /// args[1] - путь до второго файла. - static void Main(string[] args) - { - IReadOnlyStream singleLetterStream = GetInputStream(args[0]); - IReadOnlyStream doubleLetterStream = GetInputStream(args[1]); - - IList singleLetterStats = FillSingleLetterStats(singleLetterStream); - IList doubleLetterStats = FillDoubleLetterStats(doubleLetterStream); - - RemoveCharStatsByType(singleLetterStats, CharType.Vowel); - RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); - - PrintStatistic(singleLetterStats); - PrintStatistic(doubleLetterStats); - - Console.ReadKey(); - } - - /// - /// Ф-ция возвращает экземпляр потока с уже загруженным файлом для последующего посимвольного чтения. - /// - /// Полный путь до файла для чтения - /// Поток для последующего чтения. - private static IReadOnlyStream GetInputStream(string fileFullPath) - { - return new ReadOnlyStream(fileFullPath); - } - - /// - /// Ф-ция считывающая из входящего потока все буквы, и возвращающая коллекцию статистик вхождения каждой буквы. - /// Статистика РЕГИСТРОЗАВИСИМАЯ! - /// - /// Стрим для считывания символов для последующего анализа - /// Коллекция статистик по каждой букве, что была прочитана из стрима. - private static IList FillSingleLetterStats(IReadOnlyStream stream) - { - stream.ResetPositionToStart(); - while (!stream.IsEof) - { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - регистрозависимый. - } - - //return ???; - - throw new NotImplementedException(); - } - - /// - /// Ф-ция считывающая из входящего потока все буквы, и возвращающая коллекцию статистик вхождения парных букв. - /// В статистику должны попадать только пары из одинаковых букв, например АА, СС, УУ, ЕЕ и т.д. - /// Статистика - НЕ регистрозависимая! - /// - /// Стрим для считывания символов для последующего анализа - /// Коллекция статистик по каждой букве, что была прочитана из стрима. - private static IList FillDoubleLetterStats(IReadOnlyStream stream) - { - stream.ResetPositionToStart(); - while (!stream.IsEof) - { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - НЕ регистрозависимый. - } - - //return ???; - - throw new NotImplementedException(); - } - - /// - /// Ф-ция перебирает все найденные буквы/парные буквы, содержащие в себе только гласные или согласные буквы. - /// (Тип букв для перебора определяется параметром charType) - /// Все найденные буквы/пары соответствующие параметру поиска - удаляются из переданной коллекции статистик. - /// - /// Коллекция со статистиками вхождения букв/пар - /// Тип букв для анализа - private static void RemoveCharStatsByType(IList letters, CharType charType) - { - // TODO : Удалить статистику по запрошенному типу букв. - switch (charType) - { - case CharType.Consonants: - break; - case CharType.Vowel: - break; - } - - } - - /// - /// Ф-ция выводит на экран полученную статистику в формате "{Буква} : {Кол-во}" - /// Каждая буква - с новой строки. - /// Выводить на экран необходимо предварительно отсортировав набор по алфавиту. - /// В конце отдельная строчка с ИТОГО, содержащая в себе общее кол-во найденных букв/пар - /// - /// Коллекция со статистикой - private static void PrintStatistic(IEnumerable letters) - { - // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! - throw new NotImplementedException(); - } - - /// - /// Метод увеличивает счётчик вхождений по переданной структуре. - /// - /// - private static void IncStatistic(LetterStats letterStats) - { - letterStats.Count++; - } - - - } -} +using System; +using System.Collections.Generic; + +namespace TestTask +{ + public class Program + { + + /// + /// Программа принимает на входе 2 пути до файлов. + /// Анализирует в первом файле кол-во вхождений каждой буквы (регистрозависимо). Например А, б, Б, Г и т.д. + /// Анализирует во втором файле кол-во вхождений парных букв (не регистрозависимо). Например АА, Оо, еЕ, тт и т.д. + /// По окончанию работы - выводит данную статистику на экран. + /// + /// Массив аргументов командной строки: + /// args[0] - путь до первого файла, + /// args[1] - путь до второго файла. + static void Main(string[] args) + { + IReadOnlyStream singleLetterStream = GetInputStream(args[0]); + IReadOnlyStream doubleLetterStream = GetInputStream(args[1]); + + IList singleLetterStats = FillSingleLetterStats(singleLetterStream); + IList doubleLetterStats = FillDoubleLetterStats(doubleLetterStream); + + RemoveCharStatsByType(singleLetterStats, CharType.Vowel); + RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); + + PrintStatistic(singleLetterStats); + PrintStatistic(doubleLetterStats); + + Console.ReadKey(); + } + + /// + /// Ф-ция возвращает экземпляр потока с уже загруженным файлом для последующего посимвольного чтения. + /// + /// Полный путь до файла для чтения + /// Поток для последующего чтения. + private static IReadOnlyStream GetInputStream(string fileFullPath) + { + return new ReadOnlyStream(fileFullPath); + } + + /// + /// Ф-ция считывающая из входящего потока все буквы, и возвращающая коллекцию статистик вхождения каждой буквы. + /// Статистика РЕГИСТРОЗАВИСИМАЯ! + /// + /// Стрим для считывания символов для последующего анализа + /// Коллекция статистик по каждой букве, что была прочитана из стрима. + private static IList FillSingleLetterStats(IReadOnlyStream stream) + { + stream.ResetPositionToStart(); + while (!stream.IsEof) + { + char c = stream.ReadNextChar(); + // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - регистрозависимый. + } + + //return ???; + + throw new NotImplementedException(); + } + + /// + /// Ф-ция считывающая из входящего потока все буквы, и возвращающая коллекцию статистик вхождения парных букв. + /// В статистику должны попадать только пары из одинаковых букв, например АА, СС, УУ, ЕЕ и т.д. + /// Статистика - НЕ регистрозависимая! + /// + /// Стрим для считывания символов для последующего анализа + /// Коллекция статистик по каждой букве, что была прочитана из стрима. + private static IList FillDoubleLetterStats(IReadOnlyStream stream) + { + stream.ResetPositionToStart(); + while (!stream.IsEof) + { + char c = stream.ReadNextChar(); + // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - НЕ регистрозависимый. + } + + //return ???; + + throw new NotImplementedException(); + } + + /// + /// Ф-ция перебирает все найденные буквы/парные буквы, содержащие в себе только гласные или согласные буквы. + /// (Тип букв для перебора определяется параметром charType) + /// Все найденные буквы/пары соответствующие параметру поиска - удаляются из переданной коллекции статистик. + /// + /// Коллекция со статистиками вхождения букв/пар + /// Тип букв для анализа + private static void RemoveCharStatsByType(IList letters, CharType charType) + { + // TODO : Удалить статистику по запрошенному типу букв. + switch (charType) + { + case CharType.Consonants: + break; + case CharType.Vowel: + break; + } + + } + + /// + /// Ф-ция выводит на экран полученную статистику в формате "{Буква} : {Кол-во}" + /// Каждая буква - с новой строки. + /// Выводить на экран необходимо предварительно отсортировав набор по алфавиту. + /// В конце отдельная строчка с ИТОГО, содержащая в себе общее кол-во найденных букв/пар + /// + /// Коллекция со статистикой + private static void PrintStatistic(IEnumerable letters) + { + // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! + throw new NotImplementedException(); + } + + /// + /// Метод увеличивает счётчик вхождений по переданной структуре. + /// + /// + private static void IncStatistic(LetterStats letterStats) + { + letterStats.Count++; + } + + + } +} diff --git a/TestTask/Properties/AssemblyInfo.cs b/TestTask/Properties/AssemblyInfo.cs index aecb854..34d8ed5 100644 --- a/TestTask/Properties/AssemblyInfo.cs +++ b/TestTask/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TestTask")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TestTask")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6e9922f2-af75-4807-b6e4-f74499317c39")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestTask")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestTask")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6e9922f2-af75-4807-b6e4-f74499317c39")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs index cafb076..13225e7 100644 --- a/TestTask/ReadOnlyStream.cs +++ b/TestTask/ReadOnlyStream.cs @@ -1,77 +1,77 @@ -using System; -using System.IO; - -namespace TestTask -{ - public class ReadOnlyStream : IReadOnlyStream - { - private readonly StreamReader _fileStream; - - /// - /// Конструктор класса. - /// Класс реализует IDisposable — используйте using/Dispose для гарантированного закрытия файла. - /// - /// Полный путь до файла для чтения - public ReadOnlyStream(string fileFullPath) - { - _fileStream = new StreamReader(fileFullPath); - IsEof = false; - } - - /// - /// Флаг окончания файла. - /// - public bool IsEof - { - get; - private set; - } - - /// - /// Ф-ция чтения следующего символа из потока. - /// Если произведена попытка прочитать символ после достижения конца файла, метод - /// бросит соответствующее исключение - /// - /// Считанный символ. - public char ReadNextChar() - { - if (IsEof) - { - throw new InvalidOperationException("Stream is already at end of file"); - } - - var nextChar = (char)_fileStream.Read(); - - if (_fileStream.EndOfStream) - { - IsEof = true; - } - - return nextChar; - } - - /// - /// Сбрасывает текущую позицию потока на начало. - /// - public void ResetPositionToStart() - { - if (_fileStream == null) - { - throw new InvalidOperationException("Stream is not initialized."); - } - - _fileStream.BaseStream.Position = 0; - _fileStream.DiscardBufferedData(); - IsEof = false; - } - - /// - /// Освобождает ресурсы и закрывает файл. - /// - public void Dispose() - { - _fileStream.Dispose(); - GC.SuppressFinalize(this); - } - } -} +using System; +using System.IO; + +namespace TestTask +{ + public class ReadOnlyStream : IReadOnlyStream + { + private readonly StreamReader _fileStream; + + /// + /// Конструктор класса. + /// Класс реализует IDisposable — используйте using/Dispose для гарантированного закрытия файла. + /// + /// Полный путь до файла для чтения + public ReadOnlyStream(string fileFullPath) + { + _fileStream = new StreamReader(fileFullPath); + IsEof = false; + } + + /// + /// Флаг окончания файла. + /// + public bool IsEof + { + get; + private set; + } + + /// + /// Ф-ция чтения следующего символа из потока. + /// Если произведена попытка прочитать символ после достижения конца файла, метод + /// бросит соответствующее исключение + /// + /// Считанный символ. + public char ReadNextChar() + { + if (IsEof) + { + throw new InvalidOperationException("Stream is already at end of file"); + } + + var nextChar = (char)_fileStream.Read(); + + if (_fileStream.EndOfStream) + { + IsEof = true; + } + + return nextChar; + } + + /// + /// Сбрасывает текущую позицию потока на начало. + /// + public void ResetPositionToStart() + { + if (_fileStream == null) + { + throw new InvalidOperationException("Stream is not initialized."); + } + + _fileStream.BaseStream.Position = 0; + _fileStream.DiscardBufferedData(); + IsEof = false; + } + + /// + /// Освобождает ресурсы и закрывает файл. + /// + public void Dispose() + { + _fileStream.Dispose(); + GC.SuppressFinalize(this); + } + } +} diff --git a/TestTask/TestTask.csproj b/TestTask/TestTask.csproj index 22f5812..ebfb651 100644 --- a/TestTask/TestTask.csproj +++ b/TestTask/TestTask.csproj @@ -1,7 +1,7 @@ - - - Exe - net8.0 - false - - + + + Exe + net8.0 + false + + diff --git a/TestTask/TestTask.sln b/TestTask/TestTask.sln index 494bc7e..5725c91 100644 --- a/TestTask/TestTask.sln +++ b/TestTask/TestTask.sln @@ -1,25 +1,25 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27428.2011 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTask", "TestTask.csproj", "{6E9922F2-AF75-4807-B6E4-F74499317C39}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6E9922F2-AF75-4807-B6E4-F74499317C39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E9922F2-AF75-4807-B6E4-F74499317C39}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E9922F2-AF75-4807-B6E4-F74499317C39}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E9922F2-AF75-4807-B6E4-F74499317C39}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5FE530AC-4A73-424F-A09C-BA80D3FAA134} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2011 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTask", "TestTask.csproj", "{6E9922F2-AF75-4807-B6E4-F74499317C39}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6E9922F2-AF75-4807-B6E4-F74499317C39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E9922F2-AF75-4807-B6E4-F74499317C39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E9922F2-AF75-4807-B6E4-F74499317C39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E9922F2-AF75-4807-B6E4-F74499317C39}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5FE530AC-4A73-424F-A09C-BA80D3FAA134} + EndGlobalSection +EndGlobal From 232fbbb9f0acd46ae575111e8c9021f86a46e051 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 15:16:15 +0700 Subject: [PATCH 12/19] refactor: rename LetterStats to CharStats and update related naming --- TestTask/{LetterStats.cs => CharStats.cs} | 4 +-- TestTask/Program.cs | 34 +++++++++++------------ 2 files changed, 19 insertions(+), 19 deletions(-) rename TestTask/{LetterStats.cs => CharStats.cs} (87%) diff --git a/TestTask/LetterStats.cs b/TestTask/CharStats.cs similarity index 87% rename from TestTask/LetterStats.cs rename to TestTask/CharStats.cs index 90611af..295ff16 100644 --- a/TestTask/LetterStats.cs +++ b/TestTask/CharStats.cs @@ -3,12 +3,12 @@ /// /// Статистика вхождения буквы/пары букв /// - public struct LetterStats + public struct CharStats { /// /// Буква/Пара букв для учёта статистики. /// - public string Letter; + public string Char; /// /// Кол-во вхождений буквы/пары. diff --git a/TestTask/Program.cs b/TestTask/Program.cs index b29e20b..6dfadb1 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -17,17 +17,17 @@ public class Program /// args[1] - путь до второго файла. static void Main(string[] args) { - IReadOnlyStream singleLetterStream = GetInputStream(args[0]); - IReadOnlyStream doubleLetterStream = GetInputStream(args[1]); + IReadOnlyStream singleCharStream = GetInputStream(args[0]); + IReadOnlyStream doubleCharStream = GetInputStream(args[1]); - IList singleLetterStats = FillSingleLetterStats(singleLetterStream); - IList doubleLetterStats = FillDoubleLetterStats(doubleLetterStream); + IList singleCharStats = FillSingleCharStats(singleCharStream); + IList doubleCharStats = FillDoubleCharStats(doubleCharStream); - RemoveCharStatsByType(singleLetterStats, CharType.Vowel); - RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); + RemoveCharStatsByType(singleCharStats, CharType.Vowel); + RemoveCharStatsByType(doubleCharStats, CharType.Consonants); - PrintStatistic(singleLetterStats); - PrintStatistic(doubleLetterStats); + PrintStatistic(singleCharStats); + PrintStatistic(doubleCharStats); Console.ReadKey(); } @@ -48,7 +48,7 @@ private static IReadOnlyStream GetInputStream(string fileFullPath) /// /// Стрим для считывания символов для последующего анализа /// Коллекция статистик по каждой букве, что была прочитана из стрима. - private static IList FillSingleLetterStats(IReadOnlyStream stream) + private static IList FillSingleCharStats(IReadOnlyStream stream) { stream.ResetPositionToStart(); while (!stream.IsEof) @@ -69,7 +69,7 @@ private static IList FillSingleLetterStats(IReadOnlyStream stream) /// /// Стрим для считывания символов для последующего анализа /// Коллекция статистик по каждой букве, что была прочитана из стрима. - private static IList FillDoubleLetterStats(IReadOnlyStream stream) + private static IList FillDoubleCharStats(IReadOnlyStream stream) { stream.ResetPositionToStart(); while (!stream.IsEof) @@ -88,9 +88,9 @@ private static IList FillDoubleLetterStats(IReadOnlyStream stream) /// (Тип букв для перебора определяется параметром charType) /// Все найденные буквы/пары соответствующие параметру поиска - удаляются из переданной коллекции статистик. /// - /// Коллекция со статистиками вхождения букв/пар + /// Коллекция со статистиками вхождения букв/пар /// Тип букв для анализа - private static void RemoveCharStatsByType(IList letters, CharType charType) + private static void RemoveCharStatsByType(IList stats, CharType charType) { // TODO : Удалить статистику по запрошенному типу букв. switch (charType) @@ -109,8 +109,8 @@ private static void RemoveCharStatsByType(IList letters, CharType c /// Выводить на экран необходимо предварительно отсортировав набор по алфавиту. /// В конце отдельная строчка с ИТОГО, содержащая в себе общее кол-во найденных букв/пар /// - /// Коллекция со статистикой - private static void PrintStatistic(IEnumerable letters) + /// Коллекция со статистикой + private static void PrintStatistic(IEnumerable stats) { // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! throw new NotImplementedException(); @@ -119,10 +119,10 @@ private static void PrintStatistic(IEnumerable letters) /// /// Метод увеличивает счётчик вхождений по переданной структуре. /// - /// - private static void IncStatistic(LetterStats letterStats) + /// + private static void IncStatistic(CharStats charStats) { - letterStats.Count++; + charStats.Count++; } From 32ac3e10f1c7e5baa8ab10cf746dc0dabea0a825 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 18:10:35 +0700 Subject: [PATCH 13/19] fix: change CharStats from struct to class to fix mutation via reference --- TestTask/CharStats.cs | 4 ++-- TestTask/Program.cs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/TestTask/CharStats.cs b/TestTask/CharStats.cs index 295ff16..f28093d 100644 --- a/TestTask/CharStats.cs +++ b/TestTask/CharStats.cs @@ -1,9 +1,9 @@ -namespace TestTask +namespace TestTask { /// /// Статистика вхождения буквы/пары букв /// - public struct CharStats + public class CharStats { /// /// Буква/Пара букв для учёта статистики. diff --git a/TestTask/Program.cs b/TestTask/Program.cs index 6dfadb1..ee44c24 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace TestTask @@ -124,7 +124,5 @@ private static void IncStatistic(CharStats charStats) { charStats.Count++; } - - } } From 6067739cf56306f11bbbbc36f9004a8f65c65d8e Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 19:25:58 +0700 Subject: [PATCH 14/19] feat: implement CollectSingleCharStats --- TestTask/Program.cs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/TestTask/Program.cs b/TestTask/Program.cs index ee44c24..db57877 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; namespace TestTask { @@ -20,7 +21,7 @@ static void Main(string[] args) IReadOnlyStream singleCharStream = GetInputStream(args[0]); IReadOnlyStream doubleCharStream = GetInputStream(args[1]); - IList singleCharStats = FillSingleCharStats(singleCharStream); + IList singleCharStats = CollectSingleCharStats(singleCharStream); IList doubleCharStats = FillDoubleCharStats(doubleCharStream); RemoveCharStatsByType(singleCharStats, CharType.Vowel); @@ -48,18 +49,34 @@ private static IReadOnlyStream GetInputStream(string fileFullPath) /// /// Стрим для считывания символов для последующего анализа /// Коллекция статистик по каждой букве, что была прочитана из стрима. - private static IList FillSingleCharStats(IReadOnlyStream stream) + private static IList CollectSingleCharStats(IReadOnlyStream stream) { + var stats = new List(); + stream.ResetPositionToStart(); while (!stream.IsEof) { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - регистрозависимый. - } + char currentChar = stream.ReadNextChar(); - //return ???; + if (!char.IsLetter(currentChar)) + { + continue; + } - throw new NotImplementedException(); + var stat = stats.FirstOrDefault(stat => stat.Char == currentChar.ToString()); + + if (stat == null) + { + stats.Add(new CharStats { Char = currentChar.ToString(), Count = 1 }); + } + + else + { + IncStatistic(stat); + } + + } + return stats; } /// From aec6a71e1a54f2ba2cff020d2bc1da744a0053b7 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 19:52:24 +0700 Subject: [PATCH 15/19] feat: implement CollectDoubleCharStats --- TestTask/Program.cs | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/TestTask/Program.cs b/TestTask/Program.cs index db57877..2242d30 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -22,7 +22,7 @@ static void Main(string[] args) IReadOnlyStream doubleCharStream = GetInputStream(args[1]); IList singleCharStats = CollectSingleCharStats(singleCharStream); - IList doubleCharStats = FillDoubleCharStats(doubleCharStream); + IList doubleCharStats = CollectDoubleCharStats(doubleCharStream); RemoveCharStatsByType(singleCharStats, CharType.Vowel); RemoveCharStatsByType(doubleCharStats, CharType.Consonants); @@ -86,18 +86,43 @@ private static IList CollectSingleCharStats(IReadOnlyStream stream) /// /// Стрим для считывания символов для последующего анализа /// Коллекция статистик по каждой букве, что была прочитана из стрима. - private static IList FillDoubleCharStats(IReadOnlyStream stream) + private static IList CollectDoubleCharStats(IReadOnlyStream stream) { + var stats = new List(); + bool prevWasLetter = false; + char prevChar = default; + stream.ResetPositionToStart(); while (!stream.IsEof) { - char c = stream.ReadNextChar(); - // TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - НЕ регистрозависимый. - } + char currentChar = stream.ReadNextChar(); - //return ???; + if (!char.IsLetter(currentChar)) + { + prevWasLetter = false; + continue; + } - throw new NotImplementedException(); + if (prevWasLetter && char.ToLower(prevChar) == char.ToLower(currentChar)) + { + var pairKey = new string(char.ToLower(currentChar), 2); + var stat = stats.FirstOrDefault(stat => stat.Char == pairKey); + if (stat == null) + { + stats.Add(new CharStats { Char = pairKey, Count = 1 }); + } + + else + { + IncStatistic(stat); + } + } + + prevChar = currentChar; + prevWasLetter = true; + } + + return stats; } /// From b2879e7c6f3de581466ef1c23491d5e9cd91f3b3 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 20:23:08 +0700 Subject: [PATCH 16/19] feat: implement RemoveCharStatsByType --- TestTask/CharType.cs | 14 +++++++++++++- TestTask/Program.cs | 16 +++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/TestTask/CharType.cs b/TestTask/CharType.cs index 587a031..0d92220 100644 --- a/TestTask/CharType.cs +++ b/TestTask/CharType.cs @@ -1,4 +1,4 @@ -namespace TestTask +namespace TestTask { /// /// Тип букв @@ -15,4 +15,16 @@ public enum CharType /// Consonants } + + /// + /// Вспомогательный класс для классификации букв по типу. + /// + public static class CharTypeHelper + { + private const string RussianVowels = "аеёиоуыэюя"; + // TODO: добавить латинские гласные при необходимости: "aeiou" + + public static bool IsVowel(char c) => + RussianVowels.Contains(char.ToLower(c)); + } } diff --git a/TestTask/Program.cs b/TestTask/Program.cs index 2242d30..adee53e 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -134,15 +134,17 @@ private static IList CollectDoubleCharStats(IReadOnlyStream stream) /// Тип букв для анализа private static void RemoveCharStatsByType(IList stats, CharType charType) { - // TODO : Удалить статистику по запрошенному типу букв. - switch (charType) + for (int i = stats.Count - 1; i >= 0; i--) { - case CharType.Consonants: - break; - case CharType.Vowel: - break; - } + bool isVowel = CharTypeHelper.IsVowel(stats[i].Char[0]); + + bool shouldRemove = charType == CharType.Vowel ? isVowel : !isVowel; + if (shouldRemove) + { + stats.RemoveAt(i); + } + } } /// From c2869adf1dc704bc04343733648ac41b013e5707 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 20:37:56 +0700 Subject: [PATCH 17/19] feat: implement PrintStatistic --- TestTask/Program.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/TestTask/Program.cs b/TestTask/Program.cs index adee53e..ee9714b 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -156,8 +156,16 @@ private static void RemoveCharStatsByType(IList stats, CharType charT /// Коллекция со статистикой private static void PrintStatistic(IEnumerable stats) { - // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! - throw new NotImplementedException(); + var sorted = stats.OrderBy(stat => stat.Char); + int total = 0; + + foreach (var stat in sorted) + { + Console.WriteLine($"{stat.Char} : {stat.Count}"); + total += stat.Count; + } + + Console.WriteLine($"ИТОГО : {total}"); } /// From 8e9ed233bba4e0e8ae6408cacc997c1a439d3bf1 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 20:42:20 +0700 Subject: [PATCH 18/19] docs: note architectural concern about single responsibility in Program class --- TestTask/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TestTask/Program.cs b/TestTask/Program.cs index ee9714b..f335f3d 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -4,6 +4,11 @@ namespace TestTask { + /* + NOTE: В production-коде методы стоило бы вынести в отдельные классы по ответственностям: + StatisticsCollector (CollectSingle/DoubleCharStats), StatisticsFilter (RemoveCharStatsByType), + StatisticsPrinter (PrintStatistic) + */ public class Program { From 1a2f35f478356ee887ffccc1d56847df00389854 Mon Sep 17 00:00:00 2001 From: nkl Date: Thu, 26 Feb 2026 20:49:19 +0700 Subject: [PATCH 19/19] chore: add sample data files for manual testing --- data/file1.txt | 1 + data/file2.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 data/file1.txt create mode 100644 data/file2.txt diff --git a/data/file1.txt b/data/file1.txt new file mode 100644 index 0000000..365a96e --- /dev/null +++ b/data/file1.txt @@ -0,0 +1 @@ +Йода говорит мудро, но порядок слов меняет всегда. diff --git a/data/file2.txt b/data/file2.txt new file mode 100644 index 0000000..c540475 --- /dev/null +++ b/data/file2.txt @@ -0,0 +1 @@ +Фремены поодиночке уходили в пустыню Арракиса.