// ==UserScript== // @name 超星按题目的差异度排序 // @namespace http://tampermonkey.net/ // @version 2025-04-18-001 // @description 差异度越小的,排在前面。建议(1)当前页显示条目数设置为最大 (2)打开“显示题目详情”选项。 // @author 周利斌 // @match https://mooc2-ans.chaoxing.com/mooc2-ans/qbank/questionlist?courseid=* // @icon https://www.google.com/s2/favicons?sz=64&domain=chaoxing.com // @grant none // @license MIT // @downloadURL none // ==/UserScript== // 立即执行函数表达式,创建一个独立的作用域,防止全局变量污染 (function () { 'use strict'; /** * 计算两个字符串之间的 Levenshtein 距离 * Levenshtein 距离指的是将一个字符串转换为另一个字符串所需的最少编辑操作(插入、删除、替换)次数 * @param {string} s1 - 第一个字符串 * @param {string} s2 - 第二个字符串 * @returns {number} - 两个字符串之间的 Levenshtein 距离 */ function levenshteinDistance(s1, s2) { // 获取第一个字符串的长度 const m = s1.length; // 获取第二个字符串的长度 const n = s2.length; // 创建一个二维数组 dp 来存储子问题的解 const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); // 双重循环遍历所有可能的子字符串组合 for (let i = 0; i <= m; i++) { for (let j = 0; j <= n; j++) { // 如果第一个字符串为空,那么距离等于第二个字符串的长度 if (i === 0) { dp[i][j] = j; } // 如果第二个字符串为空,那么距离等于第一个字符串的长度 else if (j === 0) { dp[i][j] = i; } // 如果当前字符相同,那么距离等于前一个子问题的距离 else if (s1[i - 1] === s2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } // 如果当前字符不同,那么距离等于插入、删除、替换操作中的最小距离加 1 else { dp[i][j] = 1 + Math.min( dp[i - 1][j], // 删除操作 dp[i][j - 1], // 插入操作 dp[i - 1][j - 1] // 替换操作 ); } } } // 返回最终的 Levenshtein 距离 return dp[m][n]; } /** * 根据 Levenshtein 距离对 ul 列表中的 li 元素进行排序 */ function sortListByLevenshtein() { // 获取页面中 id 为 questionUl 的 ul 元素 const ul = document.querySelector('ul#questionUl'); // 如果未找到该 ul 元素,等待 1 秒后重新调用该函数 if (!ul) { setTimeout(sortListByLevenshtein, 1000); return; } // 获取 ul 元素下的所有 li 元素,并将其转换为数组 const lis = Array.from(ul.children); // 打印 ul 元素和 li 元素的数量,用于调试 // console.log(ul, lis.length); // 提取每个 li 元素中类名为 choose-name 的元素的文本内容及其索引 const items = lis.map((li, index) => { const text = li.querySelector('.choose-name')?.textContent.trim() || ""; const optionLis = li.querySelectorAll(".questions-details .option li") const options = Array.from(optionLis).map(a => a.textContent.substring(2)) options.sort() return { text: text + options.join("."), index }; }); console.log(items[0], items[1]) // 创建一个二维数组 distances 用于存储所有元素之间的 Levenshtein 距离 const distances = []; // 获取元素的数量 const n = items.length; for (let i = 0; i < n; i++) { distances[i] = []; for (let j = 0; j < n; j++) { // 同一元素之间的距离设为无穷大 if (i === j) { distances[i][j] = Infinity; } // 如果已经计算过 j 到 i 的距离,直接使用 else if (j < i) { distances[i][j] = distances[j][i]; } // 计算 i 到 j 的 Levenshtein 距离 else { distances[i][j] = levenshteinDistance(items[i].text, items[j].text); } } } // 用于存储排序后的元素 const newItems = []; // 用于标记元素是否已经被处理过 const used = new Array(n).fill(false); // 循环直到所有元素都被处理 while (newItems.length < n) { // 初始化最小距离为无穷大 let minDistance = Infinity; // 用于存储最小距离的元素对的索引 let minPair = []; // 遍历所有未处理的元素对,找到最小距离的元素对 for (let i = 0; i < n; i++) { if (used[i]) continue; for (let j = i + 1; j < n; j++) { if (used[j]) continue; if (distances[i][j] < minDistance) { minDistance = distances[i][j]; minPair = [i, j]; } } } // 如果找到了最小距离的元素对 if (minPair.length > 0) { // 将最小距离的元素对添加到新数组中 newItems.push(items[minPair[0]], items[minPair[1]]); // 标记这两个元素已经被处理过 used[minPair[0]] = true; used[minPair[1]] = true; // 遍历所有未处理的元素,找到与最小距离元素对中第一个元素距离相同的元素 for (let i = 0; i < n; i++) { if (!used[i] && distances[i][minPair[0]] === minDistance) { // 将这些元素添加到新数组中 newItems.push(items[i]); // 标记这些元素已经被处理过 used[i] = true; } } } // 如果没有找到最小距离的元素对,将剩余未处理的元素添加到新数组中 else { newItems.push(...items.filter((f, i) => !used[i])); } } // 获取排序后的元素的索引数组 const newOrder = newItems.map(item => item.index); // 获取原始元素的索引数组 const originalOrder = items.map(item => item.index); // 检查排序后的顺序是否与原始顺序不同 const isOrderChanged = newOrder.some((value, index) => value !== originalOrder[index]); // 如果顺序发生了改变 if (isOrderChanged) { // 清空 ul 元素 while (ul.firstChild) { ul.removeChild(ul.firstChild); } // 打印原始顺序和新顺序,用于调试 // console.log(originalOrder, newOrder) // 根据新顺序重新添加 li 元素到 ul 中 newOrder.forEach(index => { ul.appendChild(lis[index]); }); } // 等待 1 秒后再次调用该函数,实现定时排序 setTimeout(sortListByLevenshtein, 1000); } // 调用排序函数 sortListByLevenshtein(); })();