如何在通用的TList<Integer>中获取最大值?

4

如何以最简单的方式获取 TList<Integer> 中的最大值?

function GetMaximum(AList: TList<Integer>): Integer;
begin
  Assert(AList.Count > 0);
  Result := ?;
end;

我读到 C# 有一个 AList.Max,在 Delphi 中有类似的东西吗?


.NET的方式:https://dev59.com/v2435IYBdhLWcg3wpxyx - Jens Mühlenhoff
1
当然,.NET有很棒的扩展方法来完成这种操作…… - David Heffernan
3
Delphi的Spring框架定义了IEnumerable<T>接口,提供类似于LINQ的可枚举扩展方法。推荐使用! - Erwin
1
@Marjan Venema:在Generics.Collections中定义了class TEnumerable<T>。另一方面,在Delphi的Spring Framework中,集合是基于interface的。在Spring.Collections.Lists单元中定义了一个类TList<T>,它提供了扩展方法,并最终实现了IEnumerable(T)。您可以调用List.Max,也可以调用List.Reversed等。请参见Demo.Spring.Enumerators项目 - Erwin
1
@Marjan Venema:另外,Nick Hodges也写了一系列关于使用Delphi Spring Framework进行依赖注入的不错的文章。你还可以使用Lazy initializationNullable Types。就像我说的那样:强烈推荐! - Erwin
显示剩余5条评论
4个回答

10

以下是一个有趣的示例,涉及通用容器上的MaxValue实现:

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.Generics.Defaults, System.Generics.Collections;

type
  TMyList<T> = class(TList<T>)
  public
    function MaxValue: T;
  end;

{ TMyList<T> }

function TMyList<T>.MaxValue: T;
var
  i: Integer;
  Comparer: IComparer<T>;
begin
  if Count=0 then
    raise Exception.Create('Cannot call TMyList<T>.MaxValue on an empty list');
  Comparer := TComparer<T>.Default;
  Result := Self[0];
  for i := 1 to Count-1 do
    if Comparer.Compare(Self[i], Result)>0 then
      Result := Self[i];
end;

var
  IntList: TMyList<Integer>;
  DoubleList: TMyList<Double>;
  StringList: TMyList<string>;

begin
  IntList := TMyList<Integer>.Create;
  IntList.AddRange([10, 5, 12, -49]);
  Writeln(IntList.MaxValue);

  DoubleList := TMyList<Double>.Create;
  DoubleList.AddRange([10.0, 5.0, 12.0, -49.0]);
  Writeln(DoubleList.MaxValue);

  StringList := TMyList<string>.Create;
  StringList.AddRange(['David Heffernan', 'Uwe Raabe', 'Warren P', 'Jens Mühlenhoff']);
  Writeln(StringList.MaxValue);

  Readln;
end.

因为我们无法为low(Integer)找到通用替代方法,所以当在空列表上调用该方法时,我会引发异常。

输出结果为:

12
 1.20000000000000E+0001
Warren P

8
这里有一个替代方案:使用Spring4D框架中的Spring.Collections.pas单元(可在此处找到:http://code.google.com/p/delphi-spring-framework/
program ListEnumerableDemo;

{$APPTYPE CONSOLE}

uses 
    System.SysUtils 
  , Spring.Collections;

var 
  List: IList<Integer>; 
  Enumerable: IEnumerable<Integer>;

begin 
  try 
    List := TCollections.CreateList<Integer>; 
    List.AddRange([1,6,2,9,54,3,2,7,9,1]);

    Enumerable := List; 
    WriteLn(Enumerable.Max); 
    ReadLn; 
  except 
    on E: Exception do 
      Writeln(E.ClassName, ': ', E.Message); 
  end; 
end. 

2
+1,因为这根本不是TList<T>的问题,使用ENumerable<T>就可以了。 - Jeroen Wiert Pluimers
2
由于IList<T>继承自IEnumerable<T>,我们可以使用List.Max而无需将其转换为IEnumerable<T> :) - Baoquan Zuo
@Jeroen 我完全不理解那个评论。答案使用了一个通用列表,只是没有使用 Generics.Collections 中的变体。 - David Heffernan
1
在Spring中,除了IList之外,还有许多其他的类型会暴露出IEnumerable<T>。这就是Nick答案的关键所在。 - Jeroen Wiert Pluimers
@Nick IEnumerable<Integer>.Max 如何处理空容器? - David Heffernan
1
@David:好问题。我尝试了上面的演示,并注释掉了填充列表的AddRange。它做到了我希望它做的事情。引发了异常EInvalidOpException,消息为“此序列为空”。 - Warren P

3

使用 for .. in

function GetMaximum(AList: TList<Integer>): Integer;
var
  I: Integer
begin
  Assert(AList.Count > 0);
  Result := Low(Integer);
  for I in AList do
    if I > Result then
      Result := I;
end;

1
能否以与 .net 中的 IEnumerable 等效强大的方式来完成这项任务?换句话说,您可以使用泛型/特征为 GetMaximum 创建通用实现吗? - Warren P
1
@Warren -- Spring.Collections.pas单元拥有IEnumerable<T>,这将使得这个任务变得轻松。 - Nick Hodges

1

我同意使用Spring集合可能是最简单的方法。然而,可能有不使用它们的原因(已经在各个地方使用了Generics.Collections)。

因此,以下是如何创建一个扩展TEnumerable<T>并具有function Max: T的新类型。

type
  Enumerable<T> = record
  private
    source: TEnumerable<T>;
  public
    function Max: T;

    class operator Implicit(const value: TEnumerable<T>): Enumerable<T>;
  end;

class operator Enumerable<T>.Implicit(
  const value: TEnumerable<T>): Enumerable<T>;
begin
  Result.source := value;
end;

function Enumerable<T>.Max: T;
var
  default: IComparer<T>;
  x, y: T;
  flag: Boolean;
begin
  if not Assigned(source) then
    raise EArgumentNilException.Create('Source');
  default := TComparer<T>.Default;

  flag := False;
  for x in source do
  begin
    if flag then
    begin
      if default.Compare(x, y) > 0 then
        y := x;
    end
    else
    begin
      y := x;
      flag := True;
    end;
  end;
  if flag then
    Result := y
  else
    raise EListError.Create('source is empty');
end;

这段代码基本上是从 .Net 的 System.Linq 中移植过来的 Enumerable.Max<T> 扩展方法。您可以像 Nick 的示例一样使用它。

对于那些关心二进制大小的人来说,有趣的事情是:链接器能够删除从未使用过的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接