StringGrid之稀梳矩阵排序

今天终于有点时间来写这个内容了,哈哈!先来浅析下StringGrid。
TStringGrid = class(TDrawGrid) = class(TCustomDrawGrid) = class(TCustomGrid) = class(TCustomControl)
可以看到这是一个自定义的控件,这也体现了MFC(或者说Windows的API)与vcl的不同,虽然最终都调用RegisterClass,但是明显DELPHI更好及容易实现。
       
TCustomGrid,这是一个自定义GRID,再他身上基本实现了GRID表格功能,只是把DrawCell抽象给子类了。
这是他的画图信息,非常的学习例子:
  TGridAxisDrawInfo = record
    EffectiveLineWidth: Integer;  线宽,默认为1,可以设置为0
    FixedBoundary: Integer;       固定边界线 = (ColWidth + EffectiveLineWidth) * FixedCount
    GridBoundary: Integer;        Grid边界线 = FixedBoundary + (ColWidth + EffectiveLineWidth) * CellCount,如果大于GridExtent,则GridExtent     
    GridExtent: Integer;          GRID大小,包括最后未完整显示的表格,如:ClientWidth,ClientHeight
    LastFullVisibleCell: Longint; 最后完整显示的单元格编号,如有未完整的单元格,则是它的上一个
    FullVisBoundary: Integer;     最后完整显示的单元格边界
    FixedCellCount: Integer;      固定表格宽度FixedCount,如:FixedRow
    FirstGridCell: Integer;       第一个表格(它总是完整的显示),如:TopLeft.x
    GridCellCount: Integer;       表格宽度,如ColCount,RowCount 
    GetExtent: TGetExtentsFunc;   取得元素大小函数,比如:ColWidths[];
  end;

当GridBoundary = GridExtent 时, 则FullVisBoundary < GridBoundary,LastFullVisibleCell < GridCellCount 
当GridBoundary < GridExtent 时, 则FullVisBoundary = GridBoundary,LastFullVisibleCell = GridCellCount 

继承TCustomControl往往的重点在Paint函数上,TCustomGrid也不例外,这个PAINT用了几个技术,非常值得学习:
第一,小内存堆栈分配变量数据。StackAlloc和StackFree
第二,多点连接使用一个polypolyline函数,只画一次

整个GRID一个最重要策略是化矩阵为向量,化整为零。

再看TCustomDrawGrid是一个真正意义可使用的类,实现了父类几个虚拟方法(RowMoved,SelectCell)和一个抽象方法(DrawCell).
由此TDrawGrid是一个提供给用户使用的GRID,并且已经注册到DELPHI的控制面板了。
可以看到,自TDrawGrid以上所有的类,一个缺点就是纯粹意义上的GRID,没有提供持久层的支持,所以DELPHI又实现了一个类,TStringGrid,他提供这样一个持久可保存的数据界面:
TStringSparseList类,本质是一个稀梳数组:TSecDir = array[0..4095] of Pointer; 
他的生存意义在于如果某一行为空,则不用为他构建数据。注意:TStringGrid是以行单位为存储的。
这样,如果你的数据已经有自己界面,那么完全可以使用TDrawGrid,并且使用它比TStringGrid速度有很大的优势。

另外这个在TCustomGrid就为我们提供了一个可编辑的功能:TInplaceEdit对象,他是继承TCustomMaskEdit,也就是说相当于提供了一个编辑窗口类(edit),值得一提的是,TInplaceEdit并是唯一的TCustomGrid使用的编辑框,他也有一个继承类:TInplaceEditList(这个名字"List"似乎有点让人以为是管理者,其实他表示一个pick list,即弹出列表框),而TInplaceEditList这个重要的功能是由于有这样一个属性:
  TInplaceEditList = class(TInPlaceEdit)
  private
    FButtonWidth: Integer;
    FPickList: TCustomListbox;
    FActiveList: TWinControl;
    FEditStyle: TEditStyle;//这个就是本类最重要的功能,定义:TEditStyle =  (esSimple, esEllipsis, esPickList);普通EDIT,带"..."按钮EDIT,带ListBox的EDIT。
我们的想象再次打开下,如果我们很多关于“EDIT”类在此基础上实现,那就容易多了,并且这样是可行。当然,我们也可直接使用此类。


再回来,今天重点是TStringSparseList类。请看:
  TSparsePointerArray = class(TObject)
  private
    secDir: PSecDir;                                                数据节目录
    slotsInDir: Word;                                               插口数
    indexMask, secShift: Word;                                      索引掩码和节掩码,对于一个Index来说,低indexMask位是目录中索引,高secShift位是节掩码
    FHighBound: Integer;                                            索引最大值,也是由低indexMask位和高secShift位组成
    FSectionSize: Word;                                             节大小,和低indexMask位相关:FSectionSize = indexMask + 1
    cachedIndex: Integer;                                           索引缓存 
    cachedPointer: Pointer;                                         数据缓存与索引缓存共同作用
    { Return item[i], nil if slot outside defined section. }
    function  GetAt(Index: Integer): Pointer;                       取数据
    { Return address of item[i], creating slot if necessary. }
    function  MakeAt(Index: Integer): PPointer;                     生成数据
    { Store item at item[i], creating slot if necessary. }
    procedure PutAt(Index: Integer; Item: Pointer);                 存数据
  public

这个类还有重要的遍历方法:
function  ForAll(ApplyFunction: Pointer {TSPAApply}): Integer;
ApplyFunction本身也是一个方法,但是由于这个函数要使用到类变量,因此这个函数只能是:function(TheIndex: Integer; TheItem: Pointer): Integer; far;
ForAll的功能是:从第一个插口遍历执行ApplyFunction,除非ApplyFunction返回0或插口达到最后。


另外这里还有一个TSparseList类:TSparseList = class(TObject).
它提供了一种ForAll变向继承方法:
function TSparseList.ForAll(ApplyFunction: Pointer {TSPAApply}): Integer; assembler;
asm
        MOV     EAX,[EAX].TSparseList.FList
        JMP     TSparsePointerArray.ForAll
end;
当然,其实这里直接可以使用:FList.ForAll(ApplyFunction);不知道这地方为什么没有这样写.

至此,现在我们毕竟还不是直接使用上面的稀梳矩阵,那么下类是真正可使用的类:
  TStringSparseList = class(TStrings)
  private
    FList: TSparseList;                 { of StrItems }
    FOnChange: TNotifyEvent;
  protected
    function  Get(Index: Integer): String; override;
    function  GetCount: Integer; override;
    function  GetObject(Index: Integer): TObject; override;
    procedure Put(Index: Integer; const S: String); override;
    procedure PutObject(Index: Integer; AObject: TObject); override;
    procedure Changed;
  public

可以查看到这个TStrings数据存储格式,与TStringList一样:
type
  PStrItem = ^TStrItem;
  TStrItem = record
    FObject: TObject;
    FString: string;
  end;

由于时间关系,不多说,现在我直接实现这两种排序方式:
procedure TStringGridHzg.SortByCol(ACol: Integer; IsDecOrder: Boolean);
begin
  if ACol > ColCount - 1 then
    InvalidOp('ACol is Out of Range in TStringGridHzg.QuickSort');
  QuickSort(ACol, 0, RowCount - 1, CompareStr);
  Invalidate;
end;

稀梳矩阵方式排序
procedure TStringGridHzg.QuickSort(ACol, L, R: Integer; SCompare: TStringsSortCompare);
var
  I, J, P: Integer;
begin
  repeat
    I := L;
    J := R;
    P := (L + R) shr 1;
    repeat
      while SCompare(Cols[ACol][I], Cols[ACol][P]) < 0 do Inc(I);
      while SCompare(Cols[ACol][J], Cols[ACol][P]) > 0 do Dec(J);
      if I <= J then
      begin
        TSparseListHzg(FData).Exchange(I,J);  //惟一区别的地方
        if P = I then
          P := J
        else if P = J then
          P := I;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then QuickSort(ACol,L, J, SCompare);
    L := I;
  until I >= R;
end;

普通排序
procedure TStringGridHzg.QuickSort(ACol, L, R: Integer; SCompare: TStringsSortCompare);
var
  I, J, P: Integer;
  procedure Exchange(ASour,ADest: TStrings);
  var
    tmpStr: TStrings;
  begin
    tmpStr := TStringList.Create;
    try
      tmpStr.Assign(ASour);
      ASour.Assign(ADest);
      ADest.Assign(tmpStr);
    finally
      tmpStr.Free;
    end;
  end;
begin
  repeat
    I := L;
    J := R;
    P := (L + R) shr 1;
    repeat
      while SCompare(Cols[ACol][I], Cols[ACol][P]) < 0 do Inc(I);
      while SCompare(Cols[ACol][J], Cols[ACol][P]) > 0 do Dec(J);
      if I <= J then
      begin   
        Exchange(Rows[I],Rows[J]);
        if P = I then
          P := J
        else if P = J then
          P := I;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then QuickSort(ACol,L, J, SCompare);
    L := I;
  until I >= R;
end;

下面具体时间比较,大概使用稀梳矩阵方式排序比普通排序快6倍.
普通排序
---------------------------
第一次:1,000条数据,共花时间:78
第二次:1,000条数据,共花时间:62
---------------------------

---------------------------
第一次:10,000条数据,共花时间:891
第二次:10,000条数据,共花时间:906
---------------------------

---------------------------
第一次:10,0000条数据,共花时间:11360
第二次:10,0000条数据,共花时间:11422
---------------------------

---------------------------
第一次:100,0000条数据,共花时间:142672
第二次:100,0000条数据,共花时间:144219
---------------------------


稀梳矩阵排序
---------------------------
第一次:1000条数据,共花时间:15
第二次:1000条数据,共花时间:15
---------------------------

---------------------------
第一次:1,0000条数据,共花时间:109
第二次:1,0000条数据,共花时间:109
---------------------------

---------------------------
第一次:10,0000条数据,共花时间:1703
第二次:10,0000条数据,共花时间:1719
---------------------------

---------------------------
第一次:100,0000条数据,共花时间:22640
第二次:100,0000条数据,共花时间:23078
---------------------------

  • 0
    点赞
  • 2
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值