delphi array of string stringlist conversion - arrays

Is there a simple way in delphi to convert an array of strings to a tstringlist?

Once you have created the string list, you can simply call AddStrings().
Or for older versions of Delphi that do not support the AddStrings() overloads that accept arrays, you can roll your own.
function StringListFromStrings(const Strings: array of string): TStringList;
var
i: Integer;
begin
Result := TStringList.Create;
for i := low(Strings) to high(Strings) do
Result.Add(Strings[i]);
end;
Using an open array parameter affords the maximum flexibility for the caller.

For pre-generic versions of Delphi, you can use something like this:
type
TStringArray = array of string;
procedure StringListFromStrings(const StringArray: TStringArray;
const SL: TStringList);
var
// Versions of Delphi supporting for..in loops
s: string;
// Pre for..in version
// i: Integer;
begin
// TStringList should be created and passed in, so it's clear
// where it should be free'd.
Assert(Assigned(SL));
// Delphi versions with for..in support
for s in StringArray do
SL.Add(s);
// Pre for..in versions
// for i := Low(StringArray) to High(StringArray) do
// SL.Add(StringArray[i]);
end;

With recent Delphi version you can call "addStrings" method of a TStrings and TstringList classes like this:
function StringListFromStrings(const Strings: array of string): TStringList;
var
i: Integer;
begin
Result := TStringList.Create;
Result.addStrings(Strings);
end;
Yuo can find documentation about this method at:
https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Classes.TStrings.AddStrings

Related

Delphi: How to reference an array from within a class

I work with arrays and have tested the functionality within the context of a class, like:
Ttest = class
values : array of integer;
procedure doStuff;
end;
The methods, like doStuff all operate on the values array without the need to pass the array as a parameter. This suits me and it is fast.
Now I want to use this class to work with an external array, like Ttest.create(myValues) In the constructor I could copy myValues to the internal values but that would be quite a waste and moreover, at the end the copy would have to be reversed to pass the updated values back.
My question is how can I extend this class so that it can efficiently work with an external array. In pseudo code like this:
constructor create(var myValues : array of integer);
begin
address of values := address of myValues;
doSTuff;
end;
Lesson 1
In Delphi, dynamic arrays are reference types. A variable of dynamic array type contains only a pointer to the actual dynamic array heap object, and in an assignment,
A := B
where A and B are dynamic arrays of the same type, the dynamic array heap object isn't copied. The only thing that happens is that A and B will point to the same dynamic array heap object (that is, the 32- or 64-bit B pointer is copied to A) and that the reference count of the heap object is increased by 1.
Lesson 2
When you write
constructor Create(var AValues: array of Integer);
you need to realise that this, despite the appearance, isn't a dynamic array parameter, but an open array parameter.
If you explicitly want a dynamic array parameter, you need to use such a type explicitly:
constructor Create(AValues: TArray<Integer>);
By definition, TArray<Integer> = array of Integer is a dynamic array of Integers.
Please note that the language only has two types of arrays – static and dynamic; the open array concept is only about function parameters.
If you want to work with dynamic arrays, taking advantage of their nature as reference types, I would suggest you use dynamic array parameters. Then the only thing that is passed to the function (the constructor in this case) is the pointer to the heap object. And the heap object's reference count is increased, of course.
Lesson 3 – An example
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
After SetLength, Arr points to a dynamic array heap object of reference count 1. You can see it in your RAM (press Ctrl+Alt+E, then Ctrl+G and goto Arr[0]). When you enter Test, the reference count is increased to 2 because both Arr and A refer to it. When you leave Test, the reference count is reduced back to 1 because A goes out of scope: now again only Arr refers to it.
Lesson 4
Now, a “gotcha”: if you change the number of elements of a dynamic array, it needs to be reallocated (*). Hence, a new dynamic array heap object is created with a reference count of 1, and the old object has its reference count decreased by 1 (and removed if it becomes zero).
Thus, while the previous example works as expected, the following will not:
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
The SetLength will create a new dynamic array heap object with refcount 1 and copy half of the old array into this, put the new address in the local A parameter, and Test will transform this new array, not touching the old one which the global Arr variable points to. The reference count of the original heap object is decreased by one.
However, if you use a var parameter,
procedure Test(var A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
it will work as before. This effectively works with Arr. If we had increased the number of elements, the array would probably have been reallocated and the global Arr variable would have been updated with the new address.
Conclusion
As long as you don't need to reallocate the memory (change the number of elements), Delphi already gives you what you want, because dynamic arrays are reference types. If you do need to reallocate, at least now you know enough of the technical details in order to reason about it.
Update: Hence, the suggestion is to do
type
TTest = class
FData: TArray<Integer>;
constructor Create(AData: TArray<Integer>);
procedure Enlarge;
procedure Shrink;
procedure ShowSum;
end;
{ TTest }
constructor TTest.Create(AData: TArray<Integer>);
begin
FData := AData; // will NOT copy the array, since dynamic arrays are reference types
end;
procedure TTest.Enlarge;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := 2*FData[i];
end;
procedure TTest.ShowSum;
var
s: Integer;
i: Integer;
begin
s := 0;
for i := 0 to High(FData) do
Inc(s, FData[i]);
ShowMessage(s.ToString);
end;
procedure TTest.Shrink;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := FData[i] div 2;
end;
To test it:
procedure TForm1.FormCreate(Sender: TObject);
var
MyArray: TArray<Integer>;
t: TTest;
begin
MyArray := [1, 2, 3, 4, 5];
t := TTest.Create(MyArray);
try
t.ShowSum;
t.Enlarge;
t.ShowSum;
t.Shrink;
t.ShowSum;
finally
t.Free;
end;
end;
Footnotes
If you (1) decrease the number of elements and (2) the reference count is 1, typically the data isn't moved in memory. If the reference count is > 1, the data is always moved, because SetLength guarantees that the reference count of its argument is 1 when it returns.

How can I pass array of string a parameter to function in delphi

I have a problem with Delphi.
I wrote a function like this:
function MyFunction(arr: array of AnsiString): Boolean;
begin
//code here
end;
And now, when I pass an array of AnsiString directly into function, like this, everything works perfectly:
MyFunction(['one', 'two', 'three']);
But, when I try to store this array like this:
var arr: array of AnsiString;
procedure MyProcedure;
begin
arr[0] := ['one', 'two', 'three'];
MyFunction(arr[0]);
end;
There is a mismatch error.
I'm a beginner with Delphi, but this is really confusing.
Your second example is not functionally identical to the fist example.
The first example is fine. The function takes an open array as an input parameter, and you are constructing a fixed array of strings directly in that parameter, which is perfectly fine. Any array type can be passed to an open array parameter.
In the second example, you are declaring a dynamic array of strings, but you are not allocating any memory for the array, and you are trying to assign its first element (which is a single string) to point at a fixed array of strings. And then you are trying to pass that element (again, a single string) where an array is expected. That is why the code fails to compile.
The correct way to write your procedure would like more like this:
procedure MyProcedure;
var
arr: array of AnsiString;
begin
SetLength(arr, 3);
arr[0] := 'one';
arr[1] := 'two';
arr[2] := 'three';
MyFunction(arr);
end;
Alternatively:
procedure MyProcedure;
var
arr: array of AnsiString;
begin
arr := ['one', 'two', 'three'];
MyFunction(arr);
end;
Alternatively:
type
TAnsiStringArray = array of AnsiString;
procedure MyProcedure;
var
arr: TAnsiStringArray;
begin
arr := TAnsiStringArray.Create('one', 'two', 'three');
MyFunction(arr);
end;

How to refactor copying/moving data between arrays?

I have a process that reads data into 150+ temp arrays, process the data and copies from temp arrays into work arrays. Work arrays are in one global array so I can import multiple data, means I can repeat the same process up to 100 times and end up with set of on big array that holds 100x work data I can work with, compare and do stuff.
I have 150+ arrays, so 150 times:
// for each array
SetLength(myData[Idx].WorkNames,Length(tmpNames)); // <- prepare to copy
for i := 0 to High(tmpNames) do // <- copy
myData[Idx].WorkNames[i]:=tmpNames[i];
SetLength(tmpNames,0); // <- clear tmp array
4 lines of code for each array - 150x4 = 600 loc + initial + empty lines - around 900 loc.
Here is the example of what I do:
type
TName = record
NameID:integer;
Description:string;
end;
TItem = record
ItemID:integer;
Description:string;
Active:boolean;
end;
TWorkData = record
WorkDataType:string;
WorkNames:array of TName;
WorkItems:array of TItem;
end;
var
AllWorkData:array of TWorkData; // <- global array that has all work data - up to 100x sets of work data
tmpNames:array of TName; // <- tmp arrays before saving to work array
tmpItems:array of TItem; //
procedure TForm1.Button1Click(Sender: TObject);
var i,Idx:integer;
begin
// 1. read data into tmp arrays
ReadDataIntoTmpArrays;
ProcessTmpData;
// 2. copy tmp arrays into work data
Idx:=GetWorkDataIdx; // <- work data sequence number; start with 0
AllWorkData[Idx].WorkDataType:=GetWorkDataName(Idx);
SetLength(AllWorkData[Idx].WorkNames,Length(tmpNames));
SetLength(AllWorkData[Idx].WorkItems,Length(tmpItems));
for i := 0 to High(tmpNames) do
AllWorkData[Idx].WorkNames[i]:=tmpNames[i];
for i := 0 to High(tmpItems) do
AllWorkData[Idx].WorkItems[i]:=tmpItems[i];
// 3. clear tmp arrays
SetLength(tmpNames,0);
SetLength(tmpItems,0);
end;
Question: Is there something I can do that is easier to maintain, refactor the code?
If you really do want to copy, then do so using generics. You could derive from the TArray class of static class methods declared in System.Generics.Collections. For instance:
type
TArray = class(Generics.Collections.TArray)
public
class function Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>; overload; static;
class function Copy<T>(const Source: array of T): TArray<T>; overload; static;
end;
....
class function TArray.Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>;
var
i: Integer;
begin
SetLength(Result, Count);
for i := 0 to high(Result) do begin
Result[i] := Source[i+Index];
end;
end;
class function TArray.Copy<T>(const Source: array of T): TArray<T>;
var
i: Integer;
begin
SetLength(Result, Length(Source));
for i := 0 to high(Result) do begin
Result[i] := Source[i];
end;
end;
Note that all of the above requires you to stop using array of TMyType and instead start using the generic dynamic array TArray<TMyType>.
In your case though you are overcomplicating. Replace:
SetLength(myData[Idx].WorkNames,Length(tmpNames)); // <- prepare to copy
for i := 0 to High(tmpNames) do // <- copy
myData[Idx].WorkNames[i]:=tmpNames[i];
SetLength(tmpNames,0); // <- clear tmp array
with:
myData[Idx].WorkNames := tmpNames;
tmpNames := nil;
If you are prepared to let tmpNames simply leave scope then you can use a single line:
myData[Idx].WorkNames := tmpNames;
Although, if tmpNames is re-used for a different array, then the nil assignment is needed.
Then again, as far as I can see from the code in the question you don't need the temporary arrays at all. Why not operate directly on the long lived data structures.
These array assignments are only permissible if the source and target of the assignment are assignment compatible. Your types are not because you have used distinct types. Switch to TArray<T> to avoid that. See this question for more: Why are two seemingly identical dynamic array types deemed not assignment compatible?
Remember that dynamic arrays are reference types. In the usage shown here, all you need to do is copy the reference. You only need one instance of the actual array. So copying is not necessary at all.

How to change the values of a boolean array in delphi

I'm making a small Delphi program using Delphi XE5. In my code there is a dynamic boolean array and I'm no able to change the value of some of the arrays elements. I tried to initialize the array after setting it's length but it didn't help. Here is part of the code:
procedure DoSomething(names: array of string);
var startWithA: array of Boolean;
i: integer;
begin
SetLength(startWithA, Length(names)); // each element is false by default
for i := 0 to Length(names) - 1 do begin
if (names[i].indexOf('A') = 0) then begin
startWithA[i] := true; // the value is not changed after executing this line
end;
end;
end;
Your code works absolutely fine. Here is the proof:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
function StartsWithAIndices(const Names: array of string): TArray<Boolean>;
var
i: Integer;
begin
SetLength(Result, Length(Names));
for i := 0 to high(Result) do begin
if (Names[i].IndexOf('A') = 0) then begin
Result[i] := true;
end;
end;
end;
var
Indices: TArray<Boolean>;
b: Boolean;
begin
Indices := StartsWithAIndices(['Bob', 'Aaron', 'Aardvark', 'Jim']);
for b in Indices do begin
Writeln(BoolToStr(b, True));
end;
Readln;
end.
Output
False
True
True
False
Perhaps your confusion stems from the fact that you assign to an array that is a local variable and whose values are never read. How can you say that the array values are not modified if you never read from them? Or perhaps you have optimizations enabled and the compiler decided to optimize away the local variable whose values are written to but never read.
As an aside, your function could be written more simply like this:
function StartsWithAIndices(const Names: array of string): TArray<Boolean>;
var
i: Integer;
begin
SetLength(Result, Length(Names));
for i := 0 to high(Result) do begin
Result[i] := Names[i].StartsWith('A');
end;
end;

Create a constant array of strings

Is there a way in Delphi declaring an array of strings such as following one?
{'first','second','third'}
In XE7 you can declare a dynamic array constant like this:
const
MyArray: TArray<String> = ['First','Second','Third'];
try this
Const
Elements =3;
MyArray : array [1..Elements] of string = ('element 1','element 2','element 3');
You can use dynamic arrays and try this:
var
FMyArray: TArray<string>;
function MyArray: TArray<string>;
begin
if Length(FMyArray) = 0 then
FMyArray := TArray<string>.Create('One', 'Two', 'Three');
Result := FMyArray;
end;
While this does do a run-time initialization of a dynamic array on the heap, it also shows that Delphi supports a "pseudo-constructor" on dynamic arrays that allow in-place initialization. (NOTE: the above code isn't thread-safe).
Now all you need to do to find out the length of the array, is use the Length() standard function, or to find the allowed index range, use the Low() and High() standard functions.
If you're using an older version of Delphi, replace the TArray with your own dynamic-array string type such as:
type
TStringArray = array of string;
You can do this in a indirect way. Create a function like:
procedure assignStringArray(var rasVelden: ArrayOfString; const asVeldenIn: Array Of String);
var
iLengte, iT1: Integer;
begin
iLengte := Length(asVeldenIn);
SetLength(rasVelden, iLengte);
for iT1 := iLengte-1 downto 0 do
rasVelden[iT1] := asVeldenIn[iT1];
end;
and call this function like:
assignStringArray(asVelden, ['String1', 'String2', 'String3']);
where:
asVelden: ArrayOfString;

Resources