Skip to content

Савицких Антон#39

Open
Xineev wants to merge 8 commits intokontur-courses:masterfrom
Xineev:master
Open

Савицких Антон#39
Xineev wants to merge 8 commits intokontur-courses:masterfrom
Xineev:master

Conversation

@Xineev
Copy link
Copy Markdown

@Xineev Xineev commented Dec 10, 2025

No description provided.

Copy link
Copy Markdown

@SquirrelLeonid SquirrelLeonid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Плюс исправить другие моменты.

Comment on lines +1 to +8
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using System.Linq;
using TagCloudGenerator.Infrastructure.Filters;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот тут есть неиспользуемые зависимости. Нужно убрать

Comment on lines +16 to +20
[SetUp]
public void Setup()
{
filter = new BoringWordsFilter();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет особой нужды создавать новый объект перед каждым тестом. Внутри класса у тебя ведь нет кэширования, чувствительной информации или логики, которая может повлиять на последующие прогоны.

В прочем если на перспективу будешь реализовывать управление списком скучных слов, то тогда Setup будет уместнее. Но там скорее каждый тест будет внутри себя конфигурировать этот объект

Comment on lines +1 to +13
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using TagCloudGenerator.Core.Interfaces;
using TagCloudGenerator.Core.Models;
using TagCloudGenerator.Core.Services;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь тоже есть неиспользуемые зависимости.

Вообще, во многих классах они есть. Нужно пройтись и подчистить

File.Delete(tempFilePath);
}

private void WriteToTempFile(string content)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Приватные методы следует располагать после публичных

var expectedLines = new[] { "line1", "line2", "line3" };
WriteToTempFile(string.Join(Environment.NewLine, expectedLines));

var result = reader.TryRead(tempFilePath).ToList();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь можем свалиться на null reference exception при вызове ToList. В других местах тоже

{
public class RenderSettings : IRenderSettings
{
public string OutputPath { get; set; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не уверен, что это должно быть у RenderSettings. Лучше убрать его отсюда и в CloudGenerator просто передавать путь в вызов PngRenderer


namespace TagCloudGenerator.Core.Models
{
public class RenderSettings : IRenderSettings
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тоже вопрос - а насколько это вероятная точка расширения? С точки зрения новых реализаций.

То, что RenderSettings может быть дополнен - это бесспорно, но вот наличие разных IRenderSettings скорее запутает, чем поможет разделить код

Comment on lines +14 to +22
public string FontFamily { get; set; } = "Arial";
public float MinFontSize { get; set; } = 12f;
public float MaxFontSize { get; set; } = 72f;
public Color BackgroundColor { get; set; }
public Color TextColor { get; set; }
public Size CanvasSize { get; set; }
public bool CenterCloud { get; set; }
public bool ShowRectangles { get; set; }
public int Padding { get; set; } = 50;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы предложил сгруппировать настройки. Как минимум можно выделить в отдельный класс настройки для текста (семейство шрифта, размер, цвет)


namespace TagCloudGenerator.Core.Models
{
public class CloudItem : ICloudItem
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В другом комментарии уже поднял вопрос о целесообразности интерфейса

Comment on lines +42 to +61
public static CloudItem Create(string word, Rectangle rectangle, float fontSize)
{
return new CloudItem(word, rectangle, fontSize);
}

public CloudItem WithRectangle(Rectangle newRectangle)
{
return new CloudItem(Word, newRectangle, FontSize, Color, FontFamily, FontStyle, Frequency, Weight);
}

public CloudItem WithFontSize(float newFontSize)
{
return new CloudItem(Word, Rectangle, newFontSize, Color, FontFamily, FontStyle, Frequency, Weight);
}

public CloudItem WithColor(Color newColor)
{
return new CloudItem(Word, Rectangle, FontSize, newColor, FontFamily, FontStyle, Frequency, Weight);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ранее я писал замечание про то, что мы переходим на зависимость от конкретной реализации.

Этого набора методов не видел. В целом он тоже решает проблему. У нас есть метод Create, который создает объект и есть набор методов для редактирования полей.

Только если будешь оставлять интерфейс ICloudItem, то убедись, что нужные методы в нем объявлены. Плюс стоит сделать конструктор приватным и пользоваться методами интерфейса

Copy link
Copy Markdown

@SquirrelLeonid SquirrelLeonid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предпроверку засчитываю.

  1. Посмотри новые комментарии
  2. Просьба: когда вносишь правки по комментарию - отпишись под ним, что было сделано (кратко). Это значительно упрощает ревью и дает понять, что комментарий был увиден
  3. Правки стоит разбивать на более мелкие коммиты. Когда все одной пачкой - проверять сложнее

Comment on lines +121 to +122
ts.MinFontSize == 12f &&
ts.MaxFontSize == 72f),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут IDE подсказывает, что сравнение может давать погрешность (скажем, в 10-ом знаке после запятой). В таких случаях сравнение лучше проводить через сравнение модуля разности:
| actualValue - expectedValue | <= epsilon, где epsilon - какая-то маленькая константа

}

private bool IsFlag(string arg)
private static Color? TryParseColor(string? colorStr)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'string?' не нужен, т.к. ты отключил nullable в csproj

Comment on lines +23 to +24
[Option("padding", HelpText = "Canvas padding (default if 50).")]
public int? Padding { get; set; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Есть некоторые непонятки с тем, за что именно отвечает это свойство. Отступ от центра? Отступ от краев холста? Стоит уточнить имя


namespace TagCloudGenerator.Algorithms
{
public class BasicTagCloudAlgorithm : ITagCloudAlgorithm
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Только сейчас обратил внимание на имя класса. Я бы предложил в названии как-то сослаться на спиральную раскладку или что-то вроде того.

Comment on lines +10 to +20
private readonly Random random = new Random();

private readonly List<Rectangle> rectangles = new List<Rectangle>();

private double currentAngle = 0;
private double currentRadius = 0;

private int countToGenerate = 10;

private (int min, int max) width = new(20, 21);
private (int min, int max) height = new(20, 21);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Часть полей не используются


namespace TagCloudGenerator.Infrastructure.Filters
{
public class ToLowerCaseFilter : IFilter
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Помню, что сам предлагал вынести это в фильтры, но только сейчас понял, что под такие действия правильнее завести что-то вроде INormalizer.
Мы ведь не фильтруем здесь слова, а проводим какие-то действия. Так что будет хорошо ввести такую абстракцию

Comment on lines +8 to +16
public Size Measure(string word, float fontSize, string fontFamily)
{
using var font = new Font(fontFamily, fontSize);
using var bitmap = new Bitmap(1, 1);
using var graphics = Graphics.FromImage(bitmap);

var size = graphics.MeasureString(word, font);
return new Size((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На заметку. Здесь просматриваются два момента для оптимизации.
Во-первых, можно оптимизировать работу с памятью. Обрати внимание, что bitmap и graphics у тебя для любого случая одинаковые. Класс мог бы единожды определить их при создании и хранить на протяжении своего жизненного цикла. Но для этого он обязан реализовать DIspose и этот метод должен откуда-то вызываться, иначе наоборот получим утечку. Можешь попробовать реализовать это

Во-вторых, если бы сценарий предполагал наличие повторов вида слово-шрифт, то можно было бы ввести некоторый кэш, который перед расчетами проверял - было ли такое.

@@ -0,0 +1,20 @@
using TagCloudGenerator.Core.Interfaces;

namespace TagCloudGenerator.Infrastructure.Reader
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поправь пространство имен

public void Render(IEnumerable<CloudItem> items, CanvasSettings canvasSettings, TextSettings textSettings, string outputFile)
{
var itemsList = items.ToList();
if (items.Count() == 0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Используй свойство Count

Comment on lines +39 to +58
private (int offsetX, int offsetY) CalculateOffset(
List<CloudItem> items,
CanvasSettings settings)
{
if (!settings.CenterCloud)
return (settings.Padding, settings.Padding);

var minX = items.Min(i => i.Rectangle.X);
var minY = items.Min(i => i.Rectangle.Y);
var maxX = items.Max(i => i.Rectangle.Right);
var maxY = items.Max(i => i.Rectangle.Bottom);

var cloudWidth = maxX - minX;
var cloudHeight = maxY - minY;

var offsetX = (settings.CanvasSize.Width - cloudWidth) / 2 - minX;
var offsetY = (settings.CanvasSize.Height - cloudHeight) / 2 - minY;

return (offsetX, offsetY);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Могу ошибаться, но кажется эта логика лишняя

  1. Координаты центра облака мы можем принимать как входной параметр (в собственных координатах)
  2. Расположение прямоугольников относительно центра мы уже посчитали заранее
  3. Фактически все элементы у нас уже должны быть размещены там, где надою. Нужно только привести их координаты к координатам холста. Это, по сути, можно делать налету для каждого тэга прямо перед вставкой

Copy link
Copy Markdown

@SquirrelLeonid SquirrelLeonid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Выглядит хорошо. Но нужно внести правки

Еще у тебя кажется какие-то проблемы с версткой формы.
image

А еще тебе нужно уничтожать Bitmap при завершении работы gui.

It.IsAny<IEnumerable<CloudItem>>(),
It.IsAny<CanvasSettings>(),
It.IsAny<TextSettings>()))
.Returns(new Bitmap(1, 1));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот тут у тебя утечка. Bitmap - это объект, требующий уничтожения после использования.

Comment on lines +10 to +11
private Mock<IFormatReader> reader1Mock;
private Mock<IFormatReader> reader2Mock;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше словами. firstReaderMock, secondReaderMock

Comment on lines +1 to +9
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TagCloudGenerator.Infrastructure.Readers;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не используемый using. В других местах тоже проверь

Comment on lines +1 to +15
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\TagCloudGenerator\TagCloudGenerator.csproj" />
</ItemGroup>

</Project> No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У тебя этот проект не включен в решение. Подключи его.
Могут быть конфликты из-за двух методов Main. Можешь удалить старый и в TagCloudGenerator.csproj в тэге OutputType сменить на Library, Либо сделать это через IDE (свойства проекта)

Comment on lines +1 to +14
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\TagCloudGenerator\TagCloudGenerator.csproj" />
</ItemGroup>

</Project>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Проект тоже не подключен к решению

Comment on lines +22 to +26
var result = wordFreqDictionary
.OrderByDescending(pair => pair.Value)
.ThenBy(pair => pair.Key)
.Select(pair => (pair.Key, pair.Value))
.ToList();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вообще, есть некоторые сомнения касательно сортировки слов как либо.
Здесь просматривается введение в цепочку какого-то аналога INormalizer, но только для коллекции целиком, а не отдельных слов. Грубо говоря:

  1. Вычитай слова
  2. Нормализуй каждое из них
  3. Отфильтруй каждое из них
  4. Посчитай частоту оставшихся слов
  5. -- Отсортируй слова так или иначе---
  6. Помести в облако тегов

Тебе предложение на подумать.

Comment on lines +9 to +19
public BoringWordsFilter() { }

public BoringWordsFilter(string[] words)
{
boringWords = words;
}

public BoringWordsFilter(List<string> wordsList)
{
boringWords = wordsList.ToArray();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно схлопнуть до одного конструктора, принимающего IEnumerable<sting> или ICollection<string>


namespace TagCloudGenerator.Infrastructure.Readers
{
public class CompositeReader : IReader
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Про него уже писал в интерфейсе

{
using var doc = WordprocessingDocument.Open(filePath, false);

return doc.MainDocumentPart.Document.Body
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можешь свалиться с NullReference.

Офисные пакеты так устроены, что существование документа не гарантирует существование каких-либо элементов в нем. К примеру, можно программно создать абсолютно пустой docx контейнер, который будет валиден с точки зрения внутренней структуры.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправил

Comment on lines +10 to +13
<ItemGroup>
<None Include="Infrastructure\Readers\TxtReader.cs" />
</ItemGroup>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот это по-моему лишнее вообще. Класс ведь определен в этом же проекте

Copy link
Copy Markdown

@SquirrelLeonid SquirrelLeonid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Оставил еще несколько комментариев.

Тем не менее задачу засчитываю на полный балл. Выполнены обязательные требования и несколько пунктов на перспективу

Comment on lines 37 to +40
words = ApplyFilters(words, filters);
if(words.Count == 0) return null;

var wordsWithFreq = _analyzer.Analyze(words);
var wordsWithFreq = _sorterer.Sort(_analyzer.Analyze(words));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предложение: применение фильтра и сортировку (да и в целом, наверное, любую предобработку слов) можно в целом утащить из CloudGenerator.

Тут можно по разному смотреть. Зависит от того, какой смысл вкладывать в метод Generate.

Решение за тобой

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я решил что пусть лучше останется как есть, изначально я и задумал генератор как некоторое "ядро" в котором у меня будет собрана вся обработка и генерация

Comment on lines 18 to 19
if (!wordFreqDictionary.ContainsKey(word)) wordFreqDictionary.Add(word, 1);
else wordFreqDictionary[word]++;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это можно заменить вызовом TryAdd (..., 1). Если не получилось, то делать инкремент. Так ты избежишь двойного прохода по словарю

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправил

try
{
using var doc = WordprocessingDocument.Open(filePath, false);
if (doc == null) return new List<string>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если прям хорошо провалиться в исходники WordprocessingDocument.Open(), то увидим, что null никогда не возвращается. Т.е. эта проверка всегда ложна.

Я имел ввиду, что NullReferenceException может быть при обращении к doc.MainDocumentPart - вот она точно может быть null

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправил

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А проверка то все равно нужна, просто на другое поле :)

Оставлю комментарий уже в рамках следующей задачи

Comment on lines +19 to +25
public IFormatReader TryGetReader(string filePath)
{
var reader = _readers.FirstOrDefault(r => r.CanRead(filePath));
if (reader == null)
throw new NotSupportedException("Формат не поддерживается");

return reader.TryRead(filePath);
return reader;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А является ли для ReadersRepository отсутствие подходящего читателя исключительным состоянием?

Вообще, для методов вида Try<что-то там> очень удобна такая сигнатура:
bool Try...(<Тип> param1, <Тип> param2, ..., out <Тип> outputResult)

Об успешности операции сообщается через булевое значение. При этом если ты ищешь что-то, например, в словаре, то возвращаемый результат передается через out параметр.

Таким образом на вызывающей стороне код будет выглядеть вот так:

if (Try...(param1, param2, out <Тип>outputResult))
// Делаем, что хотели
else
// Вот для внешнего кода это может быть исключительным состоянием. Можем кинуть Exception

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправлено

Comment on lines +5 to +10
/// <summary>
/// Тут пожалуй отвечу насчет использования списка кортежей, насколько я выяснил в интернете, в целом словари можно сортировать,
/// но вообще говоря Dictionary не гарантирует верный порядок элементов из-за особенностей своего устройства
/// В большинстве случаев - да, будет сортировать, но гарантий нам никто не дает, особенно на больших словарях
/// есть SortedDictionary, но он сортирует по ключу, поэтому я решил пойти таким путем - отделить сортировку от анализа частот
/// </summary>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Такие комментарии в коде оставлять не нужно. Это больше подойдет для ревью.

Комментарии стоит использовать либо для документации, либо пояснения каких-то нетривиальных вещей.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправил

/// </summary>
public class FrequencyDescendingSorterer : ISorterer
{
public List<(string Word, int Frequency)> Sort(Dictionary<string, int> wordsWithFreqs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Принято, я сам запамятовал, что словарь не гарантирует порядок. Молодец, что не стал просто заменять на словарь.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Выяснил случайно пока искал как это сделать, спасибо что предложил попробовать, теперь точно запомню что с сортировкой словарей есть нюансы

Comment on lines +140 to +148

Assert.IsNotNull(result);

result.Dispose();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У IDE (во всяком случае у Rider) подсветка с ума сходит от того, что Bitmap (и в целом System.Drawing) доступен только на платформе windows. Вещь, конечно, правильная, т.к. на Unix такой код не запустится (там по другому устроены графические примитивы и работа с ними).

Но мы здесь не решаем проблему кроссплатформенности. Снять предупреждение можно, если в csproj файлах явно указать платформу под windows. В этом случае это будет вот так:

<TargetFramework>net8.0-windows</TargetFramework>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправил

Comment on lines +36 to +41
Assert.That(result[0].Key, Is.EqualTo("hello"));
Assert.That(result[0].Value, Is.EqualTo(3));
Assert.That(result[1].Key, Is.EqualTo("world"));
Assert.That(result[1].Value, Is.EqualTo(2));
Assert.That(result[2].Key, Is.EqualTo("test"));
Assert.That(result[2].Value, Is.EqualTo(1));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот здесь кстати хорошо видно, чем словарь не удобен. Раньше были конкретные поля Word и Frequency.

Предложение: Из WordsFrequencyAnalyzer возвращать не словарь строка-число, а список объектов, каждый из которых содержит слово и его частоту.

Решение за тобой

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно неверно понял суть, но вроде как мы сознательно ушли от этого к использованию словаря и разделения на анализатор частоты и сортировщик слов по частоте.

Поскольку тут у меня есть выбор, я наверное оставлю как есть, мне кажется что названия и содержания тестов должно быть достаточно чтобы понять что анализатор хранит пары "слово - его частота"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants