改善CEGUIMultiColumnList默认排序方式存在的问题
2013-08-15 23:38
232 查看
最近在用CEGUIMultiColumnList对控件中的元素进行排序时,有的列会出现排序混乱的情况。研究了一天CEGUIMultiColumnList的源代码,发现其默认实现方式有一些问题。下面对于CEGUIMultiColumnList简称为列表,对于没用过CEGUIMultiColumnList的各位,想象一下EXCEL表格的样子就好。
在使用列表时,点击列表的表头(列表最上面一行,各种列名所在的行)中不同列改变升序或降序排序时,在列表内部会调用到CEGUIMultiColumnList::resortList():
还是没有找到std::sort真正处理的元素,继续找,ListRow定义:
重载的operator<和operator >,排序就靠他俩了。
下面的介绍都以operator<为例,ListRow::operator<()的定义:
*a < *b的比较又是哪来的呢?看到a,b的类型为ListboxItem*,找到ListboxItem,其中对于operator<()的定义:
上述定义为virtual funtion,这时要看看有没有派生类重写了这个函数,查看CEGUIListboxItem的继承关系时,发现继承自CEGUIListboxItem的派生类有两个:CEGUIListboxTextItem,CEGUIListboxImageItem。而向列表填充数据时,我通常用的函数是CEGUIMultiColumnList::ListboxTextItem*
setItem(...)。由此,我应该查找ListboxTextItem中operator<的定义:
可以看到,列表在按列排序时默认的操作是直接对两个字符串进行比较,字符串比较实际上是逐一对字符的ASCII值进行比较。而在我所用的环境中,某一列填充的元素除了为字符串(玩家的姓名)之外,还有有全为数字(玩家的等级)的可能。这时按照ASCII进行比较会出现预期以外的情况,比如对101,13进行升序排序,按照ASCII排序的结果是101,13;按数字排序的结果是13,101。经过初步分析,我打算重写ListboxTextItem::operator<()以实现对数字的比较:
上述版本大体实现了我的预期,不过却引入了2个问题:
1.这种方式每次都会对传入的文本调用isdigit(),无论文本是数字与否,在列表中的条目越来越多的情况下,这样会很影响效率。于是我想到,能不能传入一个标志,来标示文本是否为纯数字。这样可以避免某列存储的全是或部分是字符串的情况,这种情况下按ASCII比较即可。那么标志究竟在什么地方设置呢?其实列表在填充数据的时候,我们就应该清楚该列的内容是字符串还是数字,所以可以在填充数据时设置标志,我是通过调用CEGUIMultiColumnList::ListboxTextItem* setItem(...)对列表填充数据的。在setItem最后面加一个参数即可,CEGUIMultiColumnList::ListboxTextItem*
setItem(...,bool isDigit = false)。
2.第30行的条件判断,在实际运行中,当某列的元素不为纯数字时,由于itemTextIsDigit和textIsDigit的默认值为true,会执行return itemTextValue < textValue,这并不是我预期的结果。修复这个问题的直观办法是将itemTextIsDigit和textIsDigit的默认值由true改为false,相应的第30行的条件判断也进行对应的修改。但是这样的改动之后,第9行和第11行中的for循环由原本判断某个字符串中有一个字符不为数字时跳出循环,变为遍历整个字符串每个字符都是数字的时候才能结束循环,不存在提前跳出循环的可能性。这样进行的额外循环次数多了必然会影响效率,CEGUI底层的改动,效率还是很重要的。再往下看,看到了30行先处理数字后处理字符串,其实只要将两者的位置进行调整即可,毕竟列表默认处理方式就是针对字符串的,将默认处理置于最前面也符合设计需要。
改良后的ListboxTextItem::operator<():
总结一下,CEGUIMultiColumnList没有实现按照数值排序,应该是出于通用性和效率的考虑。上述文章是按照我查找这个问题,解决这个问题的过程一步步写下来的,难免有啰嗦之处。这种解决方案也只是初步的想法,实现上和效率上还有欠缺,有了新的方法也会拿出来跟大家分享,也希望有想法的各位提出想法交流一下。
在使用列表时,点击列表的表头(列表最上面一行,各种列名所在的行)中不同列改变升序或降序排序时,在列表内部会调用到CEGUIMultiColumnList::resortList():
void CEGUIMultiColumnList::resortList() { ListHeaderSegment::SortDirection dir = getSortDirection(); if (dir == ListHeaderSegment::Descending) { std::sort(d_grid.begin(), d_grid.end()); } else if (dir == ListHeaderSegment::Ascending) { std::sort(d_grid.begin(), d_grid.end(), pred_descend); } }默认情况下,std::sort将使用operator<对传入区间内的所有元素进行排序,而传入的参数是[d_grid.begin(), d_grid.end()),这时就要看看d_grid的定义了:
typedef std::vector<ListRow> ListItemGrid ListItemGrid d_grid;
还是没有找到std::sort真正处理的元素,继续找,ListRow定义:
struct ListRow { typedef std::vector<ListboxItem*> RowItems; RowItems d_items; uint d_sortColumn; uint d_rowID; ListboxItem* const& operator[](uint idx) const {return d_items[idx];} LIstboxItem*& operator[](uint idx) {return d_items[idx];} bool operator<(const ListRow& rhs) const; bool operator>(const ListRow& rhs) const; }
重载的operator<和operator >,排序就靠他俩了。
下面的介绍都以operator<为例,ListRow::operator<()的定义:
bool MultiColumnList::ListRow::operator<(const ListRow& rhs) const { ListboxItem* a = d_items[d_sortColumn]; ListboxItem* b = rhs.d_items[d_sortColumn]; if(!b) { return false; } else if (!a) { return true; } else { return *a < *b; } }
*a < *b的比较又是哪来的呢?看到a,b的类型为ListboxItem*,找到ListboxItem,其中对于operator<()的定义:
virtual bool operator<(const ListboxItem& rhs) const { return true; }
上述定义为virtual funtion,这时要看看有没有派生类重写了这个函数,查看CEGUIListboxItem的继承关系时,发现继承自CEGUIListboxItem的派生类有两个:CEGUIListboxTextItem,CEGUIListboxImageItem。而向列表填充数据时,我通常用的函数是CEGUIMultiColumnList::ListboxTextItem*
setItem(...)。由此,我应该查找ListboxTextItem中operator<的定义:
virtual bool operator<(const ListboxItem& rhs) const { return d_itemText < ((ListboxTextItem*)&rhs)->getText(); }
可以看到,列表在按列排序时默认的操作是直接对两个字符串进行比较,字符串比较实际上是逐一对字符的ASCII值进行比较。而在我所用的环境中,某一列填充的元素除了为字符串(玩家的姓名)之外,还有有全为数字(玩家的等级)的可能。这时按照ASCII进行比较会出现预期以外的情况,比如对101,13进行升序排序,按照ASCII排序的结果是101,13;按数字排序的结果是13,101。经过初步分析,我打算重写ListboxTextItem::operator<()以实现对数字的比较:
bool ListboxTextItem::operator<(const ListboxTextItem& rhs) const { CEGUI::String text = ((ListboxTextItem*)&rhs)->getText(); bool itemTextIsDigit = true; bool textIsDigit = true; int itemTextValue = 0; int textValue = 0; for (size_t i = 0; i < d_itemText.size(); ++i) { if(!isdigit(d_itemText[i])) { itemTextIsDigit = false; bresk; } } for (size_t j = 0; j < text.size(); ++j) { if(!isdigit(text[j])) { textIsDigit = false; break; } } itemTextValue = CEGUI::PropertyHelper::stringToInt(d_itemText); textValue = CEGUI::PropertyHelper::stringtoInt(text); if (itemTextIsDigit && textIsDigit) { return itemTextValue < textValue; } else { return d_itemText < text; } return false; }
上述版本大体实现了我的预期,不过却引入了2个问题:
1.这种方式每次都会对传入的文本调用isdigit(),无论文本是数字与否,在列表中的条目越来越多的情况下,这样会很影响效率。于是我想到,能不能传入一个标志,来标示文本是否为纯数字。这样可以避免某列存储的全是或部分是字符串的情况,这种情况下按ASCII比较即可。那么标志究竟在什么地方设置呢?其实列表在填充数据的时候,我们就应该清楚该列的内容是字符串还是数字,所以可以在填充数据时设置标志,我是通过调用CEGUIMultiColumnList::ListboxTextItem* setItem(...)对列表填充数据的。在setItem最后面加一个参数即可,CEGUIMultiColumnList::ListboxTextItem*
setItem(...,bool isDigit = false)。
2.第30行的条件判断,在实际运行中,当某列的元素不为纯数字时,由于itemTextIsDigit和textIsDigit的默认值为true,会执行return itemTextValue < textValue,这并不是我预期的结果。修复这个问题的直观办法是将itemTextIsDigit和textIsDigit的默认值由true改为false,相应的第30行的条件判断也进行对应的修改。但是这样的改动之后,第9行和第11行中的for循环由原本判断某个字符串中有一个字符不为数字时跳出循环,变为遍历整个字符串每个字符都是数字的时候才能结束循环,不存在提前跳出循环的可能性。这样进行的额外循环次数多了必然会影响效率,CEGUI底层的改动,效率还是很重要的。再往下看,看到了30行先处理数字后处理字符串,其实只要将两者的位置进行调整即可,毕竟列表默认处理方式就是针对字符串的,将默认处理置于最前面也符合设计需要。
改良后的ListboxTextItem::operator<():
bool ListboxTextItem::operator<(const ListboxTextItem& rhs) const { CEGUI::String text = ((ListboxTextItem*)&rhs)->getText(); bool itemTextIsDigit = true; bool textIsDigit = true; int itemTextValue = 0; int textValue = 0; if (!d_bIsDigit) { return d_itemText < text; } else { for (size_t i = 0; i < d_itemText.size(); ++i) { if(!isdigit(d_itemText[i])) { itemTextIsDigit = false; bresk; } } for (size_t j = 0; j < text.size(); ++j) { if(!isdigit(text[j])) { textIsDigit = false; break; } } itemTextValue = CEGUI::PropertyHelper::stringToInt(d_itemText); textValue = CEGUI::PropertyHelper::stringtoInt(text); if (!itemTextIsDigit || !textIsDigit) { return d_itemText < text; } else { return itemTextValue < textValue; } } return false; }
总结一下,CEGUIMultiColumnList没有实现按照数值排序,应该是出于通用性和效率的考虑。上述文章是按照我查找这个问题,解决这个问题的过程一步步写下来的,难免有啰嗦之处。这种解决方案也只是初步的想法,实现上和效率上还有欠缺,有了新的方法也会拿出来跟大家分享,也希望有想法的各位提出想法交流一下。
相关文章推荐
- 改善CEGUIMultiColumnList默认排序方式存在的问题
- 中文乱码问题 ? 原因:JSP中默认使用iso-8859-1字符编码方式,不支持中文 ? 常见的支持中文的编码方式:gb2312(常用简体汉字)、gbk(简体和繁体汉字)、utf-8 ? 解决中文乱码
- List不用比较器排序 默认升序
- Collections.sort方法对list的两种排序方式
- c# list排序的三种实现方式
- C# List的排序问题
- 判断字符串中是否存在的几种方案:string.indexof、string.contains、list.contains、list.any几种方式效率对比
- JDK1.7和1.8中List集合中sort方法排序问题【JAVA】
- List<T> 排序问题分享
- DataGridView使用非泛型或者未实现IBindingList接口泛型集合的列排序问题
- 解决DataGridView绑定List后不能排序的问题
- Collections.sort( )方法对list排序的两种方式
- select子句的默认排序方式
- 修改sql查询默认排序问题
- JAVA默认排序算法问题 java.lang.IllegalArgumentException
- 关于css中列表(ul ol)存在默认间距的问题
- flyway初始化脚本存在默认特殊字符的问题处理
- Windows下Sublime Text 默认打开方式问题解决办法
- list元素为结构体,而按其中一元素排序问题
- 关于Myeclipse的mxml页面中的脚本呈现绿色不可调试的问题(没有设计界面,更改默认打开方式)