mathematical expression parser in Delphi? [closed] - algorithm

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 4 years ago.
Improve this question
Duplicate
Best algorithm for evaluating a mathematical expression?
Is there a built-in Delphi function which would convert a string such as '2*x+power(x,2)' or any equation to float? StrToFloat raises an exception because of the char X and power.
Thanks.

The free JCL includes TEvaluator, a parser written by one of the current Delphi compiler engineers. It will likely be far more efficient than an expression evaluator based on Windows Script Host.

Long ago (iirc 2005), some SIG did an comparison of various expression parsers. The results are at:
http://www.mindspring.com/~rbwinston/ParserTestFiles.zip
including the classic Turbo Pascal one by Renate Schaaf.
In general, the faster ones generate native code, but are unportable, and might need fixing for DEP etc.
Writing a basic one yourself isn't that hard, and a standard task in many programming courses. I wrote one in FPC/Delphi (now part of the freepascal distribution as "Symbolic") and converted it later to Java (as an exercise in Java string handling. I still wake up screaming at night sometimes).
Its SVN location is
http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/symbolic/
Note to self: I still have some unfinished code somewhere to add user definable functions and boolean arithmetic. Must finish it someday :-)

You are looking for something that can evaluate an expression.
Since Delphi is a compiled language, it does not have built-in support for that.
However, there are external tools that can help you with that.
For instance: the Free Pascal Scripting engine from RemObjects can do what you want.
--jeroen

No, it's impossible except of parsing string. And how can you convert unknown number x to float?

In our SMImport suite we wrote the own expression parser/evaluator which is based on original TFatExpression component by Gasper Kozak, gasper.kozak#email.si
Works very good.

You can use my unit, its still basic but im still writing it, it does basic bodmas right now but i will post the whole unit when i am done
Unit BODMAS;
Interface
Uses
System.SysUtils,
Math;
{
!!!!!!!!!!!!!!!!!!!!!! GLOBAL DEFINITIONS !!!!!!!!!!!!!!!!!!!!
EXPR = EXPRESSION
CURRENTPOS = POSSITION OF THE CURRENT OPPERATOR OF WHICH MATH IS BEING PERFORMED
}
Function EvalFunction(Expr: String): String;
Implementation
Function PrevOppPos(Expr: String; CurrentPos: Integer): Integer; // GETS THE PREVIOUS OPPERATOR
Var
I: Integer;
bSet: Boolean;
Begin
// THEORY
// KEEP MOVING POSITIONS DOWN FROM I ... ( MEANING < WAY IN EXPR)
// UNTIL AN OPPERATOR IS FOUND. IF NO OPPERATOR IS FOUND THE RESULT
// WILL BE THE BEGINING OF THE EXPRESSION
I := CurrentPos - 1;
bSet := False;
While ((I <= CurrentPos) AND (I >= 1)) OR (bSet = False) Do
Begin
// CHECK IF THE CHACHARACTER OF POSITION I IN EXPR IS AN OPPERATOR
// "." AND "," IS NOT AN OPPERATOR!!
If Expr[I] In ['(', ')', '+', '-', 'x', '/'] Then
Begin
Result := I;
bSet := True;
Dec(I); // Dec 1 more time to break loop
End;
Dec(I);
If (I = 0) AND (NOT(bSet)) Then
Begin
Result := 1;
bSet := True;
End;
End;
End;
Function NextOppPos(Expr: String; CurrentPos: Integer): Integer;
Var
I: Integer;
bSet: Boolean;
Begin
// THEORY
// KEEP MOVING POSITIONS UP FROM I ... ( MEANING > WAY IN EXPR)
// UNTIL AN OPPERATOR IS FOUND. IF NO OPPERATOR IS FOUND THE RESULT
// WILL BE THE LENGHT OF THE EXPRESSION
I := CurrentPos + 1;
bSet := False;
While ((I <= Length(Expr)) AND (I >= CurrentPos)) OR (bSet = False) Do
Begin
// CHECK IF THE CHACHARACTER OF POSITION I IN EXPR IS AN OPPERATOR
// "." AND "," IS NOT AN OPPERATOR!!
If Expr[I] In ['(', ')', '+', '-', 'x', '/'] Then
Begin
Result := I;
bSet := True;
Inc(I); // Inc 1 more time to break loop
End;
Inc(I);
If (I = Length(Expr) + 1) AND (NOT(bSet)) Then
Begin
Result := Length(Expr);
bSet := True;
End;
End;
End;
// EVALUATE BRACKET EXPRESSION
Function EvalBracetExpr(Expr: String): String;
Var
OppCount, I: Integer;
Ans: String;
NewExpr: String;
nOpp, pOpp, OppPos: Integer;
nExpr, pExpr: String;
Begin
Ans := '';
// EVALUATE EXPRESSION
// ALL MULTIPLICATION IN BRACKETS
While Pos('x', Expr) <> 0 Do
Begin
OppPos := Pos('x', Expr); // Opperator Position
nOpp := NextOppPos(Expr, OppPos); // Next Opperator Position
pOpp := PrevOppPos(Expr, OppPos); // Previous Opperator Position
// COPY FROM THE OPPERATOR POS TO THE LENGTH OF THE EXPRESSION - THE POSITION OF THE NEXT EXPRESSION
// When Next opperator is the length of the expression
If nOpp = Length(Expr) Then
nExpr := Copy(Expr, OppPos + 1, Length(Expr) - (Length(Expr) - 1))
Else
nExpr := Copy(Expr, OppPos + 1, Length(Expr) - nOpp);
// COPY FROM THE PREVIOUS OPPERATOR POS TO THE OPPERATOR POSITION -1
pExpr := Copy(Expr, pOpp + 1, (OppPos - 1) - pOpp);
Delete(Expr, pOpp, nOpp);
Ans := Ans + FloatToStr(StrToFloat(pExpr) * StrToFloat(nExpr));
End;
// ALL ADDITION IN BRACKETS
While Pos('+', Expr) <> 0 Do
Begin
OppPos := Pos('+', Expr); // Opperator Position
nOpp := NextOppPos(Expr, OppPos); // Next Opperator Position
pOpp := PrevOppPos(Expr, OppPos); // Previous Opperator Position
// COPY FROM THE OPPERATOR POS TO THE LENGTH OF THE EXPRESSION - THE POSITION OF THE NEXT EXPRESSION
// When Next opperator is the length of the expression
If nOpp = Length(Expr) Then
nExpr := Copy(Expr, OppPos + 1, Length(Expr) - (Length(Expr) - 1))
Else
nExpr := Copy(Expr, OppPos + 1, Length(Expr) - nOpp - 1);
// COPY FROM THE PREVIOUS OPPERATOR POS TO THE OPPERATOR POSITION -1
pExpr := Copy(Expr, pOpp + 1, (OppPos - 1) - pOpp);
Delete(Expr, pOpp, nOpp);
Ans := Ans + FloatToStr(StrToFloat(pExpr) + StrToFloat(nExpr));
End;
Result := Ans;
End;
// EVALUTE ADDITION EXPRESSION
Function EvalAddExpr(Expr: String): String;
Var
Expr1, Expr2: String;
Begin
Expr1 := Copy(Expr, 1, Pos('+', Expr) - 1);
Expr2 := Copy(Expr, Pos('+', Expr) + 1, Length(Expr));
Result := FloatToStr(StrToFloat(Expr1) + StrToFloat(Expr2));
End;
Function EvalFunction(Expr: String): String;
Var
bOPos, bCPos: Integer; // bracket Open/Closed Position
sExpr: String;
FinalExpr: String;
OppPos: Integer;
PrevOpp, NextOpp: Integer;
Begin
While Pos('(', Expr) <> 0 Do
Begin
// Find first open bracket
bOPos := Pos('(', Expr);
// Find first closed bracket
bCPos := Pos(')', Expr);
// Get the expression between the 2 brackets
sExpr := Copy(Expr, bOPos, bCPos);
// Remove sExpr from the Expression
Delete(Expr, bOPos, bCPos + 1 - bOPos);
// Concatenate the expression of what was before the bracket and that after the bracket, as well as the result in the middle
FinalExpr := Copy(Expr, 1, bOPos - 1) + EvalBracetExpr(sExpr) + Copy(Expr, bOPos, Length(Expr));
// Return the result
Expr := FinalExpr;
End;
While Pos('+', Expr) <> 0 Do
Begin
// 1) Find the first + opperator in expression
OppPos := Pos('+', Expr);
// 2) find first part of expression
PrevOpp := PrevOppPos(Expr, OppPos);
// 3) find the next part of the expression
NextOpp := NextOppPos(Expr, OppPos);
// 4) get the full expression between the opperators
//
// if prev opp <> 1 then
// move indicator 1 pos ahead
If PrevOpp <> 1 Then
Inc(PrevOpp);
// if next opp <> len of expr then
// move indicator 1 pos back
If NextOpp <> Length(Expr) Then
Dec(NextOpp);
sExpr := Copy(Expr, PrevOpp, NextOpp);
// 5) evaluating expression
Delete(Expr, PrevOpp, NextOpp);
FinalExpr := Copy(Expr, 1, PrevOpp-1) + EvalAddExpr(sExpr) + Copy(Expr, PrevOpp, Length(Expr));
End;
Result := Expr;
End;
End.
you will use the EvalFunction to return results

Related

TStringList.CustomSort: Compare() with variables

I am trying to custom sort a TStringList by a column in a .CSV file. My code below works (slowly, about 14 seconds for 200,000+ lines):
function Compare(List: TStringList; Index1, Index2: Integer): Integer;
function ColStr(const Ch: Char; const S: String; First, Last: Integer): String;
var
p1, p2: Integer;
function GetPos(const N: Integer; Start: Integer = 1): Integer;
var
I, Len, Count: Integer;
begin
Result := 0;
Len := Length(S);
if (Len = 0) or (Start > Len) or (N < 1) then Exit;
Count := 0;
for I := Start to Len do begin
if S[I] = Ch then begin
Inc(Count);
if Count = N then begin
Result := I;
Exit;
end;
end;
end;
end;
begin
p1 := GetPos(4, 1); // 4 should be a variable
p2 := GetPos(5, 1); // 5 should be a variable
if Last = 0 then Result := Copy(S, p1 + 1, length(S)) else Result := Copy(S, p1 + 1, p2 - p1 - 1);
end;
begin
Result := AnsiCompareStr(ColStr(',', List[Index1], 0, 1), ColStr(',', List[Index2], 0, 1));
end;
What I would want to do is not have this hard-coded but (where commented "should be a variable" depending on which column to sort). I know I can't use:
function Form1.Compare(List: TStringList; Index1, Index2: Integer): Integer;
for inserting variables, as I get the error:
Incompatible types: 'method pointer and regular procedure'.
I have searched through SO looking for instances of this error but cannot find one that fits my question. I would appreciate any pointers in the right direction.
This has to be done with Delphi 7 and Windows 11.
TStringList.CustomSort() does not let you pass in extra parameters, and it does not accept class methods or anonymous procedures. But, what it does do is pass the actual TStringList itself to the callback, so I would suggest deriving a new class from TStringList to add extra fields to it, and then you can access those fields inside the callback, eg:
type
TMyStringList = class(TStringList)
public
Count1: Integer;
Count2: Integer;
end;
function Compare(List: TStringList; Index1, Index2: Integer): Integer;
...
p1 := GetPos(TMyStringList(List).Count1, 1);
p2 := GetPos(TMyStringList(List).Count2, 1);
...
begin
...
end;
...
List := TMyStringList.Create;
// fill List ...
List.Count1 := ...;
List.Count2 := ...;
List.CustomSort(Compare);
So you are performing searching for k-th occurence of Ch and substring creation at every comparison.
You can optimize this process - before sorting make list/array of stringlists, created from every string, separated by needed character - use DelimitedText.
Inside compare function just work with this array and column numbers - sadly, you have to define them as global variables in current unit (for example, after Form1: TForm1)

Limiting Ord to letters only

I'm trying to create a simple Crypting method for a school project, the idea is to change a character by increasing it's ascii with a user entered number then replacing it back.
So my problem is when I do it, it works, but it also includes symboles like %$! ...etc.
What I want to do is to limit the Ord function to letters only, For example if the user entered the number 100 but there's only 26 letters in the alphabet, it will keep looping over and over through that 26 till it reaches the 100th.
Hope I'm clear enough lol
Here's what I have so far, a part of a whole code:
Procedure Crypting( Var cryptFile : Text; tempVar2 : String; pNumber: Integer);
Begin
Writeln('Enter P : ');
Readln(P);
Reset( cryptFile );
For i:= 1 to length(tempVar2) do
Write(Chr(Ord(tempVar2[i])+P));
End;
It seems like you are looking to implement the Caesar cipher.
First, you need to use an if statement to check if the current character is a letter or not. If so, you transform it; if not, you leave it as it is.
Second, it is not enough to simply add P to the character code. Although it works for A and P = 3, producing D, what will happen for Y and P = 3? You need to use modular arithmetic so you get Y → Z → A → B.
Third, in programming, it is important to structure your code well and refactor it properly. Currently, you mix input and transformation. You should keep these separate. If you create a Caesar function, you can use it every time you need to perform the Caesar cipher.
If we also need to support both capital and small letters, it is better to use a case construct instead of an if .. else if .. else construct.
Putting it all together:
function Caesar(const S: string; N: Integer): string; // slow
var
i: Integer;
begin
Result := '';
for i := 1 to Length(S) do
case S[i] of
'A'..'Z':
Result := Result + Chr(Ord('A') + (Ord(S[i]) - Ord('A') + N) mod 26);
'a'..'z':
Result := Result + Chr(Ord('a') + (Ord(S[i]) - Ord('a') + N) mod 26);
else
Result := Result + S[i];
end;
end;
This function works, but is not optimal from a performance point of view, since you need a heap allocation for every iteration. It is better to allocate the result string once and then only fill it:
function Caesar(const S: string; N: Integer): string;
var
i: Integer;
begin
SetLength(Result, Length(S));
for i := 1 to Length(S) do
case S[i] of
'A'..'Z':
Result[i] := Chr(Ord('A') + (Ord(S[i]) - Ord('A') + N) mod 26);
'a'..'z':
Result[i] := Chr(Ord('a') + (Ord(S[i]) - Ord('a') + N) mod 26);
else
Result[i] := S[i];
end;
end;
A complete example:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
function Caesar(const S: string; N: Integer): string;
var
i: Integer;
begin
SetLength(Result, Length(S));
for i := 1 to Length(S) do
case S[i] of
'A'..'Z':
Result[i] := Chr(Ord('A') + (Ord(S[i]) - Ord('A') + N) mod 26);
'a'..'z':
Result[i] := Chr(Ord('a') + (Ord(S[i]) - Ord('a') + N) mod 26);
else
Result[i] := S[i];
end;
end;
var
s: string;
N: Integer;
begin
Writeln('Please enter a string to transform:');
Readln(s);
Writeln('Please enter shift size:');
Readln(N);
Writeln;
Writeln('Result: ', Caesar(s, N));
Writeln;
Writeln('Thank you for using this program! Have a nice day!');
Writeln('Press Return to exit.');
Readln;
end.
(The precise program structure depends on the kind of Pascal you are using -- there are many different kinds of Pascal.)
Screenshot:

Read integers from a string

I'm learning algorithms and I'm trying to make an algorithm that extracts numbers lets say n in [1..100] from a string. Hopefully I get an easier algorithm.
I tried the following :
procedure ReadQuery(var t : tab); // t is an array of Integer.
var
x,v,e : Integer;
inputs : String;
begin
//readln(inputs);
inputs:='1 2 3';
j:= 1;
// make sure that there is one space between two integers
repeat
x:= pos(' ', inputs); // position of the space
delete(inputs, x, 1)
until (x = 0);
x:= pos(' ', inputs); // position of the space
while x <> 0 do
begin
x:= pos(' ', inputs); //(1) '1_2_3' (2) '2_3'
val(copy(inputs, 1, x-1), v, e); // v = value | e = error pos
t[j]:=v;
delete(inputs, 1, x); //(1) '2_3' (2) '3'
j:=j+1; //(1) j = 2 (2) j = 3
//writeln(v);
end;
//j:=j+1; // <--- The mistake were simply here.
val(inputs, v, e);
t[j]:=v;
//writeln(v);
end;
I get this result ( resolved ) :
1
2
0
3
expected :
1
2
3
PS : I'm not very advanced, so excuse me for reducing you to basics.
Thanks for everyone who is trying to share knowledge.
Your code is rather inefficient and it also doesn't work for strings containing numbers in general.
A standard and performant approach would be like this:
type
TIntArr = array of Integer;
function GetNumbers(const S: string): TIntArr;
const
AllocStep = 1024;
Digits = ['0'..'9'];
var
i: Integer;
InNumber: Boolean;
NumStartPos: Integer;
NumCount: Integer;
procedure Add(Value: Integer);
begin
if NumCount = Length(Result) then
SetLength(Result, Length(Result) + AllocStep);
Result[NumCount] := Value;
Inc(NumCount);
end;
begin
InNumber := False;
NumCount := 0;
for i := 1 to S.Length do
if not InNumber then
begin
if S[i] in Digits then
begin
NumStartPos := i;
InNumber := True;
end;
end
else
begin
if not (S[i] in Digits) then
begin
Add(StrToInt(Copy(S, NumStartPos, i - NumStartPos)));
InNumber := False;
end;
end;
if InNumber then
Add(StrToInt(Copy(S, NumStartPos)));
SetLength(Result, NumCount);
end;
This code is intentionally written in a somewhat old-fashioned Pascal way. If you are using a modern version of Delphi, you wouldn't write it like this. (Instead, you'd use a TList<Integer> and make a few other adjustments.)
Try with the following inputs:
521 cats, 432 dogs, and 1487 rabbits
1 2 3 4 5000 star 6000
alpha1beta2gamma3delta
a1024b2048cdef32
a1b2c3
32h50s
5020
012 123!
horses
(empty string)
Make sure you fully understand the algorithm! Run it on paper a few times, line by line.

Expanding string to specific length

How do I expand string of text?
I need to turn e. g. string which is 'abcde' into newstring, 'abcdeabcdeabcd', so length(newstring) is equal to length(someotherstring). Main purpose - generating keys for the Vigenere's encryption algorithm.
The following function expands a string by repeating its characters:
function RepeatString(const AText: string; ANewLength: Integer): string;
var
i: Integer;
begin
if ANewLength <= Length(AText) then
begin
Result := Copy(AText, 1, ANewLength);
Exit;
end;
SetLength(Result, ANewLength);
for i := 1 to Length(Result) do
Result[i] := AText[(i - 1) mod Length(AText) + 1];
end;
If you are using a modern Delphi version of Pascal, this can be written more neatly:
function RepeatString(const AText: string; ANewLength: Integer): string;
var
i: Integer;
begin
if ANewLength <= AText.Length then
Exit(Copy(AText, 1, ANewLength));
SetLength(Result, ANewLength);
for i := 1 to Result.Length do
Result[i] := AText[(i - 1) mod AText.Length + 1];
end;
There is a "but"
However, in your case (implementing the Vigenère cipher), it is a bad idea to use such a function. You don't need it, so you will only waste memory (and CPU usage) creating this extended version of the string.
Instead, use the original string. Instead of using chars 1, 2, 3, 4, 5, 6, 7, ... of an extended version of the string, use chars 1, 2, 3, 1, 2, 3, 1, ... of the original string.
Something like this (haven't tested fully):
function Vigenere(const AText, AKey: string): string;
var
KeyChrs: array of Byte;
n, i: Integer;
begin
n := Length(AKey);
if n = 0 then
raise Exception.Create('Vigenère key is empty.');
SetLength(KeyChrs, n);
for i := 1 to n do
if InRange(Ord(AKey[i]), Ord('A'), Ord('Z')) then
KeyChrs[i - 1] := Ord(AKey[i]) - Ord('A')
else
raise Exception.Create('Invalid character in Vigenère key. Only upper-case English letters allowed.');
SetLength(Result, Length(AText));
for i := 1 to Length(AText) do
if InRange(Ord(AText[i]), Ord('A'), Ord('Z')) then
Result[i] := Chr(Ord('A') + (Ord(AText[i]) - Ord('A') + KeyChrs[(i - 1) mod n]) mod 26)
else if InRange(Ord(AText[i]), Ord('a'), Ord('z')) then
Result[i] := Chr(Ord('a') + (Ord(AText[i]) - Ord('a') + KeyChrs[(i - 1) mod n]) mod 26)
else
Result[i] := AText[i];
end;
If you are using Lazarus or Free Pascal, use strutils.dupestring like this:
newstr:=dupestring(oldstr,4); // concatenates oldstr 4 times into dupestring
// If you need to remove n chars from the last pattern (in your example n=1), use
setlength(newstr,length(newstr)-n);

Check if bracket order is valid

what I am trying to do, is determine, whether brackets are in correct order. For example ([][[]]<<>>) is vallid, but ][]<<(>>) is not.
I got a working version, but it has terrible efficiency and when it gets 1000+ brackets, its just crazy slow. I was hoping someone might suggest some possible improvements or another way to do it.
Here is my code:
program Codex;
const
C_FNAME = 'zavorky.in';
var TmpChar : char;
leftBrackets, rightBrackets : string;
bracketPos : integer;
i,i2,i3 : integer;
Arr, empty : array [0..10000] of String[2];
tfIn : Text;
result : boolean;
begin
leftBrackets := ' ( [ /* ($ <! << ';
rightBrackets := ' ) ] */ $) !> >> ';
i := 0;
result := true;
Assign(tfIn, C_FNAME);
Reset(tfIn);
{ load data into array }
while not eof(tfIn) do
begin
while not eoln(tfIn) do
begin
read(tfIn, TmpChar);
if (TmpChar <> ' ') then begin
if (TmpChar <> '') then begin
Arr[i] := Arr[i] + TmpChar;
end
end
else
begin
i := i + 1;
end
end;
i2 := -1;
while (i2 < 10000) do begin
i2 := i2 + 1;
{if (i2 = 0) then
writeln('STARTED LOOP!');}
if (Arr[i2] <> '') then begin
bracketPos := Pos(' ' + Arr[i2] + ' ',rightBrackets);
if (bracketPos > 0) then begin
if (i2 > 0) then begin
if(bracketPos = Pos(' ' + Arr[i2-1] + ' ',leftBrackets)) then begin
{write(Arr[i2-1] + ' and ' + Arr[i2] + ' - MATCH ');}
Arr[i2-1] := '';
Arr[i2] := '';
{ reindex our array }
for i3 := i2 to 10000 - 2 do begin
Arr[i3 - 1] := Arr[i3+1];
end;
i2 := -1;
end;
end;
end;
end;
end;
{writeln('RESULT: ');}
For i2:=0 to 10 do begin
if (Arr[i2] <> '') then begin
{write(Arr[i2]);}
result := false;
end;
{else
write('M');}
end;
if (result = true) then begin
writeln('true');
end
else begin
writeln('false');
end;
result := true;
{ move to next row in file }
Arr := empty;
i := 0;
readln(tfIn);
end;
Close(tfIn);
readln;
end.
The input data in the file zavorky.in look for example like this:
<< $) >> << >> ($ $) [ ] <! ( ) !>
( ) /* << /* [ ] */ >> <! !> */
I determine for each row whether it is valid or not. Max number of brackets on a row is 10000.
You read chars from your file. File read in byte-by-byte mode is very slow. You need to optimize the way to read the strings (buffers) instead or load the file in memory first.
Hereunder I propose the other way to process the fetched string.
First I declare the consts that will state the brackets that you might have:
const
OBr: array [1 .. 5{6}] of string = ('(', '[', '/*', '<!', '<<'{, 'begin'});
CBr: array [11 .. 15{16}] of string = (')', ']', '*/', '!>', '>>'{, 'end'});
I decided to do this as now you are not limited to the length of the brackets expression and/or number of brackets' types. Every closing and corresponding opening bracket has index difference equal to 10.
And here is the code for the function:
function ExpressionIsValid(const InputStr: string): boolean;
var
BracketsArray: array of byte;
i, Offset, CurrPos: word;
Stack: array of byte;
begin
result := false;
Setlength(BracketsArray, Length(InputStr) + 1);
for i := 0 to High(BracketsArray) do
BracketsArray[i] := 0; // initialize the pos array
for i := Low(OBr) to High(OBr) do
begin
Offset := 1;
Repeat
CurrPos := Pos(OBr[i], InputStr, Offset);
if CurrPos > 0 then
begin
BracketsArray[CurrPos] := i;
Offset := CurrPos + 1;
end;
Until CurrPos = 0;
end; // insert the positions of the opening brackets
for i := Low(CBr) to High(CBr) do
begin
Offset := 1;
Repeat
CurrPos := Pos(CBr[i], InputStr, Offset);
if CurrPos > 0 then
begin
BracketsArray[CurrPos] := i;
Offset := CurrPos + 1;
end;
Until CurrPos = 0;
end; // insert the positions of the closing brackets
Setlength(Stack, 0); // initialize the stack to push/pop the last bracket
for i := 0 to High(BracketsArray) do
case BracketsArray[i] of
Low(OBr) .. High(OBr):
begin
Setlength(Stack, Length(Stack) + 1);
Stack[High(Stack)] := BracketsArray[i];
end; // there is an opening bracket
Low(CBr) .. High(CBr):
begin
if Length(Stack) = 0 then
exit(false); // we can not begin an expression with Closing bracket
if Stack[High(Stack)] <> BracketsArray[i] - 10 then
exit(false) // here we do check if the previous bracket suits the
// closing bracket
else
Setlength(Stack, Length(Stack) - 1); // remove the last opening
// bracket from stack
end;
end;
if Length(Stack) = 0 then
result := true;
end;
Perhaps, we do an extra work by creating a byte array, but it seems that this method is i) more easy to understand and ii) is flexible as we can change the length of brackets expressions for example use and check begin/end brackets etc.
Appended
As soon as I see that the major problem is in organizing block reading of file I give here an idea of how to do it:
procedure BlckRead;
var
f: file;
pc, pline: { PChar } PAnsiChar;
Ch: { Char } AnsiChar;
LngthLine, LngthPc: word;
begin
AssignFile(f, 'b:\br.txt'); //open the file
Reset(f, 1);
GetMem(pc, FileSize(f) + 1); //initialize memory blocks
inc(pc, FileSize(f)); //null terminate the string
pc^ := #0;
dec(pc, FileSize(f)); //return the pointer to the beginning of the block
GetMem(pline, FileSize(f)); //not optimal, but here is just an idea.
pline^ := #0;//set termination => length=0
BlockRead(f, pc^, FileSize(f)); // read the whole file
//you can optimize that if you wish,
//add exception catchers etc.
LngthLine := 0; // current pointers' offsets
LngthPc := 0;
repeat
repeat
Ch := pc^;
if (Ch <> #$D) and (Ch <> #$A) and (Ch <> #$0) then
begin // if the symbol is not string-terminating then we append it to pc
pline^ := Ch;
inc(pline);
inc(pc);
inc(LngthPc);
inc(LngthLine);
end
else
begin //otherwise we terminate pc with Chr($0);
pline^ := #0;
inc(LngthPc);
if LngthPc < FileSize(f) then
inc(pc);
end;
until (Ch = Chr($D)) or (Ch = Chr($A)) or (Ch = Chr($0)) or
(LngthPc = FileSize(f));
dec(pline, LngthLine);
if LngthLine > 0 then //or do other outputs
Showmessage(pline + #13#10 + Booltostr(ExpressionIsValid(pline), true));
pline^ := #0; //actually can be skipped but you know your file structure better
LngthLine := 0;
until LngthPc = FileSize(f);
FreeMem(pline); //free the blocks and close the file
dec(pc, FileSize(f) - 1);
FreeMem(pc);
CloseFile(f);
end;
You are saving all the data into memory (even couple of times) and then you have a lot of checks. I think you are on the right track but there are much easier steps you could follow.
Make an array of integers (default = 0) with length the number of brackets you have (e.g. ' ( [ /* ($ <! << ' ==> 6)
Now to make sure that you are following the requirements. Read the file line by line and take into account only the first 10000. This could help.
Every time you find an element from the first array (e.g. leftBrackets) add +1 to the value of the coresponding index of the array of step 1. Example would be:
'[' ==> checkArray[1] += 1
Do the same for rightBrackets but this time check if the value is larger than 0. If yes then subtract 1 the same way (e.g. ']' ==> checkArray[1] -= 1) otherwise you just found invalid bracket
I hope this helps and Good luck.
I think the following should work, and will be order O(n), where n is the length of the string. First build two function.
IsLeft(bra : TBracket) can determine if a bracket is a left bracket or a right bracket, so IsLeft('<') = TRUE, IsLeft('>>') = FALSE.
IsMatchingPair(bra, ket : TBracket) can determine if two brackets are of the same 'type', so IsMatchingPair('(',')') =TRUE, but IsMatchingPair('{','>>') = FALSE.
Then build a stack TBracketStack with three functions procedure Push(bra : TBracket), and function Pop : TBracket, and function IsEmpty : boolean.
Now the following algorithm should work (with a little extra code required to ensure you don't fall off the end of the string unexpectedly):
BracketError := FALSE;
while StillBracketsToProcess(BracketString) and not BracketError do
begin
bra := GetNextBracket(BracketString);
if IsLeft(bra) then
Stack.Push(bra)
else
BracketError := Stack.IsEmpty or not IsMatchingPair(Stack.Pop,bra)
end;

Resources