Solved

Array of String problem

Posted on 2002-03-30
36
354 Views
Last Modified: 2010-05-18
Hi! I seem to be having a dillema. My app doesn't use classes and I want it to stay that way. My problem is that I need to add/delete/edit strings which I normally do in a TStringList. I know I can do this with an Array of String as well. Can someone post some code that will allow me to this. Please remember that I don't want anything to do with CLASSES. Thanks!
0
Comment
Question by:DelFreak
  • 17
  • 11
  • 7
  • +1
36 Comments
 
LVL 1

Expert Comment

by:Alone
ID: 6907638
var
  StrArray: array of string; // dynamic array
  I: Integer;
  S: string;
begin
  SetLength(StrArray, 100); // 100 strings
  FillChar(StrArray[0], SizeOf(string) * 100, 0);
  StrArray[0] := 'Blabla';
  StrArray[1] := 'Blablabla';
  StrArray[3] := 'Blablablabla'; // :-))
// and more operations
  SetLength(StrArray, 200); // increases to 200 items
// some other operations
  SetLength(StrArray, 40); // decreases to 40 items
  S := '';
  for I := Low(StrArray) to High(StrArray) do
    if StrArray[I] > S then S := StrArray[I];
  WriteLn(S);  // S contains latest string in alphabetical order
end;

Usage of dynamic arrays maybe inefficient and allocate too much memory than TStrings. See Classes source to example for manual memory allocation using GetMem and FreeMem (SetCapacity method)

Regards...
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6907668
Alone is pointing you in the right direction. However, the FillChar() is unecessary and should be avoided anyways. New strings are always initialized empty.

Note that inserting strings is an expensive operation (in means of processor time) when you are using arrays. It's usually better to insert them all and sort them as you need when the array if filled up (exchanging two items is a cheap operation).

I suppose that you don't want to use the CLASSES library, correct? In this case, you could also copy the relevant parts of CLASSES.PAS into a unit of yours, therefore not adding all the additional code of the streaming subsystem which you don't need.
0
 

Author Comment

by:DelFreak
ID: 6908994
AvonWyss,
I tried copying the code I needed from Classes.pas but I still got errors that had to do with TPersistent and my main window. A very long story. Anyway, without classes.pas I never get any errors. Well, I just need these functions to be used with an Array of String:

1. Add to array
2. Delete from array
3. Edit item in array

Aside from these three, I don't think I'll be needing anything else since the functionality of this array is just like that of a TListView. Add, Delete, if there's anything incorrect, be editable but still be saved in the same instance of the array. I'd really appreciate it if you could help out. Thanks!


P.S. What's wrong with FillChar?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6909052
DelPhreak, try this:

type
     TStringArray=array of string;

procedure AddString(var Strings: TStringArray; const Item: string);
begin
     SetLength(Strings,Length(Strings)+1);
     Strings(High(Strings)):=Item;
end;

procedure DelString(var Strings: TStringArray; Index: integer);
var
     I: Integer;
begin
     for I:=Index to High(Strings)-1 do
          Strings[I]:=Strings[I+1];
     SetLength(Strings,Length(Strings)-1);
end;

Reading and writing a string is easy, just acces the array for that:

var
     Strings: TStringArray;
     I: Integer;
[...]
     AddString(Strings,'Test 0'); // index 0
     AddString(Strings,'Test 1'); // index 1
     AddString(Strings,'Garbage'); // index 2
     Strings[2]:='Test 2';
     DelString(Strings,1);
     for I:=Low(Strings) to High(Strings) do
          WriteLn(Strings[I]); // will output 'Test 0' and 'Test 2' lines

Fillchar, move etc. work directly on memory. Strings (long ones) and dynamic arrays are automatically managed by Delphi, and in fact they are just pointers. Now, if you change the pointer contents using FillChar or Move, this can result in access violations, memory leaks, and invalid pointer errors. The code supplied by Alone does not do any of this because the array is initially filled with 0's, but fillig it with anythingt else, or clearing it like this when the array has already been used, will result in the errors mentionned before, and they are very hard to track down. Also, the FillChar assumes that the array is packed (no gaps between entries), but that assumtion may be wrong, since the compiler is allowed to add empty space in order to speed up the array (the CPU works faster if the data is aligned on certain boundaries). Especially on arrays of records and other types which are not 4 bytes large or more, the compiler will move things around (same is valid for records). Therefore the code does not even guarantee to clear up the full array.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6909245
AvonWyss is right with everything he said, of course. Just one little addition: You *can* use Move/FillChar with string arrays, if you do it right, and if you know exactly what you're doing. It will be a bit faster, but it's dangerous, only recommended for pointer junkies (like me)...   :-)

Regards, Madshi.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6909349
Madshi, I didn't say you couldn't use it, I just said that it was not recommended. Right? :-)

Btw, are you smoking pointers to get high? *fg*
0
 

Author Comment

by:DelFreak
ID: 6909358
So if I don't use FillChar, my program/PC will run slower dur to the Memory issues?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6909375
No. It will run faster in this particular case.

However, the loop in DelString that copies the strings to allow for the deletion could be replaced with a Finalize() call, a move, and a typecasted nil assignment for a little speed gain. But I don't think that you will be able to "feel" a difference at all, unless you do virtually nothing in your program but delete strings (in which case an array like this is anyways not a good solution).
0
 

Author Comment

by:DelFreak
ID: 6909383
Just so you know, I'm going to use this on a pure WinAPI based program. The Array will hold internal messages received and once the program has finished, it'll save everything in the Array to a text file for logging/debugging purposes.  Faster processing time and less memory issues is a definite issue. What would be the best way to use an Array of String?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6909397
If you are doing nothing but appending messages, you should consider using a file directly and write to it sequentially.
0
 

Author Comment

by:DelFreak
ID: 6909557
Hmmm... I have considered this. But since my program also uses these messages to process requests, etc. I thought it best to add each message to an Array of String as it is received and processed and then save them to a file when everything is done. Will this be a problem?
0
 
LVL 20

Accepted Solution

by:
Madshi earned 150 total points
ID: 6909856
>> Madshi, I didn't say you couldn't use it, I just said that it was not recommended. Right? :-)

Right. That's why I rated your comments as "right in everything"...   (-:

DelFreak, if speed is a big issue for you, you should use an approach like the following to avoid as much reallocations as possible. I'm using such constructions myself quite often:

type
  TDynamicStringArray = record
    size     : integer;
    capacity : integer;
    items    : array of string;
  end;

procedure DSA_AddItem(var sa: TDynamicStringArray; item: string);
begin
  if sa.size = sa.capacity then begin
    if sa.capacity = 0 then
         sa.capacity := 64;
    else sa.capacity := sa.capacity * 3 div 2;
    SetLength(sa.items, sa.capacity);
  end;
  sa.items[sa.size] := item;
  inc(sa.size);
end;

procedure DSA_DelItem(var sa: TDynamicStringArray; index: integer);
begin
  if (index > 0) and (index < sa.size) then begin
    dec(sa.size);
    sa.items[index] := '';
    if index < sa.size then begin
      Move(sa.items[index + 1], sa.items[index], (sa.size - index) * 4);
      integer(sa.items[sa.size]) := 0;
    end;
  end;
end;

Looping through the items works like this:

for i := 0 to sa.size - 1 do
  ShowMessage(sa.items[i]);

Changing a specific item works like this:

sa.items[index] := 'newString';

Regards, Madshi.

P.S: This is *not* tested, just written from my head. Hopefully it works, especially the Move construct, otherwise AvonWyss will kill me, I guess...   :-)
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6909858
P.P.S: The move construct is a bit faster, because it does not mess with the string reference counting. AvonWyss' string loop touches the string reference counting, which costs a bit of time. Not much, but a bit...
0
 

Author Comment

by:DelFreak
ID: 6912139
AvonWyss, I get an error in your code. Incompatible types String and TStringArray.

Madshi, I'll try your code.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6912253
DelFreak, yet there is a typo:

     Strings(High(Strings)):=Item;
should of course read
     Strings[High(Strings)]:=Item;

Sorry about that.
0
 

Author Comment

by:DelFreak
ID: 6912570
Oh yeah! I missed that as well. Thanks! I'll try it again.
0
 

Author Comment

by:DelFreak
ID: 6914031
Hi! I was trying out your codes and then it hit me while trying to save the strings in the array... the function SaveToFile is in classes.pas under TStrings. Can you please add file saving capabilities to your codes. Please! I'm really sorry for this.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6914044
Code to save to a text file:

var
     OutFile: Text;
     I: Integer;
     Strings: TStringArray;

[...]
     AssignFile(OutFile,'FileName.txt');
     RewriteFile(OutFile);
     for I:=Low(Strings) to High(Strings) do
          WriteLn(OutFile,Strings[I]);
     CloseFile(OutFile);
[...]

That should do the trick.
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 

Author Comment

by:DelFreak
ID: 6915310
Okay! I know I posted a question about adding/deleting, editing strings from a dynamic array to replace TStringList. I forgot to broaden my question a bit and now I am stuck. I know it isn't fair to make additional questions but I'm afraid I have to. If there are any objections, I can add more points. Just say so. Here's what I really need.

1. Load a text file and place it into a dynamic array.
2. Add/Edit/Delete the neccessary strings.
3. Save it back into the same text file without deleting the old/un-edited strings.

Basically, just like a TStringList heh. Thanks!
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6916263
Well. Part 2 and 3 you already have, right? So, what you need, is just the loading part. Something like this:

var
     InFile: Text;
     S: string;
     Strings: TStringArray;

[...]
     AssignFile(InFile,'FileName.txt');
     ResetFile(InFile);
     while not EOF(InFile) do begin
          ReadLn(InFile,S);
          AddString(Strings,S);
     end;
     CloseFile(InFile);
[...]
0
 

Author Comment

by:DelFreak
ID: 6923558
Work's great! Now, how will I be able to split the points between you and Madshi?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6923581
I'd suggest to split (or award) them so to reflect whose code was most useful to you.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6923631
If you're using AvonWyss' code, you should give the points to him. Let me just ask you one thing: You said, performance is an issue for you, so did you compare the performance of both suggestions?
0
 

Author Comment

by:DelFreak
ID: 6923662
Yes! And your code is what I'm using. But you both gave me good answers and that's why I want to give both of you points. But how can I split them? :(
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6923684
No problem for me, okay, you can't split the points yourself. But you can ask the customer service to do that for you. There is a special customer service forum. Just post a 0 point question there...

Regards, Madshi.
0
 

Author Comment

by:DelFreak
ID: 6923744
Thought of a better idea. The expert (AvonWyss or Madshi)that can give me another function to count the number of items in the Dynamic String Array will get the 150 points. Otherwise, only 100 points which can be found here: http://www.experts-exchange.com/delphi/Q.20285752.html

I hope this is okay with both of you! :)
0
 

Author Comment

by:DelFreak
ID: 6923745
Oops! I meant a function called Count that will give me the number of items in the Dynamic String Array. Something like TStringList's Count function. :)
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6923818
Well, that's only 8 characters for 50 points?    :-)

sa.Size;

Regards, Madshi.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6923821
Longer form:

function DSA_Count(const sa: TDynamicStringArray) : integer;
begin
  result := sa.size;
end;

Well, I wouldn't even care of writing a function for it, just using "sa.size" is easier/faster to type than "DSA_Count(sa)".
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6923889
Hehe madshi... wasn't very hard, was it? ;-)

Btw, DelFreak, the count of items in the unbuffered array - and any array for that matter - is to be retrieved like this:
  Count:=Length(Strings);
0
 

Author Comment

by:DelFreak
ID: 6924039
Hmmm... AvonWyss, I tried that but my program spewed out a blue screen error. That's why I thought I was doing it wrong and decided to ask for a better soultion. Do you happen to know why it did that?
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 6924051
If you were doing this on a correctly allocated dynamic string, this should really NOT give you any error. If you do get a blue screen, you are very likely being doing something wrong, but not necessarily with that particular statement. For instance, if you erase/overwrite the memory where the pointer is located, you can get such errors.
0
 

Author Comment

by:DelFreak
ID: 6924054
Thanks Madshi!
0
 

Author Comment

by:DelFreak
ID: 6924057
Hmmm... probably have to check my code again. Anyway, thanks again! If I have any more questions or problems, I'll just post them here but everything should be fine now. Or am I missing any other important TStringList functions?!
0
 

Author Comment

by:DelFreak
ID: 6927480
I decided to create a new unit with the code you guys supplied. The problem is, I get an error saying something about TMyStringList not being initialized. Could you tell me what I'm doing wrong. Thanks!

Oh! And AvonWyss, your code to load from file doesn't work. :(





unit NewStringList;

interface

uses Windows;
type
  TStringArray = record
    Size: Integer;
    Capacity: Integer;
    Items: Array of String;
  end;

  TMyStringList = class(TObject)
    List: TStringArray;
//    constructor Create; //Don't know how to do this!
//    destructor Destroy; //Don't know how to do this!
    procedure Add(Item: String);
    procedure Delete(Index: Integer);
    function Count: Integer;
    procedure LoadFromFile(FileName: String);
    procedure SaveToFile(FileName: String);
  end;

implementation

constructor TMyStringList.Create;
begin
//Don't know how to do this!
end;

destructor TMyStringList.Destroy;
begin
//Don't know how to do this!
end;

procedure TMyStringList.Add(Item: String);
begin
  if List.Size = List.Capacity then
    begin
      if List.Capacity = 0 then
        List.Capacity := 64
      else
        List.Capacity := List.Capacity * 3 div 2;

      SetLength(List.Items, List.Capacity);
    end;

  List.Items[List.Size] := Item;

  Inc(List.Size);
end;

procedure TMyStringList.Delete(Index: Integer);
begin
  if (Index > 0) and (Index < List.Size) then
    begin
      Dec(List.Size);
      List.Items[Index] := '';

      if Index < List.Size then
        begin
          Move(List.Items[Index + 1], List.Items[Index], (List.Size - Index) * 4);
          Integer(List.Items[List.Size]) := 0;
        end;
    end;
end;

function TMyStringList.Count: Integer;
begin
 Result := List.Size;
end;

procedure TMyStringList.LoadFromFile(FileName: String);
var
  InFile: Text;
  S: String;
begin
  AssignFile(InFile, FileName);
  Reset(InFile);

  while not EOF(InFile) do
    begin
      ReadLn(InFile, S);
      Add(List.Items, S); //This gives out an error!
    end;

  CloseFile(InFile);
end;

procedure TMyStringList.SaveToFile(FileName: String);
var
  OutFile: Text;
  i: Integer;
begin
  AssignFile(OutFile,FileName);
  Rewrite(OutFile);

  for i := Low(List.Items) to High(List.Items) do
    WriteLn(OutFile, List.Items[i]);

  CloseFile(OutFile);
end;

end.
0
 

Author Comment

by:DelFreak
ID: 6934003
Hi! I have posted a new question for you guys. You may view it here: http://www.experts-exchange.com/delphi/Q.20287390.html
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now