[Erledigt] Frage zu Objektliste

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

[Erledigt] Frage zu Objektliste

Beitrag von MacWomble »

A = Adresse aus Adressstamm
TempObj = Adresse in Akte (Aktenadresse)
Adressen = Liste der Aktenadressen


Code: Alles auswählen

procedure TAktenEditor.btnAdtresseZuweisenClick(Sender: TObject);
var
  A: TAdresse;
  TempObj: TAktenAdresse;
 
begin
  if ListeAdresse(A) then //Gibt A zurück
  begin
    TempObj := TAktenAdresse.Create; //???
 
    TempObj.ID := 0;
    TempObj.IDAdresse := A.iD;
    TempObj.IDAdressart := A.IDAdressArt;
    TempObj.Zeichen := 'Abc';
 
    If not Assigned(Adressen) Then
      Adressen:= TAktenAdressListe.Create;
 
    Adressen.Add(TempObj);
 
    //FreeAndNil(TempObj); // Wenn aktiv wird die neue Adresse in Adressen gelöscht.
 
    InitGridAktenAdresse;  // Hat Problem mit der neuen Adresse (leere Fehlermeldung)
 
  end;
end;


Die Frage ist nun, wie ich die Rückgabe direkt in die Adressen eintragen kann, ohne TempObj.
Wahrscheinlich sehe ich vor lauter Bäumen den Wald wieder nicht ... :oops:
Zuletzt geändert von MacWomble am Di 24. Sep 2019, 18:03, insgesamt 1-mal geändert.
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

Benutzeravatar
six1
Beiträge: 782
Registriert: Do 1. Jul 2010, 19:01

Re: Frage zu Objektliste

Beitrag von six1 »

...falls ich es richtig verstanden habe, sollte es so gehen:

Code: Alles auswählen

 
If not Assigned(Adressen) Then
      Adressen:= TAktenAdressListe.Create;
setlength(Adressen, high(Adressen)+2);
Adressen[high(Adressen)].ID := 0;
Adressen[high(Adressen)].IDAdresse := A.iD;
Adressen[high(Adressen)].IDAdressart := A.IDAdressArt;
Adressen[high(Adressen)].Zeichen := 'Abc';
 
Gruß, Michael

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Frage zu Objektliste

Beitrag von m.fuchs »

Also: ein TAktenAdresse-Objekt musst du erzeugen, weil deine Liste das erwartet. Oder haben TAktenAdresse und TAdresse eine verwandtschaftliche Beziehung?

Du kannst aber einen Constructor schreiben, der eine TAdresse übergeben bekommt. Dann ist das ein bisschen versteckter.

Code: Alles auswählen

constructor TAktenAdresse.Create(FromData: TAdresse);
begin
  inherited Create;
  Self.ID := 0;
  Self.IDAdresse := FromData.iD;
  Self.IDAdressart := FromData.IDAdressArt;
  Self.Zeichen := 'Abc';
end;
 
(* ... *)
 
procedure TAktenEditor.btnAdtresseZuweisenClick(Sender: TObject);
var
  A: TAdresse;
begin
  if ListeAdresse(A) then begin
    if not Assigned(Adressen) then
      Adressen:= TAktenAdressListe.Create;
    Adressen.Add(TAktenAdresse.Create(A));
    InitGridAktenAdresse;  // Hat Problem mit der neuen Adresse (leere Fehlermeldung)
  end;
end;
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Frage zu Objektliste

Beitrag von m.fuchs »

six1 hat geschrieben:...falls ich es richtig verstanden habe, sollte es so gehen:

Ich fürchte, du hast das falsch verstanden. Es handelt sich bei Adressen nicht um ein Array.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Frage zu Objektliste

Beitrag von wp_xyz »

Ich nehme an, TAktenAdressListe stammt von TList, TObjectList o.ä. ab. Dieser kannst du eine Methode AddAdresse mit den nötigen Parametern geben und du hast so die Erzeugung der temporären TAktenAdresse von deinem Programm in die Klasse TAktenAdressListe verlagert. Komplett ohne temporäres Objekt geht es nicht.

Code: Alles auswählen

function TAktenAdressListe.AddAdresse(ID: Integer; Zeichen: string; <... etc ...>): integer;
var
  tempObj: TAktenAdresse;
begin
  tempObj := TAktenAdresse.Create;
  tempObj.ID := ID;
  tempObj.Zeichen :=Zeichen;
  ... use
  Result := Add(tempObj);
end;
 
procedure TAktenEditor.btnAdtresseZuweisenClick(Sender: TObject);
var
  A: TAdresse;
begin
  if ListeAdresse(A) then //Gibt A zurück
  begin
    If not Assigned(Adressen) Then
      Adressen:= TAktenAdressListe.Create;
    Adressen.AddAdresse, A.ID, 'Abc', <usw...>);
    InitGridAktenAdresse; 
  end;
end;

MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

Re: Frage zu Objektliste

Beitrag von MacWomble »

Danke, ich werde das nachher versuchen. :)
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Frage zu Objektliste

Beitrag von Warf »

six1 hat geschrieben:...falls ich es richtig verstanden habe, sollte es so gehen:

Selbst wenn du es richtig verstanden hast ist das extrem unperformant, da im worst case mit jedem aufruf einmal alle addressen kopiert werden müssen. Daher sollte man falls möglich immer auf TList oder so zurückgreifen, da die geometrisches wachstum haben, und nur logarithmisch oft resizen (und damit potentiell den gesammten Array kopieren) müssen

Zum Thema:
Die Frage ist nun, wie ich die Rückgabe direkt in die Adressen eintragen kann, ohne TempObj.

Sind das records oder Klassen? Denn wenn es Klassen sind, dann scheiß drauf, dein TempObj ist nur ein pointer, d.h. nur 4 oder 8 Byte groß, und den speicher auf den es zeigt brauchst du ja eh (da ja in der liste auch nur ein Pointer steht).

Ansonsten geht auch sowas:

Code: Alles auswählen

 
if ListeAdresse(A) then //Gibt A zurück
  with Adressen[Adressen.Add(TAktenAdresse.Create)] do
  begin
    ID := 0;
    IDAdresse := A.iD;
    IDAdressart := A.IDAdressArt;
    Zeichen := 'Abc';
  end;
end;

Dann musst du nur dein

Code: Alles auswählen

 
    If not Assigned(Adressen) Then
      Adressen:= TAktenAdressListe.Create;

Irgendwo anders hin verlagern (z.B. in den Konstruktor der owner klasse, immerhin zerstörst dus wahrscheinlich ja eh im destruktor)

MacWomble
Lazarusforum e. V.
Beiträge: 999
Registriert: Do 17. Apr 2008, 01:59
OS, Lazarus, FPC: Mint 21.1 Cinnamon / FPC 3.2.2/Lazarus 2.2.4
CPU-Target: Intel i7-10750 64Bit
Wohnort: Freiburg

Re: [Erledigt] Frage zu Objektliste

Beitrag von MacWomble »

Erneuten Dank für eure Hilfe. Gelöst habe ich es letztlich nun so - funktioniert wie es soll:

in der Form-unit

Code: Alles auswählen

procedure TAktenEditor.btnAdtresseZuweisenClick(Sender: TObject);
var
  A: TAdresse;
begin
  if ListeAdresse(A) then
  begin
    if not Assigned(Adressen) then
      Adressen := TAktenAdressListe.Create;
    Adressen.AddAdresse(A);
 
    InitGridAktenAdresse;
  end;
end;   


... und in der Klasseunit

Code: Alles auswählen

function TAktenAdressListe.AddAdresse(A: TAdresse): integer;
var
  TempObj: TAktenAdresse;
  s: string;
begin
  TempObj := TAktenAdresse.Create;
  TempObj.ID := 0;
  TempObj.IDAdresse := A.iD;
  TempObj.IDAdressart := A.IDAdressArt;
  TempObj.IDColor:=A.IDColor;
  s := A.Anrede + ' ' + A.Vorname + ' ' + A.FirmaZuname + ' ' + A.Firma2;
  TempObj.Adresszeile := s;
 
  Result := Add(TempObj);
end;                                           
Alle sagten, dass es unmöglich sei - bis einer kam und es einfach gemacht hat.

Benutzeravatar
six1
Beiträge: 782
Registriert: Do 1. Jul 2010, 19:01

Re: Frage zu Objektliste

Beitrag von six1 »

Warf hat geschrieben:
six1 hat geschrieben:...falls ich es richtig verstanden habe, sollte es so gehen:

Selbst wenn du es richtig verstanden hast ist das extrem unperformant, da im worst case mit jedem aufruf einmal alle addressen kopiert werden müssen. Daher sollte man falls möglich immer auf TList oder so zurückgreifen, da die geometrisches wachstum haben, und nur logarithmisch oft resizen (und damit potentiell den gesammten Array kopieren) müssen


Ok, an dieser Stelle wäre eine Übergabe des Parameter als var möglich, um dies zu verhindern.
Gruß, Michael

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: [Erledigt] Frage zu Objektliste

Beitrag von m.fuchs »

six1 hat geschrieben:
Warf hat geschrieben:
six1 hat geschrieben:...falls ich es richtig verstanden habe, sollte es so gehen:

Selbst wenn du es richtig verstanden hast ist das extrem unperformant, da im worst case mit jedem aufruf einmal alle addressen kopiert werden müssen. Daher sollte man falls möglich immer auf TList oder so zurückgreifen, da die geometrisches wachstum haben, und nur logarithmisch oft resizen (und damit potentiell den gesammten Array kopieren) müssen

Ok, an dieser Stelle wäre eine Übergabe des Parameter als var möglich, um dies zu verhindern.


Nein, das hat überhaupt nichts mit var-Parametern zu tun. Du vergrößerst dein Array da immer nur um ein Element. Wenn der Speicherplatz an der Stelle wo es liegt dafür nicht ausreicht, wird es woanders hinkopiert.
Und wenn das bei jedem Hinzufügen passiert, kann es wirklich langsam werden.
Deswegen gibt es ja diese schönen Listen, die einen Großteil der Verwaltungsarbeit bereits (und performanter) erledigen.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Frage zu Objektliste

Beitrag von Warf »

six1 hat geschrieben:Ok, an dieser Stelle wäre eine Übergabe des Parameter als var möglich, um dies zu verhindern.

siehe die antwort von m.fuchs.
Als kleine erklärung wie TList (oder auch TFPGList für generische typen) das macht, die haben neben der Länger noch eine Kapazität. Wenn du eine Liste erzeugst wird eine gewisse kapazität alloziiert, z.B. 1 eintrag. Dann, solang die Länge kleiner als die Kapazität ist kann hinzugefügt werden ohne kosten. Wenn länge gleich Kapazität ist, wird die Kapazität geometrisch erhöt. Bei TList ist das glaube ich ein Faktor 2 wenn die Liste klein ist und ein Faktor 1,5 wenn die Liste größer wird.

Als array implementierung wäre das etwa so:

Code: Alles auswählen

void addItem(Item: TSomeType);
begin
  if Length = Capacity then
  begin
    Capacity = Capacity * 2;
    SetLength(Array, Capacity);
  end;
  Array[Length] := Item;
  Inc(Length);
end;

und die entsprechende RemoveItem würde dann irgendwie so aussehen (aus einfachheit nehmen wir an das nur das letzte element gelöscht wird:

Code: Alles auswählen

void removeItem();
begin
  Dec(Length)
  if Length < Capacity div 2 then
  begin
    Capacity = Capacity div 2;
    SetLength(Array, Capacity);
  end;
end;


Denn SetLength muss ja den speicherbereich vergrößern, d.h. wenn aber an der stelle wo der Speicher liegt kein freier platz hintendran mehr frei ist (Arrays garantieren ja kontinuierlichen Speicher) muss wo ganz anders neuer Speicher gesucht werden, und alle alten elemente rüberkopiert werden. Das ist verdammt teuer. Die annahme ist jetzt, kleine listen bleiben klein. daher wachsen die listen am anfang recht langsam (1-2-4-8-16-32), außerdem ist da das kopieren noch billig (32 werte zu kopieren ist nix bei ner GHZ CPU). Wenn die Liste groß wird, im extrem fall im GB bereich, willst du nicht für jedes Add ein paar gigabyte kopieren, daher muss man da sehr selten realloziieren. Außerdem kann man davon ausgehen das sobald eine Liste "warmgelaufen" ist sich ihre größe nur sehr selten ändert, d.h. es kommen wahrscheinlich ähnlich viele elemente hinzu wie gelöscht werden, die gesammt größe ändert sich also irgendwann kaum noch.
Für die Performancekitzler die sich jetzt denken: Oh mein gott im worst case verbrauche ich also doppelt so viel speicher, das ist auch kein Problem, denn auf modernen CPU's verwendet man virtuellen speicher. D.H. du kannst auf einem 64bit system problemlos 2^64 bytes alloziieren, ohne das es kracht, unabhängig wie viel speicher deine Maschine Physisch hat. Solang da nix reingeschrieben wird ist das absolut kein problem. Also selbst wenn bei einer 2 GB liste 4GB alloziiert sind, sitzen im Physischen RAM trozdem nur 2GB.
Das gilt natürlich nicht für Microcontroller! Aber sowohl bei i386, x86_64 oder auch ARM ist das praktisch kostenlos, und gibt immense performance boosts im vergleich zu jedes mal ein SetLength machen.


Aber sowas muss man als Programmierer eigentlich nicht mal wissen, wichtig ist nur, wenn man fertige Klassen hat, sollte man die auch benutzen. Die FPC und lazarusentwickler wissen (zumindest in den aller meißten fällen) was sie tun!

PS: Wenn man eine Liste von Komplexeren Objekten z.B. records hat, ist es dementsprechend auch schlauer einen Kontinuierlichen Memory block (z.B. TFPGList<RecordType>) zu verwenden, statt eine TList und pointer zu übergeben, da die TFPGList sich bereits um das alloziieren des speichers kümmert. Das new und dispose (oder auch bei Klassen Create und Free) sind relativ teure operationen, und wenn man große listen hat lohnt sich ne FPGList von nem (advanced)record oder nem object(Nicht Klasse) manchmal deutlich mehr (Memory Operationen sind mit das teuerste was man machen kann).

Benutzeravatar
six1
Beiträge: 782
Registriert: Do 1. Jul 2010, 19:01

Re: [Erledigt] Frage zu Objektliste

Beitrag von six1 »

Hey Danke für deine tolle Erklärung!
Werde das Thema demnächst für mich mal praktisch aufarbeiten!

Allerdings weiß ich nicht genau, ob es für mich Vorteile hat, da ich normalerweise eng mit Datenbanken zusammen arbeite und keine Kopie einer Datenbanktabelle im Speicher abbilde :D
Aber die Idee dahinter ist mir jetzt klar geworden.

Danke Warf
Gruß, Michael

Antworten