Algol60 passing integer element of array as parameter - error bad type - algol

I have following problem.
When I try to run the code with arun file.obj (I have compiled with algol.exe file)
BEGIN
INTEGER PROCEDURE fun(tab,index,lower,upper);
INTEGER tab,index,lower,upper;
BEGIN
INTEGER t;
text (1, "Start");
t := 0;
FOR index := lower STEP 1 UNTIL upper DO
t := t + tab;
fun := t;
END;
INTEGER ARRAY t[1:10];
INTEGER i,result,lower,upper;
lower := 1;
upper := 10;
FOR i := 1 STEP 1 UNTIL 10 DO
t[i] := i;
i := 1;
result := fun(t[i],i,lower,upper);
END FINISH;
I am still getting error:
ERROR 3
ADD PBASE PROC LOC
07D4 0886 1 13
083A 0842 0 115
The compiler I use is "The Rogalgol Algol60" product of RHA (Minisystems) Ltd.
Error 3 means "3 Procedure called where the actual and the formal parameter types do not match."
But I do not understand why. The reason of error is t[i] (If I change to i - it is ok).
Someone knows what I am doing wrongly?
I compile the code on the dosbox (linux)

Problem is that the index of the integer array that you're passing to your procedure isn't the same as the integer that he's expecting. I can't remember what an integer array is full of, but I guess it isn't integers... Have to admit I never use them, but can't remember why. Possibly because of limitations like this. I stick to Real arrays and EBCDIC ones.
You can almost certainly fix it by defining a new integer, j; inserting "j := t[i];" before your invocation of 'fun'; then invoking 'fun' with 'j' rather than t[i].
BTW you may want to make the array (and the 'for' loop) zero-relative. ALGOL is mostly zero-relative and I think it may save memory if you go with the flow.
Let me know if this helps....

Related

Low bound exceeds high bound & Duplicate case label if you swap'em cntCaseTbl(Z : Int64): Int64; Error in Case Table

I'm getting this error on the code below:
Low bound exceeds high bound
If you swap them, you get a different error:
Duplicate case label
What can I do?
I've tried all of them without Int64() and with Int64(), but it didn't help.
Try for yourself, errors exist no matter if you type cast or not.
There's several ways to do this below, but I want to know why this code errors the way it does:
Delphi can not use below because it can not achieve 64-bit processing on case statements.
function cntCaseTbl(Z: Int64): Int64;
begin
case Abs(Z) of
0..9: Exit(1);
10..99: Exit(2);
100..999: Exit(3);
1000..9999: Exit(4);
10000..99999: Exit(5);
100000..999999: Exit(6);
1000000..9999999: Exit(7);
10000000..99999999: Exit(8);
100000000..999999999: Exit(9);
1000000000..9999999999: Exit(10);
10000000000..99999999999: Exit(11);
100000000000..999999999999: Exit(12);
1000000000000..9999999999999: Exit(13);
10000000000000..99999999999999: Exit(14);
100000000000000..999999999999999: Exit(15);
1000000000000000..9999999999999999: Exit(16);
10000000000000000..99999999999999999: Exit(17);
100000000000000000..999999999999999999: Exit(18);
1000000000000000000..9223372036854775807: Exit(19);
end;
end;
On line 131, 10000000000 and 99999999999 are Integers that are being type-casted to Int64, and they are both outside of the range of Integer, so they are overflowing before being casted, thus causing the 1st value to be less than the 2nd value, hence the error.
Same with the other lines, too.
This is described in Delphi's documentation:
Case Statements
The case statement may provide a readable alternative to deeply nested if conditionals. A case statement has the form:
case selectorExpression of
caseList1: statement1;
...
caseListn: statementn;
end
where selectorExpression is any expression of an ordinal type smaller than 32 bits (string types and ordinals larger than 32 bits are invalid) ...
I would suggest getting rid of the case altogether and just use a loop instead, eg:
function cntCaseTbl(Z: Int64): Int64;
begin
Result := 0;
Z := Abs(Z);
while Z > 0 do begin
Inc(Result);
Z := Z div 10;
end;
end;

Is the use of "for i := x to y" well defined behaviour, if x > y?

The intuitive answer would be that the loop is never entered. And this seems to be case in all tests I could come up with. I'm still anxious and always test it before entering the loop.
Is this necessary?
No, it is not necessary.
The documentation clearly states :
for counter := initialValue to finalValue do statement
or:
for counter := initialValue downto finalValue do statement
...
If initialValue is equal to finalValue, statement is executed exactly once. If initialValue is greater than finalValue in a for...to statement, or less than finalValue in a for...downto statement, then statement is never executed.
There is no need for anxiety.
If we want to examine further what happens, let's make a few examples. Consider first :
program Project1;
{$APPTYPE CONSOLE}
var
i : integer;
begin
for i := 2 to 1 do WriteLn(i);
end.
This produces a compiler hint:
[dcc32 Hint] Project1.dpr(6): H2135 FOR or WHILE loop executes zero times - deleted
So the compiler will simply throw away a loop with constants that produce no loop iterations. It does this even with optimizations turned off - no code is produced for the loop at all.
Now let's be a bit more clever :
program Project1;
{$APPTYPE CONSOLE}
var
i, j, k : integer;
begin
j := 2;
k := 1;
for i := j to k do WriteLn(i);
end.
This actually compiles the loop. The output is as below:
Project1.dpr.8: for i := j to k do WriteLn(i);
004060E8 A1A4AB4000 mov eax,[$0040aba4] {$0040aba4 -> j = 2}
004060ED 8B15A8AB4000 mov edx,[$0040aba8] {$0040aba8 -> k = 1}
004060F3 2BD0 sub edx,eax {edx = k - j = -1}
004060F5 7C2E jl $00406125 {was k-j < 0? if yes, jmp to end.}
004060F7 42 inc edx {set up loop}
004060F8 8955EC mov [ebp-$14],edx
004060FB A3A0AB4000 mov [$0040aba0],eax
00406100 A118784000 mov eax,[$00407818] {actual looped section}
00406105 8B15A0AB4000 mov edx,[$0040aba0]
0040610B E8E8D6FFFF call #Write0Long
00406110 E8C3D9FFFF call #WriteLn
00406115 E8EECCFFFF call #_IOTest
0040611A FF05A0AB4000 inc dword ptr [$0040aba0] {update loop var}
00406120 FF4DEC dec dword ptr [ebp-$14]
00406123 75DB jnz $00406100 {loop ^ if not complete}
Project1.dpr.9: end.
00406125 E88EE1FFFF call #Halt0
So, the very first thing a loop does is to check whether it needs to execute at all. If the initial is greater than the final (for a for..to loop) then it skips straight past it entirely. It doesn't even waste the cycles to initialize the loop counter.
There are some edge-cases in which you may be surprised to discover that the code does unexpectedly enter the loop. And still other cases where you may be tempted to pre-check whether to call the loop. But before I get into those details, I want to try impress on you the importance of not pre-checking your loop with an if condition.
Every line of code, no matter how easy to understand draws attention. It's more to read and more to confirm is correct. So if it's not important, or if it's technically redundant: it's best left out.
A for loop is conceptually translated as follows:
Initialise loop index to starting value.
If iteration constraint is valid (e.g. Index <= EndValue in case of forward loop):
Perform iteration (code within loop block/statement)
Perform loop control operations (increment loop index)
Repeat 2
Otherwise continue at first instruction after loop.
The way in which Step 2 is checked, makes an extra if condition before the loop completely redundant.
So if you (or another developer) is later maintaining code with a redundant if condition, they're left to wonder:
Is the line correct?
It seems redundant; is there a special condition it's trying to handle?
If it currently serves no purpose, perhaps it was intended to prevent calling the loop on a different condition?
In simple case, redundant lines of code can create some confusion. In more complex cases, they can result in whole new sections of irrelevant code being developed; that tries to cater for irrelevant scenarios implied by legacy redundant code.
Recommendation: Stamp out redundant code as much as possible. Including redundant pre-checks for "should the loop execute at all".
The most important benefit of stamping out redundant code is that: it correctly draws attention to peculiar cases whenever special handling actually is required.
There are 2 potential pitfalls, and the first is the more dangerous one as it deals with implicit type conversion. So it may not always be easy to detect. The following code was tested on rextester using fpc, but I have verified the same issue on Delphi 2007 / 2009 in the past.
//fpc 2.6.2
program UnexpectedForIteration;
{$MODE DELPHI}
{ Ensure range-checking is off. If it's on, a run-time error
prevents potentially bad side-effects of invalid iterations.}
{$R-,H+,W+}
var
IntStart, IntEnd, IntIndex: Integer;
UIntStart, UIntEnd, UIntIndex: Cardinal;
IterCount: Integer;
begin
Writeln('Case 1');
IntStart := High(Integer) - 1;
IntEnd := -IntStart;
UIntStart := Cardinal(IntStart);
UIntEnd := Cardinal(IntEnd);
{This gives a clue why the problem occurs.}
Writeln('From: ', IntStart, ' To: ', IntEnd);
Writeln('From: ', UIntStart, ' To: ', UIntEnd, ' (unsigned)');
Writeln('Loop 1');
IterCount := 0;
for IntIndex := IntStart to IntEnd do Inc(IterCount);
Writeln(IterCount);
Writeln('Loop 2');
IterCount := 0;
{ The loop index variable is a different type to the initial &
final values. So implicit conversion takes place and:
IntEnd **as** unsigned is unexpectedly bigger than IntStart }
for UIntIndex := IntStart to IntEnd do Inc(IterCount);
Writeln(IterCount, ' {Houston we have a problem}');
Writeln();
Writeln('Case 2');
UIntStart := High(Cardinal) - 2;
UIntEnd := 2;
IntStart := Integer(UIntStart);
IntEnd := Integer(UIntEnd);
{This gives a clue why the problem occurs.}
Writeln('From: ', UIntStart, ' To: ', UIntEnd);
Writeln('From: ', IntStart, ' To: ', IntEnd, ' (signed)');
Writeln('Loop 3');
IterCount := 0;
for UIntIndex := UIntStart to UIntEnd do Inc(IterCount);
Writeln(IterCount);
Writeln('Loop 4');
IterCount := 0;
{ The loop index variable is a different type to the initial &
final values. So implicit conversion takes place and:
UIntStart **as** signed is unexpectedly less than UIntEnd }
for IntIndex := UIntStart to UIntEnd do Inc(IterCount);
Writeln(IterCount, ' {Houston we have a problem}');
end.
The output is as follows:
Case 1
From: 2147483646 To: -2147483646
From: 2147483646 To: 2147483650 (unsigned)
Loop 1
0
Loop 2
5 {Houston we have a problem}
Case 2
From: 4294967293 To: 2
From: -3 To: 2 (signed)
Loop 3
0
Loop 4
6 {Houston we have a problem}
In many cases the problem is resolved by ensuring the same types are used for loopIndex, initialValue and finalValue. As this means there won't be an implicit type conversion, and the loop will reliably iterate as the initialValue and finalValue would suggest.
It would be easier if the compiler emits appropriate warnings for implicit type conversion in for loops. Unfortunately fpc didn't; I don't recall whether Delphi 2007/2009 does; and have no idea whether any recent versions do.
However, the preferred approach would be to favour container iteration syntax (pushing responsibility for 'correct' iteration on the enumerators). E.g.: for <element> in <container> do ...;. This should not iterate empty containers provided the enumerator's methods are implemented correctly.
The only time I'd say a pre-check is worth considering is:
when for in is not feasible for some reason
and the loop index needs to be zero-based
and support large unsigned integers (High(Integer) < index < High(Cardinal))
because this leaves no space for a reliable sentinel less than all possible initial values.
Even in this case, consider using an Int64 loop index instead of if (initialValue <= finalValue) then for ....
The second pitfall involves what I would in any case consider to be a design flaw. So the problem can be avoided entirely by rather being aware of this design consideration. It is demonstrated in code that looks as follows:
if Assigned(AnObject) then
for LIndex := 0 to AnObject.Count - 1 do ...;
In this case the if condition may in fact be necessary as a result of dubious design. Certainly, if AnObject hasn't been created, you do not want to access its Count property/method. But the dubious aspect of the design is the fact that you're uncertain whether AnObject exists. Yes, you may have employed a lazy-initialisation pattern. But it doesn't change the fact that in the above code, there's no way to differentiate between: "zero iterations" because AnObject doesn't exist or because AnObject.Count = 0.
I'd like to point out that when code has many redundant if Assigned(AnObject) then (or similar) lines, it leads to one of the problems I described in section 1. Local code caters for 2 possibilities. And by extension, client code also caters for 2 possibilities. And by induction, this problem eventually leaks throughout the code-base.
The solution is to first and foremost limit the cases where existence of AnObject is uncertain.
It's much easier to ensure an empty object with Count = 0 is guaranteed to be created (typically only affecting a small number of places in code).
It's far more work to deal with the ripple effects of a large number of places where the object might not exist yet; yielding 2 possible states and code paths.
If lazy-initialisation is required, try to ensure the code surface where existence is optional is kept as small as possible.

Delphi TValueListEditor Strings prop-ed quirk?

Investigating odd behaviour of a TValueListEditor being used to generate a filter
expression for a ClientDataSet, I've traced it to a situation where if the first entry
in it apparently had nothing in the Value column, it returned #13#10 as the Value, rather than
''.
In the following, the TStringlist TL is initialized with the same contents as the ValueListEditor
Strings property has in my app. The Assert does not fail for the TStringlist, but it does for the
ValueListEditor. These results occurred with D7 and XE4.
procedure TDefaultForm.ApplyFilter;
var
i,
Max : Integer;
Key,
Value : String;
TL : TStringlist;
begin
TL := TStringlist.Create;
try
TL.Add('Country=');
TL.Add('Class=CON');
for i:= 0 to TL.Count - 1 do begin
Key := TL.Names[i];
Value := TL.Values[Key];
Assert(Value <> #13#10); // succeeds for all i
end;
Max := ValueListEditor1.RowCount;
for i:= 1 to Max do begin
Key := ValueListEditor1.Keys[i];
Value := ValueListEditor1.Values[Key];
// Value := ValueListEditor1.Strings.ValueFromIndex[i-1];
Assert(Value <> #13#10); //Fails for i = 1!
end;
finally
TL.Free;
end;
end;
Btw, the TVLE was set up entirely in the Object Inspector: I simply dragged a TVLE off the palette, clicked Strings in the OI, clicked in the LH cell and typed 'Country' (sans quotes), pressed the Down key and typed 'Class' then right-arrow and typed 'CON'.
Obviously, I could avoid this by Value := Trim(Value), but was curious where the #13#10 was coming from.
Update: Prompted by #Deltic's answer and helpful comments, I decided to re-trace my steps and added another TVLE to my form. The following extracts from the DFM are revealing:
object ValueListEditor1: TValueListEditor
Left = 16
Top = 224
Width = 306
Height = 135
KeyOptions = [keyEdit, keyAdd]
Strings.Strings = (
'Country='#13#10
'Class=CON')
TabOrder = 2
end
[...]
object ValueListEditor2: TValueListEditor
Left = 440
Top = 192
Width = 306
Height = 246
KeyOptions = [keyEdit, keyAdd]
Strings.Strings = (
'A='
'B=ValueOfB')
TabOrder = 5
end
So, with hindsight, my question really boils down to how did the #13#10 get into the DFM? And then it came back to me ...
With no previous experience of the TVLE, when I set up the form, I got stuck at the point where I needed to add a second row. I tried pressing [Enter], but that did nothing, so then I tried Ctrl-Enter and that did nothing either. But repeating the exercise now has confirmed that that's how the CR/LF got into the TVLE's Strings.
So, it seems that the answer to my q is "No, the TVLE isn't broken, but its Strings property editor
has a quirk regarding Ctrl-Enter". In other circs, I would consider deleting my q, seeing as it's at least partly caused by operator aberration, but perhaps it's better left to assist any others who trip over the same point.
Update #2 I see that my curiousity has earned me a -1. Fair enough, but I'm still inclined to leave this q & a in place, if only as an illustration of the fact that problems have deterministic causes, which can often be identified by simple things such as re-tracing one's steps, particularly with someone obviously knowledgeable looking over one's shoulder, as it were. Perhaps the down-voter would care to enlighten readers what help to future readers such a silent -1 is.
You have not shown how your value list editor is initialised, and I suspect that this is where your problem is. Behind a TValueListEditor is nothing more than a TStringList (strictly speaking a subclass of one, but the subclass doesn't change the fundamental behaviour w.r.t named values).
If your apparently empty value in the value list is yielding a value of #13#10 then it must be because that is the actual value that it has.
This simple test snippet verifies this:
var
i:Integer;
k, v:String;
begin
ed.InsertRow('Country', '', TRUE);
ed.InsertRow('Class', 'CON', TRUE);
for i:= 1 to ed.RowCount - 1 do
begin
k := ed.Keys[i];
v := ed.Values[k];
ASSERT(v <> #13#10); // Never fails
end;
end;
Where ed is a TValueListEditor on the form.
Replace the first line of code in the above snippet with this however:
ed.InsertRow('Country', #13#10, TRUE);
And the ASSERT() fails.
I suggest you investigate the initialisation of your value list editor. My guess is that it is being populated by reading from a file using a mechanism which is reading the entire line into a string, including the line end sequences, and the code that is adding the values for each read line is not stripping the #13#10 line terminators, resulting in the values being added as <name>=<value>#13#10 in each case.

Delphi - Calculate Minimum Maximum and Average in items in a list

I am building a single application to Calculate Min Max and Avg of Values in a List.
It is actually Temperatures. So I think I am Almost correct but there are 2 Errors.
var
Count, Average, Sum,i, Max, Min, K : Integer;
Temperatures : Array of Integer;
NoItems : Double;
begin
Count := 0;
Sum := 0;
Max := 0;
Min := 0;
Average := 0;
Count := lstTemp.Items.Count;
{Calculate Sum of Values in the list}
for i := 0 to Count - 1 do
Sum := Sum + StrToInt(lstTemp.Items[i]);
{Calculate Min and Max}
SetLength(Temperatures,Count);
for K:=0 to Count-1 do
Temperatures[K] := lstTemp.Items[K];
if (Temperatures[K] > Max) then
Max := Temperatures[K];
if (Temperatures[K] < Min) then
Min := Temperatures[K];
{Calculate Average}
Average := Sum / Count;
edtAvg.Text:=IntToStr(Average); //Display Average
edtAvg.Text:=IntToStr(Min); //Display Minimum Temp.
edtAvg.Text:=IntToStr(Max); //Display Maximum Temp.
end;
So the 2 Errors are
Error: Incompatible types: got "AnsiString" expected "LongInt"
This is for Average := Sum / Count;
Error: Incompatible types: got "Set Of Byte" expected "Double"
This Error is for Temperatures[K] := lstTemp.Items[K];
Any Ideas how to solve this?
Sum and Count are both Integers so I dont know why it shouldnt work!
Thanks
There is a number of problems. First, when you write
for K:=0 to Count-1 do
Temperatures[K] := lstTemp.Items[K];
if (Temperatures[K] > Max) then
Max := Temperatures[K];
if (Temperatures[K] < Min) then
Min := Temperatures[K];
you actually do
for K:=0 to Count-1 do
Temperatures[K] := lstTemp.Items[K];
if (Temperatures[K] > Max) then
Max := Temperatures[K];
if (Temperatures[K] < Min) then
Min := Temperatures[K];
which is nonsense. You want all these lines to be part of the for loop:
for K:=0 to Count-1 do
begin
Temperatures[K] := lstTemp.Items[K];
if (Temperatures[K] > Max) then
Max := Temperatures[K];
if (Temperatures[K] < Min) then
Min := Temperatures[K];
end;
Second, in order for this algorithm to work, the initial value of Min (Max) needs to be larger (smaller) than the values in the list. This might work for Max := 0, but probably not for Min := 0. You need to set Min to a very large value before you run the loop, obviously. The best value you can use is the highest-possible signed 32-bit integer value, that is, 2^31 - 1, which is the value of the MaxInt constant.
Third,
Temperatures[K] := lstTemp.Items[K];
is probably wrong. Temperatures is an array of integers, while lstTemp.Items[K] is a string (at least according to StrToInt(lstTemp.Items[i])), so you need
Temperatures[K] := StrToInt(lstTemp.Items[K]);
Fourth, you declare Average as an integer, but it needs to be a floating-point number (obviously), like real or double.
Fifth,
edtAvg.Text:=IntToStr(Average); //Display Average
edtAvg.Text:=IntToStr(Min); //Display Minimum Temp.
edtAvg.Text:=IntToStr(Max); //Display Maximum Temp.
is not techncally incorrect, but will most likely not do what you want.
Sixth, although not an error, there is no need for you to initialise Count and Average to 0. Finally, you only need a single for loop.
There is (at least in Delphi 2010 - unit Math) one function that will calculate the mean and standard deviation in one step and functions that return the minimum and maximum values in an array. BTW, Mean is the arithmetic average of all the values and is the correct term. (I copied an example that I am working on and modified to your example - it compiles at least):
type
a = array of double;
var
Temperatures : a;
Average,stddev3, Max, Min : extended;
// Compiler insists on extended for these properties
begin
Max := Math.MaxValue(Temperatures);
Min := Math.MinValue(Temperatures);
Math.MeanAndStdDev(Temperatures ,Average,stddev3);
end;
For the maximum value in an array use (it takes an array of double and returns double):
function MaxValue(const Data: array of Double): Double;
For the minimum value use the corresponding:
function MinValue(const Data: array of Double): Double;
I agree that average cannot be an integer, but there are 2 similar functions for integer arrays:
function MinIntValue(const Data: array of Integer): Integer; and
function MaxIntValue(const Data: array of Integer): Integer;
0909EM's reply was very well done, but I have a few disagreements. First, I don't believe there's a need to set any sentinel value at all; simply use the first temperature value. Second, if we put a Begin and End around every single line If statement we'd approach COBOL-like levels of English verbosity. As it is, it's a crying shame this simple problem takes so much code. Third, I would not use StrToIntDef. Remember these lines from the Zen Of Python (I don't care if you don't know Python; everyone should memorize it, at least until we get an I Ching of Intersimone):
Errors should never pass silently.
Unless explicitly silenced.
If a user passes incorrect data into the temperature stats procedure, StrToIntDef is going to silently convert these values to zeroes, an unexpected and undesired behavior. The caller is going to get back answers that they assume are ok (because of no errors), yet will have incorrect values (especially the average). It is a far better thing to let the procedure blow up so testing will reveal the incorrect input.
I'd also replace the For loops with For...in. I banged this together:
program temps;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Classes, Generics.Collections, Math;
Var
someTemps : TStringList;
Procedure TempStats(temperatures : TStringList);
Var
temps : TList<Real>;
minTemp, maxTemp, sumTemps : Real;
numTemps : Integer;
tempStr : String;
temp : Real;
avgTemp : Real;
Begin
numTemps := temperatures.Count;
If numTemps > 0 then
Begin
temps := TList<Real>.Create;
For tempStr in temperatures Do
temps.Add(StrToFloat(tempStr));
minTemp := temps[0];
maxTemp := temps[0];
sumTemps := 0;
For temp in temps Do
Begin
minTemp := Min(minTemp, temp);
maxTemp := Max(maxTemp, temp);
sumTemps := sumTemps + temp;
End;
avgTemp := sumTemps / numTemps;
WriteLn(avgTemp:0:2);
WriteLn(minTemp:0:2);
WriteLn(maxTemp:0:2);
temps.Free;
End
Else
WriteLn('No temperatures passed.');
End;
Begin
someTemps := TStringList.Create;
someTemps.AddStrings(TArray<String>.Create('72', '93', '84', '76', '82'));
TempStats(someTemps);
ReadLn;
someTemps.Clear;
TempStats(someTemps);
someTemps.Free;
ReadLn;
end.
Firstly, Consider using StrToIntDef (String To Integer with a Default value) instead of StrToInt (String to Integer) this will yield the following...
value := StrToIntDef('Abcdef', 0); // value will be zero
vs
value := StrToInt('Abcdef'); // exception
But the question is do you want integers or floating point values for your temperatures? (eg 1 or 1.6?) If you want floating point values, maybe use StrToFloatDef...
Second, I've seen lots of grads that use Delphi make this mistake, try to always use begin and end, it'll help... because it makes it really clear what you are doing inside a if/for/while and what you intend to do outside..
for i := 0 to lstTemp.Items.Count - 1 do
begin
// Sum all the items in the list
Sum := Sum + StrToIntDef(lstTemp.Items[i], 0);
end;
Next up your array is a bit pointless, the SetLength and adding items bit is OK, but its not very functional, when you could just use the items in the list. All you need to do is hang onto the max and min values.
Then your last problem is that Average isn't going to be a whole integer, its going to have a fractional part. Eg. 5 divided by 2 is 2.5, not 2 and not 3. You could use trunc to return just the integer part, or change Average so that its a floating point number...
for K:=0 to lstTemp.Items.Count-1 do
begin
if (StrToIntDef(lstTemp.Items[K], 0) > Max) then
begin
Max := StrToIntDef(lstTemp.Items[K], 0);
end;
if (StrToIntDef(lstTemp.Items[K], 1000) < Min) then // note, really high number
begin
Min := StrToIntDef(lstTemp.Items[K], 1000);
end;
end;
{Calculate Average}
Average := Trunc(Sum / Count); // do you really want to trunc this? I suspect not.
if Min = 1000 then // just incase
begin
Min := 0;
end;
The final problem you will face is that your always setting the text of the same text box...
edtAvg.Text:=IntToStr(Average); //Display Average
edtMin.Text:=IntToStr(Min); //Display Minimum Temp. (I assume this is supposed to be edtMin)
edtMax.Text:=IntToStr(Max); //Display Maximum Temp. (I assume this is supposed to be edtMax)
I suppose the final improvement I'd make is noticing that you only need one for loop...
for K:=0 to lstTemp.Items.Count-1 do
begin
// Sum all the items in the list
Sum := Sum + StrToIntDef(lstTemp.Items[K], 0);
if (StrToIntDef(lstTemp.Items[K], Low(Integer)) > Max) then // A really low value
begin
Max := StrToIntDef(lstTemp.Items[K], Low(Integer));
end;
if (StrToIntDef(lstTemp.Items[K], High(Integer)) < Min) then // A really high value
begin
Min := StrToIntDef(lstTemp.Items[K], High(Integer));
end;
end;
The most important idea on how to solve this is to read your error messages properly. On a previous question you commented: "the error was saying it is an overloaded function or something". That attitude is not going to help you understand the problem. You need to read the error messages properly.
In this question you give the following description of your errors:
So the 2 Errors are Error: Incompatible types: got "AnsiString" expected "LongInt" This is for Average := Sum / Count; Error: Incompatible types: got "Set Of Byte" expected "Double" This Error is for Temperatures[K] := lstTemp.Items[K];
However, the description does not correspond to the errors you should be seeing based on the code provided.
It looks like you didn't read your errors, and just blindly started making changes in the hopes you would accidentally do something right. Because you didn't read the errors, you didn't notice that they changed. So when you came to us looking for help, you provided old errors with new code or vice-versa.
If you had actually read your error messages properly, you might have been able to solve the problem yourself. At the least, you would have been able to ask a better question with a description that actually matched the code.
Average := Sum / Count;
Average, Sum and Count are all declared as Integer. The error message you should be getting is: "Incompatible types: Integer and Extended".
If you read the error message, it should give you a clue to read up on Integer and Extended.
The problem here is that, in maths, division produces a Rational number. And correspondingly the result of a division operation in a program is not an Integer. So you need to declare Average as either Double or Extended.
Temperatures[K] := lstTemp.Items[K];
Temperatures is declared as an array of Integer. You haven't shown the declaration of lstTemp, but based on other code it's one of the standard Delphi Controls that has Items declared as TStrings. So the error message you should be getting is: "Incompatible types: Integer and string".
If you read the error message, it should give you a clue to do the same thing you did 5 lines earlier.
The reason for this error is that Delphi is a "strongly typed" language. The compiler tries to prevent you from making certain kinds of mistakes because it is much better catch them early. Imagine what might happen if one of the values in lstTemp were 'Hello'. That cannot be converted to an Integer; and would cause a "run-time" error in your program.
To fix this problem you need to tell the compiler: "I know the value is a string and could be any string, but I want you to convert it to an Integer". You do this by calling the StrToInt function. NOTE: You will still get a run time error if an invalid string is passed to the function, but by being forced to explicitly do the conversion, you can think about whether you want to do some pre-validation of your input data.
You asked about the errors reported by the compiler. That's just one kind of error you'll face when programming - and usually the easiest to resolve. You'll also encounter logic errors: where your program compiles successfully, but doesn't behave correctly. Andreas's excellent answer has covered those already, so I'll not repeat them.
However, I will give you some valuable advice. Once you've gotten over the hurdle of resolving compiler errors, and are able to do so easily - you need to as quickly as possible:
Get into the habit of testing your code thoroughly.
Learn how to use the integrated debugger.
Learn about its limitations.
Learn other debugging techniques: logging, profiling, pre- and post-condition checking.
Finally, as a response to alcalde's rant about there not being any simple functions to get Min, Max, Sum or Avg: I offer another possible implementation.
Basically the rant was about the fact that he'd far rather write something along the lines of:
begin
if (lstTemp.Count > 0) then
begin
edtMin.Text := lstTemp.Min;
edtMax.Text := lstTemp.Max;
edtAvg.Text := lstTemp.Average;
end
else
begin
ShowMessage('List is empty');
end;
end;
Obviously the above code won't compile, but with a little work we can achieve something similar.
He's perfectly right on two counts: (1) that this implementation would be cleaner, much easier to maintain and with less chance of errors. (2) Delphi doesn't provide a way to simply do that.
In fact, if you follow a top-down design approach, this might be your initial pseudo-code. You should be taught about top-down design, if not demand your money back. :)
The whole point behind the top-down-design approach is that you're looking for an ideal implementation. You're not worrying about what is/isn't there. If the current library and tools don't provide a Min function, you can write your own.
You are a programmer, you have the power!
I sometimes like to call this "wishful thinking programming". You're wishing if other things were in place, I could implement the functionality much more easily like "this". Then you go about making your wish come true.
Without further ado, here's the implementation. You will need to use the Math unit.
type
{ We will call existing functions that take TDoubleArray as input }
TDoubleArray = array of Double;
TStringsHelper = class(TStrings)
{ A useful class to help us convert TStrings into TDoubleArray }
public
class function Using(AStrings: TStrings): TStringsHelper;
function AsDoubleArray: TDoubleArray;
end;
{ TStringsHelper }
function TStringsHelper.AsDoubleArray: TDoubleArray;
var
LoopI: Integer;
begin
SetLength(Result, Count);
for LoopI := 0 to Count - 1 do
begin
Result[LoopI] := StrToFloat(Strings[LoopI]);
end;
end;
class function TStringsHelper.Using(AStrings: TStrings): TStringsHelper;
begin
Result := TStringsHelper(AStrings);
end;
var
LTemperatures: TDoubleArray;
begin
{ This code is almost the same as our "ideal" implementation }
if (lstTemp.Items.Count > 0) then
begin
LTemperatures := TStringsHelper.Using(lstTemp.Items).AsDoubleArray;
edtMin.Text := FloatToStr(MinValue(LTemperatures));
edtMin.Text := FloatToStr(MaxValue(LTemperatures));
edtMin.Text := FloatToStr(Mean(LTemperatures));
end
else
begin
ShowMessage('List is empty');
end;
end;
What values are in lstTemp.Items[i]?
I suppose the values are integers (without floating points), because you are using IntToStr.
Average cannot be an integer. Integer is a number (4 bytes) without a floating point. A simple numbers, such as 2,3,50,1500, -100
Assume that Sum = 100, and the Count = 3.
What Average will be?
So, you have to use float variable type, Double for example.
I hope it helps...

Enumerate possible set values in Delphi

I have a calculation algorithm in Delphi with a number of different options, and I need to try every combination of options to find an optimal solution.
TMyOption = (option1, option2, option3, option4);
TMyOptions = set of TMyOption;
I wondered about using an Integer loop to enumerate them:
for EnumerationInteger := 0 to 15 do begin
Options := TMyOptions(EnumerationInteger);
end;
This does not compile. What I was wondering was if there was any fairly simple method to convert from Integer to Set (most questions on the Web try to go the other way, from Set to Integer), and if so what is it?
Another possibility is to just use the Integer as a bit-field:
C_Option1 = 1;
C_Option2 = 2;
C_Option3 = 4;
C_Option4 = 8;
and then test membership with a bitwise and:
if (Options and C_Option2) > 0 then begin
...
end;
I've tried this, and it works, but it feels like working with sets would be more natural and use the type system better (even though I'm going outside the said type system to enumerate the sets).
Is there a better/safer way to enumerate all possible set combinations than enumerating the underlying integer representation?
Notes:
I know that the integer values of a set are not guaranteed in theory (though I suspect they are in practice if you don't play with the enumeration numbering).
There could be more than four options (yes, I know that it grows exponentially and if there are too many options the algorithm could take forever).
I know this question is quite old, but this is my preference since it's simple and natural to me :
function NumericToMyOptions(n: integer): TMyOptions;
var
Op: TMyOption;
begin
Result:= [];
for Op:= Low(TMyOption) to High(TMyOption) do
if n and (1 shl ord(Op)) > 0 then Include(Result, Op);
end;
Try
var EnumerationByte: Byte;
...
for EnumerationByte := 0 to 15 do begin
Options := TMyOptions(EnumerationByte);
end;
Your code does not compile because your enumeration (TMyOption) have less than 8 values, and Delphi utilize the minimum possible size (in bytes) for sets. Thus, a byte variable will work for you.
If you have a set with more than 8 but less than 16 possible elements, a Word will work (and not an integer).
For more than 16 but less than 32 a DWord variable and typecast.
For more than 32 possible elements, I think a better approach is to use an array of bytes or something like that.
500 - Internal Server Error's answer is probably the most simple.
Another approach that would less likely to break with changes to the number of options would be to declare an array of boolean, and switch them on/off. This is slower than working with pure integers though. The main advantage, you won't need to change the integer type you use, and you can use it if you have more than 32 options.
procedure DoSomething
var BoolFlags : Array[TOption] of Boolean;
I: TOption;
function GetNextFlagSet(var Bools : Array of Boolean) : Boolean;
var idx, I : Integer;
begin
idx := 0;
while Bools[idx] and (idx <= High(Bools)) do Inc(idx);
Result := idx <= High(Bools);
if Result then
for I := 0 to idx do
Bools[I] := not Bools[I];
end;
begin
for I := Low(BoolFlags) to High(BoolFlags) do BoolFlags[i] := False;
repeat
if BoolFlags[Option1] then
[...]
until not GetNextFlagSet(BoolFlags);
end;
Casting from an Integer to a Set is not possible, but Tondrej once wrote a blog article on SetToString and StringToSet that exposes what you want in the SetOrdValue method:
uses
TypInfo;
procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Byte(SetParam) := Value;
otSWord, otUWord:
Word(SetParam) := Value;
otSLong, otULong:
Integer(SetParam) := Value;
end;
end;
Your code then would become this:
for EnumerationInteger := 0 to 15 do begin
SetOrdValue(TypeInfo(TMyOptions), Options, EnumerationInteger);
end;
--jeroen
The problem is that you are trying to cast to the set type instead of the enumerated type. You can cast between integer and enumerated because both are ordinal types, but you can't cast to a set because they use bitfiels as you already noted. If you use:
for EnumerationInteger := 0 to 15 do begin
Option := TMyOption(EnumerationInteger);
end;
it would work, although is not what you want.
I had this same problem a few months ago and came to the conclusion that you can't enumerate the contents of a set in Delphi (at least in Delphi 7) because the language doesn't define such operation on a set.
Edit: It seems that you can even in D7, see coments to this answer.

Resources