views:

115

answers:

3

Hi all,

I would like to have a dictionary that returns a default value when the search key is not found. Reading from the documentation:

Generics.Collections.Tdictionary […] This class provides a mapping […] and initial content.

1 - How? Is there a way to do it ala Python: {1: ‘one’; 2:’two’} ?

Generics.Collections.TDictionary.TryGetValue [...] TryGetValue returns true if the given key is in the dictionary and provides its value in Value. Otherwise, it returns false and Value is set to the default value type of Tvalue.

2 - How can I set the this default value ? I can’t find a constructor (may be I’ve just searched in the wrong place. I’m expecting something like “constructor Create(DefaultValue: TValue);” )

So I’m trying to implement my own (may be is not necessary. See above):

The code is (reviews and suggestions are welcome!):

unit Util;

interface

uses
    Generics.collections;

type

    //
    // Dictionary with default response
    //
    TDefaultDictonary<K, V> = class(TObjectDictionary<K, V>)
    private
        M_DefaultValue : V;

    public
        constructor Create(Defaultvalue : V);
        destructor Destroy; reintroduce;
        function GetDefaultValue : V;
        function TryGetValue(const Key: K; out Value: V): Boolean;
        function GetValueOf(const Key: K) : V;
    end;

implementation

//
// Contructor and destructor
//
constructor TDefaultDictonary<K, V>.Create(Defaultvalue : V);
begin
    inherited Create;

    M_DefaultValue := Defaultvalue;
end;

destructor TDefaultDictonary<K, V>.Destroy;
begin
    inherited Destroy;
end;

//
// Get the default Value
//
function TDefaultDictonary<K, V>.GetDefaultValue : V;
begin
    Result := M_DefaultValue;
end;


//
// Try to get a value from the dictionary for the given key.
//
// If the value is found then "Value" holds it and the function returns true.
// If the value is not found then "Value" holds the default value and the
// function returns false.
//
function TDefaultDictonary<K, V>.TryGetValue(const Key: K; out Value: V): Boolean;
var
    IsKeyFound : boolean;
    DictVal : V;

begin
    IsKeyFound := inherited TryGetValue(Key, DictVal);
    if not IsKeyFound then begin
        DictVal := M_DefaultValue;
    end;

    // Outputs:
    Value := DictVal;
    Result := IsKeyFound;
end;


//
// Get a value from the dictionary for the given key.
//
// If the value is found then the function returns it.
// If the value is not found the function returns the default value.
//
function TDefaultDictonary<K, V>.GetValueOf(const Key: K) : V;
var
    DictVal : V;

begin
    TryGetValue(Key, DictVal);

    Result := DictVal;
end;

And the tests are:

unit Test_Utils;
{
    Test the TDefaultDictionary functionality
}

interface

uses
    Sysutils, Math, TestFramework, Util;

type

    TestUtil = class(TTestCase)

    public
        procedure SetUp; override;
        procedure TearDown; override;

    published
        procedure TestDefaultDictionaryGetDefaultResponse;
        procedure TestDefaultDictionaryExistingKey;
        procedure TestDefaultDictionaryNotExistingKey;

    end;


implementation


procedure TestUtil.SetUp;
begin
end;

procedure TestUtil.TearDown;
begin
end;


procedure TestUtil.TestDefaultDictionaryGetDefaultResponse;
var
    dd : TDefaultDictonary<integer, string>;

begin
    dd := TDefaultDictonary<integer, string>.Create('Default response');
    checkEquals('Default response', dd.GetDefaultValue);

    dd.Free;
end;

procedure TestUtil.TestDefaultDictionaryExistingKey;
var
    dd : TDefaultDictonary<integer, string>;
    outVal : string;
    isKeyFound : boolean;

begin
    dd := TDefaultDictonary<integer, string>.Create('Default response');
    dd.Add(1, 'My one');

    checkEquals(1, dd.Count,
        'One element as count');

    isKeyFound := dd.TryGetValue(1, outVal);
    check(isKeyFound,
        'Key not found by TryGetValue');

    checkEquals('My one', outVal,
        'Value given by TryGetValue');  

    checkEquals('My one', dd[1],
        'Value given by indexing as array');

    dd.Free;
end;


procedure TestUtil.TestDefaultDictionaryNotExistingKey;
var
    dd : TDefaultDictonary<integer, string>;
    outVal : string;
    isKeyFound : boolean;

begin
    dd := TDefaultDictonary<integer, string>.Create('Default response');
    dd.Add(1, 'one');

    isKeyFound := dd.TryGetValue(2, outVal);
    check(not isKeyFound,
        'Key should not be found by TryGetValue');

    checkEquals('Default response', outVal,
        'Default Value given by TryGetValue');

    checkEquals('Default response', dd.GetValueOf(2),
        'Default Value given by indexing as array');

    //
    // It is possible to oveload the indexer operator?
    // Please review or delete me !
    //
    //checkEquals('Default response', dd[2],
    //        'Value given by indexing as array');
    //

    dd.Free;
end;


initialization
    RegisterTest(TestUtil.Suite);
end.

That’s far from being complete. I would like to have the indexer operator working also (see the last test). It is that possible? What should be also implemented?

Does this implementation leaks M_DefaultValue (I'm new to Delphi). I can't do something M_DefaultValue.Free in the destructor (is not so flexible due the constructor constrain) What can be done here?

Thanks in advance,

Francis

A: 

Before writing all this code yourself, you might want to look at the generic classes in the DeHL library.

It supports this:

Type Support concept that defines a set of default "support classes" for each built-in Delphi types (used as defaults in collections). Custom "type support" classes can be registered for your custom data types.

--jeroen

Jeroen Pluimers
I expect that's something different: default comparers and the like, not a default value returned from a dictionary in case of "key not present".
Barry Kelly
A: 

As Barry says it's not exactly that but anyway thank you for pointing me to DeHL: it a nice library that for sure i'll consider in the future !!

Francis

xiscu
A: 

The main problem is GetItem is not virtual in TDictionary<>. This can be solved by just adding

 property Items[const Key: K]: V read GetValueOf write SetItem; default;

to your class.

Btw, destructors should be overriden, not reintroduced so when you store it in a variable defined as higher hierarchy class it calls the right destructor. That's just a best practice, but in this specific case Items[] wouldn't work as you want if you did that.

God bless you.

Trinidad