起因,某天某个产品提了某个需求指派给一个倒霉蛋:
怕我看不明白还特地把百度到的网址给我放上边了:知乎地址
看完了之后我哭了,这特么也太复杂了!(好想给产品一个卡普的爱的铁拳),任务都已经派下来只好慢慢做了,下面记录一下开发过程。
目标很明确,我们只需要实现一个两个字符串比较的函数:
1.开发之前前首先要明白QString怎么存储字符串的:
- QString 类存储的字符串默认是Unicode编码格式
- 来自 char* 的数据 - 默认被当作UTF-8编码格式
2.确定比较规则:
参考知乎文档整理排序规则:(优先级递减)
核心规则:
- 文件夹 < 文件:文件夹排在文件之前
- 空字符串 < 非空字符串
- 忽略名称中的前导字符(空格(中/英)或 ’ 或 - 或 ——),自左向右依次比较所有字符,字符相同按前导(’ < - < ——)
字符具体排序规则:
- 先比较类型:符号 < 数字 < 英文字母 < 汉字
- 类型相同,按下列规则排序:
- 符号 :固定顺序(查表)
- 数字:直接比较数值
- 英文字母:按a-z的顺序排列(先不区分大小写比较,相同则遵循大写在前原则,实际Windows文件名好像不区分大小写)
- 汉字:转拼音后按英文字母排序
3.编码思路
//比较字符串
CompareResult compareQString(const QString& name1, const QString& name2);
- 区分文件和文件夹类型,类型不同直接出结果
- 类型相同情况下判断是否为空字符串,name1和name2含有空字符串直接出结果
- 都不非空则循环取一个合法字符(非先导字符),然后比较字符,比较字符时如果得出结果不相等则直接出结果,相等则需要根据先导字符确定结果
// 比较字符
CompareResult compareChar(const QChar& char1, const QChar& char2);
- 先判断字符类型,不相同直接出结果
- 类型相同则根据内容比较大小(不同类型规则不同)
- 符号 :固定顺序(查表)
- 数字:直接比较数值
- 英文字母:按a-z的顺序排列(先不区分大小写比较,相同则遵循大写在前原则,实际Windows文件名好像不区分大小写)
- 汉字:转拼音后按英文字母排序
- 未知类型直接按Unicode数值排序
4.代码示例
下面只是提供一个思路,目前已经能解决我这个任务了,后续有机会完善的话会重构一下代码写一个小demo出来(想不出怎么用一个精巧的方式写一个排序算法,后续代码功底变强了可能会写的更好吧)
// 准备工作1.定义两个枚举类型
// 比较结果
enum CompareResult
{
LessThan = 0, //小于
GreaterThan, //大于
Equality //相等
};
//字符类型
enum CharType
{
Symbol = 0, //符号映射表中符号
Digit, //数字
Letter, //字母
ChineseCharacters, //汉字
UnKonw //未知类型
};
// 准备工作2.建立符号顺序表和汉字拼音映射表,拼音文件网上找的
QHash<QChar, quint32> m_symbolTable; //标点符号映射表(value决定顺序)
QHash<QChar, QString> m_pinyinTable; //汉字拼音映射表
bool tableInit()
{
//符号表初始化
m_symbolTable.insert(QString(" ").at(0), 0);
m_symbolTable.insert(QString("!").at(0), 1);
m_symbolTable.insert(QString("!").at(0), 2);
m_symbolTable.insert(QString("#").at(0), 3);
m_symbolTable.insert(QString("$").at(0), 4);
m_symbolTable.insert(QString("%").at(0), 5);
m_symbolTable.insert(QString("&").at(0), 6);
m_symbolTable.insert(QString("(").at(0), 7);
m_symbolTable.insert(QString("(").at(0), 8);
m_symbolTable.insert(QString(")").at(0), 9);
m_symbolTable.insert(QString(")").at(0), 10);
m_symbolTable.insert(QString(",").at(0), 11);
m_symbolTable.insert(QString(",").at(0), 12);
m_symbolTable.insert(QString("、").at(0), 13);
m_symbolTable.insert(QString(".").at(0), 14);
m_symbolTable.insert(QString("。").at(0), 15);
m_symbolTable.insert(QString(";").at(0), 16);
m_symbolTable.insert(QString(";").at(0), 17);
m_symbolTable.insert(QString("?").at(0), 18);
m_symbolTable.insert(QString("@").at(0), 19);
m_symbolTable.insert(QString("[").at(0), 20);
m_symbolTable.insert(QString("]").at(0), 21);
m_symbolTable.insert(QString("^").at(0), 22);
m_symbolTable.insert(QString("_").at(0), 23);
m_symbolTable.insert(QString("`").at(0), 24);
m_symbolTable.insert(QString("{").at(0), 25);
m_symbolTable.insert(QString("}").at(0), 26);
m_symbolTable.insert(QString("~").at(0), 27);
m_symbolTable.insert(QString("’").at(0), 28);
m_symbolTable.insert(QString("“").at(0), 29);
m_symbolTable.insert(QString("”").at(0), 30);
m_symbolTable.insert(QString("《").at(0), 31);
m_symbolTable.insert(QString("》").at(0), 32);
m_symbolTable.insert(QString("¥").at(0), 33);
m_symbolTable.insert(QString("【").at(0), 34);
m_symbolTable.insert(QString("】").at(0), 35);
m_symbolTable.insert(QString("+").at(0), 36);
m_symbolTable.insert(QString("=").at(0), 37);
m_symbolTable.insert(QString("·").at(0), 38);
m_symbolTable.insert(QString("…").at(0), 39);
//汉字拼音映射初始化
//1.从配置文件读取拼音数组
QFile file(":/resource/PinyinTable/PinyinTable.txt");
QStringList pinyinList;
if (file.open(QFile::ReadOnly | QFile::Text)) {
QString str = file.readAll();
pinyinList = str.split(" ");
}
if (pinyinList.size()!=(0x9FA5 - 0x4E00 + 1))
{
return false;
}
//2.建立映射
for (int i = 0x4E00;i<= 0x9FA5;i++)
{
//拼音后加上索引位置(确保后续同音字也能比较大小)
m_pinyinTable.insert(QChar(i), pinyinList.at(i - 0x4E00) + QString::number(i));
}
}
// 编码
// 判断字符是不是前导字符
int isPlaceholder(const QChar& _char)
{
if (_char == QChar('’'))
{
return 1;
}
if (_char == QChar('-'))
{
return 2;
}
if (_char == QChar('—'))
{
return 3;
}
return -1;
}
// 获取字符类型
CharType getCharType(const QChar& _char)
{
if (m_symbolTable.contains(_char))
{
return Symbol;
}
if (_char.isDigit())
{
return Digit;
}
if (_char.isLetter())
{
return Letter;
}
if (_char.unicode() <= 0x9FA5 && _char.unicode() <= 0x4E00)
{
return ChineseCharacters;
}
return UnKonw;
}
// 比较先导字符映射表(ps:测试发现先导字符比较不正确,这块需要重写)
CompareResult comparePlaceholderMap(const QMap<int, int>& placeholderMap1, const QMap<int, int>& placeholderMap2)
{
if (placeholderMap1.isEmpty()&& placeholderMap2.isEmpty())
{
//全部为空则直接认为相等
return Equality;
}
QMap<int, int>::const_iterator index1 = placeholderMap1.begin();
QMap<int, int>::const_iterator index2 = placeholderMap2.begin();
while (1)
{
//原则1:空小于非空
if (index1== placeholderMap1.end()&& index2!= placeholderMap2.end())
{
return LessThan;
}
if (index1 != placeholderMap1.end() && index2 == placeholderMap2.end())
{
return GreaterThan;
}
if (index1 == placeholderMap1.end() && index2 == placeholderMap2.end())
{
return Equality;
}
// //原则2:都非空优先比较数量(在字符串中的位置)
//if (index1.key() < index2.key())
//{
// return LessThan;
//}
// if (index1.key() > index2.key())
// {
// return GreaterThan;
// }
//原则3:key值相同比较value(按先导字符排序规则)
if (index1.value() < index2.value())
{
return LessThan;
}
if (index1.value() > index2.value())
{
return GreaterThan;
}
//原则4:以上规则比较不出大小则去下一个先导字符继续比较
index1++;
index2++;
}
//以上规则都比较不出大小则认为相等
return Equality;
}
// 比较字符
CompareResult compareChar(const QChar& char1, const QChar& char2)
{
//相等直接返回
if (char1 == char2)
{
return Equality;
}
CharType type1 = getCharType(char1);
CharType type2 = getCharType(char2);
//原则1:类型不同直接根据类型出结果
if (type1 < type2)
{
return LessThan;
}
if (type1 > type2)
{
return GreaterThan;
}
//原则2:类型相同则根据内容比较大小
switch (type1)
{
case HiChineseStringHelper::Symbol:
{
//符号查表比较大小
int value1 = m_symbolTable.value(char1);
int value2 = m_symbolTable.value(char2);
if (value1 < value2)
{
return LessThan;
}
if (value1 > value2)
{
return GreaterThan;
}
return Equality;
}
case HiChineseStringHelper::Letter:
{
//字母
//原则1:先统一转成小写尝试比较大小
QChar char1Lower = char1.toLower();
QChar char2Lower = char2.toLower();
if (char1Lower.unicode() < char2Lower.unicode())
{
return LessThan;
}
if (char1Lower.unicode() > char2Lower.unicode())
{
return GreaterThan;
}
//原则2:相同则比原串unicode,根据ascii大写在前值特点确定大小关系
if (char1.unicode() < char2.unicode())
{
return LessThan;
}
if (char1.unicode() > char2.unicode())
{
return GreaterThan;
}
return Equality;
}
case HiChineseStringHelper::ChineseCharacters:
{
// 中文字符转拼音比较字符串
QString pinyin1 = m_pinyinTable.value(char1);
QString pinyin2 = m_pinyinTable.value(char1);
return compareQString(pinyin1, pinyin2);
}
case HiChineseStringHelper::Digit:
case HiChineseStringHelper::UnKonw:
{
if (char1.unicode() < char2.unicode())
{
return LessThan;
}
if (char1.unicode() > char2.unicode())
{
return GreaterThan;
}
return Equality;
}
default:
break;
}
return Equality;
}
//最后:比较两个字符串函数(没判断文件和文件夹类型)
/*
参考知乎文档整理排序规则:(优先级递减)
核心规则:
1. 文件夹 < 文件:文件夹排在文件之前
2. 空字符串 < 非空字符串
3. 忽略名称中的前导字符(’ 或 - 或 ——),自左向右依次比较所有字符,字符相同按前导(’ < - < ——)
字符具体排序规则:
1. 先比较类型:符号 < 数字 < 英文字母 < 汉字
2. 类型相同,按下列规则排序:
- 符号 :(固定顺序)查表
- 数字:直接比较数值
- 英文字母:按a-z的顺序排列(先不区分大小写比较,相同则遵循大写在前原则)
- 汉字:转拼音后按英文字母排序
*/
CompareResult compareQString(const QString& name1, const QString& name2)
{
//qDebug() << "name1 : " << name1 << "name2 : " << name2;
//步骤1:根据是不是空串比较
{
if (name1.isEmpty() && name2.isEmpty())
{
return Equality;
}
if (name1.isEmpty() && !name2.isEmpty())
{
return LessThan;
}
if (!name1.isEmpty() && name2.isEmpty())
{
return GreaterThan;
}
//步骤1保证了两个字符串都不为空
}
// 各自索引
int index1 = 0;
int index2 = 0;
// 字符串长度
int lenth1 = name1.length();
int lenth2 = name2.length();
// 前导符索引映射
QMap<int, int> placeholderMap1;
QMap<int, int> placeholderMap2;
// 获取一个字符
QChar char1 = name1.at(index1);
QChar char2 = name2.at(index2);
//循环步骤2,3,直到得出确定结果后跳出
while (1)
{
// 步骤2:取一个合法的字符(非先导字符),先导字符存表
{
// 判断字符是不是前导字符(’ 或 - 或 ——)
int ret1 = isPlaceholder(char1);
for ( ; ret1 != -1; ret1 = isPlaceholder(char1))
{
placeholderMap1.insert(index1, ret1);
if (index1++ == lenth1)
{
char1 = QChar();
break;
}
char1 = name1.at(index1);
}
int ret2 = isPlaceholder(char2);
for (; ret2 != -1; ret2 = isPlaceholder(char2))
{
placeholderMap2.insert(index2, ret2);
if (index2++ == lenth2)
{
char2 = QChar();
break;
}
char2 = name2.at(index2);
}
}
// 步骤3:逐字符比较
{
// 根据字符是否为空确定结果
if (char1.isNull() && !char2.isNull())
{
return LessThan;
}
if (char2.isNull() && !char1.isNull())
{
return GreaterThan;
}
if (char2.isNull() && char1.isNull())
{
//根据先导字符确定最终结果
return comparePlaceholderMap(placeholderMap1, placeholderMap2);
}
//都不为空进行字符比较
//不相等->出结果
//相等取下一个继续比较
CompareResult ret = compareChar(char1, char2);
if (ret != Equality)
{
return ret;
}
ret = comparePlaceholderMap(placeholderMap1, placeholderMap2);
if (ret != Equality)
{
return ret;
}
placeholderMap1.clear();
placeholderMap2.clear();
char1 = ++index1 == lenth1 ? QChar() : name1.at(index1);
char2 = ++index2 == lenth2 ? QChar() : name2.at(index2);
}
}
return Equality;
}