diff --git a/.gitignore b/.gitignore index 42d3208..173c19b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,3 @@ -## .gitignore -#.gitignore - -### -## *.html -### -## !special.html -### .. -## *.[ao] - - *.suo *.user .vs/ diff --git a/TestTask/CharType.cs b/TestTask/CharType.cs deleted file mode 100644 index 899564d..0000000 --- a/TestTask/CharType.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace TestTask -{ - /// - /// Тип букв - /// - public enum CharType - { - /// - /// Гласные - /// - Vowel, - - /// - /// Согласные - /// - Consonants - } -} diff --git a/TestTask/Enums/CharType.cs b/TestTask/Enums/CharType.cs new file mode 100644 index 0000000..c1f896a --- /dev/null +++ b/TestTask/Enums/CharType.cs @@ -0,0 +1,18 @@ +namespace TestTask.Enums +{ + /// + /// Тип букв + /// + public enum CharType + { + /// + /// Гласные + /// + Vowel, + + /// + /// Согласные + /// + Consonants + } +} \ No newline at end of file diff --git a/TestTask/Helpers/Extensions/CharExtension.cs b/TestTask/Helpers/Extensions/CharExtension.cs new file mode 100644 index 0000000..3ca4ae0 --- /dev/null +++ b/TestTask/Helpers/Extensions/CharExtension.cs @@ -0,0 +1,74 @@ +namespace TestTask.Helpers.Extensions +{ + /// + /// Расширение для char. + /// + internal static class CharExtension + { + #region Public Methods + + /// + /// Проверяет символ английская ли глассная буква? + /// + /// Входной символ. + /// Английская глассная буква? + public static bool IsEnglishVowel(this char value) + { + value = char.ToLowerInvariant(value); + + switch (value) + { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return true; + + default: + return false; + } + } + + /// + /// Проверяет символ русская ли глассная буква? + /// + /// Входной символ. + /// Русская глассная буква? + public static bool IsRussianVowel(this char value) + { + value = char.ToLowerInvariant(value); + + switch (value) + { + case 'а': + case 'е': + case 'ё': + case 'и': + case 'о': + case 'у': + case 'ы': + case 'э': + case 'ю': + case 'я': + return true; + + default: + return false; + } + } + + /// + /// Проверяет символ глассная ли буква? + /// + /// Входной символ. + /// Глассная буква? + /// Проверка русского и английского алфовита. + public static bool IsVowel(this char value) + { + return IsRussianVowel(value) || IsEnglishVowel(value); + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/TestTask/Helpers/FillLetterStatsHelper.cs b/TestTask/Helpers/FillLetterStatsHelper.cs new file mode 100644 index 0000000..f822aea --- /dev/null +++ b/TestTask/Helpers/FillLetterStatsHelper.cs @@ -0,0 +1,99 @@ +namespace TestTask.Helpers +{ + using System.Collections.Generic; + using TestTask.Streams.Interfaces; + using TestTask.Structs; + + /// + /// Хелпер для составления статистики букв. + /// + internal static class FillLetterStatsHelper + { + #region Public Methods + + /// + /// Считывает из входящего потока все буквы, и возвращающает коллекцию статистик вхождения парных букв. + /// + /// Стрим для считывания символов для последующего анализа + /// Коллекция статистик по каждой букве, что была прочитана из стрима. + /// + /// В статистику должны попадать только пары из одинаковых букв, например АА, СС, УУ, ЕЕ и т.д. + /// Статистика - НЕ регистрозависимая! + /// + public static Dictionary FillDoubleLetterStats(IReadOnlyStream stream) + { + stream.ResetPositionToStart(); + + var statsByLetter = new Dictionary(); + char? prevLetter = null; + + while (!stream.IsEof) + { + char originalLetter = stream.ReadNextChar(); + + // В статистику идут только буквы + if (!char.IsLetter(originalLetter)) + { + prevLetter = null; + + continue; + } + + char letter = char.ToUpper(originalLetter); // НЕ регистрозависимо + + // Если текущая буква равна предыдущей — это парная буква (AA, CC, ...) + if (prevLetter.HasValue && prevLetter.Value == letter) + { + // ВАЖНО: инкрементим статистику именно по букве + if (statsByLetter.TryGetValue(letter, out LetterStats stats)) + { + stats.Count++; + statsByLetter[letter] = stats; // обязательно вернуть обратно + } + else + { + statsByLetter[letter] = new LetterStats { Count = 1, Letter = $"{letter}{letter}" }; + } + } + + prevLetter = letter; + } + + return statsByLetter; + } + + /// + /// Считывает из входящего потока все буквы, и возвращающает коллекцию статистик вхождения каждой буквы. + /// + /// Стрим для считывания символов для последующего анализа + /// Коллекция статистик по каждой букве, что была прочитана из стрима. + /// Статистика РЕГИСТРОЗАВИСИМАЯ! + public static Dictionary FillSingleLetterStats(IReadOnlyStream stream) + { + stream.ResetPositionToStart(); + Dictionary statsByLetter = new Dictionary(); + + while (!stream.IsEof) + { + char letter = stream.ReadNextChar(); + + if (char.IsLetter(letter)) + { + if (statsByLetter.TryGetValue(letter, out LetterStats stats)) + { + stats.Count++; + statsByLetter[letter] = stats; // обязательно вернуть обратно + } + else + { + statsByLetter[letter] = new LetterStats { Count = 1, Letter = letter.ToString() }; + } + } + } + + return statsByLetter; + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/TestTask/Helpers/FilterCharStats.cs b/TestTask/Helpers/FilterCharStats.cs new file mode 100644 index 0000000..132b31e --- /dev/null +++ b/TestTask/Helpers/FilterCharStats.cs @@ -0,0 +1,47 @@ +namespace TestTask.Helpers +{ + using System.Collections.Generic; + using System.Linq; + using TestTask.Enums; + using TestTask.Helpers.Extensions; + using TestTask.Structs; + + /// + /// Класс для фильтрации статистики букв. + /// + internal static class FilterCharStats + { + /// + /// Фильтруются все найденные буквы/парные буквы, содержащие в себе только гласные или согласные буквы. + /// (Тип букв для перебора определяется параметром charType) + /// Все найденные буквы/пары соответствующие параметру поиска - удаляются из переданной коллекции статистик. + /// + /// Коллекция со статистиками вхождения букв/пар + /// Тип букв для анализа + public static void RemoveCharStatsByType(Dictionary letters, CharType charType) + { + List list = letters.Select(x => x.Key).ToList(); + + foreach (char value in list) + { + switch (charType) + { + case CharType.Consonants: + + if (!value.IsVowel()) + { + letters.Remove(value); + } + break; + + case CharType.Vowel: + if (value.IsVowel()) + { + letters.Remove(value); + } + break; + } + } + } + } +} \ No newline at end of file diff --git a/TestTask/IReadOnlyStream.cs b/TestTask/IReadOnlyStream.cs deleted file mode 100644 index 6946745..0000000 --- a/TestTask/IReadOnlyStream.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace TestTask -{ - /// - /// Интерфейс для работы с файлом в сильно урезаном виде. - /// Умеет всего 2 вещи: прочитать символ, и перемотать стрим на начало. - /// - internal interface IReadOnlyStream - { - // TODO : Необходимо доработать данный интерфейс для обеспечения гарантированного закрытия файла, по окончанию работы с таковым! - char ReadNextChar(); - - void ResetPositionToStart(); - - bool IsEof { get; } - } -} diff --git a/TestTask/LetterStats.cs b/TestTask/LetterStats.cs deleted file mode 100644 index aa10728..0000000 --- a/TestTask/LetterStats.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace TestTask -{ - /// - /// Статистика вхождения буквы/пары букв - /// - public struct LetterStats - { - /// - /// Буква/Пара букв для учёта статистики. - /// - public string Letter; - - /// - /// Кол-во вхождений буквы/пары. - /// - public int Count; - } -} diff --git a/TestTask/Program.cs b/TestTask/Program.cs index fdf048e..6897b46 100644 --- a/TestTask/Program.cs +++ b/TestTask/Program.cs @@ -1,129 +1,90 @@ using System; using System.Collections.Generic; +using System.Linq; +using TestTask.Enums; +using TestTask.Helpers; +using TestTask.Streams; +using TestTask.Streams.Interfaces; +using TestTask.Structs; namespace TestTask { - public class Program - { - - /// - /// Программа принимает на входе 2 пути до файлов. - /// Анализирует в первом файле кол-во вхождений каждой буквы (регистрозависимо). Например А, б, Б, Г и т.д. - /// Анализирует во втором файле кол-во вхождений парных букв (не регистрозависимо). Например АА, Оо, еЕ, тт и т.д. - /// По окончанию работы - выводит данную статистику на экран. - /// - /// Первый параметр - путь до первого файла. - /// Второй параметр - путь до второго файла. - static void Main(string[] args) - { - IReadOnlyStream inputStream1 = GetInputStream(args[0]); - 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); - - // TODO : Необжодимо дождаться нажатия клавиши, прежде чем завершать выполнение программы. - } - - /// - /// Ф-ция возвращает экземпляр потока с уже загруженным файлом для последующего посимвольного чтения. - /// - /// Полный путь до файла для чтения - /// Поток для последующего чтения. - 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++; - } - - - } -} + /// + /// Корневой класс программы. + /// + public class Program + { + /// + /// Программа принимает на входе 2 пути до файлов. + /// Анализирует в первом файле кол-во вхождений каждой буквы (регистрозависимо). Например А, б, Б, Г и т.д. + /// Анализирует во втором файле кол-во вхождений парных букв (не регистрозависимо). Например АА, Оо, еЕ, тт и т.д. + /// По окончанию работы - выводит данную статистику на экран. + /// + /// Первый параметр - путь до первого файла. + /// Второй параметр - путь до второго файла. + private static void Main(string[] args) + { + try + { + if (args.Length != 2) + { + throw new ArgumentException("Входных аргументов должно быть два ", nameof(args)); + } + + Dictionary singleLetterStats = null; + Dictionary doubleLetterStats = null; + + using (IReadOnlyStream inputStream1 = new ReadOnlyStream(args[0])) + { + singleLetterStats = FillLetterStatsHelper.FillSingleLetterStats(inputStream1); + } + using (IReadOnlyStream inputStream2 = new ReadOnlyStream(args[1])) + { + doubleLetterStats = FillLetterStatsHelper.FillDoubleLetterStats(inputStream2); + } + + FilterCharStats.RemoveCharStatsByType(singleLetterStats, CharType.Vowel); + FilterCharStats.RemoveCharStatsByType(doubleLetterStats, CharType.Consonants); + + PrintStatistic(singleLetterStats); + PrintStatistic(doubleLetterStats); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + finally + { + Console.ReadKey(); + } + } + + /// + /// Выводит на экран полученную статистику в формате "{Буква} : {Кол-во}" + /// Каждая буква - с новой строки. + /// Выводить на экран необходимо предварительно отсортировав набор по алфавиту. + /// В конце отдельная строчка с ИТОГО, содержащая в себе общее кол-во найденных букв/пар + /// + /// Коллекция со статистикой + private static void PrintStatistic(Dictionary letters) + { + // TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту! + IEnumerable> orderCollection = letters.OrderBy(x => x.Key).Select(x => x); + int uniqueCount = 0; + int generalCount = 0; + + foreach (KeyValuePair item in orderCollection) + { + Console.WriteLine(string.Format("{0} : {1}", item.Value.Letter, item.Value.Count)); + + uniqueCount++; + generalCount += item.Value.Count; + } + + //По задаче не понял что имелось в виду под ИТОГО. + Console.WriteLine("ИТОГО уникальных: " + uniqueCount); + Console.WriteLine("ИТОГО сумма: " + generalCount); + Console.WriteLine(); + } + } +} \ No newline at end of file diff --git a/TestTask/ReadOnlyStream.cs b/TestTask/ReadOnlyStream.cs deleted file mode 100644 index a51a61e..0000000 --- a/TestTask/ReadOnlyStream.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; - -namespace TestTask -{ - public class ReadOnlyStream : IReadOnlyStream - { - private Stream _localStream; - - /// - /// Конструктор класса. - /// Т.к. происходит прямая работа с файлом, необходимо - /// обеспечить ГАРАНТИРОВАННОЕ закрытие файла после окончания работы с таковым! - /// - /// Полный путь до файла для чтения - public ReadOnlyStream(string fileFullPath) - { - IsEof = true; - - // TODO : Заменить на создание реального стрима для чтения файла! - _localStream = null; - } - - /// - /// Флаг окончания файла. - /// - public bool IsEof - { - get; // TODO : Заполнять данный флаг при достижении конца файла/стрима при чтении - private set; - } - - /// - /// Ф-ция чтения следующего символа из потока. - /// Если произведена попытка прочитать символ после достижения конца файла, метод - /// должен бросать соответствующее исключение - /// - /// Считанный символ. - public char ReadNextChar() - { - // TODO : Необходимо считать очередной символ из _localStream - throw new NotImplementedException(); - } - - /// - /// Сбрасывает текущую позицию потока на начало. - /// - public void ResetPositionToStart() - { - if (_localStream == null) - { - IsEof = true; - return; - } - - _localStream.Position = 0; - IsEof = false; - } - } -} diff --git a/TestTask/Streams/Interfaces/IReadOnlyStream.cs b/TestTask/Streams/Interfaces/IReadOnlyStream.cs new file mode 100644 index 0000000..fdc33b1 --- /dev/null +++ b/TestTask/Streams/Interfaces/IReadOnlyStream.cs @@ -0,0 +1,26 @@ +namespace TestTask.Streams.Interfaces +{ + using System; + + /// + /// Интерфейс для работы с файлом в сильно урезаном виде. + /// + internal interface IReadOnlyStream : IDisposable + { + /// + /// Читает следующий символ из потока. + /// + /// Считанный символ. + char ReadNextChar(); + + /// + /// Сбрасывает текущую позицию потока на начало. + /// + void ResetPositionToStart(); + + /// + /// Флаг окончания файла. + /// + bool IsEof { get; } + } +} \ No newline at end of file diff --git a/TestTask/Streams/ReadOnlyStream.cs b/TestTask/Streams/ReadOnlyStream.cs new file mode 100644 index 0000000..5523baf --- /dev/null +++ b/TestTask/Streams/ReadOnlyStream.cs @@ -0,0 +1,89 @@ +using System.IO; +using System.Text; +using TestTask.Streams.Interfaces; + +namespace TestTask.Streams +{ + /// + /// Кастомный класс потока для чтения текстовых файлов. + /// + public class ReadOnlyStream : IReadOnlyStream + { + #region Fields + + /// + /// Поток для чтения файла. + /// + private StreamReader _streamReader; + + #endregion Fields + + #region Properties + + /// + public bool IsEof + { + get; + private set; + } + + #endregion Properties + + #region Public Constructors + + /// + /// Конструктор класса. + /// + /// Полный путь до файла для чтения + public ReadOnlyStream(string fileFullPath) : this(fileFullPath, Encoding.UTF8) + { + } + + /// + /// Конструктор класса. + /// + /// Полный путь до файла для чтения. + /// Кодировка. + public ReadOnlyStream(string fileFullPath, Encoding encoding) + { + var localStream = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read); + _streamReader = new StreamReader(localStream, encoding); + } + + #endregion Public Constructors + + #region Public Methods + + /// + public void Dispose() + { + _streamReader?.Dispose(); + _streamReader = null; + } + + /// + public char ReadNextChar() + { + char letter = (char)_streamReader.Read(); + + if (_streamReader.EndOfStream) + IsEof = true; + + return letter; + } + + /// + public void ResetPositionToStart() + { + if (_streamReader != null) + { + _streamReader.DiscardBufferedData(); + _streamReader.BaseStream.Position = 0; + } + + IsEof = false; + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/TestTask/Structs/LetterStats.cs b/TestTask/Structs/LetterStats.cs new file mode 100644 index 0000000..4c666d0 --- /dev/null +++ b/TestTask/Structs/LetterStats.cs @@ -0,0 +1,18 @@ +namespace TestTask.Structs +{ + /// + /// Статистика вхождения буквы/пары букв + /// + public struct LetterStats + { + /// + /// Буква/Пара букв для учёта статистики. + /// + public string Letter { get; set; } + + /// + /// Кол-во вхождений буквы/пары. + /// + public int Count { get; set; } + } +} \ No newline at end of file diff --git a/TestTask/TestTask.csproj b/TestTask/TestTask.csproj index dc470b0..765e16f 100644 --- a/TestTask/TestTask.csproj +++ b/TestTask/TestTask.csproj @@ -8,9 +8,10 @@ Exe TestTask TestTask - v4.6.1 + v4.8 512 true + AnyCPU @@ -42,12 +43,15 @@ - - - + + + + + + - + \ No newline at end of file