在Delphi中不进行排序从TStringList中删除重复行

7

我知道如何使用dupignore从已排序的TStringList中删除重复字符串。

CallData := TStringList.Create;
CallData.Sorted := True;
Call.Duplicates := dupIgnore;

但在我的情况下,字符串不应该被排序。

使用FOR循环查找重复项非常慢(即使使用indexOF()),当TStringList有数十万行时。

 if OpenDialog1.Execute then
  begin
    Try
      y := TStringList.create;
      f := TStreamReader.create(OpenDialog1.FileName, TEncoding.UTF8, True);
      while not f.EndOfStream do
      begin
        l := f.ReadLine;
        X.Add(l);
      end;

      g := Tstreamwriter.create('d:\logX.txt', True, TEncoding.UTF8);
      for I := 0 to X.count - 1 do
      begin


          if y.IndexOf(X[I]) = -1 then

          y.Add(X[I]);

      end;

      for j := 0 to y.count - 1 do
        g.WriteLine(y[j]);

    Finally
      f.free;
      y.free;
      g.free;
    End;
  end;

有没有更好的方法?

3个回答

6
以下是我解决这个问题的方法:
  1. 创建以字符串为键的字典。 值类型无关紧要。
  2. 按照相反的顺序遍历字符串列表。
  3. 对于每个字符串,请检查它是否在字典中。
  4. 如果它在字典中,则从字符串列表中删除。否则添加到字典中。

如果有大量重复项需要删除,则以上操作的性能将受到从字符串列表中重复删除的影响。这是因为每个要删除的项目都会导致后面的项目向下移动一个索引。您可以通过将其复制到新列表而不是原地删除来避免这种情况。

或者,您可以像这样就地操作:

  1. 创建以字符串为键的字典。 值类型无关紧要。
  2. 将名为Count的变量初始化为零。
  3. 按正向顺序遍历字符串列表。
  4. 对于每个字符串,请检查它是否在字典中。
  5. 如果它在字典中,则不执行任何操作。否则将其添加到字典中,在列表的索引Count处进行复制,然后将Count增加1。
  6. 一旦迭代完成,请调整列表大小为Count个元素。

字典的作用是查找是一个O(1)操作,因此第二个算法具有O(n)时间复杂度。


非常感谢。它比其他的更快。 - Shahram Banazadeh

2
我会使用诡计,通过创建一个已排序和未排序的列表来实现。就像这样:

我会使用诡计,通过创建一个已排序和未排序的列表来实现。

  y := TStringList.create;
  s := TStringList.create;
  s.Sorted := TRUE;
  s.Duplicates := dupIgnore;

  f := TStreamReader.create(OpenDialog1.FileName, TEncoding.UTF8, True);
  while not f.EndOfStream do
  begin
    l := f.ReadLine;
    s.Add(l);
    if s.Count > y.Count then y.Add(l);
  end;

  // etc.

1
function compareobjects
          (list     : Tstringlist;
           index1   : integer;
           index2   : integer
          )         : integer;
begin
  if index1 = index2 then
    result := 0
  else
    if integer(list.objects[index1]) < integer(list.objects[index2]) then
      result := -1
    else
      result := 1;
end;

begin
  Try
    y := TStringList.create;
    y.Sorted := true;
    y.Duplicates := dupignore;
    f := TStreamReader.create('c:\106x\q47780823.bat');
    i := 0;
    while not f.EndOfStream do
    begin
      inc(i);
      line := f.readline;
      y.Addobject(line,tobject(i));
    end;
    y.Sorted := false;
    y.CustomSort(compareobjects);

    for i := 0 to y.count - 1 do
      WriteLn(y[i]);

    Finally
      f.free;
      y.free;
  End;
  readln;
end.

我会跟踪行号(i),并将其分配给字符串,通过将其转换为对象进行转型;然后像以前一样对列表进行排序并删除重复项,但是最后使用自定义排序取消排序对象。


1
这里的 Try 放错了位置。它必须紧接着受保护的资源被分配后才能使用。例如,如果文件不存在,则您的代码会在 TStreamReader.create 中引发异常,然后调用 f.Free,而此时 f 尚未初始化。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接