dramaling-vocab-learning/frontend/utils/answerExtractor.ts

198 lines
5.3 KiB
TypeScript

/**
* 答案推導工具 - 從例句和挖空例句中動態推導正確答案
*/
export interface AnswerExtractionResult {
answers: string[];
isValid: boolean;
error?: string;
}
/**
* 從例句和挖空題目中提取答案
* @param example 原始例句
* @param filledQuestion 挖空後的題目
* @returns 提取的答案陣列
*/
export function extractAnswerFromBlanks(example: string, filledQuestion: string): AnswerExtractionResult {
try {
// 輸入驗證
if (!example || !filledQuestion) {
return {
answers: [],
isValid: false,
error: "例句或挖空題目為空"
};
}
if (!filledQuestion.includes('____')) {
return {
answers: [],
isValid: false,
error: "挖空題目中沒有找到 ____"
};
}
// 方法1: 正則匹配法 (推薦用於單個空格)
if (filledQuestion.split('____').length === 2) {
return extractSingleBlankAnswer(example, filledQuestion);
}
// 方法2: 差異比對法 (用於多個空格)
return extractMultipleBlanksAnswers(example, filledQuestion);
} catch (error) {
return {
answers: [],
isValid: false,
error: `答案提取失敗: ${error instanceof Error ? error.message : '未知錯誤'}`
};
}
}
/**
* 提取單個空格的答案 (正則匹配法)
*/
function extractSingleBlankAnswer(example: string, filledQuestion: string): AnswerExtractionResult {
try {
// 轉義特殊字符並替換 ____ 為捕獲群組
const escapedPattern = filledQuestion
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // 轉義正則特殊字符
.replace(/____/g, '(.+?)'); // 替換為非貪婪捕獲群組
const regex = new RegExp(`^${escapedPattern}$`, 'i');
const match = example.match(regex);
if (match && match[1]) {
const answer = match[1].trim();
return {
answers: [answer],
isValid: true
};
}
// 如果完全匹配失敗,嘗試部分匹配
const partialRegex = new RegExp(escapedPattern, 'i');
const partialMatch = example.match(partialRegex);
if (partialMatch && partialMatch[1]) {
const answer = partialMatch[1].trim();
return {
answers: [answer],
isValid: true
};
}
return {
answers: [],
isValid: false,
error: "無法匹配例句和挖空題目"
};
} catch (error) {
return {
answers: [],
isValid: false,
error: `正則匹配失敗: ${error instanceof Error ? error.message : '未知錯誤'}`
};
}
}
/**
* 提取多個空格的答案 (差異比對法)
*/
function extractMultipleBlanksAnswers(example: string, filledQuestion: string): AnswerExtractionResult {
try {
const parts = filledQuestion.split('____');
const answers: string[] = [];
let currentPos = 0;
for (let i = 0; i < parts.length - 1; i++) {
const beforePart = parts[i];
const afterPart = parts[i + 1];
// 找到前半部分的結束位置
const startPos = currentPos + beforePart.length;
// 找到後半部分的開始位置
let endPos: number;
if (afterPart === '') {
// 如果是最後一個空格,到句子結尾
endPos = example.length;
} else {
endPos = example.indexOf(afterPart, startPos);
if (endPos === -1) {
return {
answers: [],
isValid: false,
error: `無法找到後半部分: "${afterPart}"`
};
}
}
// 提取中間的詞作為答案
const answer = example.substring(startPos, endPos).trim();
answers.push(answer);
currentPos = endPos;
}
return {
answers,
isValid: answers.length > 0 && answers.every(ans => ans.length > 0)
};
} catch (error) {
return {
answers: [],
isValid: false,
error: `多空格提取失敗: ${error instanceof Error ? error.message : '未知錯誤'}`
};
}
}
/**
* 獲取填空題的第一個答案 (最常用)
* @param example 原始例句
* @param filledQuestion 挖空後的題目
* @param fallbackAnswer 降級答案 (通常是 word 屬性)
* @returns 正確答案字串
*/
export function getCorrectAnswer(
example: string,
filledQuestion: string | undefined,
fallbackAnswer: string
): string {
if (!filledQuestion) {
return fallbackAnswer;
}
const result = extractAnswerFromBlanks(example, filledQuestion);
if (result.isValid && result.answers.length > 0) {
return result.answers[0];
}
// 推導失敗時使用降級答案
console.warn('答案推導失敗,使用降級答案:', result.error);
return fallbackAnswer;
}
/**
* 驗證用戶答案是否正確
* @param userAnswer 用戶輸入的答案
* @param example 原始例句
* @param filledQuestion 挖空後的題目
* @param fallbackAnswer 降級答案
* @returns 是否正確
*/
export function validateAnswer(
userAnswer: string,
example: string,
filledQuestion: string | undefined,
fallbackAnswer: string
): boolean {
const correctAnswer = getCorrectAnswer(example, filledQuestion, fallbackAnswer);
return userAnswer.toLowerCase().trim() === correctAnswer.toLowerCase().trim();
}