Create an Unknown Number of Loops - delphi

this is my simple code to generate
all possible combinations of a set for
example
1,2,3:
Display:
123
132
213
231
312
321
i want to create variable number of for loops to let the user determine the length of given string...
does anyone have an idea...
thank's in advance.
type
TNumber = '0'..'9';
procedure TForm1.Button1Click(Sender: TObject);
var
Numbers: array[0..3] of TNumber;
a, b, c, d: Integer;
s: string;
begin
Numbers[0] := '1';
Numbers[1] := '8';
Numbers[2] := '7';
Numbers[3] := '2';
for a := low(Numbers) to High(Numbers) do
for b := low(Numbers) to High(Numbers) do
for c := low(Numbers) to High(Numbers) do
for d := low(Numbers) to High(Numbers) do
begin
s := Numbers[a] + Numbers[b] + Numbers[c] + Numbers[d];
if
(Occurrences('1', s) > 1 ) or
(Occurrences('8', s) > 1 ) or
(Occurrences('7', s) > 1 ) or
(Occurrences('2', s) > 1 )
then
Continue
else
Memo1.Lines.Add(s);
end;
end;
function TForm1.Occurrences(const Substring, Text: string): Integer;
var
Offset: Integer;
begin
Result := 0;
Offset := PosEx(Substring, Text, 1);
while Offset <> 0 do
begin
Inc(Result);
Offset := PosEx(Substring, Text, offset + length(Substring));
end;
end;
end.

Here is some code that produces the output you desire. You'd need to work it around a bit for your needs, but the concept expressed in this recursive solution is the important thing:
program Permuatations;
{$APPTYPE CONSOLE}
type
TElements = '1'..'3';
procedure EnumerateCombinations(const Stem: string; Len: Integer);
var
i: Integer;
el: TElements;
Used: set of TElements;
begin
if Len=0 then
exit;
Used := [];
for i := 1 to Length(Stem) do
Include(Used, Stem[i]);
for el := low(el) to high(el) do
begin
if el in Used then
continue;
if Len=1 then
Writeln(Stem+el)
else
EnumerateCombinations(Stem+el, Len-1)
end;
end;
procedure Main;
begin
EnumerateCombinations('', 1+ord(high(TElements))-ord(low(TElements)));
end;
begin
Main;
Readln;
end.
Output:
123
132
213
231
312
321
If you change the definition of TElements, for example to '1'..'4' then you will see the 24 possible permutations.

Related

How to find the numbers in the thousands, hundreds, tens, and ones place in DELPHI for an input number? For example: 155 has 5 ones, 5 tens, etc

var
iNum, iNumHun, iNumTens, iNumOnes : Integer;
begin
iNum := StrToInt(edtInput.Text);
iNumHun := iNum DIV 100;
iNumTens := iNum DIV 10;
iNumOnes = iNum DIV 1;
You are very close to a solution. Indeed, you need to iterate and divide the number by 10 on each iteration. The only thing missing is that you need to extract the least significant digit. To do this, you can use the mod operator:
program Digits;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
var
x, p: Integer;
begin
while True do
begin
Writeln('Enter a non-negative integer:');
Readln(x);
p := 1;
while x <> 0 do
begin
Writeln('position ', p, ': ', x mod 10);
x := x div 10;
Inc(p);
end;
Writeln('---');
end;
end.
Enter a non-negative integer:
155
position 1: 5
position 2: 5
position 3: 1
---
Enter a non-negative integer:
653281205
position 1: 5
position 2: 0
position 3: 2
position 4: 1
position 5: 8
position 6: 2
position 7: 3
position 8: 5
position 9: 6
---
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TSplitNumber=record
type
TDigits = -1..9;
private
data: integer;
function GetDigit(Idx: word): TDigits;
public
property digit[Idx: word] : TDigits read GetDigit;
class operator Implicit(const nr: integer): TSplitNumber;
class operator Implicit(const snr: TSplitNumber): integer;
end;
{ TSplitNumber }
function TSplitNumber.GetDigit(Idx: word): TDigits;
var
Pow :integer;
begin
Result := Low(TDigits);
Pow := round(Power10(10, Idx-2));
// position 0, impossible
// Value -1 or 0, that is the question
if (Idx = 0) or (Pow > ABS(data)) then
Exit;
Result := (data div Pow) mod 10;
Result := ABS(Result);
end;
class operator TSplitNumber.Implicit(const snr: TSplitNumber): integer;
begin
Result := snr.data;
end;
class operator TSplitNumber.Implicit(const nr: integer): TSplitNumber;
begin
Result.data := nr;
end;
var
i: integer;
q: TSplitNumber;
begin
try
i := -123;
q := i;
i := q.digit[0]; //-1
i := q.digit[1]; //3
i := q.digit[2]; //2
i := q.digit[3]; //1
i := q.digit[4]; //-1
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

TStringList enable Binary search without resorting?

I am building a stringlist from an ADO query, in the query it is much faster to return sorted results and then add them in order. this gives me an already sorted list and then calling either Sort or setting sorted true costs me time as the Quicksort algorithm does not preform well on an already sorted list. Is there some way to set the TStringList to use the Binary search without running the sort?
before you ask I don't have access to the CustomSort attribute.
I am not sure I understand what you are worried about, assuming the desired sort order of the StringList is the same as the ORDER BY of the AdoQuery.
Surely the thing to do is to set Sorted on your StringList to True while it is still empty and then insert the rows from the AdoQuery. That way, when the StringList is about to Add an entry, it will search for it using IndexOf, which will in turn use Find, which does a binary search, to check for duplicates. But using Add in this way does not involve a quicksort because the StringList is already treating itself as sorted.
In view of your comments and your own answer I ran the program below through the Line Timer profiler in NexusDB's Quality Suite. The result is that although there are detectable differences in execution speed using a binary search versus TStringList.IndexOf, they are nothing to do with the use (or not) of TStringList's QuickSort. Further, the difference is explicable by a subtle difference between how the binary search I used and the one in TStringList.Find work - see Update #2 below.
The program generates 200k 100-character strings and then inserts them into a StringList. The StringList is generated in two ways, first with Sorted set to True before any strings are added and then with Sorted set to True only after the strings have been added. StringList.IndexOf and your BinSearch is then used to look up each of the strings which has been added. The results are as follows:
Line Total Time Source
80 procedure Test;
119 0.000549 begin
120 2922.105618 StringList := GetList(True);
121 2877.101652 TestIndexOf;
122 1062.461975 TestBinSearch;
123 29.299069 StringList.Free;
124
125 2970.756283 StringList := GetList(False);
126 2943.510851 TestIndexOf;
127 1044.146265 TestBinSearch;
128 31.440766 StringList.Free;
129 end;
130
131 begin
132 Test;
133 end.
The problem I encountered is that your BinSearch never actually returns 1 and the number of failures is equal to the number of strings searched for. If you can fix this, I'll be happy to re-do the test.
program SortedStringList2;
[...]
const
Rows = 200000;
StrLen = 100;
function ZeroPad(Number : Integer; Len : Integer) : String;
begin
Result := IntToStr(Number);
if Length(Result) < Len then
Result := StringOfChar('0', Len - Length(Result)) + Result;
end;
function GetList(SortWhenEmpty : Boolean) : TStringList;
var
i : Integer;
begin
Result := TStringList.Create;
if SortWhenEmpty then
Result.Sorted := True;
for i := 1 to Rows do
Result.Add(ZeroPad(i, StrLen));
if not SortWhenEmpty then
Result.Sorted := True;
end;
Function BinSearch(slList: TStringList; sToFind : String) : integer;
var
i, j, k : integer;
begin
try
i := slList.Count div 2;
k := i;
if i = 0 then
begin
Result := -1;
// SpendLog('BinSearch List Empty, Exiting...');
exit;
end;
while slList.Strings[i] <> sToFind do
begin
if CompareText(slList.Strings[i], sToFind) < 0 then
begin
j := i;
k := k div 2;
i := i + k;
if j=i then
break;
end else
if CompareText(slList.Strings[i], sToFind) > 0 then
begin
j := i;
k := k div 2;
i := i - k;
if j=i then
break;
end else
break;
end;
if slList.Strings[i] = sToFind then
result := i
else
Result := -1;
except
//SpendLog('<BinSearch> Exception: ' + ExceptionMessage + ' At Line: ' + Analysis.LastSourcePos);
end;
end;
procedure Test;
var
i : Integer;
StringList : TStringList;
procedure TestIndexOf;
var
i : Integer;
Index : Integer;
Failures : Integer;
S : String;
begin
Failures := 0;
for i := 1 to Rows do begin
S := ZeroPad(i, StrLen);
Index := StringList.IndexOf(S);
if Index < 0 then
Inc(Failures);
end;
Assert(Failures = 0);
end;
procedure TestBinSearch;
var
i : Integer;
Index : Integer;
Failures : Integer;
S : String;
begin
Failures := 0;
for i := 1 to Rows do begin
S := ZeroPad(i, StrLen);
Index := BinSearch(StringList, S);
if Index < 0 then
Inc(Failures);
end;
//Assert(Failures = 0);
end;
begin
StringList := GetList(True);
TestIndexOf;
TestBinSearch;
StringList.Free;
StringList := GetList(False);
TestIndexOf;
TestBinSearch;
StringList.Free;
end;
begin
Test;
end.
Update I wrote my own implementation of the search algorithm in the Wikipedia article https://en.wikipedia.org/wiki/Binary_search_algorithm as follows:
function BinSearch(slList: TStringList; sToFind : String) : integer;
var
L, R, m : integer;
begin
L := 0;
R := slList.Count - 1;
if R < L then begin
Result := -1;
exit;
end;
m := (L + R) div 2;
while slList.Strings[m] <> sToFind do begin
m := (L + R) div 2;
if CompareText(slList.Strings[m], sToFind) < 0 then
L := m + 1
else
if CompareText(slList.Strings[m], sToFind) > 0 then
R := m - 1;
if L > R then
break;
end;
if slList.Strings[m] = sToFind then
Result := m
else
Result := -1;
end;
This seems to work correctly, and re-profiling the test app using this gave these results:
Line Total Time Source
113 procedure Test;
153 0.000490 begin
154 3020.588894 StringList := GetList(True);
155 2892.860499 TestIndexOf;
156 1143.722379 TestBinSearch;
157 29.612898 StringList.Free;
158
159 2991.241659 StringList := GetList(False);
160 2934.778847 TestIndexOf;
161 1113.911083 TestBinSearch;
162 30.069241 StringList.Free;
On that showing, a binary search clearly outperforms TStringList.IndexOf and contrary to my expectations it makes no real difference whether TStringList.Sorted is set to True before or after the strings are added.
Update#2 it turns out that the reason BinSearch is faster than TStringList.IndexOf is purely because BinSearch uses CompareText whereas TStringList.IndexOf uses AnsiCompareText (via .Find). If I change BinSearch to use AnsiCompareText, it becomes 1.6 times slower than TStringList.IndexOf!
I was about to suggest using an interposer class to directly change the FSorted field without calling its setter method which as a side effect calls the Sort method. But looking at the implementation of TStringList in Delphi 2007, I found that Find will always do a binary search without checking the Sorted property. This will, of course fail, if the list items aren't sorted, but in your case they are. So, as long as you remember to call Find rather than IndexOf, you don't need to do anything.
in the end I just hacked up a binary search to check the stringlist like an array:
Function BinSearch(slList: TStringList; sToFind : String) : integer;
var
i, j, k : integer;
begin
try
try
i := slList.Count div 2;
k := i;
if i = 0 then
begin
Result := -1;
SpendLog('BinSearch List Empty, Exiting...');
exit;
end;
while slList.Strings[i] <> sToFind do
begin
if CompareText(slList.Strings[i], sToFind) < 0 then
begin
j := i;
k := k div 2;
i := i + k;
if j=i then
break;
end else
if CompareText(slList.Strings[i], sToFind) > 0 then
begin
j := i;
k := k div 2;
i := i - k;
if j=i then
break;
end else
break;
end;
if slList.Strings[i] = sToFind then
result := i
else
Result := -1;
except
SpendLog('<BinSearch> Exception: ' + ExceptionMessage + ' At Line: ' + Analysis.LastSourcePos);
end;
finally
end;
end;
I'll clean this up later if needed.

Loops and increasing letter values for cells in string grid

So this could be hard to explain but i want to do a for ... := 1 to 10 do statement but i want it to be for A to N do. The main purpose of this excersise is to load data into a string grid. So lets have it load the cells 0,1 0,2 0,3 0,4 0,5 0,6 0,7 with the Letter A, B, C, D, E all the way up to 14. If anyone knows how to do this i would be extremely thankful!
Here you got it, but I'm not sure if it's a good way how to learn programming (I mean asking question as requests so that someone else write code for you):
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
StringGrid1.FixedCols := 1;
StringGrid1.ColCount := 15;
for I := 1 to 14 do
StringGrid1.Cells[I, 1] := Chr(Ord('A') + I - 1);
end;
If you want to fill the StringGrid control one row at a time, you can do
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
StringGrid1.FixedCols := 1;
StringGrid1.FixedRows := 1;
for i := 0 to Min(25, (StringGrid1.ColCount-1) * (StringGrid1.RowCount-1)) do
StringGrid1.Cells[i mod (StringGrid1.ColCount - 1) + 1,
i div (StringGrid1.ColCount - 1) + 1] := Chr(Ord('A') + i);
end;
which works no matter how many rows and cols there are.
Want to fuse TLama's answer with that "want to do a for ... := 1 to 10 do statement but i want it to be for A to N do"
Don't know if it will be pun, or enlightening.
var c: char; i: integer;
s: string;
...
i := 0; s:= EmptyStr;
for c := 'A' to 'N' do begin
s := s + c + ',';
Inc(i);
end;
SetLength(s, Length(s) - 1); // we do not need last comma there
StringGrid1.ColCount := i;
StringGrid1.Rows[0].CommaText := s;
Or the same using TStringBuilder - which would be faster than re-arranging Heap on each new string modification.
uses SysUtils;
...
var c: char; i: integer;
s: string;
...
i := 0;
with TStringBuilder.Create do try
for c := 'A' to 'N' do begin
Append(c + ',');
Inc(i);
end;
s := ToString;
finally
Free;
end;
SetLength(s, Length(s) - 1); // we do not need last comma there
StringGrid1.ColCount := i;
StringGrid1.Rows[0].CommaText := s;

how to get two different file with this procedure in deplhi

i want to get value from two file .txt, one file contain different dimension matrix with other
i have try this code:
procedure TfrmJST.ParseDelimited(const S1: TStrings; const Value: String; const Delimiter: String);
var
dx,cx: integer;
ns,ms: String;
txt: string;
delta,teta: integer;
procedure TfrmJST.ParseDelimited(const S1: TStrings; const Value: String; const Delimiter: String);
var
dx,cx: integer;
ns,ms: String;
txt: string;
delta,teta: integer;
begin
Col := 1;
Delta := Length(Delimiter);
Txt := Value+Delimiter;;
begin
while Length(Txt) > 1 do
begin
Dx := Pos(Delimiter, Txt);
Ns := Trim(Copy(Txt, 1, Dx-1));
// S1.Add('#'+Ns+'*'); //only needed for testing
if Ns <> '' then
begin
Matrix[Row,Col] := StrToFloat(Ns); //for first matrix
Inc(Col);
end;
Txt := Copy(Txt, Dx+Delta, MaxInt);
end;
end;
Col := 1;
teta := Length(delimiter);
txt := value+delimiter;
begin
while Length(txt) > 1 do
begin
cx := Pos(delimiter, txt);
ms := Copy(txt, 1, cx-1);
if ms <> '' then
begin
ref[Row,Col] := StrToFloat(ms); ///for 2nd matrix
Inc(Col);
end;
txt := Copy(txt, cx+teta, MaxInt);
end;
end;
end;
and this is initialize of matrix:
private
{ Private declarations }
Row, Col: integer;
Matrix: array[1..140,1..141] of double;
Ref: array[1..2,1..140] of double ;
this is the implementation:
begin
Temp := TStringList.Create;
MemoSL:= TStringList.Create ;
Temp.LoadFromFile('trainer.txt');
Row := 1;
for I := 0 to Temp.Count-1 do
begin
ParseDelimited(MemoSL, Trim(Temp.Strings[I]), ' ');
Inc(Row); //stackoverflow error in this line
end;
Temp.Free;
//parsing second matrix
TempList := TStringList.Create;
Templist.LoadFromFile('refbaru.txt');
row := 1;
for J := 0 to Templist.Count-1 do
begin
T := Templist[J];
ParseDelimited(Memo1.Lines, T, ' ');
Inc(row);
end;
Templist.Free;
i tried that code but give me error,
the error was stackoverflow error in line 'inc(row)' that process first matrix.
and while i gave comment out at the second function that process 2nd matrix, Temp[i] only returns 2 rows of matrix[140x141]. does it mean the code can't process two different file? and why it only return two rows of the matrix?
anyone can help me?
while Length(Txt) > 1 do
begin
Dx := Pos(Delimiter, Txt);
Ns := Trim(Copy(Txt, 1, Dx-1));
// S1.Add('#'+Ns+'*'); //only needed for testing
if Ns <> '' then
begin
Matrix[Row,Col] := StrToFloat(Ns); //for first matrix
Inc(Col);
end;
Txt := Copy(Txt, Dx+Delta, MaxInt);
end;
Looking at this piece of code I see the posibility of an endless loop: what happens if there is no Delimiter found? It will keep running and forever increase your 'col' value. Make sure to have a condition to stop your while loop if no delimeter is found.
It is pointless to look for a specific stack overflow error when many ordinary errors already exist.
If your code is clean programmed and it is still stack overflow, then of course, is time to look deeper into the code.
But first ! As long as you can see obvious errors, you should remove them.
1.) "Row" used in the same procedure on a 140 dimension array and on a only 2 dimension array.
How can that work ?
Matrix: array[1..140,1..141] of double;
Ref: array[1..2,1..140] of double ;
File 'trainer.txt' 140 Lines
File 'refbaru.txt' 2 Lines.
for I := 0 to Temp.Count-1 do // 140 lines
// ParseDelimited() will only run properly if Row < 3
// remember -> Ref: array[1..2,1..140])
// if Row > 2 , with Ref[Row,Col] := , 137 times data is overwritten.
procedure ParseDelimited(MemoSL, Trim(Temp.Strings[I]), ' ');
....
Matrix[Row,Col] := StrToFloat(Ns);
....
Ref[Row,Col] := StrToFloat(ms);
....
end;
Inc(Row);
end;
2.) If you run the second loop with refbaru.txt and the two arrays are present together in the procedure ParseDelimited(), then you overwrite 2 values of array Matrix
recommendation
make sure: Loop through trainer.txt, writes values only to the Matrix array.
make sure: Loop through refbaru.txt, writes values only to the Ref array.
Your code could look something like:
[...]
filetoload: String;
[...]
procedure TfrmJST.ParseDelimited(S1: TStrings; Value: String; const Delimiter: String);
var
f:double;
[...]
Col := 1;
txt := Value+Delimiter;
[...]
if filetoload='trainer.txt' then begin
Delta := Length(Delimiter);
while Length(txt) > 1 do
begin
Dx := Pos(Delimiter, txt);
Ns := Trim(Copy(txt, 1, Dx-1));
if Ns <> '' then
begin
if TryStrToFloat(Ns,f) then Matrix[Row,Col]:=f;
Inc(Col);
if Col > MatrixColMax then break;
txt := Copy(txt, Dx+Delta, MaxInt);
end else txt:='';
end;
end;
if filetoload='refbaru.txt' then begin
teta := Length(delimiter);
while Length(txt) > 1 do
begin
cx := Pos(delimiter, txt);
ms := Copy(txt, 1, cx-1);
if ms <> '' then
begin
if TryStrToFloat(ms,f) then Ref[Row,Col]:=f;
Inc(Col);
if Col > RefColMax then break;
txt := Copy(txt, cx+teta, MaxInt);
end else txt:='';
end;
end;
begin
[...]
filetoload:='trainer.txt';
Temp := TStringList.Create;
Temp.LoadFromFile(filetoload);
if Temp.Count > MatrixRowMax then LinesToLoad:=MatrixRowMax-1 else
LinesToLoad:=Temp.Count-1;
for I := 0 to LinesToLoad do
[...]
ParseDelimited(MemoSL, Trim(Temp.Strings[I]), ' ');
[...]
end;
filetoload:='refbaru.txt';
TempList := TStringList.Create;
TempList.LoadFromFile(filetoload);
if TempList.Count > RefRowMax then LinesToLoad:=RefRowMax-1 else
LinesToLoad:=TempList.Count-1;
for J := 0 to LinesToLoad do
[...]
ParseDelimited(Memo1.Lines, T, ' ');
[...]
end;
end;
You should also compare the linesize of the file with the size of the arrays
RefRowMax: integer;
RefColMax: integer;
MatrixRowMax: integer;
MatrixColMax: integer;
LinesToLoad: integer;
....
RefRowMax:=2;
RefColMax:=140;
MatrixRowMax:=140;
MatrixColMax:=141;
....
procedure ParseDelimited()
if filetoload='trainer.txt' then begin
[...]
Inc(Col)
if Col > MatrixColMax then break;
end;
if filetoload='refbaru.txt' then begin
[...]
Inc(Col)
if Col > RefColMax then break;
end;
You should also look for a valid value of Ns , StrToFloat(Ns) before you write to the arrays in ParseDelimited()
function TryStrToFloat(const S: string; out Value: Double): Boolean;
or
Val();
var
f:double;
....
begin
....
if TryStrToFloat(Ns,f) then Matrix[Row,Col]:=f;
....
The OP overwritting many of used data.
And when he has enough data overwritten, he gets a stack overflow error.

How to insert character in all possible positions of a string?

I want to insert a char into every possible position of s string except start and end.
e.g.
abc
I want to have
a-bc
ab-c
a-b-c
Below is my test, but not correct:
procedure TForm1.Button2Click(Sender: TObject);
var
start, i,j,k,position,loop: integer;
laststart,lastend:integer;
c,item,stem:string;
str, prefix:string;
begin
str:='abcd';
memo1.clear;
memo1.Lines.Add(str);
laststart:=0;
lastend:=memo1.lines.count-1;
position:=0;
prefix:='';
loop:=0;
while loop<=length(str)-1 do
begin
for j:= laststart to lastend do
begin
item:=memo1.lines[j];
for k:=length(item) downto 1 do
begin
if item[k]='-' then
begin
position:=j;
break;
end;
end; //for k
prefix:=copy(item,1,position);
stem:=copy(item,position+1, length(item));
for start:=1 to length(stem)-1 do
begin
c:=prefix+copy(stem,1,start)+'-'+
copy(stem, start+1,length(stem));
memo1.lines.add(c);
end;
end; //for j
laststart:=lastend+1;
lastend:=memo1.Lines.Count-1;
inc(loop);
end; //end while
end;
it outputs:
abcd
a-bcd
ab-cd
abc-d
a--bcd // not correct
a-b-cd
a-bc-d
ab--cd //incorrect
ab-c-d
abc--d //incorrect
a--bc-d //incorrect
I feel the maximum possible breaks is lenth(str)-1, abc->most possible is insert 2 '-' (twice). Is this correct?
And are there other faster ways to do it?
Thanks a lot.
Recursive version.
procedure InsertSymbols(s: string; c: Char; Position: Integer = 1);
var
i: Integer;
begin
Memo1.Lines.Add(s);
for i := Position to Length(s) - 1 do
InsertSymbols(Copy(s, 1, i) + c + Copy(s, i + 1, Length(s) - i), c, i + 2);
end;
begin
InsertSymbols('Test', '-');
end;
This works:
procedure TForm4.Button1Click(Sender: TObject);
var
S: string;
N: integer;
Marker: cardinal;
MaxMarker: cardinal;
Str: string;
i: Integer;
begin
S := Edit1.Text;
N := length(S);
Marker := 0;
MaxMarker := 1 shl (N - 1) - 1;
Memo1.Clear;
Memo1.Lines.BeginUpdate;
for Marker := 0 to MaxMarker do
begin
Str := S[1];
for i := 2 to N do
begin
if (Marker shr (N-i)) and 1 <> 0 then
Str := Str + '-';
Str := Str + S[i];
end;
Memo1.Lines.Add(Str);
end;
Memo1.Lines.EndUpdate;
end;
As you can see, it works by using binary representation of numbers:
t e s t
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
Why all the difficult solutions?
Just copy the string to a new one char by char, add hyphens in between, except for the last one.
I needed to separate a string to use as a serial number and here is the code:
Function GetDashedKey(Key: string): string
const
PartSize = 7;
var
Indx: Integer;
dashedKey : string;
begin
repeat
if Trim(dashedKey)<>'' then
dashedKey := dashedKey + ' - ';
if Length(Key) < PartSize then
begin
dashedKey := dashedKey + Key;
Key := '';
end
else
begin
dashedKey := dashedKey + Copy(Key, 1, PartSize);
Key := Copy(Key, PartSize + 1, Length(Key)-1);
end;
until Trim(Key) = '';
Result := dashedKey;
end;

Resources