From 9431e981969e392b6c3b8e405d5c736ceed76f60 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 14 Jun 2018 19:46:47 +0900 Subject: [PATCH] Improved question generator --- .../multiple-choice-test.ts | 59 ++++++++++++++++--- scripts/levenshtein.ts | 42 +++++++++++++ scripts/randomItem.ts | 3 - 3 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 scripts/levenshtein.ts delete mode 100644 scripts/randomItem.ts diff --git a/elements/multiple-choice-test/multiple-choice-test.ts b/elements/multiple-choice-test/multiple-choice-test.ts index 90c149d..45615b6 100644 --- a/elements/multiple-choice-test/multiple-choice-test.ts +++ b/elements/multiple-choice-test/multiple-choice-test.ts @@ -5,9 +5,9 @@ import StatusMessages from "../status-messages/status-messages" import QuestionStatistics from "scripts/QuestionStatistics" import Statistics from "scripts/Statistics" import State from "scripts/State" -import randomItem from "scripts/randomItem" import copyToClipboard from "scripts/copyToClipboard" import cloneTemplate from "scripts/cloneTemplate" +import levenshtein from "scripts/levenshtein" const preventClicksTimeThreshold = 400 @@ -208,13 +208,49 @@ export default class MultipleChoiceTest extends HTMLElement { } generateAnswers() { - let allKana = [...State.words.values()] + let wordPool = [...State.words.values()] let used = new Set() + let question = this.kanjiView.kanji + let correctAnswer = this.correctAnswer + + // Sort by Levenshtein distance + wordPool.sort((a, b) => levenshtein(correctAnswer, a.hiragana) - levenshtein(correctAnswer, b.hiragana)) + + // Find kana at the end + let i = question.length - 1 + + for(; i >= 0; i--) { + let charCode = question.charCodeAt(i) + let isHiragana = charCode >= 0x3040 && charCode <= 0x309f + let isKatakana = charCode >= 0x30a0 && charCode <= 0x30ff + + if(!isHiragana && !isKatakana) { + break + } + } + + let kana = question.slice(i + 1) + + // If there are kana at the end, + // only allow answers that end with the given kana. + if(kana.length > 0) { + let filteredWords = wordPool.filter(x => x.hiragana.endsWith(kana)) + + // If the filtered version doesn't have enough options, + // combine it with all the other vocab. + if(filteredWords.length < this.answers.length) { + filteredWords = filteredWords.concat(wordPool) + } + + wordPool = filteredWords + } // Add correct answer let correctAnswerIndex = Math.floor(Math.random() * this.answers.length) - this.answers[correctAnswerIndex].innerText = this.correctAnswer - used.add(this.correctAnswer) + this.answers[correctAnswerIndex].innerText = correctAnswer + used.add(correctAnswer) + + let count = 0 for(let answer of this.answers) { // Skip existing answers @@ -222,12 +258,19 @@ export default class MultipleChoiceTest extends HTMLElement { continue } - let text = randomItem(allKana).hiragana + if(count >= wordPool.length) { + answer.innerText = "-" + continue + } + + let text = wordPool[count].hiragana + count++ // Avoid duplicate answers - if(allKana.length >= this.answers.length) { - while(used.has(text)) { - text = randomItem(allKana).hiragana + if(wordPool.length >= this.answers.length) { + while(used.has(text) && count < wordPool.length) { + text = wordPool[count].hiragana + count++ } } diff --git a/scripts/levenshtein.ts b/scripts/levenshtein.ts new file mode 100644 index 0000000..3de90d6 --- /dev/null +++ b/scripts/levenshtein.ts @@ -0,0 +1,42 @@ +export default function levenshtein(a: string, b: string): number { + if(a.length === 0) return b.length + if(b.length === 0) return a.length + + let tmp, i, j, prev, val, row + + // swap to save some memory O(min(a,b)) instead of O(a) + if(a.length > b.length) { + tmp = a + a = b + b = tmp + } + + row = Array(a.length + 1) + + // init the row + for(i = 0; i <= a.length; i++) { + row[i] = i + } + + // fill in the rest + for(i = 1; i <= b.length; i++) { + prev = i + + for (j = 1; j <= a.length; j++) { + if (b[i-1] === a[j-1]) { + val = row[j-1] // match + } else { + val = Math.min(row[j-1] + 1, // substitution + Math.min(prev + 1, // insertion + row[j] + 1)) // deletion + } + + row[j - 1] = prev + prev = val + } + + row[a.length] = prev + } + + return row[a.length] +} \ No newline at end of file diff --git a/scripts/randomItem.ts b/scripts/randomItem.ts deleted file mode 100644 index 1a9b143..0000000 --- a/scripts/randomItem.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function randomItem(collection: T[]) { - return collection[Math.floor(Math.random() * collection.length)] -} \ No newline at end of file