按频率对Delphi TStringList进行排序

时间:2020-07-17 21:23:54

标签: delphi

我有一个csv值的TStringList;目前,每个字符串都有四个值v0,v1,v2,v3,其中任何一个都可以重复。我想向每个字符串添加第5个值(v4),这将是v3的计数。然后,列表应按v4,v3降序排列。

例如,此数据:
1,2,3,4
1,3,4,4
2,2,1,2
1,2,3,4
3,1,2,2
3,1,2,1

应返回以下列表:
1,2,3,4,3
1,3,4,4,3
1,2,3,4,3
2,2,1,2,2
3,1,2,2,2
3,1,2,1,1
其中每行中的第5个元素是第4个元素的频率,并按该元素降序排列。

这不是家庭作业问题,

我当前正在使用COM将列表保存到Excel,因此要有一个包含4列的工作表。然后,我在第5列= COUNTIF(D:D,D1)中插入一个公式,该公式计算v4的出现次数。然后,我按第5列的降序对工作表进行排序,然后将工作表重新导入到字符串列表中。这可行,但是我知道并不是我所有的用户都拥有Excel,因此我希望有一个更简单的解决方案。

1 个答案:

答案 0 :(得分:2)

一种简单的方法可能看起来像这样:

uses
  Classes,
  StrUtils;

function SortValues(List: TStringList; Index1, Index2: Integer): Integer;
var
  Left, Right: TStringDynArray;
begin
  Left := SplitString(List[Index1], ',');
  Right := SplitString(List[Index2], ',');
  Result := Right[4] - Left[4];
  if Result = 0 then
    Result := Right[3] - Left[3];  
end;

var
  CSV: TStringList;
  I, J, Frequency: Integer;
  Values, Value2: TStringDynArray;
begin
  CSV := TStringList.Create;
  try
    // populate CSV as needed...

    for I := 0 to CSV.Count-1 do
    begin
      Values := SplitString(CSV[I], ',');
      if Length(Values) <> 4 then
        raise Exception.Create('Bad Input!');

      Frequency := 1;
      for J := 0 to CSV.Count-1 do
      begin
        if J <> I then
        begin
          Values2 := SplitString(CSV[J], ',');
          if Length(Values2) <> 4 then
            raise Exception.Create('Bad Input!');
          if Values2[3] = Values[3] then
            Inc(Frequency);
        end;
      end;

      CSV[I] := CSV[I] + ', ' + IntToStr(Frequency);
    end;

    CSV.CustomSort(@SortValues);

    // use CSV as needed...

  finally
    CSV.Free;
  end;
end;

但是,这需要大量的开销,一遍又一遍地解析和重新解析CSV字符串。可以通过减少解析CSV字符串的次数并缓存结果来更好地进行优化,例如:

uses
  Classes,
  StrUtils;

type
  PCSVInfo = ^CSVInfo;
  CSVInfo = record
    Line: string;
    Value: Integer;
    Frequency: Integer;
  end;

function SortValues(List: TStringList; Index1, Index2: Integer): Integer;
var
  Left, Right: PCSVInfo;
begin
  Left := PCSVInfo(List.Objects[Index1]);
  Right := PCSVInfo(List.Objects[Index2]);
  Result := Right.Frequency - Left.Frequency;
  if Result = 0 then
    Result := Right.Value - Left.Value;
end;

var
  CSV: TStringList;
  I, J: Integer;
  Values: TStringDynArray;
  Info: CSVInfo;
  InfoArr: array of CSVInfo;
begin
  CSV := TStringList.Create;
  try
    // populate CSV as needed...

    SetLength(InfoArr, CSV.Count);

    for I := 0 to CSV.Count-1 do
    begin
      Values := SplitString(CSV[I], ',');
      if Length(Values) <> 4 then
        raise Exception.Create('Bad Input!');
      InfoArr[I].Line := CSV[I];
      InfoArr[I].Value := Values[3];
      InfoArr[I].Frequency := 0;
    end;

    for I := 0 to CSV.Count-1 do
    begin
      InfoArr[I].Frequency := 1;
      for J := 0 to CSV.Count-1 do
      begin
        if (J <> I) and (InfoArr[J].Value = InfoArr[I].Value) then
          Inc(InfoArr[I].Frequency);
      end;
      CSV[I] := CSV[I] + ', ' + IntToStr(InfoArr[I].Frequency);
      CSV.Objects[I] := TObject(@InfoArr[I]);
    end;

    CSV.CustomSort(@SortValues);

    // use CSV as needed...

  finally
    CSV.Free;
  end;
end;

或者,您可以在分析CSV字符串时计算频率并将其存储在TDictionary中,然后使用TList<T>对详细信息进行排序,例如:

uses
  System.Classes,
  System.Generics.Defaults,
  System.Generics.Collections,
  System.StrUtils;

type
  CSVInfo = record
    Line: string;
    Value: Integer;
    Frequency: Integer;
  end;

var
  CSV: TStringList;
  I, Frequency: Integer;
  Values: TStringDynArray;
  Info: CSVInfo;
  InfoList: TList<CSVInfo>;
  Frequencies: TDictionary<Integer, Integer>;
begin
  CSV := TStringList.Create;
  try
    // populate CSV as needed...

    InfoList := TList<CSVInfo>.Create;
    try
      InfoList.Count := CSV.Count;

      Frequencies := TDictionary<Integer, Integer>.Create;
      try
        for I := 0 to CSV.Count-1 do
        begin
          Values := SplitString(CSV[I], ',');
          if Length(Values) <> 4 then
            raise Exception.Create('Bad Input!');

          Info.Line := CSV[I];
          Info.Value := Values[3];
          Info.Frequency := 0;
          InfoList[I] := Info;

          if Frequencies.TryGetValue(Info.Value, Frequency) then
            Inc(Frequency)
          else
            Frequency := 1;
          Frequencies.AddOrSetValue(Info.Value, Frequency);
        end;

        for I := 0 to InfoList.Count-1 do
        begin
          Info := InfoList[I];
          Info.Frequency := Frequencies[Info.Value];
          InfoList[I] := Info;
        end;
      finally
        Frequencies.Free;
      end;

      InfoList.Sort(
        TDelegatedComparer<CSVInfo>.Create(
          function(const Left, Right: CSVInfo): Integer
          begin
            Result := Right.Frequency - Left.Frequency;
            if Result = 0 then
              Result := Right.Value - Left.Value;
          end
        )
      );

      for I := 0 to InfoList.Count-1 do
      begin
        Info := InfoList[I];
        CSV[I] := Info.Line + ', ' + IntToStr(Info.Frequency);
      end;
    finally
      List.Free;
    end;

    // use CSV as needed...

  finally
    CSV.Free;
  end;
end;
相关问题