Delphi - 在TArray <string> </string>中检查元素是否存在两次

时间:2013-01-25 14:56:46

标签: arrays delphi

我有这段代码

var
arr: TArray<string>;
e1, e2, e3, e4: string;
begin

e1 := 'val1';
e2 := 'val2';
e3 := 'val3';
e4 := 'val4';

arr := TArray<string>.Create(e1, e2, e3, e4);

我现在需要检查上面的数组中是否存在e1到e4 两次最好的方法是什么? 我还应该跳过检查任何具有空值的元素。

还请告知我是否应该手动释放此阵列

由于

4 个答案:

答案 0 :(得分:8)

为了玩算法的乐趣;如果原始数组足够大以使天真O(n^2)算法不实用,并且由于OP使用的是具有泛型的Delphi版本,我建议使用TDictionary<string, integer>来跟踪所有字符串没有排序,并识别重复。

由于多种原因,这将是有效的:

  • TDictionary提供常量时间插入,几乎为O(n)。我们从一开始就知道数组的大小,因此我们可以使用TDictionary而不会增长它。这使得整个算法O(n)
  • 由于字符串已经在数组中,并且Delphi字符串是引用计数的,因此将字符串放入数组中并不会实际复制字符串!
  • 使用此算法,数组只会走一次。

代码:

type
  TZeroWidthRecord = record
  end;

function FindFirstDuplicate(const ArrayOfString: array of string): Integer;
var Dict: TDictionary<string, TZeroWidthRecord>;
    i: Integer;
    ZW: TZeroWidthRecord;
begin
  Dict := TDictionary<string, TZeroWidthRecord>.Create(Length(ArrayOfString));
  try
    for i:=0 to High(ArrayOfString) do
      try
        Dict.Add(ArrayOfString[i], ZW);
      except on E:Exception do
        Exit(i);
      end;
  finally Dict.Free;
  end;
  // No duplicates found:
  Result := -1;
end;

为了回答David的评论,我做了一个简短的测试程序,将基于TDictionary的算法与sort-and-search算法进行比较。我创建了随机的字符串数组,然后尝试找到第一个副本。我的数组不包含重复项,如果有重复项,那么TDictionary的运行时间将与平均首次重复的重复项成比例。例如,如果TDictionary平均在阵列中间找到重复,那么TDict算法的平均运行时间将是一半。基于排序的算法需要对整个数组进行排序,排序是占用最多时间的。

与基于排序和字典的算法一样,人们需要使用逼真的数据进行测试。例如,如果我在OP的问题中使用短字符串进行测试,则TDict和sort之间不会有竞争:即使对于琐碎的长度数组,Dict也会更快。但是当平均字符串长度增加时,基于排序的算法开始变得更好;但话说再次,这取决于字符串:例如,如果大多数字符串共享一个长前缀,那么排序算法中的“比较”阶段将花费更长的时间,使TDictionary再次看起来更好!

测试表1,无重复

*==========*===========================*
|          | Number of strings         |
| Avg str  | in the Array for          |
| length   | TDictionary to be faster  |
*======================================*
| 7        | 33                        |
| 10       | 73                        |
| 13       | 73                        |
| 16       | 163                       |
| 19       | 163                       |
| 22       | 366                       |
| 25       | 366                       |
| 28       | 549                       |
| 37       | 2776                      |
| 40       | 2776                      |
| 43       | 2776                      |
| 46       | 4164                      |
| 49       | 9369                      |
| 52       | 9369                      |
| 55       | 9369                      |
| 58       | 21079                     |
*==========*===========================*

测试表2,在阵列的1/2处复制

如果第一个副本恰好位于数组的中间,那么这将是结果。请注意平均字符串长度58的巨大差异:

*==========*===========================*
|          | Number of strings         |
| Avg str  | in the Array for          |
| length   | TDictionary to be faster  |
*======================================*
| 30       | 109                       |
| 33       | 163                       |
| 36       | 163                       |
| 58       | 366                       |
*==========*===========================*

测试表3,以1/4 复制

如果在阵列的1/4左右找到第一个副本,就会发生这种情况:

*==========*===========================*
|          | Number of strings         |
| Avg str  | in the Array for          |
| length   | TDictionary to be faster  |
*======================================*
| 29       | 73                        |
| 32       | 73                        |
| 38       | 73                        |
| 57       | 109                       |
*==========*===========================*

以下是测试应用程序:http://pastebin.com/vDznwKtZ

答案 1 :(得分:6)

不需要释放动态数组。它们是托管类型,编译器确保当没有对该对象的更多引用时,该对象将被销毁。

对于检测重复项,你可以这样做:

function HasDuplicates(const arr: array of string): Boolean;
var
  i, j: Integer;
begin
  for i := 0 to high(arr) do
    if arr[i]<>'' then
      for j := i+1 to high(arr) do
        if arr[i]=arr[j] then
          exit(True);
  Result := False;
end;

我假设当你说&#34; null&#34;时,在字符串的上下文中你的意思是字符串是空的。

这是一种复杂度为O(n ^ 2)的算法。如果阵列很大,这是个坏消息。

如果您的阵列已经订购,您可以使用O(n)算法进行测试。

function OrderedHasDuplicates(const arr: array of string): Boolean;
var
  i: Integer;
begin
  for i := 0 to high(arr)-1 do
    if arr[i]<>'' then
      if arr[i]=arr[i+1] then
        exit(True);
  Result := False;
end;

当然,可以轻松修改这些函数以识别哪个索引是重复的:

function IndexOfDuplicate(const arr: array of string): Integer;
var
  i: Integer;
begin
  for Result := 0 to high(arr) do
    if arr[Result]<>'' then
      for i := Result+1 to high(arr) do
        if arr[Result]=arr[i] then
          exit;
  Result := -1;
end;

答案 2 :(得分:4)

要检查字符串是否存在两次,请使用TStringList的功能。创建一个TStringList对象,添加数组的所有元素(字符串),将Sorted属性设置为True,然后从开头到结尾循环检查当前元素是否等于最后一个元素,如果它等于你找到了重复。

使用TStringList的主要优点是提供的排序功能。

答案 3 :(得分:2)

TArray类型是一种经典的动态数组,具有泛型风格。此类型由Delphi管理,因此您无需手动释放所涉及的内存。如果您存储对象或其他动态创建的变量,您仍然有责任释放该内存,但同样不是数组本身。

特别是在TArray上,作为两种托管类型,你根本不关心内存。

要检查非有序数组中的重复项,您必须循环使用它并为每个元素进行循环,与数组的其余部分进行比较,这些内容如下:

var
  arr: TArray<string>;
  e1, e2, e3, e4: string;
  I, J: Integer;
begin
  e1 := 'val1';
  e2 := 'val2';
  e3 := 'val3';
  e4 := 'val4';

  arr := TArray<string>.Create(e1, e2, e3, e4);
  //check for duplicates
  for I := low(arr) to High(arr) do
    for J := I + 1 to High(arr) do
      if arr[I] = arr[J] then
        ShowMessage('A duplicate was found');

最后,字符串不能为空,因此您不必检查空元素。

准确地说,任何空字符串('')实际上都是一个零指针,但那是故事的另一面。