arrays - 通过带有起始和长度说明符的引用传递静态/动态数组的切片

标签 arrays delphi slice

使用 open array parameters 将数组(动态或静态)传递给方法/过程/函数, 声明可以如下所示:

procedure WorkWithArray( const anArray : array of Integer);
(* or procedure WorkWithArray( var anArray : array of Integer); *)
var
  i : Integer;
begin
  for i := Low(anArray) to High(anArray) do
  begin
    // Do something with the "open array" anArray
    WriteLn(anArray[i]);
  end;
end;

...
var
  staticArray : array[0..2] of Integer;
  dynArray : array of integer;
  dynArrayG : TArray<Integer>;
begin
  SetLength(dynArray,10);
  SetLength(dynArrayG,10);

  WorkWithArray(staticArray);  // Using a static array
  WorkWithArray(dynArray);     // Using a dynamic array
  WorkWithArray(dynArrayG);    // Using a dynamic generic array
  ...
end;

像这样传递数组是整个 Delphi RTL 中非常常见的习惯用法,包括一些用于处理数据数组的非常优化的函数/过程。

<小时/>

假设我们需要使用数组的子范围调用WorkWithArray。然后我们可以使用内在的 Slice()功能。

首先没有偏移量,从第一个索引开始:

Type
  // Helper declarations
  TIntLongArray = array[0..MaxInt div SizeOf(Integer) - 1] of integer;
  PIntLongArray = ^TIntLongArray;

WorkWithArray(Slice(staticArray,2)); // No type cast needed for static arrays
WorkWithArray(Slice(PIntLongArray(@dynArray)^,2));
WorkWithArray(Slice(PIntLongArray(@dynArrayG)^,2));

注意:动态数组不能直接放入 Slice() 函数中, 请参阅"Slice does not work with dynamic arrays" 。 因此必须使用类型转换的解决方法。

<小时/>

如果我们想要使用不从第一个元素开始的子范围怎么办?

也可行:

WorkWithArray(Slice(PIntLongArray(@staticArray[1])^,2));
WorkWithArray(Slice(PIntLongArray(@dynArray[1])^,2));
WorkWithArray(Slice(PIntLongArray(@dynArrayG[1])^,2));

注意:偏移量和切片的总和不得超过数组的元素数。

I know that using Copy(myArray,x1,x2) could be used in cases where the input is declared as a const, but this will make a copy of the the array, and is ineffiecient for large arrays. (With risk of stack overflow as well).

<小时/>

最后,我的问题:

虽然这演示了一种使用起始索引和长度说明符通过引用传递数组子范围的方法, 看起来有点尴尬。 有更好的选择吗?如果有的话如何选择?

最佳答案

已更新请参阅泛型解决方案的一些内容。

这是一个替代方案,它将偏移量所需的类型转换封装在函数内,该函数驻留在声明为类函数的高级记录中。 除了隐藏类型转换之外,还根据数组的高索引检查偏移量的范围。

如果需要,可以添加更多类型。

Type
  SubRange = record
    Type
      TIntLongArray = array[0..MaxInt div SizeOf(Integer) - 1] of integer;
      PIntLongArray = ^TIntLongArray;
      TByteLongArray = array[0..MaxInt div SizeOf(Byte) - 1] of Byte;
      PByteLongArray = ^TByteLongArray;

    class function Offset( const anArray : array of Integer; 
                                 offset  : Integer) : PIntLongArray; overload; static;
    class function Offset( const anArray : array of Byte; 
                                 offset  : Integer) : PByteLongArray; overload; static;
    // ToDo: Add more types ...
  end;

class function SubRange.Offset(const anArray : array of Integer; 
                                     offset  : Integer): PIntLongArray;
begin
  Assert(offset <= High(anArray));
  Result := PIntLongArray(@anArray[offset]);
end;

class function SubRange.Offset(const anArray : array of Byte; 
                                     offset  : Integer): PByteLongArray;
begin
  Assert(offset <= High(anArray));
  Result := PByteLongArray(@anArray[offset]);
end;

注意:偏移量和切片的总和不得超过数组的元素数。

调用示例:

WorkWithArray( Slice(SubRange.Offset(staticArray,1)^,2));
WorkWithArray( Slice(SubRange.Offset(dynArray,1)^,2));
WorkWithArray( Slice(SubRange.Offset(dynArrayG,1)^,2));

虽然这看起来更好,但我仍然不相信这是最佳解决方案。

<小时/>

更新

在编写上述解决方案时,我的最终目标是泛型解决方案。

这是一个利用匿名方法和泛型来实现 Slice(anArray,startIndex,Count) 的答案。 可用于静态和动态数组的方法。

直接的泛型解决方案将依赖于在每个使用它的地方关闭范围检查, 这不是一个很好的解决方案。 原因是SizeOf(T)无法用于声明最大大小的静态数组类型:

TGenericArray = array[0..MaxInt div SizeOf(T) - 1] of T; // SizeOf(T) not resolved

所以我们必须使用:

TGenericArray = array[0..0] of T;

相反。当索引 > 0 时,这会触发范围检查。

解决方案

但是这个问题可以通过另一种策略来解决,callbacks或者更现代的术语是 Inversion of Control (国际奥委会)或Dependeny Injection (DI)。 这个概念最好的解释是“不要调用我,我们调用你”。

我们不使用直接函数,而是将操作代码作为匿名方法与所有参数一起传递。 现在范围检查问题包含在 Slice<T> 中框架。

Slice<Integer>.Execute(
  procedure(const arr: array of Integer)
  begin
    WriteLn(Math.SumInt(arr));
  end, dArr, 2, 7);
<小时/>
unit uGenericSlice;

interface

type
  Slice<T> = record
  private
    type
      PGenericArr = ^TGenericArr;
      TGenericArr = array [0..0] of T;
  public
    type
      TConstArrProc = reference to procedure(const anArr: array of T);
    class procedure Execute(       aProc: TConstArrProc;
                             const anArray: array of T;
                                   startIndex,Count: Integer); static;
  end;

implementation

class procedure Slice<T>.Execute(aProc: TConstArrProc;
  const anArray: array of T; startIndex, Count: Integer);
begin
  if (startIndex <= 0) then
    aProc(Slice(anArray, Count))
  else
  begin
    // The expression PGenericArr(@anArray[startIndex]) can trigger range check error
    {$IFOPT R+}
      {$DEFINE RestoreRangeCheck}
      {$R-}
    {$ENDIF}
    Assert((startIndex <= High(anArray)) and (Count <= High(anArray)-startIndex+1),
      'Range check error');
    aProc(Slice(PGenericArr(@anArray[startIndex])^, Count));
    {$IFDEF RestoreRangeCheck}
      {$UNDEF RestoreRangeCheck}
      {$R+}
    {$ENDIF}
  end;
end;

end.

以下是一些示例用例:

program ProjectGenericSlice;

{$APPTYPE CONSOLE}

uses
  Math,
  uGenericSlice in 'uGenericSlice.pas';

function Sum(const anArr: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i in anArr do
    Result := Result + i;
end;

procedure SumTest(const arr: array of integer);
begin
  WriteLn(Sum(arr));
end;

procedure TestAll;
var
  aProc: Slice<Integer>.TConstArrProc;
  dArr: TArray<Integer>;
  mySum: Integer;
const
  sArr: array [1 .. 10] of Integer = (
    1,2,3,4,5,6,7,8,9,10);

begin
  dArr := TArray<Integer>.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

  aProc :=
    procedure(const arr: array of Integer)
    begin
      WriteLn(Sum(arr));
    end;

  // Test predefined anonymous method
  Slice<Integer>.Execute( aProc, dArr, 2, 7);

  // Test inlined anonymous method
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      WriteLn(Sum(arr));
    end, dArr, 2, 7);

  // Test call to Math.SumInt
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      WriteLn(Math.SumInt(arr));
    end, dArr, 2, 7);

  // Test static array with Low(sArr) > 0
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      WriteLn(Sum(arr));
    end, sArr, 3 - Low(sArr), 7);

  // Using a real procedure
  Slice<Integer>.Execute(
    SumTest, // Cannot be nested inside TestAll
    dArr, 2, 7);

  // Test call where result is passed to local var
  Slice<Integer>.Execute(
    procedure(const arr: array of Integer)
    begin
      mySum := Math.SumInt(arr);
    end, dArr, 2, 7);
  WriteLn(mySum);

end;

begin
  TestAll;
  ReadLn;
end.

关于arrays - 通过带有起始和长度说明符的引用传递静态/动态数组的切片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15993428/

相关文章:

Javascript:无法从循环内部修改在循环外部声明的数组

arrays - 如何在 SwiftUI 中计算数组的总计、小计和平均值

delphi - 设置 Delphi XE 使用 Vim 作为默认合并查看器

delphi - 在 Delphi 中加载同一 DLL 的两个实例

json - 使用 Echo 或 Gin 框架的大型数组的内存消耗

c - 数组随机排序

javascript - 如何在javascript中过滤数据

delphi - 弹出菜单点击源自哪个组件

arrays - 如何将IoSliceMut数组转换为Vec <u8>?

javascript - 从切片方法返回剩余的整个文本