Parametrem variant

0

Witam

często w kodzie gdzie używam tablic dynamicznych mam taki zapis gdzie dodaję nowy element do tablicy a potem zapisuję w nim dane:


type Trec = record
            x,y:integer;
            s:string;
            end;
var

idx:integer;
t:array of Trec

setlength(t,length(t)+1);
idx:=length(t)-1

t[idx].s:='text';
t[idx].x:=1;
t[idx].y:=2;

Chciałbym uprościć te dwie linie :

setlength(t,length(t)+1);
idx:=length(t)-1

do jednej np. :

idx:=AddArrayRec(t);

Funkcja może przyjmować jako parametr zmienną która jest tablicą dynamiczną dowolnego typu - jako przykład Setlength :)
Chciałbym zadeklarować funkcję / makro (ale Delphi nie ma makr - chyba) na kształt :

Function AddArrayRec(var a:genericarray):integer; inlinie;
begin
setlength(a,length(a)+1);
result:=length(a)-1
end;
3

Aby zadeklarowanie funkcji było możliwe dla tablicy itemów typu TRec, powinieneś mieć zadeklarowany typ takie macierzy i użyć go w nagłówku funkcji:

type
  TRec = record
    X: Integer;
    Y: Integer;
    S: String;
  end;

type
  TRecArr = array of TRec;

Potem możesz sobie zadeklarować funkcję:

function RecArrAppend(var AArr: TRecArr; const ARec: TRec): Integer; inline;
var
  Size: Integer;
begin
  Size := Length(AArr);
  
  SetLength(AArr, Size + 1);
  AArr[Size] := ARec;
  
  Result := Size;
end;

Ewentualnie możesz też skorzystać z macierzy otwartych, bez deklaracji typu macierzowego:

function RecArrAppend(var AArr: array of TRec; const ARec: TRec): Integer; inline;

Jeśli potrzebujesz czegoś takiego dla dowolnych macierzy i itemów, spróbuj wykorzystać generyki. Mimo wszystko coś takiego nie jest dobrym rozwiązaniem, bo każdorazowe dodanie itemka do tablicy bardzo często spowoduje relokację bloku pamięci, a więc wydajność będzie niska.


To jest jeden z przykładów, dla których nie używam macierzy o dynamicznym rozmiarze, a także wudowanych generyków, bo tylko komplikują składnię, a w rzeczywistości i tak mają ograniczenia co do typów danych.

Obecnie pracuję nad projektem gry, pisanym czysto proceduralnie, w którym dane trzymane są w prostych strukturach. Zamiast macierzy używam instancji własnoręcznie zaimplementowanej listy (oraz kolejki i stosu). Daje mi to znacznie więcej możliwości, w porównaniu do macierzy dynamicznych:

  • mogę przechowywać w listach dane dowolnych typów (dane proste i całe struktury), a więc pełna generyczność,
  • ograniczam ilość operacji na pamięci, bo:
    • listę tworzę z jednoczesną alokacją pamięci dla kilku itemów,
    • pamięć zawsze alokuję z zapasem dla kolejnych itemów (wykładniczo), dzięki czemu dodanie itema rzadko kiedy wymaga relokacji bloku listy,
    • usuwanie itemów nie powoduje relokacji całego bloku danych itemów,
    • dodatkowo są funkcje do szybkiego wstawiania i usuwania itemów, nie wymagające w ogóle relokacji pamięci ani przesuwania całych (dużych) bloków pamięci itemów (wzamian zmienia się kolejność itemów w liście),
    • jeśli potrzebuję iterować po całej liście, odkrywam wskaźniki na pierwszy i ostatni element w buforze listy i na ich podstawie poruszam się po liscie, co jest szybsze niż iterowanie po tablicach za pomocą indeksów.
    • jeśli potrzebuję odczytać jakieś dane konkretnego itema, zwracam jego wskaźnik, zamiast kopiować paczkę jego danych.
    • wydajność jest bardzo wysoka, ze względu na rzadkość alokacji/relokacji pamięci listy.
  • w dowolnym momencie mogę zmienić zachowanie listy, tak aby wyciągnąć jak najwyższą wydajność lub rozszerzyć jej funkcjonalność.

To jest nieco inne podejście, daje naprawdę sporo korzyści i zapewnia wysoką wydajność, jednak kosztem nastukania ~200 linijek kodu i wyższej trudności obsługi takiego kontenerka. Troszkę ograniczeń też jest, ale to i tak nic w porównaniu do ilości zalet.

0

Lepszy TDictionary do tego

0

Od Delphi XE7 jest o wiele fajniejsza konstrukcja dodawania do tablicy dynamicznej.

var 
rec: Trec;
t:array of Trec

// 
 rec.X := 1;
 rec.Y := 1;
 rec.S := 'test';

 t := t + [rec]

Edit:
Znalazłem https://blog.marcocantu.com/blog/2014_september_dynamic_arrays_delphixe7.html

0

Dziękuję za odpowiedzi ..... jednak żadna nie była odpowiedzią na moje pytanie - wnioskuję zatem że się nie da.

2
Andrzej Gicala napisał(a):

Dziękuję za odpowiedzi ..... jednak żadna nie była odpowiedzią na moje pytanie - wnioskuję zatem że się nie da.

Ależ da się ! O czym świadczą powyższe posty.

0

Jeśli funkcja ma móc przyjmować dowolne tablice dynamiczne o dowolnym typie przechowywanych danych, to pozostają albo generyki, albo wskaźniki. Przy czym nie jestem pewny co do generyków — musiałbym się pobawić.

Ja na swoje potrzeby wybrałem oczywiście wskaźniki, a zamiast macierzy dynamicznych (które posysają), zaimplementowałem własną listę, która może przechowywać dane absolutnie dowolnego typu i przy okazji która ma wydajność porównywalną do macierzy, a niekiedy nawet wyższą. Po to pisałem w swoim pierwszym poście o tym, abyś z tego skorzystał.

0
Robert Karpiński napisał(a):
Andrzej Gicala napisał(a):

Dziękuję za odpowiedzi ..... jednak żadna nie była odpowiedzią na moje pytanie - wnioskuję zatem że się nie da.

Ależ da się ! O czym świadczą powyższe posty.

Wynikowe pytanie z mojego posta (które może powinienem zadać explicite):
Czy można zdefiniować procedurę / funkcję przyjmującą jako parametr tablicę dynamiczną o elementach dowolnego typu - jak robi to np. procedura wbudowana SetLength.

Nie chciałem innego rozwiązania podanego przykładu.
Nie jest to przytyk do nikogo personalnie ale na wielu forach odpowiadający na pytania omijają odpowiedź wprost tylko :
(proszę to czytać z uśmiechem)

  • podają jak zrobić to samo inaczej

  • podają rozwiązanie innego problemu

  • zaczynają analizować problem i zastanawiają się po co go w ogóle rozwiązywać

    Pozdrawiam :)

0
procedure foo<T>(arr:array of T);
var val:T;
begin
    for val in arr do WriteLn(val);
end;

foo<string>(['13','666']);
0
furious programming napisał(a):

Jeśli funkcja ma móc przyjmować dowolne tablice dynamiczne o dowolnym typie przechowywanych danych, to pozostają albo generyki, albo wskaźniki. Przy czym nie jestem pewny co do generyków — musiałbym się pobawić.

Ja na swoje potrzeby wybrałem oczywiście wskaźniki, a zamiast macierzy dynamicznych (które posysają), zaimplementowałem własną listę, która może przechowywać dane absolutnie dowolnego typu i przy okazji która ma wydajność porównywalną do macierzy, a niekiedy nawet wyższą. Po to pisałem w swoim pierwszym poście o tym, abyś z tego skorzystał.

Dziękuję za te informacje - pewnie w razie potrzeby użycia większych tablic tak będę robił - ale ja piszę o przypadku kiedy deklaruję dyn tablice żeby przechować 5-10 elementów bez żadnych obostrzeń wydajnościowych - to się nie opłaca pisać tyle kodu.

0
_13th_Dragon napisał(a):
procedure foo<T>(arr:array of T);
var val:T;
begin
    for val in arr do WriteLn(val);
end;

foo<string>(['13','666']);
> Function IncArray<T>(arr:array of T):integer;
> begin
>    setlength(a,length(a)+1);
>    result:=high(a);
> end;

Fajnie by było - ale XE7 mówi o lini 1:
[dcc32 Error] DHelper.pas(27): E2530 Type parameters not allowed on global procedure or function

2
Andrzej Gicala napisał(a):
> Function IncArray<T>(arr:array of T):integer;
> begin
>    setlength(a,length(a)+1);
>    result:=high(a);
> end;

Parametr tej funkcji to arr, a Ty używasz a.


Sprawdziłem jak to wygląda we Free Pascalu, czy da się to zrobić z generykami:

{$MODE DELPHI}{$LONGSTRINGS ON}

  function ArrayAdd<T>(var AArray: array of T; const AItem: T): Integer;
  begin
    Result := Length(AArray);
    SetLength(AArray, Result + 1); // Error: Type mismatch
    AArray[Result] := AItem;
  end;

  procedure ArrayPrint<T>(const ATitle: String; const AArray: array of T);
  var
    Item: T;
  begin
    for Item in AArray do
      Write(Item, ' ');

    WriteLn();
  end;

var
  NumArray: array of Integer;
  StrArray: array of String;
begin
  NumArray := [0, 1, 2];
  StrArray := ['foo', 'bar', 'baz'];

  ArrayPrint<Integer>('NumArray before: ', NumArray);
  ArrayPrint<String>('StrArray before: ', StrArray);

  ArrayAdd<Integer>(NumArray, 9);
  ArrayAdd<String>(StrArray, 'last');

  ArrayPrint<Integer>('NumArray after: ', NumArray);
  ArrayPrint<String>('StrArray after: ', StrArray);

  ReadLn();
end.

Dostaję błąd project1.lpr(6,5) Error: Type mismatch w linijce z SetLength. Spróbowałem z {$MODE OBJFPC} i dodałem słówka generic oraz specialize, aby dopasować kod do składni tego trybu — ten sam błąd. Reszta kodu działa poprawnie, w tym procedura ArrayPrint (poprawnie wyświetla zawartość macierzy). Umie ktoś to naprawić?

Ehh, a wy się dziwicie, że mam w dupie generyki i inne składniowe zabawki i że jadę niskopoziomowo na pointerach. :D

2
furious programming napisał(a):

Umie ktoś to naprawić?

Owszem:

{$MODE DELPHI}{$LONGSTRINGS ON}

  type TArrayOf<T>=array of T;

  function ArrayAdd<T>(var AArray: TArrayOf<T>; const AItem: T): Integer;
  begin
    Result := Length(AArray);
    SetLength(AArray, Result + 1); // Error: Type mismatch
    AArray[Result] := AItem;
  end;

  procedure ArrayPrint<T>(const ATitle: String; const AArray: array of T);
  var
    Item: T;
  begin
    for Item in AArray do
      Write(Item, ' ');

    WriteLn();
  end;

var
  NumArray: array of Integer;
  StrArray: array of String;
begin
  NumArray := [0, 1, 2];
  StrArray := ['foo', 'bar', 'baz'];

  ArrayPrint<Integer>('NumArray before: ', NumArray);
  ArrayPrint<String>('StrArray before: ', StrArray);

  ArrayAdd<Integer>(NumArray, 9);
  ArrayAdd<String>(StrArray, 'last');

  ArrayPrint<Integer>('NumArray after: ', NumArray);
  ArrayPrint<String>('StrArray after: ', StrArray);

  ReadLn();
end.
1

Wersja dla {$MODE OBJFPC} niżej:

{$MODE OBJFPC}{$LONGSTRINGS ON}

type
  generic TArrayOf<T> = array of T;

  generic function ArrayAdd<T>(var AArray: specialize TArrayOf<T>; const AItem: T): Integer;
  begin
    Result := Length(AArray);
    SetLength(AArray, Result + 1);
    AArray[Result] := AItem;
  end;

  generic procedure ArrayPrint<T>(const ATitle: String; const AArray: array of T);
  var
    Item: T;
  begin
    Write(ATitle:20);
  
    for Item in AArray do
      Write(Item, ' ');

    WriteLn();
  end;

var
  NumArray: array of Integer;
  StrArray: array of String;
begin
  NumArray := [0, 1, 2];
  StrArray := ['foo', 'bar', 'baz'];

  specialize ArrayPrint<Integer>('NumArray before: ', NumArray);
  specialize ArrayPrint<String>('StrArray before: ', StrArray);

  specialize ArrayAdd<Integer>(NumArray, 9);
  specialize ArrayAdd<String>(StrArray, 'last');

  specialize ArrayPrint<Integer>('NumArray after: ', NumArray);
  specialize ArrayPrint<String>('StrArray after: ', StrArray);

  ReadLn();
end.

Trochę więcej cudów, ale też się da. Wyjście:

   NumArray before: 0 1 2
   StrArray before: foo bar baz
    NumArray after: 0 1 2 9
    StrArray after: foo bar baz last

Czyli śmiga.

1 użytkowników online, w tym zalogowanych: 0, gości: 1