Передача среза статического/динамического массива путем ссылки с помощью указателя начала и длины

Передача массивов (динамических или статических) в методы/процедуры/функции с помощью 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;

Передача таких массивов - это очень распространенная идиома, используемая во всем RTL Delphi, включая некоторые очень оптимизированные функции/процедуры для обработки массивов данных.


Предположим, что нам нужно вызвать 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));

Примечание: сумма смещения и среза не должна превышать количество элементов массива.

Я знаю, что использование Копировать (myArray, x1, x2) можно использовать в случаях, когда ввод объявляется как const,    но это сделает копию массива и неэффективно для больших массивов. (С риском также).


Наконец, мой вопрос:

Пока это демонстрирует способ передачи поддиапазона массива по ссылке с индексом начала и спецификатором длины, это выглядит немного неудобно. Есть ли лучшие альтернативы, и если да, то как?

Ответ 1

Обновлено. См. бит для решения generics.

Вот альтернатива, которая инкапсулирует тип, необходимый для смещения внутри функции, которая находится в расширенной записи, объявленной как функция класса. Помимо скрытия типа cast, смещение проверяется диапазоном на высокий индекс массива.

При необходимости могут быть добавлены дополнительные типы.

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));

Пока это выглядит лучше, я все еще не убежден, что это оптимальное решение.


Обновление

При написании вышеупомянутого решения у меня в качестве конечной цели было решение generics.

Вот ответ, который использует анонимные методы и обобщения для реализации Slice(anArray,startIndex,Count) метод, который может использоваться как с статическими, так и с динамическими массивами.

Решение с прямыми генериками будет полагаться на проверку диапазона, которая будет отключена на каждом месте, где она была использована, и это не будет хорошим решением. Причина в том, что SizeOf(T) не может использоваться для объявления типа статического массива максимального размера:

TGenericArray = array [0..MaxInt div SizeOf (T) - 1] of T;//SizeOf (T) не разрешено

Поэтому нам нужно будет использовать:

TGenericArray = массив [0..0] of T;

вместо этого. И это вызывает проверку диапазона, когда он включен, для индексa > 0.

Решение

Но проблема может быть решена другой стратегией, callbacks или более современной терминологией будет Inversion of Control (IoC) или 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.

Ответ 2

Как избежать открытых массивов и фрагментов и использовать что-то вроде этого?

type
   TArrayRef<T> = record
   strict private
     type PointerOfT = ^T;
     FItems: PointerOfT;
     FCount: Integer;
   public  
     // FItems := @AItems[Offset]; FCount := Count;
     constructor Create(AItems: array of T; Offset, Count: Integer);
     property Items[Index: Integer]: T read GetItem; // Exit(FItems[Index])
     property Count: Integer read FCount; 
   end;
   TArrayRef = record // helpers
     class function Create<T>(AItems: array of T; Offset, Count: Integer); static;
     class function Create<T>(AItems: array of T; Count: Integer); static;
     class function Create<T>(AItems: array of T); static;
   end; 

procedure WorkWithArray(const anArray : TArrayRef<Integer>);
var
  I: Integer;
begin
  for I := 0 to anArray.Count - 1 do WriteLn(anArray[I]);
end;

WorkWithArray(TArrayRef.Create(StaticArray, 3)); // first three items
WorkWithArray(TArrayRef.Create(DynArray, 10, 3)); 

Ответ 3

В случае, если кто-то еще был споткнулся как я. В более старых версиях Delphi (D2007 и более ранних. Не уверен насчет версий XE) вы также получите ошибки E2193, если будете использовать перегрузки:

  procedure Polygon(Points: array of TPoint); overload;
  procedure Polygon(Points: array of TDPoint); overload;

Работает, если снять перегрузку:

  procedure Polygon(Points: array of TPoint); 
  procedure PolygonD(Points: array of TDPoint);

Это исправлено в Delphi 10.3.0 (и, возможно, в других более старых версиях).