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

KTB-07 Реализовать функционал изучения слов

📝 Описание

Разработка функционала изучения слов

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

Логика режима изучения

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

Таким образом, потребуется запустить вложенный цикл для режима изучения слов (пункта "1" в меню).

Инициализация данных для обучения

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

В противном случае программа переходит к изучению слов и отображает блок в следующем формате:

// здесь пустая строка
cat:
 1 - спасибо
 2 - кошка
 3 - шляпа
 4 - собака

Реализация слова и вариантов ответа

  • Необходимо отобразить изучаемое слово и 4 невыученных слова. Слова брать в произвольном порядке (чтобы нельзя было запомнить порядковый номер правильного слова).
  • Правильное слово берется из полученного списка вариантов ответов и устанавливается в качестве вопроса. Это слово будет считаться правильным ответом.

🗒 Задание

  1. Проинициализировать переменную со списком невыученных слов (notLearnedList).
  2. Добавить проверку на наличие невыученных слов. Если все слова выучены – выводить сообщение “Все слова в словаре выучены” и выходить в меню.
  3. Создать и проинициализировать переменную со списком слов для изучения (questionWords). Список должен содержать 4 невыученных слова. Рекомендуется использовать метод take(). Для перемешивания списка рекомендуется использовать метод shuffled().
  4. В режиме изучения слов отображать одно загаданное (correctAnswer) слово и четыре варианта ответа в случайном порядке. После отображения ожидать ввод номера ответа от пользователя.

❌ Ошибки

  • сначала берут 4 слова, а только потом перемешивают
  • [bug] – если слов для изучения осталось 3 и меньше, то при обращении по индексу будет IndexOutOfBoundsException. В таком случае см. комментарий про mapIndexed.

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

Давай возьмем `questionWords` и с помощью `mapIndexed {}` и `joinToString` получим строку с вариантами ответа. 
В `joinToString` используем: параметр `separator` для переноса строки, `prefix` непосредственно для оригинального слова на английском, а  `postfix` переносы строки и пункт меню для выхода.
val variants = questionWords
    .mapIndexed { index: Int, word: Word -> " ${index + 1}${word.translate}" }
        .joinToString(
          separator = "\n",
          prefix = "\n${correctAnswer.original}\n",
          postfix = "\n 0 – выйти в меню",
    )
println(variants)
Давай возьмем `questionWords` и с помощью `mapIndexed {}` и `joinToString` получим строку с вариантами ответа. 

В `joinToString` используем: параметр `separator` для переноса строки, `prefix` непосредственно для оригинального слова на английском, а  `postfix` переносы строки и пункт меню для выхода.
Создавать словарь уже не нужно, используй существующий файл.
Лучше не использовать `indexOf` внутри цикла, потому что `indexOf` каждый раз пробегает по всему циклу.
Ты делаешь сначала `take`, и только потом `shuffled()` - это может вызывать проблемы.
`filter` - не самая дешевая операция. Во-первых, происходит перечисление всех элементов списка `dictionary`. Во-вторых, создается новая коллекция. Давай создадим переменную для хранения невыученных слов.
Ты можешь просто применить `take()` к `learnedWord`, чтобы получить требуемое количество выученных слов **и избежать цикла**.
Давай не просить вводить само слово, это же сложно. Достаточно номера варианта ответа.
Давай используем шаблоны строк, это будет компактнее.
`map` — это "преобразующая" функция. Она возвращает новый список. В твоем случае уместнее использовать `forEachIndexed`.
`mapIndexed` нужно использовать правильно, с возвращаемым значением. Иначе лучше использовать `forEachIndexed`.
Внутри цикла делай проверку на наличие слов: если список пуст, показывай сообщение "выучены все слова" и выходи.

⌨️ Решение

"1" -> {
    while (true) {
        val notLearnedList = dictionary.filter { it.correctAnswersCount < 3 }
        if (notLearnedList.isEmpty()) {
            println("Все слова в словаре выучены")
            continue
        }
        val questionWords = notLearnedList.shuffled().take(4)
        val correctAnswer = questionWords.random()
        // --- вариант 1
        println("""

            ${correctAnswer.original}:
            1 - ${questionWords[0].translate}
            2 - ${questionWords[1].translate}
            3 - ${questionWords[2].translate}
            4 - ${questionWords[3].translate}
        """.trimIndent())

          // --- продвинутое правильное решение (решает проблему индекса)

            val variants = questionWords
                .mapIndexed { index: Int, word: Word -> " ${index + 1}${word.translate}" }
                    .joinToString(
                      separator = "\n",
                      prefix = "\n${correctAnswer.original}\n",
                      postfix = "\n 0 – выйти в меню",
                )
            println(variants)

        readln()
    }
}