2013-04-13 17 views
10

mảng Passing (động hoặc tĩnh) với các phương pháp/thủ tục/chức năng với open array parameters, khai có thể trông như thế này:Đi qua lát một mảng tĩnh/động bằng cách tham chiếu với điểm bắt đầu và chiều dài specifier

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; 

mảng Đi qua như thế này là một thành ngữ rất phổ biến được sử dụng trong suốt RTL Delphi, bao gồm một số hàm/thủ tục được tối ưu hóa để xử lý mảng dữ liệu.


Giả sử chúng ta cần gọi WorkWithArray với một dãy ô của chúng tôi. Sau đó chúng ta có thể sử dụng hàm nội tại Slice().

đầu tiên mà không có một bù đắp, bắt đầu với chỉ số đầu tiên:

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

Lưu ý: mảng động không phù hợp trực tiếp vào Slice() chức năng, thấy "Slice does not work with dynamic arrays". Vì vậy, phương pháp workaround với đúc kiểu phải được sử dụng.


Điều gì sẽ xảy ra nếu chúng ta muốn làm việc với một phân đoạn không bắt đầu từ phần tử đầu tiên?

doable cũng như:

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

Lưu ý: tổng các bù đắp và lát không được vượt quá số phần tử của mảng.

Tôi biết rằng việc sử dụng Sao chép (myArray, x1, x2) thể được sử dụng trong trường hợp các đầu vào được khai báo là const, nhưng điều này sẽ tạo một bản sao của mảng, và là ineffiecient cho mảng lớn. (Với nguy cơ tràn ngăn xếp là tốt).


Cuối cùng, câu hỏi của tôi:

Trong khi điều này chứng tỏ một cách để vượt qua một subrange của một mảng bằng cách tham khảo với một chỉ số bắt đầu và chiều dài specifier, có vẻ một chút vụng về. Có lựa chọn thay thế tốt hơn và nếu có thì làm cách nào?

Trả lời

7

Đã cập nhật Xem phần giải thích cho giải pháp generics.

Dưới đây là một giải pháp thay thế đóng gói kiểu cần thiết cho bù đắp bên trong một hàm, nằm trong bản ghi nâng cao được khai báo là hàm lớp. Bên cạnh việc ẩn kiểu truyền, khoảng bù là phạm vi được kiểm tra so với chỉ số cao của mảng.

Có thể thêm các loại khác nếu cần.

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; 

Lưu ý: tổng số tiền chênh lệch và lát không được vượt quá số phần tử của mảng.

Ví dụ gọi:

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

Trong khi điều này có vẻ tốt hơn, tôi vẫn không thuyết phục này là giải pháp tối ưu.


Cập nhật

Khi viết các giải pháp trên, tôi đã có một giải pháp Generics là mục tiêu cuối cùng.

Dưới đây là câu trả lời sử dụng các phương pháp ẩn danh và generics để triển khai phương pháp Slice(anArray,startIndex,Count) có thể được sử dụng với cả mảng tĩnh và động.

Một giải pháp generics thẳng sẽ dựa vào việc kiểm tra phạm vi được tắt ở mọi nơi được sử dụng, và đó sẽ không phải là một giải pháp khá. Lý do là SizeOf(T) không thể được sử dụng để khai báo một kiểu mảng tĩnh của kích thước tối đa:

TGenericArray = array [0..MaxInt div sizeof (T) - 1] T; // sizeof (T) không được giải quyết

Vì vậy, chúng ta sẽ phải sử dụng:

TGenericArray = array [0..0] T; Thay vào đó,

. Và điều này gây nên việc kiểm tra phạm vi khi nó được bật, cho index> 0.

Giải pháp

Nhưng vấn đề có thể được giải quyết bằng chiến lược khác, callbacks hoặc một thuật ngữ hiện đại hơn sẽ Inversion of Control (IoC) hoặc Dependeny Injection (DI). Khái niệm này được giải thích tốt nhất, "Đừng gọi tôi, chúng tôi gọi cho bạn".

Thay vì sử dụng chức năng trực tiếp, chúng tôi chuyển mã hoạt động dưới dạng một phương thức ẩn danh cùng với tất cả các tham số. Bây giờ vấn đề kiểm tra phạm vi được chứa trong khung 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. 

Dưới đây là một số trường hợp ví dụ sử dụng:

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. 
+0

Tại sao không đơn giản triển khai lại 'Slice' để chấp nhận mảng động? – user3764855

+0

@ user3764855, 'Slice' là một hàm ma thuật (nội tại) được trình biên dịch chèn vào, chứ không phải là hàm bình thường để quá tải hoặc thay đổi theo từng phần. –

+0

Bạn không thể trả lại con trỏ cho mảng thay vì sử dụng các phương thức nặc danh này. – user3764855

0

Làm thế nào về tránh mảng mở và lát và sử dụng một cái gì đó như thế này?

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