Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions TestTask/IReadOnlyStream.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
namespace TestTask
using System;

namespace TestTask
{
/// <summary>
/// Интерфейс для работы с файлом в сильно урезаном виде.
/// Умеет всего 2 вещи: прочитать символ, и перемотать стрим на начало.
/// </summary>
internal interface IReadOnlyStream
internal interface IReadOnlyStream : IDisposable
{
// Интерфейс пополнили IDisposable, чтобы клиенты могли гарантированно закрывать файл после окончания работы.
// TODO : Необходимо доработать данный интерфейс для обеспечения гарантированного закрытия файла, по окончанию работы с таковым!
char ReadNextChar();

Expand Down
164 changes: 133 additions & 31 deletions TestTask/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;

namespace TestTask
{
Expand All @@ -16,19 +17,29 @@ public class Program
/// Второй параметр - путь до второго файла.</param>
static void Main(string[] args)
{
IReadOnlyStream inputStream1 = GetInputStream(args[0]);
IReadOnlyStream inputStream2 = GetInputStream(args[1]);
// если пути не были переданы, запрашиваем у пользователя
if (args.Length < 2)
{
Console.WriteLine("Usage: TestTask <file1> <file2>");
return;
}

IList<LetterStats> singleLetterStats = FillSingleLetterStats(inputStream1);
IList<LetterStats> doubleLetterStats = FillDoubleLetterStats(inputStream2);
using (IReadOnlyStream inputStream1 = GetInputStream(args[0]))
using (IReadOnlyStream inputStream2 = GetInputStream(args[1]))
{
IList<LetterStats> singleLetterStats = FillSingleLetterStats(inputStream1);
IList<LetterStats> doubleLetterStats = FillDoubleLetterStats(inputStream2);

RemoveCharStatsByType(singleLetterStats, CharType.Vowel);
RemoveCharStatsByType(doubleLetterStats, CharType.Consonants);
RemoveCharStatsByType(singleLetterStats, CharType.Vowel);
RemoveCharStatsByType(doubleLetterStats, CharType.Consonants);

PrintStatistic(singleLetterStats);
PrintStatistic(doubleLetterStats);
PrintStatistic(singleLetterStats);
PrintStatistic(doubleLetterStats);
}

// TODO : Необжодимо дождаться нажатия клавиши, прежде чем завершать выполнение программы.
// дождаться нажатия любой клавиши перед закрытием
Console.WriteLine("Нажмите любую клавишу для выхода...");
Console.ReadKey(true);
}

/// <summary>
Expand All @@ -49,16 +60,39 @@ private static IReadOnlyStream GetInputStream(string fileFullPath)
/// <returns>Коллекция статистик по каждой букве, что была прочитана из стрима.</returns>
private static IList<LetterStats> FillSingleLetterStats(IReadOnlyStream stream)
{
var stats = new Dictionary<string, LetterStats>();

stream.ResetPositionToStart();
while (!stream.IsEof)
while (true)
{
char c = stream.ReadNextChar();
// TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - регистрозависимый.
}
if (stream.IsEof)
break;

//return ???;
char c;
try
{
c = stream.ReadNextChar();
}
catch (EndOfStreamException)
{
// достигли конца - просто прерываем цикл
break;
}

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

string key = c.ToString();
if (!stats.TryGetValue(key, out LetterStats letterStats))
{
letterStats = new LetterStats { Letter = key, Count = 0 };
}

throw new NotImplementedException();
IncStatistic(ref letterStats); // регистрозависимо используем сам символ
stats[key] = letterStats;
}

return new List<LetterStats>(stats.Values);
}

/// <summary>
Expand All @@ -70,16 +104,52 @@ private static IList<LetterStats> FillSingleLetterStats(IReadOnlyStream stream)
/// <returns>Коллекция статистик по каждой букве, что была прочитана из стрима.</returns>
private static IList<LetterStats> FillDoubleLetterStats(IReadOnlyStream stream)
{
// используем словарь с игнорированием регистра для ключей
var stats = new Dictionary<string, LetterStats>(StringComparer.OrdinalIgnoreCase);

stream.ResetPositionToStart();
while (!stream.IsEof)
char? prevChar = null;
while (true)
{
char c = stream.ReadNextChar();
// TODO : заполнять статистику с использованием метода IncStatistic. Учёт букв - НЕ регистрозависимый.
}
if (stream.IsEof)
break;

//return ???;
char c;
try
{
c = stream.ReadNextChar();
}
catch (EndOfStreamException)
{
break;
}

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

if (prevChar.HasValue)
{
// сравниваем без учёта регистра
if (char.ToUpperInvariant(prevChar.Value) == char.ToUpperInvariant(c))
{
string key = new string(new[] { char.ToUpperInvariant(c), char.ToUpperInvariant(c) });
if (!stats.TryGetValue(key, out LetterStats letterStats))
{
letterStats = new LetterStats { Letter = key, Count = 0 };
}

IncStatistic(ref letterStats);
stats[key] = letterStats;
}
}

prevChar = c;
}

throw new NotImplementedException();
return new List<LetterStats>(stats.Values);
}

/// <summary>
Expand All @@ -91,15 +161,36 @@ private static IList<LetterStats> FillDoubleLetterStats(IReadOnlyStream stream)
/// <param name="charType">Тип букв для анализа</param>
private static void RemoveCharStatsByType(IList<LetterStats> letters, CharType charType)
{
// TODO : Удалить статистику по запрошенному типу букв.
switch (charType)
// Удаляем элементы из списка по префиксу соответствующему типу
bool IsVowelChar(char ch)
{
case CharType.Consonants:
break;
case CharType.Vowel:
break;
// базовый набор русских гласных (и их латинские аналоги, если встретятся)
const string vowels = "AEIOUYАЕЁИОУЫЭЮЯaeiouyаеёиоуыэюя";
return vowels.IndexOf(ch) >= 0;
}

for (int i = letters.Count - 1; i >= 0; i--)
{
string str = letters[i].Letter;
if (string.IsNullOrEmpty(str))
continue;

bool allVowels = true;
bool allConsonants = true;
foreach (char ch in str)
{
if (!char.IsLetter(ch))
continue; // игнорируем несравнимые символы
if (IsVowelChar(ch))
allConsonants = false;
else
allVowels = false;
}

bool remove = charType == CharType.Vowel ? allVowels : allConsonants;
if (remove)
letters.RemoveAt(i);
}

}

/// <summary>
Expand All @@ -111,15 +202,26 @@ private static void RemoveCharStatsByType(IList<LetterStats> letters, CharType c
/// <param name="letters">Коллекция со статистикой</param>
private static void PrintStatistic(IEnumerable<LetterStats> letters)
{
// TODO : Выводить на экран статистику. Выводить предварительно отсортировав по алфавиту!
throw new NotImplementedException();
var sorted = new List<LetterStats>(letters);
sorted.Sort((a, b) => string.CompareOrdinal(a.Letter, b.Letter));

int total = 0;
foreach (var ls in sorted)
{
Console.WriteLine($"{ls.Letter} : {ls.Count}");
total += ls.Count;
}

Console.WriteLine($"ИТОГО : {total}");
}

/// <summary>
/// Метод увеличивает счётчик вхождений по переданной структуре.
/// Принципиально принимаем структуру по ссылке, чтобы изменения
/// увидел вызывающий код (struct - value type).
/// </summary>
/// <param name="letterStats"></param>
private static void IncStatistic(LetterStats letterStats)
private static void IncStatistic(ref LetterStats letterStats)
{
letterStats.Count++;
}
Expand Down
40 changes: 31 additions & 9 deletions TestTask/ReadOnlyStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace TestTask
{
public class ReadOnlyStream : IReadOnlyStream
{
private Stream _localStream;
// использовали StreamReader потому, что оно корректно работает с `char` и кодировкой
private StreamReader _localStream;

/// <summary>
/// Конструктор класса.
Expand All @@ -15,18 +16,17 @@ public class ReadOnlyStream : IReadOnlyStream
/// <param name="fileFullPath">Полный путь до файла для чтения</param>
public ReadOnlyStream(string fileFullPath)
{
IsEof = true;

// TODO : Заменить на создание реального стрима для чтения файла!
_localStream = null;
// при создании открываем файл на чтение
_localStream = new StreamReader(fileFullPath);
IsEof = false;
}

/// <summary>
/// Флаг окончания файла.
/// </summary>
public bool IsEof
{
get; // TODO : Заполнять данный флаг при достижении конца файла/стрима при чтении
get;
private set;
}

Expand All @@ -38,8 +38,19 @@ public bool IsEof
/// <returns>Считанный символ.</returns>
public char ReadNextChar()
{
// TODO : Необходимо считать очередной символ из _localStream
throw new NotImplementedException();
if (_localStream == null)
{
throw new ObjectDisposedException(nameof(ReadOnlyStream));
}

int result = _localStream.Read();
if (result == -1)
{
IsEof = true;
throw new EndOfStreamException("Attempted to read past end of stream");
}

return (char)result;
}

/// <summary>
Expand All @@ -53,8 +64,19 @@ public void ResetPositionToStart()
return;
}

_localStream.Position = 0;
_localStream.BaseStream.Seek(0, SeekOrigin.Begin);
_localStream.DiscardBufferedData();
IsEof = false;
}

/// <summary>
/// Закрывает внутренний поток.
/// </summary>
public void Dispose()
{
_localStream?.Dispose();
_localStream = null;
IsEof = true;
}
}
}