【问题标题】:Fast way to find each pair of values from list从列表中查找每对值的快速方法
【发布时间】:2015-02-05 06:35:12
【问题描述】:

例如,我从一些文件中检索了随机入口点和哈希

EP   |  Hash
25432|545676343 
25732|344284432 
93632|9432763432 
45432|194363432 
35433|345676325
15434|445676337 
35439|745676343
55437|243276342
85532|476263821
85532|156743832 
85532|626343633
85531|626343633

假设列表非常庞大。

想要将所有数据放入内存,因为它们只是 Cardinal/Integer 数据类型。

如果我想找到 EP = 85532 和 Hash = 626343633,最快(est)的方法是什么。我不认为 for loop 是答案。

注意:

  • 如果仅找到 EP,将计算和搜索哈希。
  • 没有重复数据
  • 数据可以排序

谢谢。

【问题讨论】:

  • 哪个delphi版本?
  • 但是你的测试数据有重复?
  • 每个 EP 或每个 Hash 的值可以相同。但每一行都必须是唯一的。
  • 如果您可以对数据进行排序,那么这个问题和我的回答应该会对您有所帮助stackoverflow.com/questions/24584992/…
  • 我认为你真正的问题是你太面向代码了。这类问题都是关于选择正确的算法。完成后,代码将自行编写。使用从这里提取的代码进行反复试验,没有前进的道路。这是一个常见问题,反复出现。

标签: delphi delphi-xe2


【解决方案1】:

这是一个包含字典和对象的示例,如果需要,可以存储和构建散列。

program so_28337613;

{$APPTYPE CONSOLE}
{$R *.res}

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

type
  // data object
  THasher = class
  private
    FEP: Integer;
    FHasHash: Boolean;
    FHash: Cardinal;
    function GetHash: Cardinal;
  protected
    procedure BuildHash( out AHash: Cardinal );
  public
    constructor Create( const EP: Integer ); overload;
    constructor Create( const EP: Integer; const Hash: Cardinal ); overload;
    property EP: Integer read FEP;
    property Hash: Cardinal read GetHash;
  end;

  { THasher }

procedure THasher.BuildHash( out AHash: Cardinal );
begin
  Writeln( 'DEBUG: Building Hash' );
  AHash := FEP;
end;

constructor THasher.Create( const EP: Integer );
begin
  inherited Create;
  FEP := EP;
end;

constructor THasher.Create( const EP: Integer; const Hash: Cardinal );
begin
  Create( EP );
  FHash := Hash;
  FHasHash := True;
end;

function THasher.GetHash: Cardinal;
begin
  if not FHasHash
  then
    begin
      BuildHash( FHash );
      FHasHash := True;
    end;
  Result := FHash;
end;

procedure Test;
var
  LHashDict: TObjectDictionary<THasher, Boolean>;
  LSearchFor: THasher;
begin
  LSearchFor := nil;
  LHashDict := nil;
  try
    LHashDict := TObjectDictionary<THasher, Boolean>.Create(
      {Ownerships} [doOwnsKeys],
      {AEqualityComparer} TEqualityComparer<THasher>.Construct(
        {EqualityComparison} (
            function( const L, R: THasher ): Boolean
      begin
        Writeln( 'DEBUG: Compare' );
        Result := ( L.EP = R.EP ) and ( L.Hash = R.Hash );
      end ),
    {Hasher} (
      function( const I: THasher ): Integer
      begin
        Result := I.EP;
      end ) ) );

    // Add known hashes

    LHashDict.Add( THasher.Create( 1, 45 ), True );
    LHashDict.Add( THasher.Create( 2, 56 ), True );
    LHashDict.Add( THasher.Create( 3, 76 ), True );
    LHashDict.Add( THasher.Create( 4, 34 ), True );
    LHashDict.Add( THasher.Create( 5, 5 ), True );
    LHashDict.Add( THasher.Create( 6, 23 ), True );
    LHashDict.Add( THasher.Create( 7, 78 ), True );
    LHashDict.Add( THasher.Create( 8, 89 ), True );

    // Looking for an object with now unknown hash
    LSearchFor := THasher.Create( 5 );

    if LHashDict.ContainsKey( LSearchFor )
    then
      Writeln( 'GOTCHA' );

  finally
    LHashDict.Free;
  end;
end;

begin
  try
    Test;
  except
    on E: Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  Readln;

end.

由于调试输出状态,只有一个比较和一个哈希构建。

【讨论】:

  • 鉴于每个 EP 可以有多个关联的值,我会使用 EP 作为整数作为我的密钥。该值将是一个包含与该 EP 相关的所有信息的结构。
  • 得到GOTCHA如果参数为5或使用两个参数,否则为假?
  • 您可以仅使用 EP 或同时使用 EP 和 HASH 创建 THasher。在比较时首先比较 EP,如果匹配,则在两个哈希值之间进行比较。如果哈希值不存在,则生成它们。如果两者都匹配,则 HashDict.ContainsKey 返回 true,否则返回 false。
  • 你也可以反过来。在字典中添加大量THasher.Create( ep ),然后搜索THasher.Create( 5, 12345 )。仅在 EP 匹配时才生成哈希。而且只会生成一次。
【解决方案2】:

据我所知,您在 Delphi 中没有哈希列表。你当然可以很容易地写一个,但你也可以只使用一个 tDictonary

看看这个,看看它是否有意义:

procedure TForm1.FormCreate(Sender: TObject);
var
  List: TDictionary<TPair<Integer, Cardinal>, Integer>;
begin

  //Dummy data
  List := TDictionary<TPair<Integer, Cardinal>, Integer>.Create;

  List.Add(TPair<Integer, Cardinal>.Create(25432, 545676343), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(25732, 344284432), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(93632, 9432763432), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(45432, 194363432), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(35433, 345676325), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(15434, 445676337), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(35439, 745676343), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(55437, 243276342), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(85532, 476263821), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(85532, 156743832), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(85532, 626343633), List.Count);
  List.Add(TPair<Integer, Cardinal>.Create(85531, 626343634), List.Count);

  //check if exists
  List.ContainsKey(TPair<Integer, Cardinal>.Create(85531, 626343634));

  //Free data
  FreeAndNil(List);
end;

【讨论】:

  • Hash will be calculated and searched if only EP has been found。 ' 您的示例同时使用 EP 和 Hash。如果没有找到 EP,则不会计算哈希,因为它会浪费时间对文件进行哈希处理。
  • @alycia 根据问题,磁盘上有一个文件同时包含 ep 和 hash
  • 对不起,但顺序是程序检索 ep,如果在列表中找到 ep,程序检索哈希,如果在列表中找到具有相同 ep 的哈希,则宾果! list的结构是ep|hash
  • @alycia 如果你想让我们知道这些细节,那么他们需要在问题中。否则人们可能会编写无用的代码。
  • 抱歉,我的Hash will be calculated and searched if only EP has been found. 很清楚。
【解决方案3】:

除非您的数据具有比当前可以观察到的更多的结构(它似乎是无序的),并且您希望只执行一次查找,否则您将无法击败线性搜索,即使它有 O( n) 复杂性。对于第一次搜索,所有其他选项至少具有这种复杂性。

如果数据是有序的,那么您可以使用二分搜索有效地搜索多个项目。如果数据没有排序,那么对它进行排序是一个 O(n log n) 操作,这显然是昂贵的。但是,一旦排序,则二进制搜索是 O(log n)。

另一种选择是填充字典。标准的 Delphi 字典具有 O(1) 查找。然而,再次形成字典是昂贵的。但是,如果您可以在排序和构建字典之间进行选择,请选择后者,因为它应该更快地构建和执行查找。

总结:

  • 要执行单个查找,或者可能是非常少量的查找,请使用线性搜索。
  • 要执行多次查找,请使用字典。

从表面上看,人们会认为 EP 是字典的键。但是您似乎有多对具有相同的 EP。所以我想你需要一个复杂的值结构,其中包含与一个特定 EP 密钥相关的所有信息。

【讨论】:

  • 我想执行大量查找。是的,数据可以排序(或排序?),但我需要找到第一个值,如果找到则继续查找第二个值。可以使用BS吗?我现在正在搜索字典。
  • 我没有写任何代码,也没有给出更多细节,因为我仍然不明白你的问题的确切细节。这就是我保持一般性的原因。
  • 我的程序计算文件的 ep 值。如果在列表中找到 ep 值,则程序继续计算文件的哈希值。如果在列表中找到具有相同 ep 值的哈希值,则完成。如果数据是单值但这是对,我可以使用二进制搜索。
  • 我已经对你提出的问题给出了广泛的回答。我不能接受在 cmets 中提出的问题。你现在需要稍微思考一下你的问题,这样你的头脑就会更加清晰。
【解决方案4】:

虽然我的下一个建议不会为您提供最佳性能,但它很容易实施并且仍然提供相当好的性能。

现在,在数据搜索中获得性能的最简单方法是将数据分成有组织的组。

基于您声称您的主要搜索键是 EP 值并且 EP 值似乎是一个五位数的事实,我建议创建 100 个组(单独的数组)。

这些数组中的每一个都会以如下方式存储您的部分数据:
第一个数组所有 EP 值在 0 到 1000 之间的项目
第二个数组所有 EP 值在 1001 到 2000 之间的项目
...

这将允许使用启发式方法来减少需要迭代的项目数量,只需首先确定特定项目属于哪个组,然后仅迭代该特定组中的项目。您可以简单地将 EP 值除以 1000。

这大大减少了您需要迭代的项目数量,并且不需要像二分搜索那样对所有项目进行完美排序。

此外,如果有可能存在大量具有相同 EP 值的不同项目,您可能需要创建单独的数组来存储具有相同 EP 值的多个项目,以减少内存使用。

因此,例如有单独的数组来存储让我们说 100 个 EP 值为 25759 的项目只需要 32 位来引用数组和 100 乘以 32 位(整数大小),总计 3232 位或 404 字节。 但是将这些项目成对存储需要 100 乘以 32 位的基数部分和 32 位的整数部分,总计 6400 位或 800 字节。

【讨论】:

  • 我们使用二分搜索而不是三元搜索或拒绝搜索是有原因的。
【解决方案5】:

假设您可以确定需要多少这些数据项,那么这可能是一种方法。我无法想象检索会更快,但这是以内存为代价的......

unit EZStore;

interface

const
  MAX_HASHES = 5;
  MAX_EPS = 10000000;

type
  THashArray = Array[0..MAX_HASHES -1] of Int64;

  TEZStore = class(TObject)
  private
    FData : Array[0..MAX_EPS - 1] of THashArray;
  public
    procedure Initialise();
    procedure Store(const AEP : Integer; const AHash : Int64);
    function Retrieve(const AEP : Integer) : THashArray;
  end;

implementation

uses
  SysUtils;

procedure TEZStore.Initialise;
begin
  FillChar(FData, MAX_HASHES * MAX_EPS, 0);
end;

function TEZStore.Retrieve(const AEP: Integer): THashArray;
begin
  Result := FData[AEP];
end;

procedure TEZStore.Store(const AEP: Integer; const AHash: Int64);
var
  ThisHashArray : THashArray;
  i : integer;
begin
  i := 0;
  ThisHashArray := FData[AEP];

  while(FData[AEP][i] <> 0) do begin
    Inc(i);
    if (i > MAX_HASHES - 1) then
      raise Exception.Create(Format('The maximum of %d hashes per entry point has been exceeded', [MAX_HASHES]));
  end;

  FData[AEP][i] := AHash;
end;

end.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 2022-11-17
    • 2021-07-20
    • 1970-01-01
    • 2014-07-15
    相关资源
    最近更新 更多