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();
+ }
}
}