6

sharing the code on this question as reference: Delphi TPair Exception

How can I retrieve the key and value from a TObjectDictionary concrete entry without using TPair and without extracting/remove/delete the pair from the list ?

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Generics.Defaults,
  Generics.Collections;

type
  TProduct = class
  private
    FName: string;
    procedure SetName(const Value: string);
  published
  public
    property Name: string read FName write SetName;
  end;

type
  TListOfProducts = TObjectDictionary<TProduct, Integer>;

{ TProduct }

procedure TProduct.SetName(const Value: string);
begin
  FName := Value;
end;


var
  MyDict: TListOfProducts;
  MyProduct1: TProduct;
  MyProduct2: TProduct;
  MyProduct3: TProduct;
  APair: TPair<TProduct, Integer>;
  aKey: string;

begin
  try
    MyDict := TListOfProducts.Create([doOwnsKeys]);
    MyProduct1 := TProduct.Create;
    MyProduct1.Name := 'P1';
    MyProduct2 := TProduct.Create;
    MyProduct2.Name := 'P2';
    MyProduct3 := TProduct.Create;
    MyProduct3.Name := 'P3';

    MyDict.Add(MyProduct1, 1);
    MyDict.Add(MyProduct2, 2);
    MyDict.Add(MyProduct3, 3);

    //the code to look for a **concrete product** (ie: MyProduct1) goes here..

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

Thanks.

=========================

= Code with the answer =

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Generics.Defaults,
  Generics.Collections;

type
  TProduct = class
  private
    FName: string;
    procedure SetName(const Value: string);
  published
  public
    property Name: string read FName write SetName;
  end;

type
  TListOfProducts = TObjectDictionary<TProduct, Integer>;

{ TProduct }

procedure TProduct.SetName(const Value: string);
begin
  FName := Value;
end;


var
  MyDict: TListOfProducts;
  MyProduct1: TProduct;
  MyProduct2: TProduct;
  MyProduct3: TProduct;

  MySearchedProduct: TProduct;   // From Answer.

  APair: TPair<TProduct, Integer>;
  aProductName: string;

begin
  try
    MyDict := TListOfProducts.Create([doOwnsKeys]);
    MyProduct1 := TProduct.Create;
    MyProduct1.Name := 'P1';
    MyProduct2 := TProduct.Create;
    MyProduct2.Name := 'P2';
    MyProduct3 := TProduct.Create;
    MyProduct3.Name := 'P3';

    MyDict.Add(MyProduct1, 1);
    MyDict.Add(MyProduct2, 2);
    MyDict.Add(MyProduct3, 3);

    Writeln('Enter the Product Name to search: ');

    //the code to look for a **concrete product** goes here..
    Readln(aProductName);
    for MySearchedProduct in Mydict.Keys do
      if (MySearchedProduct.Name = aProductName) then
        break;
    if MySearchedProduct.Name = aProductName then
      WriteLn('I have found the product: ' + MySearchedProduct.Name)
    else
      WriteLn('I have not found a product with that name.');

    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Community
  • 1
  • 1
ferpega
  • 3,182
  • 7
  • 45
  • 65
  • @RRUZ Gracias por la edición de la pregunta. Thanks for your edit to the question. I thought was better not duplicate almost same code, but I think is better maintain it here separately. – ferpega May 03 '11 at 08:08
  • two things: (1) You are ignoring compiler warnings, don't do that. The for-loop variable (`MySearchedProduct`) will not be defined if the dictionary contains nothing, resulting in an AV. (2) If you need to search products by name, use a `TDictionary` or a `TList`. A dictionary is not supposed to be used for sequential access but for fast key-based searches. If you need sequential access, use a `TList` or `TObjectList`. – Cosmin Prund May 03 '11 at 10:59
  • @Cosmin-Prund Yes I know if there is nothing in the list `MySearchedProduct` will be `nil`. Thanks... I don't need to search products by name, this is only a sample code. I need to search products by any of its properties, functions and, perhaps, in several of them at the same time. Because of that I need the complete object, because the search will be over a variable number of `elements`. Thanks for your appointments... :-) – ferpega May 03 '11 at 14:31

3 Answers3

5

You can use The Keys and Values properties of MyDict.

In a loop like this:

var
  MyProduct: TProduct;
  Value: Integer;
begin

  for Value in MyDict.Values do
    writeln(Value);

  for MyProduct in MyDict.Keys do
    writeln(MyProduct.Name);

Or by index using ToArray:

writeln(MyDict.Keys.ToArray[1].Name);
writeln(MyDict.Values.ToArray[1]);
Mikael Eriksson
  • 136,425
  • 22
  • 210
  • 281
4

The Key and Value are saved in the dictionary as a TPair<TKey,TValue>. If you need to work with both key and value, the logical thing to do is use a TPair;

Looks like this:

for APair in MyDict do
begin
  // Your stuff goes here.
end;

If for some reason you don't want to use TPair to extract the pairs you may use something like this, but this is absolutely not a good idea - you're doing lots of dictionary queries for no good reason:

for AKey in MyDict.Keys do
begin
  AValue := MyDict[AKey];
  // Do something with both AKey and AValue
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • Glad to see `for Apair in MyDict do` runs fine (I have tested it). Because MyDict.ExtractPair doesn't run as you can see in the linked question as reference (in the original question). +1 for you. – ferpega May 03 '11 at 11:26
  • @FerPt Did you understand the fix to ExtractPair that I outlined in your previous question? – David Heffernan May 03 '11 at 14:00
  • Hummmm... if you refer to manually edit VCL source code, changin `DoRemove(Key, hc, cnExtracted);` by `result := DoRemove(Key, hc, cnExtracted);` I prefer don't do it. Any update, service pack or new installation will be destroy all the work. If there is any alternative way without modifying code "from others", I prefer it. Thanks anyway David... – ferpega May 03 '11 at 14:36
  • @Downvoter, 3 downvotes within seconds of each other, on 3 different questions? I assume asking why is futile. – Cosmin Prund May 04 '11 at 06:37
  • @FerPt, if you look at the side of each question and answer, you'll see a number with two arrows (up and down). For example, at the time of this writing, your question has a score of 4: it got 4 "up votes". At the same time my answer got 1 upvote and 1 down vote, for a score of "0". Theoretically a down vote is exactly what the tooltip on the down arrow says: it marks an answer as "not useful". Unfortunately people abuse the system and vote for all kinds of other reasons (politics, personal relations, and worst). You should read the FAQ for the site because voting is a big part of how SO works – Cosmin Prund May 05 '11 at 16:02
  • Ok... Ok... Thanks @Cosmin... I usually vote up what I consider usefull for me in the "two arrows". I always do it when I consider I must to do it or to appreciate the effort of somebody. But I was not thinking anybody could 'downvote' to any other except to remove a previous 'upvote' and always with the goal to help others.... not with the goal to annoy others. – ferpega May 06 '11 at 06:58
  • @FerPt, a downvote is also helpful if it's cast for the right reasons. It's part of the SO concept: answers aren't sorted by post time but by "score". Most useful answers gather more upvotes and "float to the top", unhelpful answers get downvotes and sink to the bottom. You'll occasionally see posts with negative score, that's a good indication the post is problematic. – Cosmin Prund May 06 '11 at 08:07
  • Thanks @Cosmin... I understand it right now. I promise read FAQ again (more slowly) :-) – ferpega May 06 '11 at 08:18
  • @FerPt, in my opinion the FAQ doesn't cover this core concept properly. When you first asked the question I re-read the FAQ myself, hoping to give you a link to some authoritative information. Didn't work out. – Cosmin Prund May 06 '11 at 08:24
1

Looping through the keys could be extrimely slow if your dictionary contains lots of members. I suggest keeping the key in the Pair along with the real value. Considering the example provided it might look like this:

type
 TListOfProducts = TObjectDictionary<TProduct, TPair<TProduct,Integer>>;
dvfedorov
  • 11
  • 2