How can I search a generic TList for a record with a certain field value?

前端 未结 5 1027
你的背包
你的背包 2020-12-17 02:47

Everything about generic TList. I have this structure:

Type
  TExtract = record
    Wheel: string;
    Extract: array [1..5] of Byte;
  end;

           


        
5条回答
  •  轮回少年
    2020-12-17 03:20

    You could also declare a helper class like this, to avoid the requirement of IComparer that both left and right side of the comparison must be of the specialized type:

    type
      TLeftComparison = reference to function(const Left: T; var Value): Integer;
    
      TListHelper = class
      public
        class function BinarySearch(Instance: TList; var Value; out FoundIndex: Integer;
          Comparison: TLeftComparison; Index, Count: Integer): Boolean; overload;
        class function BinarySearch(Instance: TList; var Value; out FoundIndex: Integer;
          Comparison: TLeftComparison): Boolean; overload;
        class function Contains(Instance: TList; var Value; Comparison: TLeftComparison): Boolean;
        class function IndexOf(Instance: TList; var Value; Comparison: TLeftComparison): Integer;
        class function LastIndexOf(Instance: TList; var Value; Comparison: TLeftComparison): Integer;
      end;
    
    class function TListHelper.BinarySearch(Instance: TList; var Value; out FoundIndex: Integer;
      Comparison: TLeftComparison; Index, Count: Integer): Boolean;
    var
      L, H: Integer;
      mid, cmp: Integer;
    begin
      Result := False;
      L := Index;
      H := Index + Count - 1;
      while L <= H do
      begin
        mid := L + (H - L) shr 1;
        cmp := Comparison(Instance[mid], Value);
        if cmp < 0 then
          L := mid + 1
        else
        begin
          H := mid - 1;
          if cmp = 0 then
            Result := True;
        end;
      end;
      FoundIndex := L;
    end;
    
    class function TListHelper.BinarySearch(Instance: TList; var Value; out FoundIndex: Integer;
      Comparison: TLeftComparison): Boolean;
    begin
      Result := BinarySearch(Instance, Value, FoundIndex, Comparison, 0, Instance.Count);
    end;
    
    class function TListHelper.Contains(Instance: TList; var Value; Comparison: TLeftComparison): Boolean;
    begin
      Result := IndexOf(Instance, Value, Comparison) >= 0;
    end;
    
    class function TListHelper.IndexOf(Instance: TList; var Value; Comparison: TLeftComparison): Integer;
    var
      I: Integer;
    begin
      for I := 0 to Instance.Count - 1 do
        if Comparison(Instance[I], Value) = 0 then
          Exit(I);
      Result := -1;
    end;
    
    class function TListHelper.LastIndexOf(Instance: TList; var Value; Comparison: TLeftComparison): Integer;
    var
      I: Integer;
    begin
      for I := Instance.Count - 1 downto 0 do
        if Comparison(Instance[I], Value) = 0 then
          Exit(I);
      Result := -1;
    end;
    

    Then you could use it like this:

    // TComparison (requires instances on both sides)
    function CompareEstr(const Left, Right: TEstr): Integer;
    begin
      if Left.Date < Right.Date then
        Exit(-1);
      if Left.Date > Right.Date then
        Exit(1);
      Result := 0;
    end;
    
    // TLeftComparison: requires instance only on the left side    
    function CompareEstr2(const Left: TEstr; var Value): Integer;
    begin
      if Left.Date < TDateTime(Value) then
        Exit(-1);
      if Left.Date > TDateTime(Value) then
        Exit(1);
      Result := 0;
    end;
    
    procedure Main;
    var
      Date: TDate;
      Comparer: IComparer;
      List: TEstrList;
      Item: TEstr;
      Index: Integer;
      I: Integer;
    begin
      Comparer := nil;
      List := nil;
      try
        // create a list with a comparer
        Comparer := TComparer.Construct(CompareEstr);
        List := TEstrList.Create(Comparer);
        // fill with some data
        Date := EncodeDate(2011, 1, 1);
        for I := 0 to 35 do
        begin
          Item.Date := IncMonth(Date, I);
          List.Add(Item);
        end;
        // sort (using our comparer)
        List.Sort;
    
        Date := EncodeDate(2011, 11, 1);
        Item.Date := Date;
    
        // classic approach, needs Item on both sides   
        Index := List.IndexOf(Item);
        Writeln(Format('TList.IndexOf(%s): %d', [DateToStr(Date), Index]));
        List.BinarySearch(Item, Index);
        Writeln(Format('TList.BinarySearch(%s): %d', [DateToStr(Date), Index]));
        Writeln;
    
        // here we can pass Date directly
        Index := TListHelper.IndexOf(List, Date, CompareEstr2);
        Writeln(Format('TListHelper.IndexOf(%s): %d', [DateToStr(Date), Index]));
        TListHelper.BinarySearch(List, Date, Index, CompareEstr2);
        Writeln(Format('TListHelper.BinarySearch(%s): %d', [DateToStr(Date), Index]));
        Readln;
      finally
        List.Free;
      end;
    end;
    

    This is of course less type-safe (due to the untyped right-side comparison parameter) but needed to allow to generically compare values of different types. With a bit of care this should not be a problem. Otherwise you could also write overloaded versions for most used types you need to compare.

提交回复
热议问题