[Last Call] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 368
  • Last Modified:

Array of String problem

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
DelFreak
Asked:
DelFreak
  • 17
  • 11
  • 7
  • +1
1 Solution
 
AloneCommented:
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
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
AvonWyssCommented:
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
 
MadshiCommented:
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
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
So if I don't use FillChar, my program/PC will run slower dur to the Memory issues?
0
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
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
 
AvonWyssCommented:
If you are doing nothing but appending messages, you should consider using a file directly and write to it sequentially.
0
 
DelFreakAuthor Commented:
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
 
MadshiCommented:
>> 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
 
MadshiCommented:
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
 
DelFreakAuthor Commented:
AvonWyss, I get an error in your code. Incompatible types String and TStringArray.

Madshi, I'll try your code.
0
 
AvonWyssCommented:
DelFreak, yet there is a typo:

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

Sorry about that.
0
 
DelFreakAuthor Commented:
Oh yeah! I missed that as well. Thanks! I'll try it again.
0
 
DelFreakAuthor Commented:
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
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
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
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
Work's great! Now, how will I be able to split the points between you and Madshi?
0
 
AvonWyssCommented:
I'd suggest to split (or award) them so to reflect whose code was most useful to you.
0
 
MadshiCommented:
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
 
DelFreakAuthor Commented:
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
 
MadshiCommented:
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
 
DelFreakAuthor Commented:
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
 
DelFreakAuthor Commented:
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
 
MadshiCommented:
Well, that's only 8 characters for 50 points?    :-)

sa.Size;

Regards, Madshi.
0
 
MadshiCommented:
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
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
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
 
AvonWyssCommented:
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
 
DelFreakAuthor Commented:
Thanks Madshi!
0
 
DelFreakAuthor Commented:
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
 
DelFreakAuthor Commented:
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
 
DelFreakAuthor Commented:
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

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

  • 17
  • 11
  • 7
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now