Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f50672b
Оформлен анализ и план выполнения работы
Oleksey12 Mar 3, 2026
7bc0df2
Merge pull request #1 from Oleksey12/feature/task-overview
Oleksey12 Mar 3, 2026
70bf2e6
Реализована безопасная обертка над потоком - ReadOnlyStream
Oleksey12 Mar 3, 2026
3e89f6c
Убраны лишине отсутпы в рамках CR
Oleksey12 Mar 3, 2026
c252eaa
Убран лишиний TODO в рамках CR
Oleksey12 Mar 3, 2026
e38ed22
Merge pull request #2 from Oleksey12/feature/implement-readonlystream
Oleksey12 Mar 3, 2026
41be204
Написана логика для безопасной инициализации и работы с файлами
Oleksey12 Mar 3, 2026
ee8c1c4
Написана логика для анализа текста в файлах
Oleksey12 Mar 3, 2026
cc4687d
Написана логика для фильтрации букв
Oleksey12 Mar 3, 2026
2c4274a
Поправлено название класса и саммари в рамках CR
Oleksey12 Mar 3, 2026
b0b3084
Merge pull request #3 from Oleksey12/feature/text-analysis
Oleksey12 Mar 3, 2026
e8ca494
Исправлены недоработки программы
Oleksey12 Mar 3, 2026
53ce7fd
Исправлены недоаботки и добавлена логика вывода информации на экран
Oleksey12 Mar 3, 2026
9520661
Улучшено название метода и изменен интерфейс в рамках CR
Oleksey12 Mar 3, 2026
5bda896
Merge pull request #4 from Oleksey12/feature/console-output
Oleksey12 Mar 3, 2026
7dbaa89
Обновле README проекта
Oleksey12 Mar 3, 2026
6154424
Merge pull request #5 from Oleksey12/feature/implementation-documenta…
Oleksey12 Mar 3, 2026
ee48c03
Заменено создание потоков внутри конструктора на фабрику с инъекцией …
Oleksey12 Mar 3, 2026
483753b
Вынесены методы анализа файлов в отдельный фасад
Oleksey12 Mar 3, 2026
8371f7e
Вынесены методы работы с консоль в отдельный Helper класс
Oleksey12 Mar 3, 2026
66284d1
Структурированы файлы проекта по фичам
Oleksey12 Mar 3, 2026
23cf476
Merge pull request #6 from Oleksey12/feature/refactoring
Oleksey12 Mar 3, 2026
eac970b
Обновлена документация в README
Oleksey12 Mar 3, 2026
2ddf957
Merge pull request #7 from Oleksey12/feature/refactoring-readme
Oleksey12 Mar 3, 2026
f6b5390
Создан NUnit проект для создания Unit тестов
Oleksey12 Mar 4, 2026
0199f15
Написаны Unit тесты для тестирования сложных комплексных методов прог…
Oleksey12 Mar 4, 2026
b9ec90d
Merge pull request #8 from Oleksey12/feature/unit-tests
Oleksey12 Mar 4, 2026
78ffa05
Обновлен Summary классов
Oleksey12 Mar 4, 2026
4d1e0c6
Обновлена документация проекта
Oleksey12 Mar 4, 2026
513d9cd
Merge pull request #9 from Oleksey12/feature/update-documentation
Oleksey12 Mar 4, 2026
ae386eb
Добавлена недостающая точка в конце предложения
Oleksey12 Mar 4, 2026
146f911
Merge pull request #10 from Oleksey12/fix/readme-dot
Oleksey12 Mar 4, 2026
287233b
Merge pull request #11 from Oleksey12/release/0.1
Oleksey12 Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Images/Main.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/Вывод.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/Диаграмма.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/Обертка.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/Подсчёт.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/Тесты.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/Хранилище.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Тестовое задание.
# Тестовое задание.
```
Доброго дня.
Данный репозиторий представляет из себя тестовое задание для выполнения перед собеседованием на вакансию разработчика C# в компанию Катрен.

Expand All @@ -20,3 +21,68 @@ __Для разработки настоятельно рекомендуетс
И по готовности результат отдать на ревью методом Pull Request'a.__

Если данный способ по каким-то причинам Вам не представляется реализуемым, можете выслать результат в виде архива на почту кадровому агенту.
```
# Обзор проекта.

**Тип проекта** - консольное приложение с версией .NET фреймворка 4.6.1. <br>
**Назначение (кратко)** - консольное приложение, анализирующее содержимое файла через обертку ReadOnlyStream и выводящее результаты анализа в консоль. <br>
**Ввод** - название файлов, передаваемых через аргументы командой строки. <br>
**Вывод** - статистика вхождений одиночных элементов в первом файле, а также количество повторяющихся элементов во втором файле. <br>
**Узкие места проекта** - *синхронное* чтение файлов без полной загрузки в память.

# План:
1. Реализация синхронной обертки чтения файлов - ReadOnlyStream с обработкой ошибок.
2. Реализация логики анализа статистики анализа вхождений (скорее всего с помощью Dictionary<string, struct>).
3. Оформление структурированного вывода в консоль.
4. Написание Unit-тестов (если останется время).*

# Реализация:

### Обертка над потоком данных файла.
Принял решение реализовать чтение в ReadOnlyStream через StreamReader, чтобы не мучаться с кодировкой и с проблемами, когда, например, файл повреждён.

![Конструктор класса](Images/Обертка.png)

Очистку стримов решил реализовать через IDisposable интерфейс и вызов Close у обоих потоков.

### Реализация логики анализа статистики вхождений.
Для реализации хранилища статистики создал специальный интерфейс, подразумевающий разные реализации.

![Конструктор класса](Images/Хранилище.png)

Для подсчёта количества букв использовал реализацию со словарём, так как у него низкое время поиска элементов + у нас может быть не такое большое количество разных элементов, чтобы беспокоиться о коллизиях.

![Конструктор класса](Images/Подсчёт.png)

**Есть сомнения насчёт логики подсчёта элементов, реализовал её, как просто подсчёт количества подряд идущих повторяющихся букв. Например, для файла с текстом "нНн" результатом будет 2.**

### Работа с консолью.

Сделал красивый вывод с помощью таблицы и с использованием PadRight.

![Конструктор класса](Images/Вывод.png)

### Рефакторинг.

Исходные методы имели модификатор private static, тем самым исключая любую возможность их переиспользования в других программах. Также создание объектов внутри конструктора является не очень хорошей практикой, поэтому метод создания ReadOnlyStream был переенсён в отдельную фабрику. <br>

По итогу были совершены следующие изменения:
1. Вынесены методы, занимающиеся анализом файла через IReadOnlyStream в отдельный LetterAnalysisFacade. Почему не в ReadOnlyStream? Потому что данные методы узконаправлены и выходят за текущий интерефейс.
2. Метод GetInputStream(string) вынесен в отдельную фабрику IReadOnlyStreamFactory с инъекцией зависимостей.
3. Метод PrintStatistics(IList\<LetterStats\>) вынесен в метод ConsoleHelperMethods. Почему сделан static? Потому что метод имеет чёткую привязку к среде выполнения и не меняет внутреннее состояние каких-либо объектов.

![Конструктор класса](Images/Main.png)

### Диаграмма классов.

Была составлена дигарамма классов для удобства других разработчиков.

![Диаграмма классов](Images/Диаграмма.png)

### Unit тесты.

Для создания Unit тестов был создан дополнительный проект TestTaskTest. Для Unit тестирования был выбран фреймворк NUnit, потому что только с этим фреймворком я знаком достаточно, чтобы использовать его в production.
<br><br>
В результате были реализованы тесты для основных алгоритмов в проекте.

![Тесты](Images/Тесты.png)
36 changes: 18 additions & 18 deletions TestTask/CharType.cs → TestTask/LetterAnalysis/CharType.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
namespace TestTask
{
/// <summary>
/// Тип букв
/// </summary>
public enum CharType
{
/// <summary>
/// Гласные
/// </summary>
Vowel,
/// <summary>
/// Согласные
/// </summary>
Consonants
}
}
namespace TestTask
{
/// <summary>
/// Тип букв
/// </summary>
public enum CharType
{
/// <summary>
/// Гласные
/// </summary>
Vowel,

/// <summary>
/// Согласные
/// </summary>
Consonants
}
}
50 changes: 50 additions & 0 deletions TestTask/LetterAnalysis/DictionaryLetterStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;

namespace TestTask
{
/// <summary>
/// Контейнер для подсчёта количества вхождений текста с помощью словаря
/// </summary>
public sealed class DictionaryLetterStorage : ILetterAnalysisStorage
{
private Dictionary<string, LetterStats> _data = new Dictionary<string, LetterStats>();

/// <summary>
/// Обрабатывает статистику по введённому тексту
/// </summary>
///<param name="text"></param>
public void Add(string text)
{
if (_data.TryGetValue(text, out LetterStats stats))
{
IncStatistic(text, stats);
}
else
{
stats = new LetterStats
{
Letter = text.ToString(),
Count = 1
};
_data.Add(text, stats);
}
}

/// <summary>
/// Метод увеличивает счётчик вхождений по переданной структуре.
/// </summary>
/// <param name="letterStats"></param>
private void IncStatistic(string key, LetterStats value)
{
value.Count++;
_data[key] = value;
}

/// <summary>
/// Возвращает список с данными вхождений текста
/// </summary>
/// <returns>Список с данными вхождений текста</returns>
public IList<LetterStats> GetStatistics() => _data.Values.ToList();
}
}
14 changes: 14 additions & 0 deletions TestTask/LetterAnalysis/ILetterAnalysisStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace TestTask
{
/// <summary>
/// Интерфейс контейнера для подсчёта количества вхождений текста
/// </summary>
internal interface ILetterAnalysisStorage
{
void Add(string text);

IList<LetterStats> GetStatistics();
}
}
113 changes: 113 additions & 0 deletions TestTask/LetterAnalysis/LetterAnalysisFacade.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Collections.Generic;
using System.Linq;

namespace TestTask
{
/// <summary>
/// Фасад для анализа файлов
/// </summary>
internal class LetterAnalysisFacade
{
private const string VOWELS = "AEIOUYАОЕЯЁЭЫУИЮaeiouyаоеяёэыуию";

/// <summary>
/// Ф-ция считывающая из входящего потока все буквы, и возвращающая коллекцию статистик вхождения каждой буквы.
/// Статистика РЕГИСТРОЗАВИСИМАЯ!
/// </summary>
/// <param name="stream">Стрим для считывания символов для последующего анализа</param>
/// <returns>Коллекция статистик по каждой букве, что была прочитана из стрима.</returns>
public IList<LetterStats> FillSingleLetterStats(IReadOnlyStream stream)
{
stream.ResetPositionToStart();
ILetterAnalysisStorage storage = new DictionaryLetterStorage();

while (!stream.IsEof)
{
char c = stream.ReadNextChar();

if (!char.IsLetter(c))
{
continue;
}

string letterText = c.ToString();
storage.Add(letterText);
}

return storage.GetStatistics();
}

/// <summary>
/// Ф-ция считывающая из входящего потока все буквы, и возвращающая коллекцию статистик вхождения парных букв.
/// В статистику должны попадать только пары из одинаковых букв, например АА, СС, УУ, ЕЕ и т.д.
/// Статистика - НЕ регистрозависимая!
/// </summary>
/// <param name="stream">Стрим для считывания символов для последующего анализа</param>
/// <returns>Коллекция статистик по каждой букве, что была прочитана из стрима.</returns>
public IList<LetterStats> FillDoubleLetterStats(IReadOnlyStream stream)
{
stream.ResetPositionToStart();
ILetterAnalysisStorage storage = new DictionaryLetterStorage();

bool hasPair = false;
char previousChar = ' ';

while (!stream.IsEof)
{
char c = stream.ReadNextChar();
if (!char.IsLetter(c))
{
hasPair = false;
continue;
}

if (!hasPair)
{
previousChar = char.ToUpper(c);
hasPair = true;
continue;
}

char upperFirstChar = previousChar;
char upperSecondChar = char.ToUpper(c);
previousChar = upperSecondChar;

if (upperFirstChar != upperSecondChar)
{
continue;
}

string letterText = string.Concat(upperFirstChar, upperSecondChar);
storage.Add(letterText);
}

return storage.GetStatistics();
}

/// <summary>
/// Ф-ция перебирает все найденные буквы/парные буквы, содержащие в себе только гласные или согласные буквы.
/// (Тип букв для перебора определяется параметром charType)
/// Все найденные буквы/пары соответствующие параметру поиска - удаляются из переданной коллекции статистик.
/// </summary>
/// <param name="letters">Коллекция со статистиками вхождения букв/пар</param>
/// <param name="charType">Тип букв для анализа</param>
public IList<LetterStats> RemoveCharStatsByType(IList<LetterStats> letters, CharType charType)
{
switch (charType)
{
case CharType.Consonants:
{
return letters.Where(x => VOWELS.Contains(x.Letter[0])).ToList();
}
case CharType.Vowel:
{
return letters.Where(x => !VOWELS.Contains(x.Letter[0])).ToList();
}
default:
{
return letters;
}
}
}
}
}
36 changes: 18 additions & 18 deletions TestTask/LetterStats.cs → TestTask/LetterAnalysis/LetterStats.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
namespace TestTask
{
/// <summary>
/// Статистика вхождения буквы/пары букв
/// </summary>
public struct LetterStats
{
/// <summary>
/// Буква/Пара букв для учёта статистики.
/// </summary>
public string Letter;
/// <summary>
/// Кол-во вхождений буквы/пары.
/// </summary>
public int Count;
}
}
namespace TestTask
{
/// <summary>
/// Статистика вхождения буквы/пары букв
/// </summary>
public struct LetterStats
{
/// <summary>
/// Буква/Пара букв для учёта статистики.
/// </summary>
public string Letter;

/// <summary>
/// Кол-во вхождений буквы/пары.
/// </summary>
public int Count;
}
}
Loading