Перейти к содержанию

KTB-04 Спарсить файл словаря и распечатать содержимое

📝 Описание

Необходимо реализовать функциональность, позволяющую отслеживать количество правильных ответов пользователя на каждое слово в тренажёре. Это соответствует 4 пункту требований к тренажёру.

Добавление свойства в класс Word

Следует использовать ранее созданный класс Word и добавить в него новое свойство correctAnswersCount со значением по умолчанию 0. Это свойство будет хранить количество правильных ответов для каждого слова, что позволит отслеживать уровень освоения пользователем каждого термина.

Хранение данных в файле

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

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

Обработка отсутствующих данных

При чтении данных из файла возможно отсутствие информации о количестве правильных ответов в некоторых строках (особенно если поле было добавлено недавно). Поэтому при прохождении по строкам файла в цикле необходимо добавить проверку на null. Для этого рекомендуется использовать элвис-оператор.

Таким образом, при отсутствии данных свойству correctAnswersCount будет присвоено значение 0, что обеспечит корректную работу приложения.

Инициализация словаря и вывод данных

После добавления нового свойства и настройки чтения данных из файла – требуется инициализировать пустой изменяемый список dictionary, который будет содержать объекты типа Word.

На каждой итерации цикла по строкам файла необходимо создавать новый объект Word и добавлять его в dictionary.

🗒 Задание

  1. Добавить новое свойство в класс для хранения слов.
  2. Вручную добавить разделитель с 0 значением, при отсутствии числа правильных ответов в файле – использовать значение по умолчанию.
  3. Инициализировать пустой изменяемый список для словаря dictionary с типом объектов Word.
  4. На каждой итерации прохода по строкам файла создавать объект типа Word и добавлять его в dictionary.
  5. Распечатать содержимое dictionary в консоль.

❌ Ошибки

  • не используют toIntOrNull() для обработки отсутствующего значения в файле

✏️ Комментарии

Если создают файл в коде и записывают туда слова:

В целом, будет гибче если мы будем считывать словарь из файла. Это дает возможность менять словарь без пересборки приложения. На крайний случай можно проверять, существует ли файл: `if(!wordsFile.exists()) { createStartWords() }`

Если просто вычитывают значение correctAnswersCount, не проверяя его наличие и Null

Тут можно сделать чуть устойчивее к ошибкам в файле: `line.getOrNull(2)?.toIntOrNull() ?: 0`
Давай переименуем класс `Words`, все-таки принято сущности называть в ед. числе.
Оператор `[2]` можно заменить вызовом метода `get()` - это будет эквивалентная операция. 
А можно использовать `getOrNull()` - которая в случае ошибки обращения по индексу возвратит `null`: `val correctAnswers: Int = line.getOrNull(2)?.toIntOrNull() ?: 0`.
Для чего тут `Map`? Нам достаточно обычного списка
Можно использовать `?.let { wordList.add(it) }` вместо `if`
Сейчас мы делаем основу для нашего консольного тренажера слов, который будем в дальнейшем апгрейдить до Телеграм-бота. На текущем этапе наша задача написать код, который грузит из файла словарь. Словарь, в нашем случае, список `Word`'ов. Логика такая - если файла нет, создаем его и заполняем словами. Если есть - ничего не делаем, ведь словарь предоставлен извне.
Давай удалим выше строку: `wordsFile.writeText("")`. Она по сути затирает все содержимое словаря, и тогда мы вынуждены всегда заполнять словарь заново.
Слово у нас представляется английским значением, переводом, а также давай добавим поле, которое обозначает - сколько раз пользователь правильно его ответил. При определенном пороге правильных ответов, считаем что слово выучено. 
А теперь давай подумаем, если не указано количество, или указано не корректно, может просто будем проставлять 0? Чтобы не работать внутри приложения с нулябельными полями.
Попробуй в `words.txt` добавить `good|хорошо|` (строка заканчивается вертикальной чертой). Приложение упадет. Надо починить.
Давай сделаем `correctAnswersCount` ненулябельным, с нулем по умолчанию.
Стоит всегда стараться избегать нулябельных полей. Тем более обработки нулябельных полей оператором `!!`. Этот оператор означает "я не хочу заморачиваться над нулябельностью в этом поле".
Мы используем `correctAnswersCount` для хранения количества ответов пользователей. И значение его всегда определено – либо пользователь еще не отвечал, либо отвечал какое-то количество раз правильно.
Давай сделаем `correctAnswersCount` ненулябельным.

⌨️ Решение

hello|привет|0
dog|собака|0
cat|кошка|0
thank you|спасибо|0
data class Word(
    val original: String,
    val translate: String,
    val correctAnswersCount: Int = 0,
)

fun main() {

    val wordsFile = File("words.txt")
    val dictionary = mutableListOf<Word>()

    for (string in wordsFile.readLines()) {
        val split = string.split("|")
        val word = Word(
            original = split[0],
            translate = split[1],
            correctAnswersCount = split[2].toIntOrNull() ?: 0
        )
        dictionary.add(word)
    }

    println(dictionary)

}