Jump to content
Sign in to follow this  
Mike Torrettinni

StringList container with ignore duplicates, non-sorted

Recommended Posts

Is there a way to use TStringList to ignore duplicates and doesn't need to be sorted? I thought this would be pretty easy, but it seems is not doable, unless of course I manage checking for duplicates and so on.

 

For example: It would be nice to have:

vSL := TStringList.Create;
vSL.Duplicates := dupIgnore;
vSL.Sorted := false;
vSL.Add('ProjectC');
vSL.Add('ProjectC');
vSL.Add('ProjectA');
vSL.Add('ProjectB');
vSL.Add('ProjectA');
vSL.Add('ProjectC');

and result would be no-duplicates, with order as they first occur:

ProjectC

ProjectA

ProjectB

 

Is there a way to use TStringList for this without me writing custom duplicate checking code?

Share this post


Link to post
5 minutes ago, Mike Torrettinni said:

Is there a way to use TStringList for this without me writing custom duplicate checking code?

 

No, there is no way and it's documented that Duplicates works only for sorted lists:

 

Set Duplicates to specify what should happen when an attempt is made to add a duplicate string to a sorted list. The CaseSensitive property controls whether two strings are considered duplicates if they are identical except for differences in case. 

The value of Duplicates should be one of the following. 

  • Thanks 1

Share this post


Link to post
12 minutes ago, Lajos Juhász said:

No, there is no way and it's documented that Duplicates works only for sorted lists:

Yes, but I thought perhaps there still is a way and just wasn't documented. Or perhaps newer version (10.3, 10.4) have such implementation.

 

I was actually almost done with my class of TList<string> that does this, but I just wanted to be sure I'm not implementing something that is ready available.

Share this post


Link to post

Check out the source and you can easily write your own version (a quick example code):

 

uses 
  System.RTLConsts;

type
  TMyStringList = class(TStringList)
  public
    function AddObject(const S: string; AObject: TObject): Integer; override;
  end;

function TMyStringList.AddObject(const S: string; AObject: TObject): Integer;
begin
  if sorted then
    result:=inherited AddObject(S, AObject)
  else
  begin
    result:=IndexOf(s);
    if (result=-1) then
    begin
      result:=count;
      InsertObject(result, S, AObject)
    end
    else
    begin
      case Duplicates of
        dupIgnore: Exit;
        dupError: Error(@SDuplicateString, 0);
        dupAccept: begin
                     result:=count;
                     InsertObject(result, S, AObject);
                   end;
      end;
    end;
  end;
end;

 

  • Thanks 1

Share this post


Link to post

I had a very simple implementation, I guess enough for my needs:

 

// String List, non-duplicates, preserve order (non sorted)
  TUniqueStringList = class(TStringList)
  private
    function StrExists(const aString: string): boolean;
  public
    function Add(const aString: string): Integer; override;
  end;
  
  function TUniqueStringList.StrExists(const aString: string): boolean;
var i: Integer;
begin
  Result := false;
  for i := 0 to Pred(Count) do
    if Self[i] = aString then
      Exit(True);
end;

function TUniqueStringList.Add(const aString: string): Integer;
begin
  if (Duplicates <> dupIgnore) or
     (Duplicates = dupIgnore) And (Not StrExists(aString))
  then
     Result := inherited Add(aString);
end;

 

and it works:

var vSL: TUniqueStringList;
begin
  vSL := TUniqueStringList.Create;
  vSL.Duplicates := dupIgnore;
  vSL.Sorted := false;

  vSL.Add('ProjectC');
  vSL.Add('ProjectC');
  vSL.Add('ProjectA');
  vSL.Add('ProjectB');  
  vSL.Add('ProjectA');
  vSL.Add('ProjectC');

result:

image.png.e6a5e51d17c094bc6e29f8242e28e62a.png

Share this post


Link to post
4 minutes ago, Lajos Juhász said:

Your implementation of   StrExists ignores the CaseSensitive property.

Good point! Although default false is OK for my needs today, but will implement it.

Share this post


Link to post
8 hours ago, David Heffernan said:

Isn't this better suited to a hash set rather than a string list?

It really depends on the data for a list of 3-5 elements my guess is a list is faster.

Share this post


Link to post
14 minutes ago, Lajos Juhász said:

It really depends on the data for a list of 3-5 elements my guess is a list is faster.

Probably, but then for small lists often performance isn't key. 

Share this post


Link to post

So, Delphi was hinting that

W1035 Return value of function 'TUniqueStringList.Add' might be undefined

And I changed that ValueExists (ex StrExists)  returns Index of existing value and all is good:

 

function TUniqueStringList.ValueExists(const aValue: string; out aIndexOfExisting: integer): boolean;
var i: Integer;
begin
  Result := false;
  for i := 0 to Pred(Count) do
    if Self[i] = aValue then
    begin
      aIndexOfExisting := i;
      Exit(True);
    end;
end;

function TUniqueStringList.Add(const aValue: string): Integer;
begin
  if (Duplicates <> dupIgnore) or
     (Duplicates = dupIgnore) And (Not ValueExists(aValue, Result))
  then
     Result := inherited Add(aValue);
end;

 

Pretty impressed by Delphi to recognize the Result is updated in the call.

Share this post


Link to post
20 hours ago, Mike Torrettinni said:

function TUniqueStringList.ValueExists(const aValue: string; out aIndexOfExisting: integer boolean;

 

O_o How about TStringList.IndexOf ?

Edited by Fr0sT.Brutal
  • Thanks 1

Share this post


Link to post
7 minutes ago, Fr0sT.Brutal said:

O_o How about TStringList.IndexOf ?

I've used that in my example and also it's better to implement the logic in AddObject as in the TStringList where method Add invokes Addobject with nil object.

Share this post


Link to post

 

52 minutes ago, Lajos Juhász said:

AddObject as in the TStringList where method Add invokes Addobject with nil object.

Thanks, but why do you suggest I re-implement AddObjects, doesn't a simple Add like in my example work?

 

 

Share this post


Link to post
3 hours ago, Fr0sT.Brutal said:

O_o How about TStringList.IndexOf ?

Yes, better. So, like this:

 

function TUniqueStringList.Add(const aValue: string): Integer;
begin
  if Duplicates = dupIgnore then
    Result := IndexOf(aValue)
  else
    Result := - 1;

  if (Duplicates <> dupIgnore) or (Result = - 1) then
    Result := inherited Add(aValue);
end;

 

Edited by Mike Torrettinni
better version, no W1035 hint

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×