use Delphi to read in a text file to a TStringList but bottom to top

439 Views Asked by At

I was looking to read a text file in reverse so it would read in from the bottom of the text file first. I did find how to reverse it but it doesn't make sense to me. Can someone explain this to me, how it's working? Also if there is a better/quicker way? It seems to do all the work after the file is read in, like it would be quicker to just read it in from the bottom.

var
  datalist : TStringList;
  lines,i  : Integer;
  saveLine : String;
begin
  datalist := TStringList.Create;
  datalist.LoadFromFile(filename);   //loads file
  lines := datalist.Count;

  for i := lines-1 downto (lines div 2) do
    begin
      saveLine := datalist[lines-i-1];
      datalist[lines-i-1] := datalist[i];
      datalist[i] := saveLine;
    end;
3

There are 3 best solutions below

1
AmigoJack On BEST ANSWER

(At least in Delphi 7, but more recent versions should act similarily)

  1. .LoadFromFile() calls
  2. .LoadFromStream(), which reads the whole stream/file into memory and then calls
  3. .SetTextStr(), which just calls per line
  4. .Add()

Knowing this helps us to avoiding to reinvent the whole wheel and instead using an own class with one subtle change in the .Add() method:

type
  TStringListReverse= class( TStringList )
    function Add( const S: String ): Integer; override;
  end;

function TStringListReverse.Add( const S: String ): Integer;
begin
  Result:= {GetCount} 0;  // Our change: always in front
  Insert( Result, S );
end;

And now we just use our own class:

var
  l: TStringListReverse;
begin
  l:= TStringListReverse.Create;
  l.LoadFromFile( 'C:\Windows\win.ini' );
  Memo1.Lines.Assign( l );
  l.Free;
4
Rohit Gupta On

If you want to use the TStringList.LoadFromFile() function, then another way to do it is to copy one TStringList to another TStringList. It would be faster than the current scheme, and is fewer lines of code.

var
  datalist1, datalist2 : TStringList;
  lines, i: Integer;
  filename : string;
begin
  datalist1 := TStringList.Create;
  datalist2 := TStringList.Create;
  datalist1.LoadFromFile(filename);   //loads file
  lines := datalist1.Count;
  data2list.Capacity := lines;  // so it allocates the memory once
  for i := lines-1 downto 0 do
  begin
    datalist2.Add (datalist1[i]);
  end;

end;

Personally, I would read the file in myself.

0
David Heffernan On

As I mentioned in a comment, it might be useful to create an adapter class that accepts a TStrings instance, and exposes it as another TStrings, but reversed.

This might look like this:

type
  TReversedStrings = class(TStrings)
  private
    FSource: TStrings;
    FOwnsSource: Boolean;
    function ReversedIndex(Index: Integer): Integer;
  protected
    procedure Put(Index: Integer; const S: string); override;
    function Get(Index: Integer): string; override;
    function GetCount: Integer; override;
    function GetObject(Index: Integer): TObject; override;
    procedure PutObject(Index: Integer; AObject: TObject); override;
  public
    constructor Create(Source: TStrings; AssumeOwnership: Boolean);
    destructor Destroy; override;
    procedure Clear; override;
    procedure Delete(Index: Integer); override;
    procedure Exchange(Index1, Index2: Integer); override;
    function IndexOf(const S: string): Integer; override;
    procedure Insert(Index: Integer; const S: string); override;
    procedure Move(CurIndex, NewIndex: Integer); override;
  end;

{ TReversedStrings }

constructor TReversedStrings.Create(Source: TStrings; AssumeOwnership: Boolean);
begin
  inherited Create;
  FSource := Source;
  FOwnsSource := AssumeOwnership;
end;

destructor TReversedStrings.Destroy;
begin
  if FOwnsSource then
    FSource.Free;
  inherited;
end;

function TReversedStrings.ReversedIndex(Index: Integer): Integer;
begin
  Result := FSource.Count - Index - 1;
end;

procedure TReversedStrings.Put(Index: Integer; const S: string);
begin
  FSource[ReversedIndex(Index)] := S;
end;

function TReversedStrings.Get(Index: Integer): string;
begin
  Result := FSource[ReversedIndex(Index)];
end;

function TReversedStrings.GetCount: Integer;
begin
  Result := FSource.Count;
end;

function TReversedStrings.GetObject(Index: Integer): TObject;
begin
  Result := FSource.Objects[ReversedIndex(Index)];
end;

procedure TReversedStrings.PutObject(Index: Integer; AObject: TObject);
begin
  FSource.Objects[ReversedIndex(Index)] := AObject;
end;

procedure TReversedStrings.Clear;
begin
  FSource.Clear;
end;

procedure TReversedStrings.Delete(Index: Integer);
begin
  FSource.Delete(ReversedIndex(Index));
end;

procedure TReversedStrings.Exchange(Index1, Index2: Integer);
begin
  FSource.Exchange(ReversedIndex(Index1), ReversedIndex(Index2));
end;

function TReversedStrings.IndexOf(const S: string): Integer;
begin
  Result := FSource.IndexOf(S);
  if Result > -1 then
    Result := ReversedIndex(Result);
end;

procedure TReversedStrings.Insert(Index: Integer; const S: string);
begin
  FSource.Insert(ReversedIndex(Index), S);
end;

procedure TReversedStrings.Move(CurIndex, NewIndex: Integer);
begin
  FSource.Move(ReversedIndex(CurIndex), ReversedIndex(NewIndex));
end;

It should be obvious how to use this, and I've not tested the code, or even executed it. Consider it a sketch of an idea.