Delphi 使用TDictionary";为了……在“中”;

Delphi 使用TDictionary";为了……在“中”;,delphi,dictionary,Delphi,Dictionary,我使用的是Delphi Berlin 10.1(最新更新),我的应用程序中有一些特殊值的TDictionary问题。“for..in”循环不正确 下面是一个示例代码,其中“for…in”不会循环遍历所有值,另一个示例则会循环遍历所有值 在第一种情况下,“for…In”循环只执行两个步骤,而在第二种情况下,它执行所有步骤 procedure TForm1.btn1Click(Sender: TObject); var tmpPar: TPair<Integer, Integer>;

我使用的是Delphi Berlin 10.1(最新更新),我的应用程序中有一些特殊值的TDictionary问题。“for..in”循环不正确

下面是一个示例代码,其中“for…in”不会循环遍历所有值,另一个示例则会循环遍历所有值

在第一种情况下,“for…In”循环只执行两个步骤,而在第二种情况下,它执行所有步骤

procedure TForm1.btn1Click(Sender: TObject);
var
  tmpPar: TPair<Integer, Integer>;
  tmpDictionary: TDictionary<Integer, Integer>;
begin
  // NOT WORKING
  tmpDictionary := TDictionary<Integer, Integer>.Create;
  try
      tmpDictionary.Add(631, 40832);
      tmpDictionary.Add(1312, 40837);
      tmpDictionary.Add(5947, 40842);

      for tmpPar in tmpDictionary do
      begin
          tmpDictionary.Remove(tmpPar.Key);
      end;
  finally
      tmpDictionary.Free;
  end;

  // WORKING
  tmpDictionary := TDictionary<Integer, Integer>.Create;
  try
      tmpDictionary.Add(123, 5432);
      tmpDictionary.Add(453, 23);
      tmpDictionary.Add(76, 2334);

      for tmpPar in tmpDictionary do
      begin
          tmpDictionary.Remove(tmpPar.Key);
      end;
  finally
      tmpDictionary.Free;
  end;
end;
程序TForm1.btn1单击(发送方:TObject);
变量
tmpPar:TPair;
tmpDictionary:t词典;
开始
//不起作用
tmpDictionary:=TDictionary.Create;
尝试
tmpDictionary.Add(63140832);
tmpDictionary.Add(1312,40837);
tmpDictionary.Add(594740842);
对于tmpDictionary do中的tmpPar
开始
tmpDictionary.Remove(tmpPar.Key);
终止
最后
tmpDictionary.Free;
终止
//工作
tmpDictionary:=TDictionary.Create;
尝试
tmpDictionary.Add(1235432);
tmpDictionary.Add(453,23);
tmpDictionary.Add(762334);
对于tmpDictionary do中的tmpPar
开始
tmpDictionary.Remove(tmpPar.Key);
终止
最后
tmpDictionary.Free;
终止
终止
第一种情况有什么问题吗


提前谢谢

你的例子仅仅是靠运气成功的-不应该期望这个构造会表现良好。如果您仔细阅读您的示例,您会发现第一个示例在删除时调用列表重新排序,而第二个示例则没有

若要查看发生了什么,请检查从字典中删除项的代码:

function TDictionary<TKey,TValue>.DoRemove(const Key: TKey; HashCode: Integer;
  Notification: TCollectionNotification): TValue;
var
  gap, index, hc, bucket: Integer;
  LKey: TKey;
begin
  index := GetBucketIndex(Key, HashCode);
  if index < 0 then
    Exit(Default(TValue));

  // Removing item from linear probe hash table is moderately
  // tricky. We need to fill in gaps, which will involve moving items
  // which may not even hash to the same location.
  // Knuth covers it well enough in Vol III. 6.4.; but beware, Algorithm R
  // (2nd ed) has a bug: step R4 should go to step R1, not R2 (already errata'd).
  // My version does linear probing forward, not backward, however.

  // gap refers to the hole that needs filling-in by shifting items down.
  // index searches for items that have been probed out of their slot,
  // but being careful not to move items if their bucket is between
  // our gap and our index (so that they'd be moved before their bucket).
  // We move the item at index into the gap, whereupon the new gap is
  // at the index. If the index hits a hole, then we're done.

  // If our load factor was exactly 1, we'll need to hit this hole
  // in order to terminate. Shouldn't normally be necessary, though.
  {...   etc   ...}
函数TDictionary.DoRemove(常量键:TKey;哈希代码:Integer;
通知:TCollectionNotification):TValue;
变量
差距、指数、hc、桶:整数;
LKey:TKey;
开始
索引:=GetBucketIndex(键,哈希代码);
如果指数<0,则
退出(默认值(TValue));
//从线性探测哈希表中删除项是适度的
//狡猾。我们需要填补空白,这将涉及移动项目
//甚至可能不会散列到同一位置。
//Knuth在第三卷第6.4节中对此做了充分的阐述。;但是要小心,R
//(第二版)有一个错误:步骤R4应该转到步骤R1,而不是R2(已经勘误)。
//然而,我的版本是向前线性探测,而不是向后线性探测。
//间隙是指需要通过向下移动项目来填充的孔。
//索引搜索已从其插槽中探测的项目,
//但是要小心,如果物品的桶在桶和桶之间,不要移动物品
//我们的差距和我们的指数(这样他们就会在他们的桶之前移动)。
//我们将索引中的项移动到间隙中,然后新的间隙被删除
//在索引处。如果索引命中一个洞,那么我们就完成了。
//如果我们的负载系数正好是1,我们就需要打这个洞
//为了终止。不过,通常不需要。
{…等…}
您可以看到,实现了一个算法,它决定在删除项时何时以及如何对基础列表重新排序(这是为了尝试优化已分配内存块中的间隙位置,以优化将来的插入)。枚举只是在基础列表中的索引中移动,因此一旦从列表中删除一个项,枚举器就不再有效,因为它只会将您移动到基础列表中的下一个索引,该索引已更改


对于普通列表,删除时通常会反向迭代。但是,对于字典,您必须首先构建一个要在第一次枚举过程中删除的键列表,然后枚举该列表以将其从字典中删除。

您的示例只是侥幸成功-不应期望此构造会表现良好。如果您仔细阅读您的示例,您会发现第一个示例在删除时调用列表重新排序,而第二个示例则没有

若要查看发生了什么,请检查从字典中删除项的代码:

function TDictionary<TKey,TValue>.DoRemove(const Key: TKey; HashCode: Integer;
  Notification: TCollectionNotification): TValue;
var
  gap, index, hc, bucket: Integer;
  LKey: TKey;
begin
  index := GetBucketIndex(Key, HashCode);
  if index < 0 then
    Exit(Default(TValue));

  // Removing item from linear probe hash table is moderately
  // tricky. We need to fill in gaps, which will involve moving items
  // which may not even hash to the same location.
  // Knuth covers it well enough in Vol III. 6.4.; but beware, Algorithm R
  // (2nd ed) has a bug: step R4 should go to step R1, not R2 (already errata'd).
  // My version does linear probing forward, not backward, however.

  // gap refers to the hole that needs filling-in by shifting items down.
  // index searches for items that have been probed out of their slot,
  // but being careful not to move items if their bucket is between
  // our gap and our index (so that they'd be moved before their bucket).
  // We move the item at index into the gap, whereupon the new gap is
  // at the index. If the index hits a hole, then we're done.

  // If our load factor was exactly 1, we'll need to hit this hole
  // in order to terminate. Shouldn't normally be necessary, though.
  {...   etc   ...}
函数TDictionary.DoRemove(常量键:TKey;哈希代码:Integer;
通知:TCollectionNotification):TValue;
变量
差距、指数、hc、桶:整数;
LKey:TKey;
开始
索引:=GetBucketIndex(键,哈希代码);
如果指数<0,则
退出(默认值(TValue));
//从线性探测哈希表中删除项是适度的
//狡猾。我们需要填补空白,这将涉及移动项目
//甚至可能不会散列到同一位置。
//Knuth在第三卷第6.4节中对此做了充分的阐述。;但是要小心,R
//(第二版)有一个错误:步骤R4应该转到步骤R1,而不是R2(已经勘误)。
//然而,我的版本是向前线性探测,而不是向后线性探测。
//间隙是指需要通过向下移动项目来填充的孔。
//索引搜索已从其插槽中探测的项目,
//但是要小心,如果物品的桶在桶和桶之间,不要移动物品
//我们的差距和我们的指数(这样他们就会在他们的桶之前移动)。
//我们将索引中的项移动到间隙中,然后新的间隙被删除
//在索引处。如果索引命中一个洞,那么我们就完成了。
//如果我们的负载系数正好是1,我们就需要打这个洞
//为了终止。不过,通常不需要。
{…等…}
您可以看到,实现了一个算法,它决定在删除项时何时以及如何对基础列表重新排序(这是为了尝试优化已分配内存块中的间隙位置,以优化将来的插入)。枚举只是在基础列表中的索引中移动,因此一旦从列表中删除一个项,枚举器就不再有效,因为它只会将您移动到基础列表中的下一个索引,该索引已更改

对于普通列表,删除时通常会反向迭代。但是,对于字典,必须首先构建要在第一次枚举过程中删除的键列表,然后枚举该列表以将其从字典中删除