TextFile ReWrite error in Lazarus - lazarus

I have two files - one contains a number of short strings (that I call Items) and the other contains the IDs for these short strings. I also have a number of other files, each of which contains a very long string that is broken up into thousands of smaller strings by means of line breaks. I need to find the number of times each short string ("Item") occurs in each of the large string files, as well as the starting and ending position for each hit.
My first task is to remove the line breaks in the large files so that the hits do not straddle across two or more smaller strings, and then find the number of occurrences and start and end positions of each short string in the large strings, and report these results using the ID numbers for each short string.
I am getting a EInOutError (Access Denied) error apparently at the ReWrite(ResultFile) line (in the bottom half of the second procedure in the code below). Not sure why. I can see the file created for the results of the first long string, but it is blank. Here is the code; pretty simple and straightforward, actually. Thanks.
procedure TForm1.OpenStrCmdBtnClick(Sender: TObject);
begin
if OpenDialog1.execute then OpenStrEditBx.Text := OpenDialog1.FileName
else ShowMessage('Cannot open file!');
end;
procedure TForm1.FindItemfCmdBtnClick(Sender: TObject);
var
I, LenItem, NumTimes, offset: integer;
Extension, FileName, Dir, Str, Item, ID: string;
TempList, IDList, ItemList: TStringList;
searchResult: TSearchRec;
ResultFile: TextFile;
begin
IDList := TStringList.Create;
ItemList := TStringList.Create;
IDList.LoadFromFile('D:\...\IDs.txt');
ItemList.LoadFromFile('D:\...\Items.txt');
try
Dir := ExtractFilePath(OpenStrEditBx.Text);
//concatenate each line in each str sequence to get full string
if FindFirst(Dir + '*.txt', faAnyFile, searchResult) = 0 then
repeat
Extension := ExtractFileExt(OpenStrEditBx.Text);
FileName := StringReplace(searchResult.Name, Extension, '', [rFReplaceAll, rFIgnoreCase]);//remove file extension to get only file name
TempList := TStringList.Create;
TempList.LoadFromFile(Dir + searchResult.Name);
Str := '';
for I := 1 to TempList.Count-1 do // I <> 0 to ignore ID in first line
begin
Str := Str + TempList[I];
ProgressBar1.Position := (ProgressBar1.Position + 10) mod ProgressBar1.Max;
end;
TempList.Free;
//Find number and location of occurrences of each Item
for I := 0 to ItemList.Count-1 do
begin
Item := ItemList[I];
LenItem := Length(Item);
ID := IDList[I];
NumTimes := 0;
Offset := PosEx(Item, Str, 1);
AssignFile(ResultFile, 'D:\...\' + FileName + '.txt');
ReWrite(ResultFile);
while Offset <> 0 do
begin
inc(NumTimes);
if NumTimes > 0 then
WriteLn(ResultFile, 'The ' + IntToStr(NumTimes) + 'th occurrence of ' + 'ID# ' + ID + ' is from Position# ' + IntToStr(Offset) + ' to Position# ' + IntToStr(Offset + LenItem- 1) + ' in ' + FileName);
Offset := PosEx(Item, Str, Offset + LenItem);
end;
WriteLn(ResultFile, 'ID# ' + ID + ' occurs ' + IntToStr(NumTimes) + ' number of times in ' + FileName);
end;
CloseFile(ResultFile);
ShowMessage(FileName + ' done!');
until FindNext(searchResult) <> 0;
FindClose(searchResult);
finally
IDList.Free;
ItemList.Free;
end;
end;

Related

Stuck on a loop while trying to verify characters from a text file

I have to make a program that reads some "random" strings of letters and numbers from a text file and checking if they meet some conditions that makes them a valid password.
The conditions are: -Have exactly 4 digits
-Have exactly 8 characters
-Have at least one uppercase letter and at least one lowercase letter
The program reads the file and it outputs the number of valid passwords.
This is the format of the text file:
"eR68G12a 91jY643ebjp eRty74kLh 24fG92 aj85gt32 dGb9357jKoup2 " (on a single line)
The code:
ยดยดยด
Program Ej23_version3;
var
char1,char2:char;
mayus,minus:boolean; // mayus and minus would be uppercase and lowercase respectively
cantDigitos,cantCaracteres,contrasenasValidas:integer;
datos:text;
Begin
assign(datos,'Datos_guia3_ej23.txt'); reset(datos);
contrasenasValidas := 0;
char1 := ' ';
Read(datos,char2);
while not eof(datos) do
Begin
mayus := false; minus := false; cantDigitos := 0; cantCaracteres := 0;
if (char1 = ' ') and (char2 <> ' ') then //check if its the beggining of the word
Begin
while not eof(datos) and (char2 <> ' ') do
Begin
cantCaracteres := cantCaracteres + 1;
if char2 = UPCASE(char2) then // if the character2 is equal to the uppercase version of the character2, character2 is uppercase
mayus := true
else
if (char2 in ['0'..'9']) then
cantDigitos := cantDigitos + 1
else
minus := true;
if eof(datos) then // when it reaches the end of the file, it also reads and checks the last character
if char2 = UPCASE(char2) then
mayus := true
else
if (char2 in ['0'..'9']) then
cantDigitos := cantDigitos + 1
else
minus := true;
End;
if minus and mayus and (cantDigitos = 4) and (cantCaracteres = 8) then //if all conditions are met, the password is valid and its added to the counter
contrasenasValidas := contrasenasValidas + 1;
char1 := char2; Read(datos, char2); //char2 should be an empty character by this point, so it passes that value to char1 and reads the next character
End
End;
WriteLn(contrasenasValidas);
End.
But when i run it, it just gets stuck there with only the prompt ticking
The problem is in the way you read the file (character by character).
It would be better to read it all at once, and examine all the sequences of eight characters one by one.
uses
SysUtils;
var
LFile: TextFile;
LStr, LSubStr: string;
LStartIndex: integer;
LExit: boolean;
begin
AssignFile(LFile, 'Datos_guia3_ej23.txt');
Reset(LFile);
ReadLn(LFile, LStr); // Get the whole line
LStartIndex := 1; // Search all 8 characters sequences, starting from the first character
LExit := FALSE;
repeat
LSubStr := Copy(LStr, LStartIndex, 8);
if Length(LSubStr) = 8 then
begin
// Here check other conditions
// ...
Inc(LStartIndex);
end else
LExit := TRUE;
until LExit;
CloseFile(LFile);
end.

Separating numbers in a string. Pascal

I have a problem. I'm learning Pascal for only a couple of weeks and I don't know much. I have to write a program that has to calculate something out of 3 entered numbers. The problem is all 3 of them need to be entered in one edit with spaces in between. So basically I have a string 'number number number'. How do I separate these numbers as 3 separate strings so I can convert them into Integer.
In pascal there are built-in procedures to retrieve the input from the console.
The easiest way to get numeric inputs is to use Read()/ReadLn(), which also can make the conversion from string to a numeric value:
procedure GetNumbers(var x,y,z: Integer);
begin
WriteLn('Enter three numbers separated with space and then press enter.');
ReadLn(x,y,z);
end;
Here, the ReadLn() detects three inputs separated with a space, waits for the [Enter] key and assigns the integer values to the x,y,z variables.
Using the copy function is one way. Sorry about the formatting, I can't understand how to paste code snippets properly in these answer sections.
function TMyForm.Add( anEdit : TEdit ) : integer;
var
Idx : integer;
TempString : string;
function GetNext : integer;
begin
result := result + StrToInt( copy( TempString, 1, Idx - 1 ) );
TempString := copy( TempString, Idx + 1, MAXINT );
end;
begin
result := 0;
TempString := anEdit.Text;
repeat
Idx := pos( ' ', TempString );
if Idx > 0 then
result := GetNext;
until Idx = 0;
if trim( TempString ) <> '' then
//this is the last piece of it then
result := result + StrToInt( trim( TempString ) );
end;
You need to also take care that the values entered are numbers and not letters, usually done with try..except blocks.

Sorting TStringlist and retaining original index

I have a Question about Delphi StringLists and sorting them. I am sorting a list of attributes (with duplicate entries) so I need to retain their original index before the sort. Here is a sample of what I am trying
procedure TestFind;
var
i, iIndex :integer;
slStrings : TStringlist;
begin
slStrings := TStringList.Create;
slStrings.Sorted := False;
slStrings.Add('Zebra');
slStrings.AddObject('Zebra',TObject(1));
slStrings.Add('Bat');
slStrings.AddObject('Bat',TObject(2));
slStrings.Add('Cat');
slStrings.AddObject('Cat',TObject(3));
slStrings.Add('Hat');
slStrings.AddObject('Hat',TObject(4));
slStrings.Add('Aat');
slStrings.AddObject('Aat',TObject(5));
slStrings.sorted := True;
if slStrings.Find('Zebra',iIndex) then
begin
while slStrings.Strings[iIndex] = slStrings.Strings[iIndex + 1] do
begin
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
inc(iIndex);
end;
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
end;
end;
When I run this I get a Value of 0,8 for Zebra, this makes no sense to me, I would expect a message of 1,4
I really can't work out what your code is trying to achieve, but it is accessing beyond the end of the list. To avoid that your while test can be modified like so:
while (iIndex<slStrings.Count-1)
and (slStrings.Strings[iIndex] = slStrings.Strings[iIndex + 1]) do
Your use of Objects[] will work. Values placed there are kept with their matching Strings[] values when the list is sorted.
However, if I were you I would not use a string list for this task. I would declare a record like this:
TMyRec = record
Name: string;
Index: Integer;
end;
I would hold them in a TList<TMyRec> and then sort them using a custom comparer.
I note that you add each object twice, once with an associated index, and once without. Those latter instances will get a default index value of 0. I also observe that the code you present will not execute because of the out of bounds error that I identified. Further, even when you fix that it does not give output of the form that you claim.
In other words, it appears that the code you posted is very different from the code that you are running. I've answered based on the code that you included in the question. I hope that you can accept an answer on that basis and don't expect help with the code that you have, that we cannot see. Perhaps I should just have voted to close.
Anyway, perhaps the main problem is here:
slStrings.Add('Zebra');
slStrings.AddObject('Zebra',TObject(1));
slStrings.Add('Bat');
slStrings.AddObject('Bat',TObject(2));
slStrings.Add('Cat');
slStrings.AddObject('Cat',TObject(3));
slStrings.Add('Hat');
slStrings.AddObject('Hat',TObject(4));
slStrings.Add('Aat');
slStrings.AddObject('Aat',TObject(5));
This is equivalent to:
slStrings.AddObject('Zebra',TObject(0));
slStrings.AddObject('Zebra',TObject(1));
slStrings.AddObject('Bat',TObject(0));
slStrings.AddObject('Bat',TObject(2));
slStrings.AddObject('Cat',TObject(0));
slStrings.AddObject('Cat',TObject(3));
slStrings.AddObject('Hat',TObject(0));
slStrings.AddObject('Hat',TObject(4));
slStrings.AddObject('Aat',TObject(0));
slStrings.AddObject('Aat',TObject(5));
Did you actually mean to write this:
slStrings.AddObject('Zebra',TObject(1));
slStrings.AddObject('Bat',TObject(2));
slStrings.AddObject('Cat',TObject(3));
slStrings.AddObject('Hat',TObject(4));
slStrings.AddObject('Aat',TObject(5));
the Solution was this:
procedure TestFind;
var
i, iIndex, iStringSize :integer;
slStrings : TStringlist;
begin
slStrings := TStringList.Create;
slStrings.Sorted := False;
slStrings.AddObject('Zebra',TObject(1));
slStrings.AddObject('Bat',TObject(2));
slStrings.AddObject('Cat',TObject(3));
slStrings.AddObject('Hat',TObject(4));
slStrings.AddObject('Zebra',TObject(6));
slStrings.AddObject('Aat',TObject(5));
slStrings.AddObject('Zebra',TObject(7));
slStrings.sorted := True;
if slStrings.Find('Bat',iIndex) then
begin
//find lowest position of string matching found string
while iIndex > 0 do
begin
if (g_slVials.Strings[iIndex] = g_slVials.Strings[iIndex-1]) then
iIndex := iIndex - 1
else
break;
end;
iStringSize := slStrings.Count;
while iIndex < iStringSize -1 do //check for more matching strings in higher range
begin
if (g_slVials.Strings[iIndex] = g_slVials.Strings[iIndex+1]) then
begin
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
inc(iIndex);
end else
break;
end;
i := ObjectToInt(slStrings.Objects[iIndex]) ;
AddMemoData ('Stringlist Found at Position: ' + inttoStr(i) + ' Current index position is: ' + inttoStr(iIndex), false);
end;
end;
this allows me to find all matching strings and return their index position

Pascal - Writing Strange Characters

I am attempting to write a comment stripper in pascal. I run my code and pass it a C source code file and it strips the comments from the file and prints the result to terminal.
I am fairly new to pascal. I am getting some very strange output and I cannot figure out why. The code checks for comments line by line and prints characters one at a time. The comment stripper is printing what seems to be random characters whenever it reaches the start of a new line. I am using pascals Write(Str[i]) function to print characters and WriteLn() once the end of a line is reached.
I have no idea why im receiving weird output. I am running Linux Mint and can compile and run my code, but I receive this strange output. I also tried running my code on a Mac and received a run-time error:
Program Path: ./Assignment1
File Name: lol.c
Runtime error 2 at $00011532
$00011532
$0002F7F6
$000113FD
$00011328
$00000002
Here is my code
program Assignment1;
uses
Sysutils;
var UserFile : TextFile;
TString : String;
OLine : String;
i : integer;
isComment : boolean;
skip : boolean;
begin
{$I+}
WriteLn('Program Path: ', ParamStr(0));
WriteLn('File Name: ', ParamStr(1));
Assign(UserFile, ParamStr(1) + '.c');
Reset(UserFile);
isComment := false;
skip := true;
Repeat
Readln(UserFile, TString);
for i:= 0 to ((Length(TString) - 1)) do
begin
if(skip) then
begin
skip := false;
continue;
end;
if(isComment = false) Then
begin
if(TString[i] = '/') Then
begin
if(TString[i+1] = '/') Then
begin
break;
end
else if(TString[i+1] = '*') Then
begin
isComment := true;
skip := true;
continue;
end;
end;
Write(TString[i]);
if(i = Length(TString) - 1) Then
begin
Write(TString[i + 1]);
end;
end
else
begin
if(TString[i] = '*') Then
begin
if(TString[i + 1] = '/') Then
begin
isComment := false;
skip := true;
continue;
end;
end;
end;
end;
WriteLn();
Until Eof(UserFile);
end.
I receive random characters which range from standard keyboard symbols to unicode blocks such as the ones found here.
Does anyone have any suggestions?
As 500 - Internal Server Error says, Pascal strings are 1-based. Your references to slot zero are returning garbage. If these are 256-byte strings you're getting the length code, I don't recall the memory layout of the pointer-based strings to know what you're getting in that case. You're also losing the last character of every string because of this.
Beyond that I see a definite bug: Look at what happens with a line ending in /
I also do not understand this:
if(i = Length(TString) - 1) Then
begin
Write(TString[i + 1]);
end;
It seems to me it's writing an extra character but I'm not sure.

Count item frequency

Hi I'm using Delphi and I have a StringList with this items:
45
A15
015
A15
A15
45
I want to process it and make a second stringlist that will have
the number of appearance of each element:
45 [2]
015 [1]
A15 [3]
How can I do this with Delphi?
You could use a dictionary:
Frequencies := TDictionary <String, Integer>.Create;
try
// Count frequencies
for Str in StringList do
begin
if Frequencies.ContainsKey (Str) then
Frequencies [Str] := Frequencies [Str] + 1
else
Frequencies.Add (Str, 1);
end;
// Output results to console
for Str in Frequencies.Keys do
WriteLn (Str + ': ' + IntToStr (Frequencies [Str]));
finally
FreeAndNil (Frequencies);
end;
The only problem might be that the order in which the results appear is completely random and dependes on the inner working of the hash map.
Thanks to daemon_x for the full unit code:
program Project1;
{$APPTYPE CONSOLE}
uses SysUtils, Classes, Generics.Collections;
var Str: String;
StringList: TStrings;
Frequencies: TDictionary <String, Integer>;
begin
StringList := TStringList.Create;
StringList.Add('45');
StringList.Add('A15');
StringList.Add('015');
StringList.Add('A15');
StringList.Add('A15');
StringList.Add('45');
Frequencies := TDictionary <String, Integer>.Create;
try
// Count frequencies
for Str in StringList do
begin
if Frequencies.ContainsKey (Str) then
Frequencies [Str] := Frequencies [Str] + 1
else
Frequencies.Add (Str, 1);
end;
// Output results to console
for Str in Frequencies.Keys do
WriteLn (Str + ': ' + IntToStr (Frequencies [Str]));
finally
StringList.Free;
FreeAndNil(Frequencies);
end;
end.
Sort the original list,
list1.sort;
create a new list
list2:=TStringList.Create;
iterate over the sorted list to count every different item
and store the a count in the objects field of the resulting list (or if you don't use it already, just typecast the count into a pointer and store it as the object).
previtem:=list1[0];
count:=1;
for i:=1 to list1.count-1 do
begin
if list1[i]=previtem then
inc(count)
else
begin
list2.addObject(previtem,pointer(count));
previtem:=list1[i];
count:=1;
end;
end;
list2.addObject(previtem,pointer(count));
finally, iterate again to add the count to the string
for i:=0 to list2.count-1 do
list2.items[i]:=list2[i]+' ['+inttostr(list2.objects[i])+']';
I coded this on my head as I don't have Delphi installed as of now. Let me know how it works for you.
Stringlist1 is the original list with the items, stringlist2 is empty and will be used to store what you want.
for i := 0 to stringlist1.Count - 1 do
begin
if (stringlist2.Values[stringlist1[i]] = '') then
stringlist2.Values[stringlist1[i]] := '1'
else
stringlist2.Values[stringlist1[i]] :=
IntToStr(StrToInt(stringlist2.Values[stringlist1[i]]) + 1);
end;

Resources