I am trying to make a basic Hex viewer out of a TMemo, I know this is probably not ideal but it will be only me personally using it so that does not really matter.
(1)
Firstly, suppose a Memo is filled with Hex information like so:
How could I get a count of all the text blocks shown, ignoring the white space? So using the image the result in this case would be 28.
This is what I tried and I know it is completely wrong as I am accessing the Memo lines but I don't know how to access each character.
I cant seem to solve this simple problem :(
function CountWordBlocks(Memo: TMemo): Integer;
var
i: Integer;
vCount: Integer;
begin
for i := 0 to Memo.Lines.Count - 1 do
begin
if Length(Memo.Lines.Strings[i]) = 2 then
begin
Inc(vCount);
end;
end;
Result := vCount;
end;
Here is the code I am using to display the Hex values in the Memo:
procedure ReadFileAsHex(const AFileName: string; ADestination: TStrings);
var
fs: TFileStream;
buff: Byte;
linecount: Byte;
line: string;
begin
linecount := 0;
line := '';
fs := TFileStream.Create(AFileName, fmOpenRead);
try
ADestination.BeginUpdate;
try
while fs.Position < fs.Size do
begin
fs.Read(buff, 1);
line := line + IntToHex(buff, 2) + ' ';
Inc(linecount);
if linecount = 16 then
begin
ADestination.Add(line);
line := '';
linecount := 0;
end;
end;
if Length(line) <> 0 then
ADestination.Add(line);
finally
ADestination.EndUpdate;
end;
finally
fs.Free;
end;
end;
(2)
If I click onto the Memo and a text block is under the cursor, how could I know which number the selected block is out of all the others?
So using the same first image, the caret is at the top line next to 68, so the result would be 3 as it is the third text block out of 28.
This should be so easy but I cannot think clearly, I don't have the right programming mind yet and so really struggle with basic logic and solving problems!
(3)
Finally I would like to select a block at runtime by passing a block number value. I tried this without much success:
procedure FindBlock(Memo: TMemo; BlockNumber: Integer);
var
i: Integer;
txt: string;
ThisWhite, PrevWhite: boolean;
vRead: Integer;
begin
txt := Memo.Text;
vRead:= 0;
PrevWhite := True;
for i := 1 to Length(txt) do
begin
ThisWhite := Character.IsWhiteSpace(txt[i]);
if PrevWhite and not ThisWhite then
begin
Inc(vRead);
PrevWhite := False;
end;
PrevWhite := ThisWhite;
if vRead = BlockNumber then
begin
Memo.SelStart := vRead;
Memo.SetFocus;
Exit;
end;
end;
end;
(1)
This works:
function TForm1.CountBlocks: integer;
var
i: Integer;
txt: string;
ThisWhite, PrevWhite: boolean;
begin
txt := Memo1.Text;
result:= 0;
PrevWhite := true;
for i := 1 to Length(txt) do
begin
ThisWhite := Character.IsWhiteSpace(txt[i]);
if PrevWhite and not ThisWhite then
begin
inc(result);
PrevWhite := false;
end;
PrevWhite := ThisWhite;
end;
end;
However, it can be optimized if more detailed information about the memo contents is available. For instance, if you know that each line consists of four blocks, then the number of blocks is simply 4*Memo1.Lines.Count. My code above will even accept blocks of different width.
(2)
Simply replace
for i := 1 to Length(txt) do
by
for i := 1 to Memo1.SelStart + 1 do
Since you are in control of the formatting of your lines, and the lines have a fixed format, it is very easy to calculate the number of bytes being displayed without resorting to looping through the individual lines one a time. Every line displays 3 characters per byte, and every line other than the last line displays 16 bytes, thus 48 characters per complete 16-byte line. Use those facts to your advantage to calculate the number of bytes based on the number of complete 16-byte lines present, and then you can add on the number of remaining bytes from just the last line:
function CountWordBlocks(Memo: TMemo): Integer;
var
Count: Integer;
begin
Count := Memo.Lines.Count;
if Count > 0 then
Result := (16 * (Count-1)) + (Length(Memo.Lines[Count-1]) div 3);
else
Result := 0;
end;
You can do something similar to translate a character offset within the Memo into a work block number:
function GetCurrentWordBlock(Memo: TMemo): Integer;
var
SelStart, LineStart, LineNum: Integer
begin
Result := 0;
SelStart := Memo.SelStart;
if SelStart < 0 then Exit;
LineStart := Memo.Perform(EM_LINEINDEX, SelStart, 0);
if LineStart < 0 then Exit;
LineNum := Memo.Perform(EM_LINEFROMCHAR, LineStart, 0);
Result := (16 * LineNum) + ((SelStart - LineStart) div 3) + 1;
end;
To select a given block number, you can do this:
procedure FindBlock(Memo: TMemo; BlockNumber: Integer);
var
LineNum, LineStart: Integer;
begin
if BlockNumber < 1 then Exit;
LineNum = (BlockNumber - 1) div 16;
LineStart = Memo.Perform(EM_LINEINDEX, LineNum, 0);
if LineStart < 0 then Exit;
Memo.SelStart = LineStart + (((BlockNumber - 1) - (16 * LineNum)) * 3);
Memo.SelLength := 2;
Memo.SetFocus;
end;
Related
This question already has answers here:
Converting decimal/integer to binary - how and why it works the way it does?
(6 answers)
Closed 4 years ago.
I have done some Example to convert a string to binary but i couldn't find a way to walk on each character in the string and complete the whole calculations process and then step to the next character in the string, Here is my code:
var i,j, rest, results :integer;
restResult : string;
begin
results := 1;
for i := 1 to length(stringValue) do
begin
while (results > 0) do
begin
results := ord(stringValue[i]) div 2;
rest := ord(stringValue[i]) mod 2;
restResult := restResult + inttostr(rest);
end;
end;
// Get The Rests Backwards
for i := length(restResult) downto 1 do
begin
result := result + restResult[i];
end;
The application always get into infinite loop, any suggestions?
Your results := ord(stringValue[i]) div 2; remains the same, because stringValue[i] does not change, so while loop is infinite.
To solve this mistake:
for i := 1 to length(stringValue) do
begin
t := ord(stringValue[i]);
repeat
restResult := restResult + inttostr(t mod 2);
t := t div 2;
until t = 0;
end;
But note that you cannot divide resulting string into pieces for distinct chars, because length of binary representation will vary depending on char itself.
This is example of code with fixed length for representation of char (here AnsiChar):
function AnsiStringToBinaryString(const s: AnsiString): String;
const
SBits: array[0..1] of string = ('0', '1');
var
i, k, t: Integer;
schar: string;
begin
Result := '';
for i := 1 to Length(s) do begin
t := Ord(s[i]);
schar := '';
for k := 1 to 8 * SizeOf(AnsiChar) do begin
schar := SBits[t mod 2] + schar;
t := t div 2
end;
Result := Result + schar;
end;
end;
'#A z': (division bars are mine)
01000000|01000001|00100000|01111010
# A space z
I am trying to find and replace text in a text file. I have been able to do this in the past with methods like:
procedure SmallFileFindAndReplace(FileName, Find, ReplaceWith: string);
begin
with TStringList.Create do
begin
LoadFromFile(FileName);
Text := StringReplace(Text, Find, ReplaceWith, [rfReplaceAll, rfIgnoreCase]);
SaveToFile(FileName);
Free;
end;
end;
The above works fine when a file is relatively small, however; when the the file size is something like 170 Mb the above code will cause the following error:
EOutOfMemory with message 'Out of memory'
I have tried the following with success, however it takes a long time to run:
procedure Tfrm_Main.button_MakeReplacementClick(Sender: TObject);
var
fs : TFileStream;
s : AnsiString;
//s : string;
begin
fs := TFileStream.Create(edit_SQLFile.Text, fmOpenread or fmShareDenyNone);
try
SetLength(S, fs.Size);
fs.ReadBuffer(S[1], fs.Size);
finally
fs.Free;
end;
s := StringReplace(s, edit_Find.Text, edit_Replace.Text, [rfReplaceAll, rfIgnoreCase]);
fs := TFileStream.Create(edit_SQLFile.Text, fmCreate);
try
fs.WriteBuffer(S[1], Length(S));
finally
fs.Free;
end;
end;
I am new to "Streams" and working with buffers.
Is there a better way to do this?
Thank You.
You have two mistakes in first code example and three - in second example:
Do not load whole large file in memory, especially in 32bit application. If file size more than ~1 Gb, you always get "Out of memory"
StringReplace slows with large strings, because of repeated memory reallocation
In second code you don`t use text encoding in file, so (for Windows) your code "think" that file has UCS2 encoding (two bytes per character). But what you get, if file encoding is Ansi (one byte per character) or UTF8 (variable size of char)?
Thus, for correct find&replace you must use file encoding and read/write parts of file, as LU RD said:
interface
uses
System.Classes,
System.SysUtils;
type
TFileSearchReplace = class(TObject)
private
FSourceFile: TFileStream;
FtmpFile: TFileStream;
FEncoding: TEncoding;
public
constructor Create(const AFileName: string);
destructor Destroy; override;
procedure Replace(const AFrom, ATo: string; ReplaceFlags: TReplaceFlags);
end;
implementation
uses
System.IOUtils,
System.StrUtils;
function Max(const A, B: Integer): Integer;
begin
if A > B then
Result := A
else
Result := B;
end;
{ TFileSearchReplace }
constructor TFileSearchReplace.Create(const AFileName: string);
begin
inherited Create;
FSourceFile := TFileStream.Create(AFileName, fmOpenReadWrite);
FtmpFile := TFileStream.Create(ChangeFileExt(AFileName, '.tmp'), fmCreate);
end;
destructor TFileSearchReplace.Destroy;
var
tmpFileName: string;
begin
if Assigned(FtmpFile) then
tmpFileName := FtmpFile.FileName;
FreeAndNil(FtmpFile);
FreeAndNil(FSourceFile);
TFile.Delete(tmpFileName);
inherited;
end;
procedure TFileSearchReplace.Replace(const AFrom, ATo: string;
ReplaceFlags: TReplaceFlags);
procedure CopyPreamble;
var
PreambleSize: Integer;
PreambleBuf: TBytes;
begin
// Copy Encoding preamble
SetLength(PreambleBuf, 100);
FSourceFile.Read(PreambleBuf, Length(PreambleBuf));
FSourceFile.Seek(0, soBeginning);
PreambleSize := TEncoding.GetBufferEncoding(PreambleBuf, FEncoding);
if PreambleSize <> 0 then
FtmpFile.CopyFrom(FSourceFile, PreambleSize);
end;
function GetLastIndex(const Str, SubStr: string): Integer;
var
i: Integer;
tmpSubStr, tmpStr: string;
begin
if not(rfIgnoreCase in ReplaceFlags) then
begin
i := Pos(SubStr, Str);
Result := i;
while i > 0 do
begin
i := PosEx(SubStr, Str, i + 1);
if i > 0 then
Result := i;
end;
if Result > 0 then
Inc(Result, Length(SubStr) - 1);
end
else
begin
tmpStr := UpperCase(Str);
tmpSubStr := UpperCase(SubStr);
i := Pos(tmpSubStr, tmpStr);
Result := i;
while i > 0 do
begin
i := PosEx(tmpSubStr, tmpStr, i + 1);
if i > 0 then
Result := i;
end;
if Result > 0 then
Inc(Result, Length(tmpSubStr) - 1);
end;
end;
var
SourceSize: int64;
procedure ParseBuffer(Buf: TBytes; var IsReplaced: Boolean);
var
i: Integer;
ReadedBufLen: Integer;
BufStr: string;
DestBytes: TBytes;
LastIndex: Integer;
begin
if IsReplaced and (not(rfReplaceAll in ReplaceFlags)) then
begin
FtmpFile.Write(Buf, Length(Buf));
Exit;
end;
// 1. Get chars from buffer
ReadedBufLen := 0;
for i := Length(Buf) downto 0 do
if FEncoding.GetCharCount(Buf, 0, i) <> 0 then
begin
ReadedBufLen := i;
Break;
end;
if ReadedBufLen = 0 then
raise EEncodingError.Create('Cant convert bytes to str');
FSourceFile.Seek(ReadedBufLen - Length(Buf), soCurrent);
BufStr := FEncoding.GetString(Buf, 0, ReadedBufLen);
if rfIgnoreCase in ReplaceFlags then
IsReplaced := ContainsText(BufStr, AFrom)
else
IsReplaced := ContainsStr(BufStr, AFrom);
if IsReplaced then
begin
LastIndex := GetLastIndex(BufStr, AFrom);
LastIndex := Max(LastIndex, Length(BufStr) - Length(AFrom) + 1);
end
else
LastIndex := Length(BufStr);
SetLength(BufStr, LastIndex);
FSourceFile.Seek(FEncoding.GetByteCount(BufStr) - ReadedBufLen, soCurrent);
BufStr := StringReplace(BufStr, AFrom, ATo, ReplaceFlags);
DestBytes := FEncoding.GetBytes(BufStr);
FtmpFile.Write(DestBytes, Length(DestBytes));
end;
var
Buf: TBytes;
BufLen: Integer;
bReplaced: Boolean;
begin
FSourceFile.Seek(0, soBeginning);
FtmpFile.Size := 0;
CopyPreamble;
SourceSize := FSourceFile.Size;
BufLen := Max(FEncoding.GetByteCount(AFrom) * 5, 2048);
BufLen := Max(FEncoding.GetByteCount(ATo) * 5, BufLen);
SetLength(Buf, BufLen);
bReplaced := False;
while FSourceFile.Position < SourceSize do
begin
BufLen := FSourceFile.Read(Buf, Length(Buf));
SetLength(Buf, BufLen);
ParseBuffer(Buf, bReplaced);
end;
FSourceFile.Size := 0;
FSourceFile.CopyFrom(FtmpFile, 0);
end;
how to use:
procedure TForm2.btn1Click(Sender: TObject);
var
Replacer: TFileSearchReplace;
StartTime: TDateTime;
begin
StartTime:=Now;
Replacer:=TFileSearchReplace.Create('c:\Temp\123.txt');
try
Replacer.Replace('some текст', 'some', [rfReplaceAll, rfIgnoreCase]);
finally
Replacer.Free;
end;
Caption:=FormatDateTime('nn:ss.zzz', Now - StartTime);
end;
Your first try creates several copies of the file in memory:
it loads the whole file into memory (TStringList)
it creates a copy of this memory when accessing the .Text property
it creates yet another copy of this memory when passing that string to StringReplace (The copy is the result which is built in StringReplace.)
You could try to solve the out of memory problem by getting rid of one or more of these copies:
e.g. read the file into a simple string variable rather than a TStringList
or keep the string list but run the StringReplace on each line separately and write the result to the file line by line.
That would increase the maximum file size your code can handle, but you will still run out of memory for huge files. If you want to handle files of any size, your second approach is the way to go.
No - I don't think there's a faster way that the 2nd option (if you want a completely generic search'n'replace function for any file of any size). It may be possible to make a faster version if you code it specifically according to your requirements, but as a general-purpose search'n'replace function, I don't believe you can go faster...
For instance, are you sure you need case-insensitive replacement? I would expect that this would be a large part of the time spent in the replace function. Try (just for kicks) to remove that requirement and see if it doesn't speed up the execution quite a bit on large files (this depends on how the internal coding of the StringReplace function is made - if it has a specific optimization for case-sensitive searches)
I believe refinement of Kami's code is needed to account for the string not being found, but the start of a new instance of the string might occur at the end of the buffer. The else clause is different:
if IsReplaced then begin
LastIndex := GetLastIndex(BufStr, AFrom);
LastIndex := Max(LastIndex, Length(BufStr) - Length(AFrom) + 1);
end else
LastIndex :=Length(BufStr) - Length(AFrom) + 1;
Correct fix is this one:
if IsReplaced then
begin
LastIndex := GetLastIndex(BufStr, AFrom);
LastIndex := Max(LastIndex, Length(BufStr) - Length(AFrom) + 1);
end
else
if FSourceFile.Position < SourceSize then
LastIndex := Length(BufStr) - Length(AFrom) + 1
else
LastIndex := Length(BufStr);
I have an app that needs to do heavy text manipulation in a TStringList. Basically i need to split text by a delimiter ; for instance, if i have a singe line with 1000 chars and this delimiter occurs 3 times in this line, then i need to split it in 3 lines. The delimiter can contain more than one char, it can be a tag like '[test]' for example.
I've wrote two functions to do this task with 2 different approaches, but both are slow in big amounts of text (more then 2mbytes usually).
How can i achieve this goal in a faster way ?
Here are both functions, both receive 2 paramaters : 'lines' which is the original tstringlist and 'q' which is the delimiter.
function splitlines(lines : tstringlist; q: string) : integer;
var
s, aux, ant : string;
i,j : integer;
flag : boolean;
m2 : tstringlist;
begin
try
m2 := tstringlist.create;
m2.BeginUpdate;
result := 0;
for i := 0 to lines.count-1 do
begin
s := lines[i];
for j := 1 to length(s) do
begin
flag := lowercase(copy(s,j,length(q))) = lowercase(q);
if flag then
begin
inc(result);
m2.add(aux);
aux := s[j];
end
else
aux := aux + s[j];
end;
m2.add(aux);
aux := '';
end;
m2.EndUpdate;
lines.text := m2.text;
finally
m2.free;
end;
end;
function splitLines2(lines : tstringlist; q: string) : integer;
var
aux, p : string;
i : integer;
flag : boolean;
begin
//maux1 and maux2 are already instanced in the parent class
try
maux2.text := lines.text;
p := '';
i := 0;
flag := false;
maux1.BeginUpdate;
maux2.BeginUpdate;
while (pos(lowercase(q),lowercase(maux2.text)) > 0) and (i < 5000) do
begin
flag := true;
aux := p+copy(maux2.text,1,pos(lowercase(q),lowercase(maux2.text))-1);
maux1.add(aux);
maux2.text := copy(maux2.text,pos(lowercase(q),lowercase(maux2.text)),length(maux2.text));
p := copy(maux2.text,1,1);
maux2.text := copy(maux2.text,2,length(maux2.text));
inc(i);
end;
finally
result := i;
maux1.EndUpdate;
maux2.EndUpdate;
if flag then
begin
maux1.add(p+maux2.text);
lines.text := maux1.text;
end;
end;
end;
I've not tested the speed, but for academic purposes, here's an easy way to split the strings:
myStringList.Text :=
StringReplace(myStringList.Text, myDelimiter, #13#10, [rfReplaceAll]);
// Use [rfReplaceAll, rfIgnoreCase] if you want to ignore case
When you set the Text property of TStringList, it parses on new lines and splits there, so converting to a string, replacing the delimiter with new lines, then assigning it back to the Text property works.
The problems with your code (at least second approach) are
You are constantly using lowecase which is slow if called so many times
If I saw correctly you are copying the whole remaining text back to the original source. This is sure to be extra slow for large strings (eg files)
I have a tokenizer in my library. Its not the fastest or best but it should do (you can get it from Cromis Library, just use the units Cromis.StringUtils and Cromis.Unicode):
type
TTokens = array of ustring;
TTextTokenizer = class
private
FTokens: TTokens;
FDelimiters: array of ustring;
public
constructor Create;
procedure Tokenize(const Text: ustring);
procedure AddDelimiters(const Delimiters: array of ustring);
property Tokens: TTokens read FTokens;
end;
{ TTextTokenizer }
procedure TTextTokenizer.AddDelimiters(const Delimiters: array of ustring);
var
I: Integer;
begin
if Length(Delimiters) > 0 then
begin
SetLength(FDelimiters, Length(Delimiters));
for I := 0 to Length(Delimiters) - 1 do
FDelimiters[I] := Delimiters[I];
end;
end;
constructor TTextTokenizer.Create;
begin
SetLength(FTokens, 0);
SetLength(FDelimiters, 0);
end;
procedure TTextTokenizer.Tokenize(const Text: ustring);
var
I, K: Integer;
Counter: Integer;
NewToken: ustring;
Position: Integer;
CurrToken: ustring;
begin
SetLength(FTokens, 100);
CurrToken := '';
Counter := 0;
for I := 1 to Length(Text) do
begin
CurrToken := CurrToken + Text[I];
for K := 0 to Length(FDelimiters) - 1 do
begin
Position := Pos(FDelimiters[K], CurrToken);
if Position > 0 then
begin
NewToken := Copy(CurrToken, 1, Position - 1);
if NewToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(NewToken);
Inc(Counter)
end;
CurrToken := '';
end;
end;
end;
if CurrToken <> '' then
begin
if Counter > Length(FTokens) then
SetLength(FTokens, Length(FTokens) * 2);
FTokens[Counter] := Trim(CurrToken);
Inc(Counter)
end;
SetLength(FTokens, Counter);
end;
How about just using StrTokens from the JCL library
procedure StrTokens(const S: string; const List: TStrings);
It's open source
http://sourceforge.net/projects/jcl/
As an additional option, you can use regular expressions. Recent versions of Delphi (XE4 and XE5) come with built in regular expression support; older versions can find a free regex library download (zip file) at Regular-Expressions.info.
For the built-in regex support (uses the generic TArray<string>):
var
RegexObj: TRegEx;
SplitArray: TArray<string>;
begin
SplitArray := nil;
try
RegexObj := TRegEx.Create('\[test\]'); // Your sample expression. Replace with q
SplitArray := RegexObj.Split(Lines, 0);
except
on E: ERegularExpressionError do begin
// Syntax error in the regular expression
end;
end;
// Use SplitArray
end;
For using TPerlRegEx in earlier Delphi versions:
var
Regex: TPerlRegEx;
m2: TStringList;
begin
m2 := TStringList.Create;
try
Regex := TPerlRegEx.Create;
try
Regex.RegEx := '\[test\]'; // Using your sample expression - replace with q
Regex.Options := [];
Regex.State := [preNotEmpty];
Regex.Subject := Lines.Text;
Regex.SplitCapture(m2, 0);
finally
Regex.Free;
end;
// Work with m2
finally
m2.Free;
end;
end;
(For those unaware, the \ in the sample expression used are because the [] characters are meaningful in regular expressions and need to be escaped to be used in the regular expression text. Typically, they're not required in the text.)
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;
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.