#include "roster.h" #include namespace Roster { ParsedNames parse(const QString &text) { ParsedNames result; QSet seen; const QStringList lines = text.split(QLatin1Char('\n')); int lineNumber = 0; for (const QString &line : lines) { ++lineNumber; const QString name = line.trimmed(); if (name.isEmpty()) continue; if (seen.contains(name)) { result.error = QStringLiteral("第 %1 行与前面的条目重复:%2。" "同名学生请加上学号后再应用名单。").arg(lineNumber).arg(name); return result; } if (name.size() > 100) { result.error = QStringLiteral("第 %1 行超过 100 个字符,请检查是否粘贴了整段文字。") .arg(lineNumber); return result; } seen.insert(name); result.names.append(name); } if (result.names.isEmpty()) result.error = QStringLiteral("名单为空,请每行输入一位学生。"); else if (result.names.size() > 5000) result.error = QStringLiteral("名单超过 5000 人,请分批操作。"); return result; } void Session::setNames(const QStringList &names) { m_names = names; resetDraw(); } void Session::resetDraw() { m_pool = m_names; } QStringList Session::draw(int count, QRandomGenerator &random) { QStringList selected; if (count <= 0 || count > m_pool.size()) return selected; for (int i = 0; i < count; ++i) { const int index = random.bounded(static_cast(m_pool.size())); // 从候选池中移除已抽到的人,本轮后续抽签就不会重复。 selected.append(m_pool.takeAt(index)); } return selected; } QList Session::groups(int value, bool valueIsGroupCount, QRandomGenerator &random) const { QList result; const int n = static_cast(m_names.size()); if (n == 0 || value <= 0 || (valueIsGroupCount && value > n)) return result; const int count = valueIsGroupCount ? value : (n + value - 1) / value; QStringList shuffled = m_names; // Fisher–Yates 洗牌。每次从尚未固定的位置中均匀选取一个。 for (int i = n - 1; i > 0; --i) qSwap(shuffled[i], shuffled[random.bounded(i + 1)]); for (int i = 0; i < count; ++i) result.append(QStringList()); // 轮流放入各组,保证每人出现一次,各组人数最多相差 1。 for (int i = 0; i < n; ++i) result[i % count].append(shuffled.at(i)); return result; } }