【问题标题】:Loop through multiple dimensions循环遍历多个维度
【发布时间】:2017-05-21 12:12:16
【问题描述】:

我有一个包含多个变量的函数 f(例如 3 个变量 f(x,y,z))。我想计算每个变量范围的函数结果并将它们存储在一个列表中。对于 3 个变量,这可能如下所示:

procedure LoopOver3Dimensions;
type
  TListItem=record
    x, y, z: Real;
    CalculationResult: Real;
  end;
var
  List: TList<TListItem>;
  NewListItem: TListItem;
  i, j, k: Integer;
  x, y, z: Real;
  xStart, yStart, zStart: Real;
  xEnd, yEnd, zEnd: Real;
  NumberOfValuesToCalculateForDimension0: Integer;
  NumberOfValuesToCalculateForDimension1: Integer;
  NumberOfValuesToCalculateForDimension2: Integer;
begin
  //set xStart, xEnd, NumberOfValuesToCalculateForDimension0 etc here

  for i := 0 to NumberOfValuesToCalculateForDimension0 do
  begin
    x:=xStart+i*Abs(xEnd-xStart)/(NumberOfValuesToCalculateForDimension0-1);
    for j := 0 to NumberOfValuesToCalculateForDimension1 do
    begin
      y:=yStart+j*Abs(yEnd-yStart)/(NumberOfValuesToCalculateForDimension1-1);
      for k := 0 to NumberOfValuesToCalculateForDimension2 do
      begin
        z:=zStart+k*Abs(zEnd-zStart)/(NumberOfValuesToCalculateForDimension2-1);
        NewListItem.x:=x;
        NewListItem.y:=y;
        NewListItem.z:=z;
        NewListItem.CalculationResult:=DoCalculation(x, y, z);
        List.Add(NewListItem);
      end;
    end;
  end;
end;

我当然可以以相同的方式对超过 3 个维度(例如 20 个维度)进行编程,但这会变得非常麻烦,而且由于所有内容都是硬编码的,因此我无法在运行时更改维度数。

最好的方法是什么?

【问题讨论】:

  • 如果您要在单独的参数中传递每个参数,那么您需要动态调用函数或硬编码。更好的可能是将参数传递到数组中。如果您真的有 20 个参数,那么您将没有时间或内存来更改所有参数。
  • 正如@David 所说,使用(动态)数组而不是 x、y、z。并循环遍历维度,而不是对嵌套循环进行硬编码。
  • 当然,循环遍历维度也有点棘手。您需要一个整数数组,长度等于维度计数。和一个增量函数。还有一个终止条件。
  • @David 您能否为我们概述一下这样的实现?谢谢!

标签: algorithm loops delphi iteration


【解决方案1】:

正如在 cmets 中所讨论的,为了支持任意数量的参数,最好使用可变长度的数组而不是参数。那是因为该语言对可变长度参数列表的支持不是很好。

一旦将所有参数打包为一个数组,那么您将面临生成所有可能的组合。这不是一个完全简单的任务。我将向您展示如何在0N[i]-1 范围内的每个维度上执行此操作,而不是使用实际值,其中i 是维度索引。一旦您可以迭代所有此类组合,您就可以轻松扩展以生成您的真实值。

基本概念是保持一个当前的迭代值,它是递增的。第一个维度是循环的最里面。当它达到最大值时,它会返回零,并且下一个外部维度会递增。等等。下面是一些示例代码:

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

procedure IterMultiDim(const N: array of Integer; const Proc: TProc<TArray<Integer>>);
var
  dim: Integer;
  Current: TArray<Integer>;
begin
  SetLength(Current, Length(N));
  while Current[high(N)]<N[high(N)] do begin
    Proc(Current);

    // increment state
    dim := 0;
    while dim<=high(N) do begin
      inc(Current[dim]);
      if Current[dim]=N[dim] then begin
        if dim<high(N) then begin
          Current[dim] := 0;
        end;
      end else begin
        break;
      end;
      inc(dim);
    end;
  end;
end;

procedure WriteIntArray(Arr: TArray<Integer>);
var
  i: Integer;
begin
  for i := 0 to high(Arr) do begin
    Write(Arr[i]);
    if i<high(Arr) then begin
      Write(', ');
    end;
  end;
  Writeln;
end;

procedure Main;
begin
  IterMultiDim([2, 3, 4], WriteIntArray);
end;

begin
  try
    Main;
  except
    on E: Exception do begin
      Writeln(E.ClassName, ': ', E.Message);
    end;
  end;
  Readln;
end.

输出:

0, 0, 0 1, 0, 0 0, 1, 0 1, 1, 0 0, 2, 0 1, 2, 0 0, 0, 1 1, 0, 1 0, 1, 1 1, 1, 1 0, 2, 1 1, 2, 1 0, 0, 2 1, 0, 2 0, 1, 2 1, 1, 2 0, 2, 2 1、2、2 0, 0, 3 1, 0, 3 0, 1, 3 1、1、3 0、2、3 1、2、3

这种技术可以通过多种方式进行封装。例如,它可以封装在一个 for/in 枚举器中,这样可以提高可读性。

【讨论】:

    【解决方案2】:

    受大卫方法的启发,我想出了另一个解决方案:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils;
    
    procedure IterMultiDim(const NumberOfCalculationsPerDimension: TArray<Integer>; const Proc: TProc<TArray<Integer>>);
    
      function ModInc(const MaxNumber: Integer; var AValue: Integer): Boolean;
      begin
        Inc(AValue);
        if AValue>MaxNumber then
        begin
          AValue:=0;
          Result:=true; //carry
        end
        else
        begin
          Result:=false; //no carry
        end;
      end;
    
    var
      NumberOfDimensions: Integer;
      CurrentIndices: TArray<Integer>;
      i, j: Integer;
      TotalNumberOfCalculations: Integer;
      Carry: Boolean;
    begin
      NumberOfDimensions:=Length(NumberOfCalculationsPerDimension);
      SetLength(CurrentIndices, NumberOfDimensions);
      TotalNumberOfCalculations:=1;
      for i := 0 to NumberOfDimensions-1 do
      begin
        TotalNumberOfCalculations:=TotalNumberOfCalculations*NumberOfCalculationsPerDimension[i];
      end;
    
      for i := 0 to TotalNumberOfCalculations-1 do
      begin
        Proc(CurrentIndices);
        Carry:=true;
        for j := 0 to NumberOfDimensions-1 do
        begin
          if Carry then
          begin
            Carry:=ModInc(NumberOfCalculationsPerDimension[j]-1, CurrentIndices[j]);
          end;
        end;
      end;
    end;
    
    procedure WriteIntArray(Arr: TArray<Integer>);
    var
      i: Integer;
      S: string;
    begin
      S:='';
      for i := 0 to High(Arr) do
      begin
        S:=S+Arr[i].ToString+'  ';
      end;
      Writeln(S);
    end;
    
    procedure Main;
    begin
      IterMultiDim([3, 4, 2], WriteIntArray);
    end;
    
    begin
      try
        Main;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      Readln;
    end.
    

    【讨论】:

    • 您可以通过多种不同的方式编写此代码。你现在知道如何解决你的问题了。我们还没有完成吗?
    • 是的,我们完成了。我只是要求 cmets 以防一种方法比另一种方法具有优势。谢谢。
    • 这不是代码审查网站。两种实现之间的明显区别在于您的实现更冗长。
    猜你喜欢
    • 2013-04-01
    • 1970-01-01
    • 2019-12-18
    • 2012-04-21
    相关资源
    最近更新 更多