1. 项目背景与需求分析
在工业制造、零件管理等场景中,经常会遇到混合了中文、英文、数字和符号的复杂型号命名。比如"不锈钢六角螺栓M8×1.25-50"这样的字符串,传统的排序方式往往无法满足业务需求。我们需要一种能够智能识别字符串中不同字符类型(汉字、字母、数字、符号),并按照特定规则进行分段比较的排序算法。
这类需求在实际开发中非常常见,特别是在:
- 工业零件管理系统
- 产品型号管理后台
- 物料编码系统
- 任何需要处理混合字符类型的排序场景
2. 技术方案设计
2.1 整体思路
要实现这种复合字符串的智能排序,我们需要解决三个核心问题:
- 字符串分割:如何将混合字符串按字符类型拆分成有意义的片段
- 片段比较:如何定义不同类型片段之间的比较规则
- 整体排序:如何组合这些比较规则实现最终的排序逻辑
2.2 关键技术选型
2.2.1 字符串分割方案
我们选择使用Qt的QRegularExpression进行字符串分割,原因如下:
- 正则表达式能精确匹配不同类型的字符
- Qt框架内置支持,无需额外依赖
- 性能足够满足常规业务需求
- 支持Unicode字符集(特别是中文)
2.2.2 排序规则设计
我们制定了以下排序优先级:
- 汉字 → 按拼音排序
- 数字 → 按数值大小排序
- 字母 → 按字母表顺序
- 符号 → 保持原顺序
这种设计符合大多数中文用户的直觉认知。
3. 核心实现详解
3.1 字符串分割实现
cpp复制void sortByDiffChars::splitStringByDifferentChar(const QString& strInput, QList<QString>& lsSplitStr)
{
static const QRegularExpression re("([\\x{4e00}-\\x{9fff}]+|\\d+|[^\\x{4e00}-\\x{9fff}\\d]+)");
QRegularExpressionMatchIterator i = re.globalMatch(strInput);
QString matched;
while (i.hasNext())
{
matched = "";
QRegularExpressionMatch match = i.next();
if (match.hasMatch()) {
matched = match.captured(0);
if (!matched.isEmpty()) {
lsSplitStr.append(matched);
}
}
}
}
这个正则表达式由三部分组成:
[\\x{4e00}-\\x{9fff}]+:匹配中文字符\d+:匹配数字[^\\x{4e00}-\\x{9fff}\\d]+:匹配既不是中文也不是数字的字符
提示:这里的Unicode范围\x{4e00}-\x{9fff}覆盖了常用汉字,如果需要支持更全的汉字集,可以扩展这个范围。
3.2 数字字符串比较
cpp复制bool sortByDiffChars::compNumberString(const QString& str1, const QString& str2)
{
int nlsSize1 = str1.size();
int nlsSize2 = str2.size();
int nCount = nlsSize1 > nlsSize2 ? nlsSize1 : nlsSize2;
for (int nIndex = 0; nIndex < nCount; nIndex++)
{
if (nIndex < nlsSize1 && nIndex < nlsSize2)
{
if (str1.at(nIndex) != str2.at(nIndex))
{
bool bIsNum1 = false;
bool bIsNum2 = false;
QString strNum1 = str1.at(nIndex);
QString strNum2 = str2.at(nIndex);
int nNum1 = strNum1.toInt(&bIsNum1);
int nNum2 = strNum2.toInt(&bIsNum2);
return nNum1 < nNum2;
}
}
else if (nIndex >= nlsSize1 && nIndex < nlsSize2)
{
return true;
}
else if (nIndex < nlsSize1 && nIndex >= nlsSize2)
{
return false;
}
}
return false;
}
这个函数实现了:
- 逐位比较数字字符串
- 处理前导零的情况(如"01"和"1")
- 处理不同长度的数字字符串(如"123"和"1234")
3.3 汉字字符串比较
cpp复制bool sortByDiffChars::compStringByHZ(const QString& str1, const QString& str2)
{
if (str1.isEmpty() || str2.isEmpty())
{
return false;
}
bool bIsNum1 = false;
bool bIsNum2 = false;
int nNum1 = str1.toInt(&bIsNum1);
int nNum2 = str2.toInt(&bIsNum2);
if (bIsNum1 && bIsNum2)
{
if (nNum1 == nNum2)
{
return compNumberString(str1, str2);
}
return nNum1 < nNum2;
}
else if (bIsNum1 && !bIsNum2)
{
return true;
}
else if (!bIsNum1 && bIsNum2)
{
return false;
}
else
{
QLocale local(QLocale::Chinese);
QCollator collator(local);
return (collator.compare(str1, str2) < 0);
}
return false;
}
这里使用了Qt的QCollator类来实现中文拼音排序,关键点:
- 设置QLocale为Chinese
- 优先处理纯数字情况
- 数字总是排在非数字前面
3.4 整体排序逻辑
cpp复制bool sortByDiffChars::sortStringListByDifferentChar(const QString& str1, const QString& str2)
{
QStringList lsPort1, lsPort2;
splitStringByDifferentChar(str1, lsPort1);
splitStringByDifferentChar(str2, lsPort2);
int nlsSize1 = lsPort1.size();
int nlsSize2 = lsPort2.size();
int nCount = nlsSize1 > nlsSize2 ? nlsSize1 : nlsSize2;
for (int nIndex = 0; nIndex < nCount; nIndex++)
{
if (nIndex < nlsSize1 && nIndex < nlsSize2)
{
if (lsPort1.at(nIndex) != lsPort2.at(nIndex))
{
return compStringByHZ(lsPort1.at(nIndex), lsPort2.at(nIndex));
}
}
else if (nIndex >= nlsSize1 && nIndex < nlsSize2)
{
return true;
}
else if (nIndex < nlsSize1 && nIndex >= nlsSize2)
{
return false;
}
}
return false;
}
这个函数实现了:
- 先分割两个字符串
- 逐段比较对应位置的片段
- 如果所有片段都相同,则较短的字符串排在前面
4. 实际应用与效果
4.1 使用示例
cpp复制QStringList strList;
// 添加各种混合字符串
strList.append("不锈钢六角螺栓M8×1.25-50");
strList.append("精密轴承608ZZ");
// ...更多字符串
// 排序
std::sort(strList.begin(), strList.end(), [&](QString str1, QString str2)->bool {
return sortStringListByDifferentChar(str1, str2);
});
4.2 排序效果对比
排序前:
code复制不锈钢六角螺栓M8×1.25-50
精密轴承608ZZ
弹簧垫圈DIN127B-10
铜接线端子OT4-6
排序后:
code复制不锈钢六角螺栓M8×1.25-50
弹簧垫圈DIN127B-10
精密轴承608ZZ
铜接线端子OT4-6
4.3 性能考虑
对于大型数据集(超过10万条记录),建议:
- 预先计算并缓存字符串的分割结果
- 考虑使用多线程排序
- 对于固定格式的字符串,可以优化正则表达式
5. 常见问题与解决方案
5.1 特殊字符处理
问题:某些特殊符号(如Φ、×等)可能影响排序结果。
解决方案:
- 在正则表达式中明确包含这些符号
- 或者将它们归类到"符号"类别统一处理
5.2 性能优化
问题:大量字符串排序时性能下降。
优化建议:
cpp复制// 预分割所有字符串
QVector<QStringList> preSplitList;
for (const auto& str : strList) {
QStringList parts;
splitStringByDifferentChar(str, parts);
preSplitList.append(parts);
}
// 排序时直接使用预分割结果
std::sort(strList.begin(), strList.end(), [&](const QString& str1, const QString& str2) {
int idx1 = strList.indexOf(str1);
int idx2 = strList.indexOf(str2);
return comparePreSplit(preSplitList[idx1], preSplitList[idx2]);
});
5.3 多语言支持
问题:需要支持其他语言(如日文、韩文等)。
解决方案:
- 扩展正则表达式以匹配其他语言的字符范围
- 使用QLocale对应的语言设置
- 对于没有拼音排序的语言,可以回退到Unicode码点排序
6. 扩展应用
6.1 支持更多字符类型
可以通过修改正则表达式来支持更多字符类型,例如:
cpp复制// 支持希腊字母
static const QRegularExpression re("([\\x{4e00}-\\x{9fff}]+|\\d+|[α-ωΑ-Ω]+|[^\\x{4e00}-\\x{9fff}\\dα-ωΑ-Ω]+)");
6.2 自定义排序规则
可以通过修改compStringByHZ函数来实现不同的排序优先级,例如:
cpp复制// 让字母排在数字前面
if (bIsNum1 && !bIsNum2) {
return false; // 数字排在后面
} else if (!bIsNum1 && bIsNum2) {
return true; // 非数字排前面
}
6.3 与数据库集成
对于需要从数据库查询并排序的场景,可以考虑:
- 在数据库中存储预分割的字段
- 或者使用数据库的自定义函数实现类似逻辑
- 对于大型数据集,最好在数据库层面解决排序问题
7. 完整代码结构
项目包含两个主要文件:
sortByDiffChars.h- 类声明sortByDiffChars.cpp- 实现代码
关键类设计:
cpp复制class sortByDiffChars : public QMainWindow {
Q_OBJECT
public:
// 构造函数等...
// 核心功能
void splitStringByDifferentChar(const QString& strInput, QList<QString>& lsSplitStr);
bool sortStringListByDifferentChar(const QString& str1, const QString& str2);
bool compStringByHZ(const QString& str1, const QString& str2);
bool compNumberString(const QString& str1, const QString& str2);
private:
// UI元素
QListWidget* m_pListWidget;
QPushButton* m_pBtn;
};
8. 实际应用建议
- 测试边界情况:特别注意测试空字符串、纯数字、纯中文等边界情况
- 性能监控:对于大型数据集,监控排序耗时
- 本地化考虑:不同地区的中文排序可能有细微差别
- 错误处理:增加对无效字符的处理逻辑
这个排序算法在实际项目中已经验证了其有效性,特别是在处理工业零件编号、产品型号等场景下表现优异。通过适当调整,可以适应各种复杂的字符串排序需求。