Tablica wskaźników jako tablica zmiennych w parametrze funkcji

0
type
  TGrid = array of Integer;
  PGrid = array of ^Integer;

procedure Foo(Grid: TGrid);
begin
...
end;

Czy da się, ale bez pośrednich konwersji, zrobić tak, żeby procedura Foo przyjęła jako parametr typ PGrid (tablicę wskaźników wskazujących na Integer), zamiast zwykłego TGrid (zawierającego po prostu integery)? Mam funkcję, która obrabia dane z tablic (operacje macierzowe) i czasami potrzebuję pracować na zwykłej tablicy (np. inicjowanej w locie), a czasami na tablicy stanowiącej "zlepek" danych wyciąganych z różnych zmiennych, należących do innych obiektów (a operacja macierzowa wykonana na tablicy Grid musi także aktualizować dane w tych zmiennych, stąd wskaźniki).

Wiem, że mógłby sobie napisać pomocniczą funkcję i konwertować PGrid na TGrid, następnie dokonywać operacji na TGrid i po zakończeniu, ponownie zawartość TGrid przenosić do PGrid, ale to strasznie dużo kopiowania.

Ewentualnie przychodzi mi do głowy napisanie sobie przeładowanej, alternatywnej funkcji, która zamiast TGrid, jako parametr będzie przyjmowała PGrid, ale ta funkcja to spory blok kodu i nie chciałbym go dublować (ciągle go aktualizuję i nie chcę tego robić w 2 miejscach jednocześnie).

Mam jeszcze jakieś inne wyjście?

2
Crow napisał(a):

Czy da się, ale bez pośrednich konwersji, zrobić tak, żeby procedura Foo przyjęła jako parametr typ PGrid (tablicę wskaźników wskazujących na Integer), zamiast zwykłego TGrid (zawierającego po prostu integery)?

No ale co chcesz przerobić? Masz procedurę, która jest zadeklarowana w taki sposób, że jako parametr przyjmuje wyłącznie dynamiczną macierz liczb, więc tylko taką macierz możesz do niej przekazać.

Poza tym Twój przykład procedury Foo przedstawia sposób przekazywania macierzy najwolniejszy ze wszystkich możliwych. Jeśli zależy Ci na efektywności, to zawsze przekazuj macierze przez referencję (ciągi znaków i duże struktury również). Z dowolnym modyfikatorem – const, var lub out – bo każdy z nich oznacza wewnętrzne przekazanie nie całego bloku danych, a wskaźnika na taki blok. Parametr bez żadnego modyfikatora to przekazanie kopii danych, a im więcej tych danych, tym dłużej trwa ich klonowanie.


Wracając do problemu – przepisz kod obecnej procedury (tej która przyjmuje całą macierz w parametrze) na taką, która przyjmuje w nim wskaźnik na macierz i na tym wskaźniku operuje. Dzięki temu będziesz mógł z niej skorzystać zarówno mając wskaźnik na macierz (po prostu go podając w parametrze), ale też mając tablicę (podając w parametrze adres tej tablicy, korzystając z operatora @).

Dodatkowo, jeśli koniecznie potrzebujesz mieć procedurkę pobierającą całą macierz (a nie jej wskaźnik), to napisz sobie wrapper na tę pobierającą wskaźnik. Przykład niżej:

type
  TMyArray = array of Integer;
  PMyArray = ^TMyArray;

  // właściwa procedura przetwarzająca macierz na podstawie wskaźnika
  procedure DoSomeStuff(AArray: PMyArray); overload;
  // wrapper na tę właściwą, operujący na macierzy
  procedure DoSomeStuff(var AArray: TMyArray); overload;

{..}

  procedure DoSomeStuff(AArray: PMyArray);
  begin
    // operacje na macierzy
  end;

  procedure DoSomeStuff(var AArray: TMyArray);
  begin
    DoSomeStuff(@AArray); // wywołanie właściwej procedury
  end;

A jeśli już koniecznie musisz mieć tylko jedną procedurkę, która przyjmuje macierz a nie wskaźnik, to też możesz z niej skorzystać, nawet jeśli masz tylko wskaźnik do macierzy. Przykład:

type
  TMyArray = array of Integer;
  PMyArray = ^TMyArray;

  procedure DoSomeStuff(var AArary: TMyArray);
  var
    Value: Integer;
  begin
    for Value in AArary do
      Write(Value:2);

    WriteLn();
  end;

var
  MyArray: TMyArray;
  PtArray: PMyArray;
begin
  MyArray := TMyArray.Create(0, 2, 4, 8); // utworzenie macierzy
  PtArray := @MyArray; // pobranie adresu do zmiennej wskaźnikowej

  {..}

  DoSomeStuff(PtArray^); // przekazanie macierzy za pomocą wskaźnika
  ReadLn();
end.

Testowane we Free Pascalu – nie wiem czy Delphi na to pozwala (powinno).


Przy okazji:

type
  TGrid = array of Integer;
  PGrid = array of ^Integer;

Tak się nie tworzy wskaźnika na macierz. Twój PGrid w dalszym ciągu jest zwykłą macierzą dynamiczną, ale zamiast przechowywać liczby, przechowuje wskaźniki na liczby. Z tego względu po pierwsze będziesz musiał ręcznie ustalać rozmiar mecierzy (w końcu jest dynamiczną), a po drugie, będziesz musiał ręcznie alokować i dealokować pamięć dla każdej jej komórki. Obsługa takiej macierzy będzie trudna – niepotrzebnie komplikujesz sobie robotę.

Z nazewnictwa tych dwóch typów wnioskuję, że chcesz mieć wskaźnik na macierz, a nie macierz wskaźników, dlatego zobacz w przykładach wyżej jak powinna wyglądać deklaracja tych typów.

0
furious programming napisał(a):

Przy okazji:

type
  TGrid = array of Integer;
  PGrid = array of ^Integer;

Tak się nie tworzy wskaźnika na macierz. Twój PGrid w dalszym ciągu jest zwykłą macierzą dynamiczną, ale zamiast przechowywać liczby, przechowuje wskaźniki na liczby. Z tego względu po pierwsze będziesz musiał ręcznie ustalać rozmiar mecierzy (w końcu jest dynamiczną), a po drugie, będziesz musiał ręcznie alokować i dealokować pamięć dla każdej jej komórki. Obsługa takiej macierzy będzie trudna – niepotrzebnie komplikujesz sobie robotę.

Z nazewnictwa tych dwóch typów wnioskuję, że chcesz mieć wskaźnik na macierz, a nie macierz wskaźników, dlatego zobacz w przykładach wyżej jak powinna wyglądać deklaracja tych typów.

Wiem, że tak się nie robi wskaźnika na macierz, ale robię tak dlatego, że zwykły wskaźnik na macierz nie wystarczy :).
Weźmy coś takiego (to tylko uproszczony przykład):

type
  TWiersz = array of Integer;
  TMacierz = array of TWiersz
  PMacierz = array of ^Wiersz;

var
  Lista1, Lista2, Lista3: TMacierz;
  WskLista: PMacierz;
...

begin
  //jakiś kod
  WskLista[0]:= @Lista1[123];
  WskLista[1]:= @Lista3[56];
  WskLista[2]:= @Lista2[973];
end;

No i mając takiego "składaka" jak WskLista, chcę wykonywać na nim operacje macierzowe tak, żeby odpowiednie wiersze w Lista1, Lista2 i Lista3 też zostały zmodyfikowane.

1

No nie za bardzo da się to zrobić, dlatego że TMacierz jest macierzą macierzy, a PMacierz jest macierzą wskaźników na macierze. Upraszczając wyjaśnienie, pierwszy typ danych nie używa wskaźników, a drugi używa – nie da się ich pogodzić i wykorzystać w jednej procedurze, bo nie da się zadeklarować jej parametru w taki sposób, aby mogła przyjąć dane obu typów. Tzn. zadeklarować się da (jako ogólny pointer lub za pomocą sztuczek), ale jednym kodem obsłużyć już nie.

Pomyślę jeszcze o tym, bo być może zaplątałem się w tych typach danych, choć na ten moment nie mam pomysłu.

0
furious programming napisał(a):

No nie za bardzo da się to zrobić, dlatego że TMacierz jest macierzą macierzy, a PMacierz jest macierzą wskaźników na macierze. Upraszczając wyjaśnienie, pierwszy typ danych nie używa wskaźników, a drugi używa – nie da się ich pogodzić i wykorzystać w jednej procedurze, bo nie da się zadeklarować jej parametru w taki sposób, aby mogła przyjąć dane obu typów. Tzn. zadeklarować się da (jako ogólny pointer lub za pomocą sztuczek), ale jednym kodem obsłużyć już nie.

Pomyślę jeszcze o tym, bo być może zaplątałem się w tych typach danych, choć na ten moment nie mam pomysłu.

No dobra, to sobie jakoś obejdę (mam pewną koncepcję jak inaczej przechowywać dane), ale nawiązując do innej, poruszonej przez ciebie kwestii - szybkości obróbki.

Mam funkcję do mnożenia macierzy:

function Multiply(Matrix1, Matrix2): TMatrix;
begin
  //mnoży macierze
end;

Dzięki temu mogę w jednej linijce kodu i bez przechowywania danych po drodze w chwilowych zmiennych, robić coś takiego:

var
  Matrix1, Matrix2, Matrix3:TMatrix 

begin
  Matrix1:= Multiply(Matrix3, Multiply(Multiply(Matrix2, Rotation), Multiply(Matrix3, Scale)));
end;

//Rotation i Scale to funkcje, które zwracają odpowiednie macierze (TMatrix)

Oczywiście to sporo kopiowania danych, ale czy da się to zrobić bardziej wydajnie?

0
Crow napisał(a):

Mam funkcję do mnożenia macierzy:

function Multiply(Matrix1, Matrix2): TMatrix;
begin
  //mnoży macierze
end;

[…]

Oczywiście to sporo kopiowania danych, ale czy da się to zrobić bardziej wydajnie?

Jeśli macierze podawane w parametrach służą wyłącznie do odczytu, a dokładniej, jeśli nie modyfikujesz wartości tych parametrów wewnątrz funkcji Multiply, to najszybszym możliwym rozwiązaniem jest przekazanie tych macierzy przez referencję, np. za pomocą const:

function Multiply(const Matrix1, Matrix2: TMatrix): TMatrix;
begin
  //mnoży macierze
end;

Dzięki temu nie będą one kopiowane przed wywołaniem tej funkcji, co przyspieszy działanie programu. Nie napiszę Ci o ile, bo to zależy od wielkości tych macierzy – im większe, tym więcej czasu się oszczędzi. Mnożenie macierzy nie wymaga modyfikacji ich zawartości, więc const to w tym przypadku mus.


Natomiast druga sprawa to już implementacja funkcji Multiply, a tej nie widziałem, więc nie wiem czy można coś poprawić czy nie. Pierwsza rzecz to alokacja pamięci – rozmiar macierzy wyjściowej ustal raz i wypełniaj ją danymi. Nie wołaj SetLength wielokrotnie gdzieś wewnątrz pętli, a już na pewno nie za każdym razem jeśli trzeba dodać liczbę do macierzy. Każde takie wywołanie oznacza relokację całego bloku, co znów drastycznie wpłynie na efektywność. Nie wiem czy tak robisz, ale jeśli tak to zmień kod.

0

Używam dosyć prostej funkcji, coś takiego:

type
  TGrid = array of Double;

function Multiply(const pMatrixL, pMatrixT: TGrid): TGrid;
var
  R, C, I, RowCount, ColCount: Integer;
begin
  RowCount:= Length(pMatrixL);
  ColCount:= Length(pMatrixT[0]);
  SetLength(Result, RowCount);
  for R:= 0 to RowCount - 1 do
    begin
      SetLength(Result[R], ColCount);
      for C:= 0 to ColCount - 1 do
        for I:= 0 to ColCount - 1 do
          Result[R][C]:= Result[R][C] + (pMatrixL[R][I] * pMatrixT[I][C]);
    end;
end;

Wydaje mi się, że szybciej się chyba nie da.

0

Upraszczając wyjaśnienie, pierwszy typ danych nie używa wskaźników, a drugi używa – nie da się ich pogodzić i wykorzystać w jednej procedurze, bo nie da się zadeklarować jej parametru w taki sposób, aby mogła przyjąć dane obu typów.

Tak uwaga odnośnie powyższego fragmentu. Wprawdzie nie do końca dotyczy to zadanego pytania, ale może w jakiś sposób ta informacja/porada przyda się pytającemu. Jeśli nie kojarzysz takiego hasła jak przeciążanie funkcji/overloading to rzuć okiem na poniższe linki, może Ci się przydadzą/zainspirują do znalezienia problemu :
http://gadamzkompem.blox.pl/2016/12/free-pascal-przeciazanie-funkcji.html
LUB
https://pl.m.wikipedia.org/wiki/Przeci%C4%85%C5%BCanie_funkcji

0

@Crow: jeśli te macierze nie mają milionów komórek i nie zależy Ci na oszczędzeniu każdej mikrosekundy, to obecny kod może zostać – jest wystarczająco szybki i czytelny.

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