【问题标题】:List and Contains method列表和包含方法
【发布时间】:2012-01-14 14:27:23
【问题描述】:

我有这个问题:从一个空列表(0 个元素)开始,我想检查一个元素是否存在于这个列表中。如果列表中不存在此记录,则我将此记录添加到列表中,否则更新列表中的元素。 我试过写这段代码:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

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

type
  TDBStats = record
    Comb: Integer;
    Freq: Integer;
  end;
  TDBStatsList = TList<TDBStats>;

procedure Add(ODBStats: TDBStatsList; const Item: TDBStats);
var
  rItem: TDBStats;
begin
  rItem := Item;
  rItem.Freq := 1;
  oDBStats.Add(rItem);
end;

procedure Update(ODBStats: TDBStatsList; const Item: TDBStats; const Index: Integer);
var
  rItem: TDBStats;
begin
  rItem := Item;
  Inc(rItem.Freq);
  oDBStats[Index] := rItem;
end;


var
  oDBStats: TDBStatsList;
  rDBStats: TDBStats;
  myArr: array [0..4] of integer;
  iIndex1: Integer;
begin
  try
    myArr[0] := 10;
    myArr[1] := 20;
    myArr[2] := 30;
    myArr[3] := 40;
    myArr[4] := 10;

    oDBStats := TList<TDBStats>.Create;
    try
      for iIndex1 := 0 to 4 do
      begin
        rDBStats.Comb := myArr[iIndex1];
        if oDBStats.Contains(rDBStats) then
          Update(oDBStats, rDBStats, oDBStats.IndexOf(rDBStats))
        else
          Add(oDBStats, rDBStats);
      end;
      // Check List
      for iIndex1 := 0 to Pred(oDBStats.Count) do
        Writeln(oDBStats[iIndex1].Comb:3, oDBStats[iIndex1].Freq:10);
    finally
      oDBStats.Free;
    end;

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

并且应该返回这个结果:

10     2
20     1
30     1
40     1
50     1

但是返回这个结果:

10     1
20     1
30     1
40     1
50     1
10     1

我了解了问题:当我使用 oDBStats.Contains(rDBStats) 时,它控制 rDBStats 元素是否包含在列表中;第一次没有找到并添加到列表中;但是当它被添加到列表中时,我将频率字段更新为 1;所以第二次当我再次检查频率 = 0 的 rdbstats 时没有找到它。 因为我可以解决这个问题?我需要一个计数器,我从输入中得到一个“梳子”,我想检查这个“梳子”是否存在于列表中,独立于记录的另一个字段的值。如果我在列表中找到“comb”,那么我会更新,增加频率字段。 感谢您的帮助。

【问题讨论】:

  • 由于随机选择的类型名称,您的代码难以阅读;使用TDBStatList 代替TDBStatsTDBStat 代替PDBStats
  • 已完成,希望对您有所帮助。
  • Freq 字段值也在Contains 方法中进行了测试;只需在rDBStats.Comb := myArr[iIndex1]; 之后添加rDBStats.Freq := 1; 行就可以得到正确的结果,但这不是一般的解决方案;您的列表需要一个不同的比较器。
  • 我还是看不到50 1的输出来自哪里。

标签: delphi delphi-xe2


【解决方案1】:

当您在通用列表上调用Contains 时,它会查看给定值是否已经在列表中。您的案例中的值是由两个字段组成的记录。由于您没有指定自定义比较器,Delphi 将使用默认比较器,在记录的情况下进行二进制比较。所以只有当两条记录是二进制相等时,它们才会被视为相等。

要使您的示例正常工作,您必须指定一个仅比较记录的梳状字段的自定义比较器。这是一个例子:

oDBStats := TList<TDBStats>.Create(TDelegatedComparer<TDBStats>.Create(
 function(const Left, Right: TDBStats): Integer
 begin
   result := CompareValue(Left.comb, Right.comb);
 end));

此外,您的更新程序有错误。您不是增加现有值,而是增加 item 参数的未定义值。第一行的更改应该使它起作用:

  rItem := oDBStats[Index];
  Inc(rItem.Freq);
  oDBStats[Index] := rItem;

【讨论】:

    【解决方案2】:

    您的数据结构错误,因为您真正需要的是dictionary

    使用列表的根本问题是您想要搜索存储记录的子集。但是没有为此设置列表。使用TDictionary&lt;Integer, Integer&gt;重写解决问题。

    我可以建议您彻底阅读dictionary code example at the Embarcadero docwiki

    字典的键是comb,值是freq。要添加项目,请执行以下操作:

    if Dict.TryGetValue(Comb, Freq) then
      Dict[Comb] := Freq+1
    else
      Dict.Add(Comb, 1);
    

    我假设你的字典是这样声明的:

    var
      Dict: TDictionary<Integer, Integer>;
    

    并像这样创建:

    Dict := TDictionary<Integer, Integer>;
    

    您可以使用简单的for in 循环枚举字典。

    var
      Item: TPair<Integer, Integer>;
    ...
      for Item in Dict do
        Writeln(Item.Key:3, Item.Value:10);
    

    尽管被警告字典将以奇数顺序枚举。您可能希望在打印前进行排序。

    如果您希望在字典中存储与每个条目相关的更多信息,请将附加字段放入记录中。

    type
      TDictValue = record
        Freq: Integer;
        Field1: string;
        Field2: TDateTime;
        //etc.
      end;
    

    然后你的字典变成TDictionary&lt;Integer, TDictValue&gt;

    【讨论】:

    • 感谢您的建议,我看了这里:docwiki.embarcadero.com/CodeSamples/en/… 以及我理解的示例;但我有一个小问题要理解,因为我可以做到这一点。特别是在我声明的类型部分?例如我有: TDBStatsList = TList 并且应该写: TDBStatsList = TDictionary;但这让我在梳子中犯了一个错误。如果我理解得很好,这个“梳子”代表主键。可以尝试举一个我可以更好理解的例子;真诚地,这是我第一次使用字典。再次感谢。
    • 谢谢,我刚刚阅读了您的示例。我试试看。再次非常感谢
    • 我已经简化了记录 TDBStats 只使用两个字段:comb(作为主键)和 freq 作为通用字段;在实际应用中,该记录由许多字段组成,这些字段一直在更新,即获取元素梳。查看您的示例,我应该将所有这些字段添加到: Dict.TryGetValue(Comb, Freq) 具有以下内容: Dict.TryGetValue(Comb, Freq, Field1, field2, fieldn)?以我发布的示例代码为例,我只能使用 Tdictionary 更新我检查元素是否存在的部分?
    • 您需要将构成键的各个字段放入一条记录中。
    • 实际上,在第二次阅读时,可能所有额外的字段都属于值而不是键。只有你能知道。您可以根据是否需要对键进行搜索来确定字段应该是键还是值。此外,Uwe 的答案将是一个可行的选择,但您的数据结构对我来说听起来更像是一本字典。也就是说,字典不灵活,不允许使用不同的键进行搜索。您必须预先决定您的搜索关键字是什么。可以调整 Uwe 的答案以允许在运行时使用不同的搜索键。
    猜你喜欢
    • 2015-03-23
    • 2022-11-17
    • 2015-01-06
    • 2020-03-04
    • 1970-01-01
    • 2020-05-30
    • 1970-01-01
    • 2015-02-02
    • 2014-12-22
    相关资源
    最近更新 更多