Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Slicing for Delphi™ (https://www.delphipraxis.net/131414-slicing-delphi%99.html)

Meflin 24. Mär 2009 17:05


Slicing for Delphi™
 
Liste der Anhänge anzeigen (Anzahl: 2)
Moin moin!

Der eine oder andere von euch kennt sicher aus anderen Programmiersprachen das Slicing-Konzept. Für die, die es nicht kennen, hier eine kurze Einführung:

Dass man auf ein Listenelement mittels Liste[Index] zugreifen kann, ist sicher nichts neues. Das dumme daran: Man bekommt immer nur ein Element zurück. Und hier kommt das Slicing ins Spiel, welches derartige Zugriffe quasi exponentiell nützlicher macht.

Die Grundlegende Syntax ist Liste[StartIndex:StopIndex]. Und eigentlich ist das schon ziemlich selbsterklärend, hier ein paar Beipsiele, was man damit so machen kann:
Code:
Liste[0:4]   0tes - 5tes Element
Liste[3:4]   4tes - 5tes Element
Liste[:5]    0tes - 6tes Element
Liste[3:]    4tes - letztes Element
Liste[1:-1]  zweites - vorletztes Element

u.v.m.
Nun, wems bis jetzt noch nicht gedämmert ist: Soetwas gibt es in Delphi leider nicht :(

Ich bin grade dabei mir zu überlgen, inwieweit man das in Delphi integrieren könnte. Herausgekommen ist dabei bis jetzt eine Beispielimplementation TSlicedStringList:
Delphi-Quellcode:
unit SlicedStringList;

interface

uses
  Classes;

type
  TSlicedStringList = class(TStringList)
  private
    function GetStrings(Slice: string): TStrings;
    //procedure SetStrings(Slice: string; const Value: TStrings);
  public
    property Strings[Index: Integer]: string read Get write Put; default;
    property Strings[Slice: string]: TStrings read GetStrings; default;
  end;

implementation

uses
  SysUtils, Dialogs;

{ TExtStringList }
type
  TIntArray = array of Integer;

function GetIntArrayFromSlice(const Slice: string; const Max: Integer): TIntArray;
var
  strStartPos, strStopPos: string;
  StartPos, StopPos, i: Integer;
begin
  strStartPos := Copy(Slice, 1, Pos(':', Slice) - 1);
  strStopPos := Copy(Slice, Pos(':', Slice) + 1, Length(Slice));

  case Length(strStartPos) of
    0: StartPos := 0;
    else StartPos := StrToInt(strStartPos);
  end;

  case Length(strStopPos) of
    0: StopPos := Max;
    else StopPos := StrToInt(strStopPos);
  end;

  if StartPos < 0 then StartPos := Max + StartPos;
  if StopPos < 0 then StopPos := Max + StopPos;

  SetLength(Result, StopPos - StartPos + 1);
  for i := StartPos to StopPos do begin
    Result[i - StartPos] := i;
  end;
end;

function TSlicedStringList.GetStrings(Slice: string): TStrings;
var
  i: Integer;
begin
  Result := TStringList.Create;
  case Pos(':', Slice) of
    0: Result.Add(Strings[StrToInt(Slice)]);
    else begin
      for i in GetIntArrayFromSlice(Slice, Count - 1) do
        Result.Add(Strings[i]);
    end;
  end;
end;

//procedure TSlicedStringList.SetStrings(Slice: string; const Value: TStrings);
//begin
//
//end;

end.
Das neue an ihr: sowas geht
Delphi-Quellcode:
lst := StringList['1:4'];
Im Anhang findet ihr eine kleine Demo-Anwendung dazu, nix großes, soll nur das Prinzip verdeutlichen und enthält KEINE Fehlerabfragen, derartige 'Bugreports' sind also überflüssig ;)




Die spannende Frage ist nun:
Was haltet ihr davon? Nützlich? Fürn Popo? Unbedingt weiterentwicklen?

Und wenn weiterentwickeln, was wären die Konventionen, die ihr bevorzugegen würdet?
Soll [-1] Das letzte oder das vorletzte Element liefern? (Beides ergibt Sinn).
Soll der EndIndex im Ergebnis enthalten sein oder nicht (beide Versionen gibt es in anderen Sprachen).

Fällt euch eine bessere Möglichkeit ein, als das ganze per string zu übergeben (was nicht sehr nützlich ist, aber es ist der einizge Weg, der mir spontan eingefallen ist)?


Nuja, her mit euren Ideen und Anregungen :)

Namenloser 24. Mär 2009 17:13

Re: Slicing for Delphi™
 
Zitat:

Zitat von Meflin
Fällt euch eine bessere Möglichkeit ein, als das ganze per string zu übergeben (was nicht sehr nützlich ist, aber es ist der einizge Weg, der mir spontan eingefallen ist)?

Ich würde es so machen, wie bei TCanvas.Pixels:
Delphi-Quellcode:
TSlicedStringList = class
private
  function GetSlice(From, To: integer): TStrings;
  ...

public
  property Slices[From,To: integer]: TStrings read GetSlice;
  ...

end;

...
Delphi-Quellcode:
lst := SlicedStringList.Slices[1,4];
lst.free;

Meflin 24. Mär 2009 17:25

Re: Slicing for Delphi™
 
Zitat:

Zitat von NamenLozer
Ich würde es so machen, wie bei TCanvas.Pixels

Habe ich auch drüber nachgedacht, der Nachteil daran ist, dass das spaßige von-rechts-Zählen quasi komplett wegfällt, weil man immer zwei Parameter angeben muss.
[-1] Wäre das letzte Element, [0, -1] würde was anderes bedeuten, man müsste dafür also [-1, -1] übergeben.

Auch irgendwie blöd :|

mr_emre_d 24. Mär 2009 17:35

Re: Slicing for Delphi™
 
Zitat:

Zitat von Meflin
Zitat:

Zitat von NamenLozer
Ich würde es so machen, wie bei TCanvas.Pixels

Habe ich auch drüber nachgedacht, der Nachteil daran ist, dass das spaßige von-rechts-Zählen quasi komplett wegfällt, weil man immer zwei Parameter angeben muss.
[-1] Wäre das letzte Element, [0, -1] würde was anderes bedeuten, man müsste dafür also [-1, -1] übergeben.

Auch irgendwie blöd :|

Ich habe heir mal eine kleine rekursive Function gepostet, mit der man einen Wert in einem Wertebereich, die man angibt, zurückbekommt !

EDIT:
http://www.delphipraxis.net/internal...929&highlight=
Siehe mein Beitrag

MfG

shmia 24. Mär 2009 17:46

Re: Slicing for Delphi™
 
Interessant!
Ich würde das mit dem Dekorator Design-Pattern lösen.
Dazu leitet man von TStrings ab und muss dann natürlich alle virtuellen (und abstrakten) Methoden überschreiben.
Dabei leitet man die Methoden nur auf das TStrings-Objekt weiter, dass im Konstruktor übergeben wurde.
Delphi-Quellcode:
TSlicedStrings = class(TStrings)
private
   FStrings : TStrings;
   function GetSlice(const Range: string): TStrings;
   //procedure SetStrings(Slice: string; const Value: TStrings);
public
   constructor Create(AStrings:TStrings);
   function Add(const S: string): Integer; override;

   property Slice[const Range: string]: TStrings read GetStrings;
   property Slice[fromIdx,toIdx:Integer]:TStrings; // 2. Variante
end;
constructor TSlicedStrings.Create(AStrings:TStrings);
begin
  inherited Create;
  FStrings := AStrings;
end;
function TSlicedStrings.Add(const S: string): Integer; override;
begin
  Result := FStrings.Add(S); // einfach auf internes Objekt umleiten
end;


function TSlicedStrings.GetSlice(const Range: string): TStrings;
var
  i,j : Integer;
begin
  Result := TStringList.Create;
  case Pos(':', Range) of
    0:
    begin
      j := StrToInt(Range);
       Result.AddObject(Strings[j], Objects[j]);
    end
    else begin
      for i in GetIntArrayFromSlice(Range, Count - 1) do
        Result.AddObject(Strings[i], Objects[i]);
    end;
  end;
end;

Dax 24. Mär 2009 17:50

Re: Slicing for Delphi™
 
Zitat:

Zitat von Meflin
[-1] Wäre das letzte Element, [0, -1] würde was anderes bedeuten, man müsste dafür also [-1, -1] übergeben.

Auch irgendwie blöd :|

Du könntest zwei Properties einbauen, einmal mit einem Parameter, einmal mit zwei. Ob man beide defaulten kann, weiß ich gerade nicht - aber schöner als Strings zu verwenden wäre es allemal.

alzaimar 24. Mär 2009 18:50

Re: Slicing for Delphi™
 
Lustig. Der Nachteil des String-Parametersi ist die etwas sehr umständliche Verwurstung von Variablen. I.A. benötige ich nicht das 2. bis 5. Element, sondern das i.te bis j.te (bzw. N Elemente ab Position I). Ich müsste mir also den Deskriptorstring mittels IntToStr oder Format zusammenbasteln. Das ist irgendwie blöd.

Delphi-Quellcode:
MySlicedStringList := StringList[Format('%d:%d',[I,N])];
Übersichlich geht anders.

Meflin 24. Mär 2009 19:04

Re: Slicing for Delphi™
 
Zitat:

Zitat von alzaimar
Der Nachteil des String-Parametersi ist die etwas sehr umständliche Verwurstung von Variablen ... Übersichlich geht anders.

Da hast du völlig Recht und das ist mir auch bewusst. Nur wenn man beim "originalen" Slicing bleibt, dann ist ja [:-1] etwas anderes als [-1] (nämlich einmal die komplette Liste und einmal das letzte Element). Die Dynamik geht irgendwie verloren, wenn man mehrere properties implementiert :gruebel: Auch gibt es ja noch erweiterte Slicing-Techniken, [1::2] wäre jedes zweite Element, oder [1::3] jedes dritte beginnend mit dem zweiten. Ich weiß nicht ob ich sowas überhaupt implementieren will. Aber da endet dann der Umgang mit blosen Integern.

Zitat:

Zitat von Dax
Du könntest zwei Properties einbauen, einmal mit einem Parameter, einmal mit zwei. Ob man beide defaulten kann, weiß ich gerade nicht

Das sollte möglich sein.

Zitat:

Zitat von shmia
Ich würde das mit dem Dekorator Design-Pattern lösen.
Dazu leitet man von TStrings ab und muss dann natürlich alle virtuellen (und abstrakten) Methoden überschreiben.
Dabei leitet man die Methoden nur auf das TStrings-Objekt weiter, dass im Konstruktor übergeben wurde.

Danke, das ist ein interessanter Ansatz :thumb:

Zitat:

Zitat von mr_emre_d
Ich habe heir mal eine kleine rekursive Function gepostet, mit der man einen Wert in einem Wertebereich, die man angibt, zurückbekommt !

Hm. Welches Problem meintest du soll die Lösen :stupid: ?

mr_emre_d 24. Mär 2009 20:17

Re: Slicing for Delphi™
 
xD
Um etwas klarzustellen -> meinst du mit [0:-1] ~ [0:Anzahl der Rechten Elemente - 1] ?

Falls nein, dann war mal mein Beitrag wieder voll daneben :roll:

MfG

himitsu 24. Mär 2009 21:46

Re: Slicing for Delphi™
 
bei Arrays kann man auch Copy nutzen ... ok, ohne das "von rechts"
Delphi-Quellcode:
var a: Array of Double;

a := Copy(a, 5, 9);
ansonsten gibt's 2 Möglichkeiten für die Parameter:
Delphi-Quellcode:
public
  property Slices[From: Integer; To: Integer = MinInt]: TStrings read GetSlice;

// if To = MinInt then {to=from}
// else {from,to}
Delphi-Quellcode:
public
  property Slices[From, To: Integer]: TStrings read GetSlice2; Default;
  property Slices[To:      Integer]: TStrings read GetSlice1; Default;
  ...

Namenloser 24. Mär 2009 21:54

Re: Slicing for Delphi™
 
MinInt? Das kennt mein Delphi gar nicht. Was ist denn das? 0? -1? $80000000?

himitsu 25. Mär 2009 11:15

Re: Slicing for Delphi™
 
Delphi-Quellcode:
const MinInt = Low(Integer)
im Grunde muß da einfach nur irgendein Wert hin, welcher (vermutlich) nie als Parameter übergeben wird.

alzaimar 25. Mär 2009 19:51

Re: Slicing for Delphi™
 
Ich finde das Slicing ja ganz nett, aber imho wird versucht, Delphi eine anderes Paradigma aufzudrängen. Die beschriebene Funktionalität lässt sich doch sehr schön mit unterschiedlichen Methoden (und aussagekräftigen Namen) bewerkstelligen. Die Syntax ist kryptisch und widerspricht daher dem Konzept der Lesbarkeit.
Delphi-Quellcode:
Const
  FromStart = -MaxInt;
  ToEnd = MaxInt;

Type
  TSliceableList = Class
    Function Copy(From,To : Integer) : TSliceableList;
    Function Probe(StartIndex, Step : Integer) : TSliceableList;
...
End;
Das wäre dann eine sinnvolle Erweiterung für Listen. Mir fällt nur grad (bis auf Copy) kein sinnvoller Einsatz ein. Hast Du Beispiele, für die Verwendung von 'nur jedes 3.Element'?

Meflin 26. Mär 2009 18:49

Re: Slicing for Delphi™
 
Zitat:

Zitat von alzaimar
Die beschriebene Funktionalität lässt sich doch sehr schön mit unterschiedlichen Methoden (und aussagekräftigen Namen) bewerkstelligen.

NAtürlich, da habe ich auch schon dran gedacht. Nur ist Slicing ja nix anderes als ein erweiterter Index - und auf das i-te Listenelement willst du ja auch nicht mit List.GetElement(i) zugreifen ;)

Zitat:

Die Syntax ist kryptisch und widerspricht daher dem Konzept der Lesbarkeit.
Da muss ich dir ganz entschieden widersprechen. Wenn man von dem ::-Zeug absieht (was es glaube ich auch nur in Python gibt), finde ich das äußerst intuitiv!

Zitat:

Hast Du Beispiele, für die Verwendung von 'nur jedes 3.Element'?
Meinst du jetzt wann man das mal in der PRaxis brauchen könnte? Nein, spontan nicht wirklich :stupid:

Christian Seehase 26. Mär 2009 19:00

Re: Slicing for Delphi™
 
Moin Zusammen,

ich würde jedenfalls in keinem Falle TStrings als Rückgabewert verwenden, sondern als Parameter übergeben, da man dann immer vor dem Problem steht, wann diese Objekt wieder zerstört werden muss, und von wem.

Apollonius 26. Mär 2009 19:14

Re: Slicing for Delphi™
 
Ich wäre für ein Interface als Rückgabewert.

Dax 26. Mär 2009 19:26

Re: Slicing for Delphi™
 
Am besten noch ein Enumerator-Interface ;)

Meflin 27. Mär 2009 11:40

Re: Slicing for Delphi™
 
Zitat:

Zitat von Apollonius
Ich wäre für ein Interface als Rückgabewert.

Zitat:

Zitat von Dax
Am besten noch ein Enumerator-Interface ;)

Das müsst ihr mir jetzt mal kurzer genauer erklären, wie ihr euch das vorstellt :stupid:

Apollonius 27. Mär 2009 16:38

Re: Slicing for Delphi™
 
Praktisch ein TStrings, aber als Interface. Durch die Referenzzählung umgehst du die manuelle Freigabe. Ein Enumerator-Interface ist eines, dass eine Methode GetEnumerator hat, welche einen Enumerator zurückgibt (mehr dazu in der Delphi-Hilfe). Dadurch wird die Verwendung der For-In-Schleife ermöglicht.

Meflin 22. Apr 2009 19:05

Re: Slicing for Delphi™
 
Soderle,

ich habe das ganze nun noch etwas weiterentwickelt, und jetzt funktioniert das ganze schon so:
Delphi-Quellcode:
uses Slicing;
...
var
  lst1, lst2: TStringList;
...

lst1 := lst2[-3];
lst1 := lst2[1,5];
lst1 := lst2['1:5'] // Aus Nostalgiegründen drinne gelassen =)
was ich schonmal ziemlich brauchbar finde :firejump: (funktioniert allerdings jetzt nur noch ab D2005).



Allerdings hat es immernoch unschönerweise TStringList als Rückgabetyp. Ich versteh einfach nicht, wie das mit interface praktischerweise gehen soll. Wenn ich irgendein interface selbst definiere, dann muss ja der Anwender immer erst eine passende Klasse dazu implementieren, das wäre ja auch bescheuert.

Oder könnte ich via class helpern der TStringList bspweise ein interface "beibringen", sodass das dann verwendet werden könnte?

Nunja, ich versteh einfach nicht, wie ihr das meint, bzw. wo da der Vorteil sein soll...

himitsu 22. Apr 2009 20:20

Re: Slicing for Delphi™
 
einfach eine StringList mit 'nem Interface versehen
und dann nur noch dem Interface die nötigen Funktionen mitgeben...

hier fehlen jetzt nur noch einige Funktionen im Interface, also welche man noch unbedingt aus den Public-Abschnitten benötigt.
Delphi-Quellcode:
Type
  IInterfacedStringList = Interface
    ['{E7FD1CCD-B023-491F-BEBA-3CEC190370D6}']
    //Private
      Function Get(Index: Integer): String;
      Function GetCount: Integer;
    //Public
      Function Add   (Const S: String): Integer;
      Function IndexOf(Const S: String): Integer;
      Property Count:                   Integer Read GetCount;
      Property Strings[Index: Integer]: String Read Get;     Default;
      Function Find  (Const S: String; Var Index: Integer): Boolean;
      //...
    End;

  TInterfacedStringList = Class (TStringList, IInterfacedStringList)
    Protected
      FRefCount: Integer;
      Function QueryInterface(Const IID: TGUID; Out Obj): HResult; StdCall;
      Function _AddRef: Integer; StdCall;
      Function _Release: Integer; StdCall;
    Public
      Procedure AfterConstruction;        Override;
      Procedure BeforeDestruction;        Override;
      Class Function NewInstance: TObject; Override;
      Property RefCount: Integer Read FRefCount;
    End;

  Function TInterfacedStringList.QueryInterface(Const IID: TGUID; Out Obj): HResult;
    Begin
      If GetInterface(IID, Obj) Then Result := 0
      Else Result := E_NOINTERFACE;
    End;

  Function TInterfacedStringList._AddRef: Integer;
    Begin
      Result := InterlockedIncrement(FRefCount);
    End;

  Function TInterfacedStringList._Release: Integer;
    Begin
      Result := InterlockedDecrement(FRefCount);
      If Result = 0 Then Destroy;
    End;

  Procedure TInterfacedStringList.AfterConstruction;
    Begin
      Inherited;
      InterlockedDecrement(FRefCount);
    End;

  Procedure TInterfacedStringList.BeforeDestruction;
    Begin
      If RefCount <> 0 Then System.Error(reInvalidPtr);
      Inherited;
    End;

  Class Function TInterfacedStringList.NewInstance: TObject;
    Begin
      Result := Inherited NewInstance;
      TInterfacedStringList(Result).FRefCount := 1;
    End;

Meflin 22. Apr 2009 20:27

Re: Slicing for Delphi™
 
OK, aber das bedeutet doch dann, dass der Benutzer wenn er Slicing verwenden will immer TInterFacedStringList verwenden muss (wenn er eigentlich mit TStringList arbeiten will) - das würde ich eigentlich gerne vermeiden :(

himitsu 22. Apr 2009 20:37

Re: Slicing for Delphi™
 
Du brauchst es doch nur da einzusetzen, wo du 'ne StringList als Result zurückgibst?

also
Delphi-Quellcode:
Function GetStrings(Index: Integer): IInterfacedStringList;
Das Ganze würde ich auch nicht für die Basisklasse umsetzen.


Witzig wäre es, wenn du deine Slicing-Erweiterung als Class-Helper für TStrings erstellst,
dann hätte man diese Funktion gleich automatisch in allen TStrings-Nachfahren (wie TStringList) drin,
und das sogar auch noch bei allen Instanzen, die man nichtmal selber erstellt hat (wie z.B. .Lines von TMemo). :angel:

Meflin 22. Apr 2009 20:54

Re: Slicing for Delphi™
 
Zitat:

Zitat von himitsu
Witzig wäre es, wenn du deine Slicing-Erweiterung als Class-Helper für TStrings erstellst

Im Moment ist es einer für TStringList, aber haste Recht, kann mna ja gleich für TStrings machen :thumb:

himitsu 22. Apr 2009 22:23

Re: Slicing for Delphi™
 
Liste der Anhänge anzeigen (Anzahl: 1)
:angel:


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:37 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