给json数组中的元素排序
起因
基本信息
-
平台:
windows
-
IDE:
Lazarus 2.2.6
-
json包:
FPC
自带的fpjson
背景
最近在搞一个小工具,数据文件
采用的是json
格式,其中一个节点存放的是一组文件的基本信息的清单,这个节点自然就是个json数组
,元素就是每个文件基本信息的json对象
。界面展示用的是经典的DBGrid + DataSource + DataSet
方案,所以会把json数组
转为DataSet
。
操作过程中会比对磁盘上的文件,该添的添,该删的的删,该改的改,该标记的标记。一番操作下来,顺序自然是乱的,虽然可以操作DataSet
或者换用带排序功能的DBGridEh
达到排序的目的,但直接看json
数据的话,依然是乱序的。因此,期望直接对json数组
进行排序。
解决过程
习惯先看源码,如果没有原生解决方案了,或者原生解决方案太别扭了,才会选择第三方解决方案或自己造轮子。
一看源码
pascal
TJSONArray = class(TJSONData)
public
...
Procedure Sort(Compare: TListSortCompare);
TJSONArray
已经提供了排序方法,不过这个参数是什么东西?
pascal
TListSortCompare = function (Item1, Item2: Pointer): Integer;
嗯,是个函数声明
,也就是说具体的算法实现要自己写,可以先找找看有默认的实现没。结果是:没有!那就自己写吧。
看声明,这个函数是要比较两个指针指向的东西,并返回一个整数。嗯,看上去很简单,不过:
-
到底是怎么实现排序的?
-
参数是指针,指向的又是什么东西?
-
返回一个什么样的整数才能实现排序呢?
没懂!!!
还是接着看源码吧:
pascal
procedure TJSONArray.Sort(Compare: TListSortCompare);
begin
FList.Sort(Compare);
end;
Procedure TFPObjectList.Sort(Compare: TListSortCompare);
begin
FList.Sort(Compare);
end;
procedure TFPList.Sort(Compare: TListSortCompare);
begin
if Not Assigned(FList) or (FCount < 2) then exit;
QuickSort(Flist, 0, FCount-1, Compare);
end;
Procedure QuickSort(FList: PPointerList; L, R : Longint;
Compare: TListSortCompare);
var
I, J : Longint;
P, Q : Pointer;
begin
repeat
I := L;
J := R;
P := FList^[ (L + R) div 2 ];
repeat
while Compare(P, FList^[i]) > 0 do
I := I + 1;
while Compare(P, FList^[J]) < 0 do
J := J - 1;
If I <= J then
begin
Q := FList^[I];
Flist^[I] := FList^[J];
FList^[J] := Q;
I := I + 1;
J := J - 1;
end;
until I > J;
// sort the smaller range recursively
// sort the bigger range via the loop
// Reasons: memory usage is O(log(n)) instead of O(n) and loop is faster than recursion
if J - L < R - I then
begin
if L < J then
QuickSort(FList, L, J, Compare);
L := I;
end
else
begin
if I < R then
QuickSort(FList, I, R, Compare);
R := J;
end;
until L >= R;
end;
嗯,原来调用了一个快排算法
,算法中用到的比较两个元素的函数就是需要实现的函数。
再看源码
印象中,TStringList
好像可以直接排序:
pascal
procedure TStringList.Sort;
begin
CustomSort(@StringListAnsiCompare);
end;
procedure TStringList.CustomSort(CompareFn: TStringListSortCompare);
begin
If (FCount>1) and (FForceSort or (FSortStyle<>sslAuto)) then
begin
Changing;
QuickSort(0,FCount-1, CompareFn);
Changed;
end;
end;
嗯,同样是调用了一个快排算法
,而且比较函数
也是实现了的:
pascal
function StringListAnsiCompare(List: TStringList; Index1, Index: Integer): Integer;
begin
Result := List.DoCompareText(List.FList^[Index1].FString,
List.FList^[Index].FString);
end;
function TStringList.DoCompareText(const s1, s2: string): PtrInt;
begin
if FCaseSensitive then
begin
if UseLocale then
result:=AnsiCompareStr(s1,s2)
else
result:=CompareStr(s1,s2);
end else
begin
if UseLocale then
result:=AnsiCompareText(s1,s2)
else
result:=CompareText(s1,s2);
end;
end;
原来如此!比较函数
只要实现类似CompareText
或CompareStr
的效果就能被快排算法
调用从而实现排序功能。
自己实现
由于json数组
里的元素全部都是相同结构的json对象
,那就简单粗暴点:
pascal
function DefaltJsonCompare(Item1, Item2: Pointer): integer;
var
s1, s2: string;
begin
s1 := TJSONObject(Item1).Get('key', '');
s2 := TJSONObject(Item2).Get('key', '');
Result := CompareText(s1, s2);
end;
测试一下,嗯,确实是期望的效果,完美收工!
总结
-
继承于
TFPList
的类,本质上都已实现了快排算法
,也就是可以直接排序,所需要的仅仅是提供一个类似CompareText
的比较函数的实现即可 -
基于
TFPList
组合并且以TFPList
为主的类,也可以像TStringList
一样封装一个方法实现排序,或者提供一个Helper
实现排序