我有一个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,因此我希望有一个更简单的解决方案。
答案 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;