Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<script type="text/javascript" src="js/game/phonology-rules.js?v=4.3.2" defer></script>
<script type="text/javascript" src="js/game/question-gen.js?v=4.6.2" defer></script>
<script type="text/javascript" src="js/game/srs.js?v=4.3.2" defer></script>
<script type="text/javascript" src="js/game/game-ui.js?v=4.6.9" defer></script>
<script type="text/javascript" src="js/game/game-ui.js?v=4.6.10" defer></script>
<script type="text/javascript" src="js/cloud-sync.js?v=4.3.4" defer></script>

<!-- Driver.js for Onboarding -->
Expand Down
58 changes: 57 additions & 1 deletion js/game/game-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,63 @@ function generateStatsHTML(title, data) {

if (Object.keys(data.levels).length > 0) {
html += `<h6 style="margin-bottom:6px;">腔級分佈</h6><div style="display: flex; flex-direction: column; gap: 10px;">`;
const sortedLevels = Object.entries(data.levels).sort((a, b) => (b[1].correct + b[1].wrong) - (a[1].correct + a[1].wrong));
const sortedLevels = Object.entries(data.levels).sort((a, b) => {
const lvlA = a[0];
const lvlB = b[0];
const dialectOrder = ['四', '海', '大', '平', '安'];
const levelOrder = ['基', '初', '中', '中高', '高', '教典'];

const parseLevel = (lvl) => {
let dialect = '';
let level = '';

// Find dialect by searching for inclusion of any dialect character
for (const d of dialectOrder) {
if (lvl.includes(d)) {
dialect = d;
break;
}
}

// Find level by searching for inclusion of level keywords
if (lvl.includes('教典')) {
level = '教典';
} else {
// Check '中高' before '中' to avoid partial matching of '中' first
const levelKeywords = ['基', '初', '中高', '中', '高'];
for (const l of levelKeywords) {
if (lvl.includes(l)) {
level = l;
break;
}
}
}
return { dialect, level };
};

const aInfo = parseLevel(lvlA);
const bInfo = parseLevel(lvlB);

let aDialectIdx = dialectOrder.indexOf(aInfo.dialect);
let bDialectIdx = dialectOrder.indexOf(bInfo.dialect);
if (aDialectIdx === -1) aDialectIdx = 999;
if (bDialectIdx === -1) bDialectIdx = 999;

if (aDialectIdx !== bDialectIdx) {
return aDialectIdx - bDialectIdx;
}

let aLevelIdx = levelOrder.indexOf(aInfo.level);
let bLevelIdx = levelOrder.indexOf(bInfo.level);
if (aLevelIdx === -1) aLevelIdx = 999;
if (bLevelIdx === -1) bLevelIdx = 999;

if (aLevelIdx !== bLevelIdx) {
return aLevelIdx - bLevelIdx;
}

return lvlA.localeCompare(lvlB);
});
Comment on lines +1131 to +1187

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defining dialectOrder, levelOrder, and the helper function parseLevel inside the sort comparator callback causes them to be re-created on every single comparison. Since sorting is an $O(N \log N)$ operation, this leads to unnecessary memory allocations and garbage collection overhead, which can impact performance on low-end or mobile devices.

Additionally, we can cache the parsed results of parseLevel to avoid redundant string scanning for the same keys during the sort process.

    const dialectOrder = ['四', '海', '大', '平', '安'];
    const levelOrder = ['基', '初', '中', '中高', '高', '教典'];
    const parseCache = {};

    const parseLevel = (lvl) => {
      if (parseCache[lvl]) return parseCache[lvl];

      let dialect = '';
      let level = '';

      // Find dialect by searching for inclusion of any dialect character
      for (const d of dialectOrder) {
        if (lvl.includes(d)) {
          dialect = d;
          break;
        }
      }

      // Find level by searching for inclusion of level keywords
      if (lvl.includes('教典')) {
        level = '教典';
      } else {
        // Check '中高' before '中' to avoid partial matching of '中' first
        const levelKeywords = ['基', '初', '中高', '中', '高'];
        for (const l of levelKeywords) {
          if (lvl.includes(l)) {
            level = l;
            break;
          }
        }
      }
      const result = { dialect, level };
      parseCache[lvl] = result;
      return result;
    };

    const sortedLevels = Object.entries(data.levels).sort((a, b) => {
      const lvlA = a[0];
      const lvlB = b[0];

      const aInfo = parseLevel(lvlA);
      const bInfo = parseLevel(lvlB);

      let aDialectIdx = dialectOrder.indexOf(aInfo.dialect);
      let bDialectIdx = dialectOrder.indexOf(bInfo.dialect);
      if (aDialectIdx === -1) aDialectIdx = 999;
      if (bDialectIdx === -1) bDialectIdx = 999;

      if (aDialectIdx !== bDialectIdx) {
        return aDialectIdx - bDialectIdx;
      }

      let aLevelIdx = levelOrder.indexOf(aInfo.level);
      let bLevelIdx = levelOrder.indexOf(bInfo.level);
      if (aLevelIdx === -1) aLevelIdx = 999;
      if (bLevelIdx === -1) bLevelIdx = 999;

      if (aLevelIdx !== bLevelIdx) {
        return aLevelIdx - bLevelIdx;
      }

      return lvlA.localeCompare(lvlB);
    });

for (const [lvl, stats] of sortedLevels) {
const lTotal = stats.correct + stats.wrong;
if (lTotal === 0) continue;
Expand Down
Loading