给json数组中的元素排序

给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; 

原来如此!比较函数只要实现类似CompareTextCompareStr的效果就能被快排算法调用从而实现排序功能。

自己实现

由于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; 

测试一下,嗯,确实是期望的效果,完美收工!

总结

  1. 继承于TFPList的类,本质上都已实现了快排算法,也就是可以直接排序,所需要的仅仅是提供一个类似CompareText的比较函数的实现即可

  2. 基于TFPList组合并且以TFPList为主的类,也可以像TStringList一样封装一个方法实现排序,或者提供一个Helper实现排序

相关推荐
莹雨潇潇2 天前
未来将要被淘汰的编程语言
编程语言
叶庭云3 天前
Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的
matlab·编程语言·r·数组索引·从 1 开始
夜阳朔3 天前
《C++ Primer》第三章知识点
c++·编程语言
MoonBit月兔4 天前
MoonBit 核心编译器正式开源!
开发语言·开源·编程语言·moonbit
Amd7945 天前
数据库与编程语言的连接
mysql·编程语言·应用开发·数据库连接·crud操作·数据访问·数据库驱动
Moonbit6 天前
编程实践|用 MoonBit 实现线段树(三)
编程语言
MoonBit月兔14 天前
GitHub 正式收录 MoonBit 作为一门通用编程语言!核心用户突破三万!
开发语言·github·编程语言·moonbit