Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Array Zeile löschen (https://www.delphipraxis.net/166862-array-zeile-loeschen.html)

VkPenguin 2. Mär 2012 22:08

Array Zeile löschen
 
Hallo,
mit eurer Hilfe habe ich inzwischen einiges an meinem Projekt geschafft. Ich brauchte mich eine Weile nicht melden,kam ich gut vorwärts . Nun gibt es aber einen Fehler in meinem Programm, der mir schon seit Anfang an zu schaffen macht, den ich aber immer vor mir hergeschoben hab. Folgendes Problem: In meinem Spiel werden die Positionen und Eigenschaften von z.B. Gegnern in Arrays (of Record) gespeichert. Wenn nun einer von ihnen stirbt (Dat[X].Alive=False) soll die Zeile gelöscht werden und alle anderen um Platz zu sparen "eins aufrücken". Grundsätzlich funktioniert das auch, aber wenn mehrere Gegner gleichzeitig ins Jenseits befördert werden entstehen Fehler. Hier meine bisherige Technik:

Delphi-Quellcode:
  X:=0;
  For I:=1 To Gegneranzahl+X DO
  Begin
   if not Gegner[I-X].alive then
   Begin
    if ((I-X)<>Gegneranzahl) THEN
    Begin
     Gegner[I-X].Wert:=Gegner[Gegneranzahl].Wert;
     Gegner[I-X].Funktion:=Gegner[Gegneranzahl].Funktion;
     Gegner[I-X].PosX:=Gegner[Gegneranzahl].PosX;
     Gegner[I-X].PosY:=Gegner[Gegneranzahl].PosY;
     Gegner[I-X].breite:=Gegner[Gegneranzahl].breite;
     Gegner[I-X].hoehe:=Gegner[Gegneranzahl].hoehe;
     Gegner[I-X].Farbe:=Gegner[Gegneranzahl].Farbe;
     Gegner[I-X].Maxzeit:=Gegner[Gegneranzahl].Maxzeit;
     Gegner[I-X].Alter:=Gegner[Gegneranzahl].Alter;
     [...............]
    End;
    X:=X+1;
    Gegneranzahl:=Gegneranzahl-1;
   End;
  End;
In einigen Tutorials hab ich "Swap Values" kennengelernt, damit könnte man das ganze sicher etwas vereinfachen; ich konnte aber nichts dazu finden, was hieran falsch sein könnte. Hat jemand vielleicht eine Idee ?

Alternativ: Könnte ich nicht theoretisch auch einfach den letzten mit demjenigen, der gelöscht werden soll Tauschen (es sei den selbiger ist der letzte) und dann den letzten löschen?

Danke schoneinmal!

Bjoerk 2. Mär 2012 22:12

AW: Array Zeile löschen
 
Ist das ein statisches array?

Bummi 2. Mär 2012 22:17

AW: Array Zeile löschen
 
Das und die Sortierung dürfte der Grund sein warum meist Listen bevorzugt werden.
Du könntest mehrfach 1 löschen oder umkopieren in ein initial leeres Array, wenn die Reihenfolge egal ist kannst Du auch wie von Dir bereits vorgeschlagen eine Lückenliste erstellen,auffüllen,löschen

Popov 2. Mär 2012 22:28

AW: Array Zeile löschen
 
@VkPenguin

Nur mal so, du kannst das auch so in einem Rutsch übergeben:

Delphi-Quellcode:
Gegner[I-X] := Gegner[Gegneranzahl];
Macht alles etwas übersichtlicher.

Und hier: http://www.delphipraxis.net/166529-f...heinander.html

Da ging es um Objekte, aber auch da wird Array von unnötigen Datensätzen befreit.

VkPenguin 4. Mär 2012 14:40

AW: Array Zeile löschen
 
Hallo, danke für eure Hilfe..

@ Bjoerk: Ja, ist es, bisher zumindest, später werde ich das aus Performancegründen vielleicht ändern.
@ Popov: Ah, das macht das ganze zumindest schon mal einfacher, Dankeschön für den Tipp.
@ Bummi: Da hast du wahrscheinlich recht. Ist es denn schwierig mein Programm auf die Verwendung von Listen umzubauen?

Kennt jemand zufällig ein gutes Tutorial, wie man Listen verwendet? Ansonsten werde ich mich mal umschauen..

Breager 5. Mär 2012 18:34

AW: Array Zeile löschen
 
Zitat:

Zitat von VkPenguin
Ist es denn schwierig mein Programm auf die Verwendung von Listen umzubauen?

Nein. Listen sind zudem viel schneller und komfortabler als dynamische Arrays. Du musst nur darauf achten, den reservierten Speicher wieder freizugeben, ansonsten entsteht ein Speicherleck.

Hier ein einfaches Beispiel:
Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  PGegnerRecord = ^TGegnerRecord;
  TGegnerRecord = record
   Name:AnsiString;
   PosX:Integer;
   PosY:Integer;
   {...}
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

  Gegnerliste:TList;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
 Gegnerliste:=TList.Create;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
Var i:Integer;
Begin
 For i:=Gegnerliste.Count-1 downto 0 do  //Löschen von Einträgen
    Dispose(PGegnerRecord(Gegnerliste.Items[i])); //Speicher wieder freigeben!

 Gegnerliste.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
Var GegnerRec:PGegnerRecord;
    index:Integer;
begin
 New(GegnerRec); //Speicher reservieren
 Gegnerliste.Add(GegnerRec);
 index:=Gegnerliste.Count;
 GegnerRec.Name:='Gegner'+IntToStr(index);
 GegnerRec.PosX:=index;
 GegnerRec.PosY:=30;
end;

procedure TForm1.Button2Click(Sender: TObject);
Var i:Integer;
begin
 For i:=0 to Gegnerliste.Count-1 do
     ShowMessage(TGegnerRecord(Gegnerliste[i]^).Name);
end;

himitsu 5. Mär 2012 20:02

AW: Array Zeile löschen
 
Zitat:

Zitat von Breager (Beitrag 1154663)
Nein. Listen sind zudem viel schneller und komfortabler als dynamische Arrays. Du musst nur darauf achten, den reservierten Speicher wieder freizugeben, ansonsten entsteht ein Speicherleck.

Schneller nicht. Ordentlioch implementiert ist Beides etwa gleich schnell.

Aber Komfortabler auf jedenfall.

Nja, es wäre ja zu praktisch, wenn die Fragesteller ihre Delphi-Version mal verrraten würden. :wall:
Ab Delphi 2009 könnte man z.B. den Generics und einer generischen TList den Komfort nochmal wesentlich erhöhen.

Und was die Speicherlecks angeht:
Eine TObjectList, bzw. einer generischen TObjektList und mit OwnsObjects=True, würde man nochmal etweas mehr Sicherheit und Kompfort rausholen.

DeddyH 6. Mär 2012 07:24

AW: Array Zeile löschen
 
Zitat:

Zitat von himitsu (Beitrag 1154671)
Eine TObjectList, bzw. einer generischen TObjektList und mit OwnsObjects=True, würde man nochmal etweas mehr Sicherheit und Kompfort rausholen.

Dazu müsste man aus den Records aber erst Klassen machen ;)

Blup 6. Mär 2012 08:32

AW: Array Zeile löschen
 
Zum ursprünglichen Problem:
Delphi-Quellcode:
for i := Gegneranzahl downto 1 do
begin
  if not Gegner[i].alive then
  Begin
    if i < Gegneranzahl then
      Gegner[I] := Gegner[Gegneranzahl];
 
    Dec(Gegneranzahl);
  end;
end;
Die Umstellung von Records und Arrays auf Objekte und Objektlisten scheint hier sinnvoll. Der Aufwand dürfte sich in Grenzen halten.
Die Alternative mit TList und Pointern zu arbeiten, erfordert mindestens den selben Aufwand und ist nicht so komfortabel.

himitsu 6. Mär 2012 08:53

AW: Array Zeile löschen
 
Zitat:

Zitat von DeddyH (Beitrag 1154693)
Dazu müsste man aus den Records aber erst Klassen machen ;)

Jupp.

Die generische TList ist leider nur für die haltung von statischen Records, deren Daten man nicht ändert, bzw. die man nur im ganzen ändert.
Das Ändern einzelner Werte eines Records ist nicht (sehr unpraktsich) änderbar.
Und da sich hier ja bestimmte mindestens Werte wie PosX und PosY ändern werden...

Ich hatte mir damit beholfen, daß ich die TList<> erweiterte und ein Items einführte, welches den Pointer auf den Record und nicht eine Kopie Records zurückliefert.

Popov 6. Mär 2012 09:20

AW: Array Zeile löschen
 
Da Ganze ist ein Dilemma. Mit Array zu arbeiten ist für Anfänger einfacher, da man die Vorgänge logisch nachvollziehen kann. Oft wollte man am Anfang auch nur eine Kleinigkeit und plötzlich hat meine ein mächtiges Array-Record an der Backe. Die Verwaltung ist aber dann etwas komplexer. Eine Klasse ist von der Arbeit am Anfang ähnlich, bringt später unheimlich Vorteile, für einen Anfänger evtl. noch schwer nachvollziehbar.

DeddyH 6. Mär 2012 09:27

AW: Array Zeile löschen
 
Zitat:

Zitat von Popov (Beitrag 1154707)
Mit Array zu arbeiten ist für Anfänger einfacher, da man die Vorgänge logisch nachvollziehen kann.

Bei dynamischen Arrays würde ich das aber nicht unterschreiben, da spielen Listen IMO alle Trümpfe aus (Add, Delete etc. gegenüber SetLength, Move usw.).

Breager 6. Mär 2012 19:09

AW: Array Zeile löschen
 
Zitat:

Zitat von himitsu
Das Ändern einzelner Werte eines Records ist nicht (sehr unpraktsich) änderbar.

Was spricht gegen folgendes?
Delphi-Quellcode:
begin
...
TGegnerRecord(Gegnerliste[i]^).Name:='Neuer Gegner';
TGegnerRecord(Gegnerliste[i]^).PosX:=10;
...
end;
Die Klasse muss man auch wieder sauber freigeben. Habe mal im Netz ein Beispiel (als Empfehlung) gefunden, mit einem wunderschönen Speicherleck. Leider weiss ich nicht mehr wo.

VkPenguin 7. Mär 2012 14:28

AW: Array Zeile löschen
 
Hallo,
habe leider bisher keine Zeit gefunden, alle Beiträge zu lesen und zu überdenken; nur kurz als Antwort: Meine Version ist Delphi 2009, entschuldigt, wusste nicht, dass das wichtig ist

himitsu 7. Mär 2012 15:11

AW: Array Zeile löschen
 
Hast du schonmal mit den Generics gearbeitet?

GegnerListe[0] liefert dort den Record, genauer eine Kopie des Records und keinen Zeiger.

Delphi-Quellcode:
type
  TGegnerRecord = record
   Name:AnsiString;
   PosX:Integer;
   PosY:Integer;
   {...}
  end;

var
  Gegner: TGegnerRecord;
  GegnerListe: TList<TGegnerRecord>;

GegnerListe := TList<TGegnerRecord>.Create;


Gegner.Name := 'ich';
Gegner.PosX := 123;
...
GegnerListe.Add(Gegner);


Label1.Caption := GegnerListe[0].Name:


GegnerListe[0].PosX := 456; // geht nicht, auch wenn der Compiler, in älteren Delphi-Versionen, keine Warnung/Fehlermeldung anzeigt


GegnerListe.Free;
Vorteil ist zwar, daß hier delphi die komplette Speicherverwaltung übernimmt, innerhalb der Liste und auch für die Einzelrecords.

Bummi 7. Mär 2012 15:19

AW: Array Zeile löschen
 
das Ändern ist aber nicht wirklich problematisch
Delphi-Quellcode:
var
  i: Integer;
begin
  GegnerListe := TList<TGegnerRecord>.Create;

  for i := 1 to 100 do
  begin
    Gegner.Name := 'ich' + IntToStr(i);
    Gegner.PosX := i;
    GegnerListe.Add(Gegner);
  end;

  Gegner := GegnerListe[10];
  Gegner.PosX := 999;
  Gegner.Name := 'geändert';
  GegnerListe[10] := Gegner;
  Showmessage(GegnerListe[10].Name + '-' + IntToStr(GegnerListe[10].PosX));
  GegnerListe.Free;
end;
benötigt generics.collections in den uses.

himitsu 7. Mär 2012 15:28

AW: Array Zeile löschen
 
Jupp, wenn man den Record vorher rausnimmt/rauskopiert und dann quasi "komplett" ersetzt.
Nur eben Einzeiler, zum Ändern eines klitzekleinen Wertes, sind standardmäßig nicht möglich.

Breager 7. Mär 2012 17:16

AW: Array Zeile löschen
 
Bei Generics muss ich leider aus Kompatibilitätsgründen passen.

Bei der von mir vorgeschlagenen Variante kann man jedoch, wie schon oben geschrieben, einzelne Werte ohne Probleme direkt ändern:
Delphi-Quellcode:
procedure TForm1.Button3Click(Sender: TObject);
begin
 TGegnerRecord(Gegnerliste[0]^).Name:='Neuer Gegner';
 TGegnerRecord(GegnerListe[0]^).PosX:=100;
end;
Oder spricht etwas dagegen?

himitsu 7. Mär 2012 18:30

AW: Array Zeile löschen
 
Nein, das geht bei der generischen TList<T> nicht, welche direkt die Records enthält.

Wenn man eine TList mit Zeigern auf "Gegner"-Records oder mit einem Gegner-Objekten befüllt hat, dann geht es, so wie du sagst.
Nur daß man andersrum wohl noch besser erkennt, warum es dort geht.
Delphi-Quellcode:
procedure TForm1.Button3Click(Sender: TObject);
begin
  PGegnerRecord(Gegnerliste[0])^.Name:='Neuer Gegner';
  PGegnerRecord(GegnerListe[0])^.PosX:=100;
end;
Nur muß man hier eben überall selber noch das New und Dispose aufrufen.

DeddyH 7. Mär 2012 18:45

AW: Array Zeile löschen
 
Oder man macht Klassen draus und... hatten wir das nicht schon? :mrgreen:

Breager 7. Mär 2012 20:01

AW: Array Zeile löschen
 
Zitat:

Zitat von DeddyH
Oder man macht Klassen draus und... hatten wir das nicht schon?

Es ging mir ja nur darum, dass es bei der von mir vorgeschlagenen Variante möglich ist, einzelne Werte des Records direkt zu ändern. Das New und Dispose brauche ich doch nur beim Hinzufügen (Add) oder Entfernen (Delete) von Items bzw. vor dem Freigeben (Free) der Liste, nicht jedoch beim Sortieren der Liste.

Ich glaube, im Endeffekt hat jede mögliche/hier genannte Lösung ihre Vor- und Nachteile. Jetzt muss man sich nur noch das rauspicken, was einem am meisten zusagt ;-)

Lg

VkPenguin 7. Mär 2012 21:09

AW: Array Zeile löschen
 
... erst einmal vielen lieben Dank für eure Hilfe und Freundlichkeit - zuerst hab ich zwar nicht ganz verstanden, wovon ihr eigentlich redet, ich konnte mich aber dann gut anhand der Codebeispiele einfinden, danke noch einmal für die Mühe an dieser Stelle. Ich mach mich dann mal an den Umbau :) Melde mich, wenn es größere Probleme gibt oder wenn es geklappt hat.

*Edit* Funktioniert soweit wie ich gekommen bin gut; eine Frage hätte ich aber - wenn ich zwei Einheiten der Liste vergleichen will, also zwei gleichzeitig brauche, muss ich dann auch zwei temporäre "Gegner"-Variablen haben?
*Edit2* Hm, bekomme folgende Fehlermeldung:

---------------------------
Anwendungsfehler
---------------------------
Exception EArgumentOutOfRangeException in Modul SpaceInvaders042.exe bei 000B6205.

Argument out of range.
---------------------------
OK
---------------------------

Ich denke mal, dass bedeutet, dass die Liste nur sagen wir mal drei Einträge hat und ich nach dem vierten Frage..? Wir die Liste mit dem ".add"-Befehl nicht automatisch um eins erweitert? Das hatte ich so verstanden..
*Edit3* wenn ich es so "For I:=1 To Schussanzahl-1 Do" mache, geht es scheinbar. Fängt die Liste also schon bei 0 an zu zählen? Aber warum funktioniert dann der erste Schuss nicht?
*Edit4*...erst Denken.. mit
Delphi-Quellcode:
For I:=0 To [...]
geht es wunderbar. Werde mich am Wochenende weiter damit beschäftigen und (voraussichtlich) Rückmeldung geben. Danke nocheinmal!

himitsu 7. Mär 2012 22:39

AW: Array Zeile löschen
 
Das Freigeben macht eine TObjectList von selber, wenn man es ihr sagt.

Delphi-Quellcode:
type
  TSimpleObjectList<T: class> = class(TList<T>)
  protected
    procedure Notify(const Value: T; Action: TCollectionNotification); override;
  public
    function Add: T; overload;
    function Insert(Index: Integer): T; overload;
  end;

function TSimpleObjectList<T>.Add: T;
begin
  Result := T.Create;
  inherited Add(Result);
end;

function TSimpleObjectList<T>.Insert(Index: Integer): T;
begin
  if (Index < 0) or (Index > Count) then
    Items[Index];
  Result := T.Create;
  inherited Insert(Index, Result);
end;

procedure TSimpleObjectList<T>.Notify(const Value: T; Action: TCollectionNotification);
begin
  try
    inherited;
  finally
    if Action = cnRemoved then
      Value.Free;
  end;
end;
Nunja, eine kleine Erweiterung und schon läßt sich auch mit einer TObjectList<T> angenehm arbeiten.


Alle Zeitangaben in WEZ +1. Es ist jetzt 21:52 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz