Skip to content

Commit

Permalink
Improved question generator
Browse files Browse the repository at this point in the history
  • Loading branch information
akyoto committed Jun 14, 2018
1 parent 31d64dd commit 9431e98
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 11 deletions.
59 changes: 51 additions & 8 deletions elements/multiple-choice-test/multiple-choice-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -208,26 +208,69 @@ export default class MultipleChoiceTest extends HTMLElement {
}

generateAnswers() {
let allKana = [...State.words.values()]
let wordPool = [...State.words.values()]
let used = new Set<string>()
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
if(answer.innerText !== "") {
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++
}
}

Expand Down
42 changes: 42 additions & 0 deletions scripts/levenshtein.ts
Original file line number Diff line number Diff line change
@@ -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]
}
3 changes: 0 additions & 3 deletions scripts/randomItem.ts

This file was deleted.

0 comments on commit 9431e98

Please sign in to comment.