2

I 'm trouble understanding in modifying the solution from GENERIC SEARCH as my class is more complex and I need to create several different search functions

procedure TForm1.Button1Click(Sender: TObject);
var
  activities: TList<TActivityCategory>;
  search: TActivityCategory;
begin
  activities := TObjectList<TActivityCategory>.Create(
    TDelegatedComparer<TActivityCategory>.Create(
     function(const Left, Right: TActivityCategory): Integer
       begin
       Result := CompareText(Left.Name, Right.Name);
       end)); 

  .....

Assume my TActivityCategory looks like

  TActivityCategory = class
    FirstName  : String;
    Secondname  : String;
    onemore .....
  end;

How to implement a search for every String inside my activtity class ?

Community
  • 1
  • 1
Franz
  • 1,883
  • 26
  • 47
  • The comparer is used for searching as well as sorting – David Heffernan Jul 18 '13 at 19:05
  • @DavidHeffernan: I see that (from your answer). Thanks; I hadn't seen that use. I've removed my comment. – Ken White Jul 18 '13 at 20:23
  • 1
    Did you tried other, more advanced container libraries like http://spring4d.org or http://code.google.com/p/delphi-coll ? Chances are there you already would find what you need. Or maybe take some non-generic library like `Delphi Inspirations` or `JCL` and - if found what you need - make a generic wrapper on top of it ? – Arioch 'The Jul 19 '13 at 07:41

2 Answers2

6

In your place I would write a subclass of TObjectList and add a custom Search method that would look like this:

TSearchableObjectList<T:class> = class(TObjectList<T>)
public
  function Search(aFound: TPredicate<T>): T;
end;

The implementation for that method is

function TSearchableObjectList<T>.Search(aFound: TPredicate<T>): T;
var
  item: T;
begin
  for item in Self do
    if aFound(item) then
      Exit(item);
  Result := nil;
end;

An example of this method is

var
  myList: TSearchableObjectList<TActivitycategory>;
  item: TActivitycategory;
  searchKey: string;
begin
  myList := TSearchableObjectList<TActivitycategory>.Create;
  // Here you load your list
  searchKey := 'WantedName';
  // Let´s make it more interesting and perform a case insensitive search,
  // by comparing with SameText() instead the equality operator
  item := myList.Search(function(aItem : TActivitycategory): boolean begin
            Result := SameText(aItem.FirstName, searchKey);
          end);
  // the rest of your code
end;

The TPredicate<T> type used above is declared in SysUtils, so be sure to add it to your uses clause.

I believe this is the closest we can get to lambda expressions in Delphi.

AlexSC
  • 1,823
  • 3
  • 28
  • 54
  • do I need to write a implementation of function Search(aFound: TPredicate): T; I guess I must write some code , what does this search mean ? – Franz Jul 18 '13 at 20:17
  • @Franz: not sure what you mean. I added the implementation of the Search method just below it´s declaration. – AlexSC Jul 18 '13 at 20:24
  • @Franz: It's right there in the code posted (the implementation and an example), in the last two code blocks. – Ken White Jul 18 '13 at 20:25
  • or other way : myList.Search(function(aItem): boolean begin Result := aItem.FirstName='WantedName'; end); Can I write/break this into several code pieces? – Franz Jul 18 '13 at 20:25
  • @Franz: sorry, I don´t get what you mean. The example I gave shows how to use an anonymous method that works as search criterium. By using such a technique you can search whatever you like in the list with that single and simple method. What exactly do you want to achieve by _breaking the code in pieces_? – AlexSC Jul 18 '13 at 20:40
3

TList supports searching for items using either linear or binary search. With binary search, the algorithm assumes an ordering. That's not appropriate for your needs. Linear search seems to me to be what you need, and it's available through the Contains method.

The problem is that Contains assumes that you are searching for an entire instance of T. You want to pass a single string to Contains but it won't accept that. It wants a complete record, in your case.

You could provide a Comparer that only compares a single field. And then pass Contains a record with just that one field specified. But that's pretty ugly. Frankly the design of this class is very weak when it comes to searching and sorting. The fact that the comparer is a state variable rather than a parameter is a shocking lapse in my view.

The bottom line is that TList does not readily offer what you are looking for without resorting to ugliness. You should probably implement an old-fashion loop across the list to look for your match.

Note that I'm assuming you want to provide a single string and search for an entry that has a field matching the string. If in fact you do want to provide a complete record and match every field, then Contains does what you need, with suitable Comparer using a lexicographic ordering.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490