在Delphi中搜索一个字符串数组

14
在Delphi标准库中是否有函数可以搜索特定值的字符串数组?
例如:
someArray:=TArray<string>.Create('One','Two','Three');
if ArrayContains(someArray, 'Two') then
    ShowMessage('It contains Two');

如果您想先进行“排序”,则可以使用“BinarySearch”,但是我不知道如何为非排序的“TArray”进行排序,希望有人能给出答案。 - Seth Carnegie
3个回答

38

完全没有必要重新发明轮子。StrUtils.MatchStr 可以完成这项工作。

procedure TForm1.FormCreate(Sender: TObject);
var
  someArray: TArray<string>;
begin
  someArray:=TArray<string>.Create('One','Two','Three');
  if MatchStr('Two', someArray) then
    ShowMessage('It contains Two');
end;
注意参数的顺序惯例。
另一个注意点:'MatchStr'是此函数在Delphi 7和Delphi 2007之间某个时候指定的规范名称。历史名称为'AnsiMatchStr'(惯例与RTL的其余部分相同:对于区分大小写,使用'Str/Text'后缀,对于MBCS/Locale,使用'Ansi'前缀)。

2
+1。不错!我没注意到这个问题(可能是因为它的名称与其实际功能不匹配)。你知道它在哪个版本的Delphi中出现吗? - Ken White
在处理数组时,使用参数化类型声明没有太多意义。数组不是类,不能扩展以支持例如Java风格的Array.indexOf - OnTheFly
嗯...我不确定你上一条评论具体指的是什么。当处理Delphi的预泛型版本时,参数化类型声明是必要的,而且这并没有标记特定的Delphi版本(尽管它使用了一个通用数组)。你是在批评我给你点赞的事实吗?如果是的话,我可以取消它。 :) - Ken White
我也不理解有关参数化类型声明的评论。能否详细说明一下? - awmross
@awmross,那更像是我自己的笔记。看看System.TArray<T>Generics.Collections.TArray<T>。它们是非常独特的类型,我认为混用它们是非常误导人的(我知道你仅仅是在使用Generics.Collections.TArray<T>.Create来利用开放数组构造函数)。 - OnTheFly
如果MatchStr('Two', ['One','Two','Three'])为真,则较短。 - Nashev

8

我写了一个类似于旧版本ClipperAScan函数(在XE中测试过)。@RRUZ的回答更正确(已经有现成的),但我的函数不需要先对数组进行排序,并且在小型数组上速度足够快。(它也可以在Delphi的早期版本中使用。)我还为各种类型的数组重载了该函数 - 这里是stringinteger的实现:

// Returns the 0-based index of Value if it's found in the array,
// -1 if not. (Similar to TStrings.IndexOf)
function AScan(const Ar: array of string; const Value: string): Integer; overload;
var
  i: Integer;
begin
  Result := -1;
  for i := Low(Ar) to High(Ar) do
    if SameText(Ar[i], Value) then
    begin
      Result := i;
      Break
    end;
end;

function AScan(const Ar: array of Integer; const Value: Integer): Integer; overload;
var
  i: Integer;
begin
  Result := -1;
  for i := Low(Ar) to High(Ar) do
    if (Ar[i] = Value) then
    begin
      Result := i;
      Break
    end;
end;

procedure TForm2.FormShow(Sender: TObject);
var
  someStrArray: TArray<string>;
  someIntArray: TArray<Integer>;
  Idx: Integer;
begin
  someStrArray := TArray<string>.Create('One', 'Two', 'Three');
  Idx := AScan(someStrArray, 'Two');
  if Idx > -1 then
    ShowMessage(Format('It contains Two at index %d', [Idx]))
  else
    ShowMessage('Not found');
  someIntArray := TArray<Integer>.Create(8, 16, 32);
  Idx := AScan(someIntArray, 32);
  if Idx > -1 then
    ShowMessage(Format('It contains 32 at %d', [Idx]))
  else
    ShowMessage('16 not found');
end;

对于支持泛型的Delphi版本,以下是一个不要求数组排序的版本,如果需要还可以提供比较函数:

接口:

type
  TGenericsUtils = class
  public
    class function AScan<T>(const Arr: array of T; const Value: T; const Comparer: IEqualityComparer<T>):  Integer; overload;
    class function AScan<T>(const Arr: array of T; const Value: T): Integer; overload;
  end;

实施

class function TGenericsUtils.AScan<T>(const Arr: array of T; const Value: T): Integer;
begin
  Result := AScan<T>(Arr, Value, TEqualityComparer<T>.Default);
end;

class function TGenericsUtils.AScan<T>(const Arr: array of T; const Value: T;
  const Comparer: IEqualityComparer<T>): Integer;
var
  i: Integer;
begin
  for i := Low(Arr) to High(Arr) do
    if Comparer.Equals(Arr[i], Value) then
      Exit(i);
  Exit(-1);
end;

测试代码:

var
  AIntTest: TIntegerDynArray;
  AStrTest: TStringDynArray;

begin
  AIntTest := TIntegerDynArray.Create(12, 15, 6, 1, 4, 9, 5);
  AStrTest := TStringDynArray.Create('One', 'Six', 'Three', 'Four', 'Twelve');
  WriteLn('AIntTest contains 9 at index ', TGenericsUtils.AScan<Integer>(AIntTest, 9));
  WriteLn('AStrTest contains ''Four'' at index ', TGenericsUtils.AScan<String>(AStrTest, 'Four'));
  ReadLn;
end.

2
“不,没有。”那TArray.BinarySearch呢? - RRUZ
1
啊,我错过了那个(当我写下那段代码时没有看到泛型;当我实际写出响应代码时发现了它们,但没有修复错误)。谢谢你,Rodrigo。 :) 已经更正,并且给你的答案加上+1分,因为它更正确。 - Ken White

5
你可以使用 TArray.BinarySearch 函数,它是 Generics.Collections 单元的一部分。
检查此示例。
{$APPTYPE CONSOLE}

{$R *.res}

uses    
  Generics.Defaults,
  Generics.Collections,
  System.SysUtils;

Var
  someArray: TArray<string>;
  FoundIndex : Integer;

begin
  try
    someArray:=TArray<string>.Create('a','b','c');
    if TArray.BinarySearch<String>(someArray, 'b', FoundIndex, TStringComparer.Ordinal) then
     Writeln(Format('Found in index %d',[FoundIndex]))
    else
     Writeln('Not Found');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

注意:二分查找要求数组已排序。

这个可以工作吗?“注意:BinarySearch要求数组已排序。”你必须先对数组进行排序吗? - awmross
我有一种感觉,对未排序的数组使用for循环会比排序本身更快。 - Kromster
2
对于一次性使用,@Krom,你是正确的。如果您需要多次搜索数组,则可以考虑对数组进行排序。对数组进行排序所付出的代价将在搜索时节省的时间中得到回报。将重复的O(N)搜索与一次性的O(N log N)排序后的重复的O(log N)搜索进行比较。如果您执行的搜索次数超过log N,则应首先对列表进行排序。 - Rob Kennedy

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