Find & Extract hashtags in text - lazarus

I'm looking for an easy/quick way to identify and extract hashtags from a string, and temporarily store them separately - e.g.:
If I have the following string:
2017-08-31 This is a useless sentence being used as an example. #Example #Date:2017-09-01 #NothingWow (and then some more text for good measure).
Then I want to be able to get this:
#Example
#Date:2017-09-01
#NothingWow
I figured storing it in a TStringList should be sufficient until I'm done. I just need to store them outside of the original string for easier cross referencing, then if the original string changes, add them back at the end.
(but that's easy - its the extracting part I'm having trouble with)
It should start at the # and end/break when it encounters a [space].
The way I initially planned it was to use Boolean flags (defaulted to False), then check for the different hashtags, set them to true if found, and extract anything after a [:] separately.
(but I'm sure there is a better way of doing it)
Any advice will be greatly appreciated.

The following shows a simple console application which you could use as the basis
for a solution. It works because assigning your input string to the DelimitedText property of a StringList causes the StringList to parse the input into a series of space-limited lines. It is then a simple matter to look for the ones which start with a #.
The code is written as a Delphi console application but should be trivial to convert to Lazarus/FPC.
Code:
program HashTags;
{$APPTYPE CONSOLE}
uses
Classes, SysUtils;
procedure TestHashTags;
var
TL : TStringList;
S : String;
i : Integer;
begin
TL := TStringList.Create;
try
S := '2017-08-31 This is a useless sentence being used as an example. #Example #Date:2017-09-01 #NothingWow (and then some more text for good measure)';
TL.DelimitedText := S;
for i := 0 to TL.Count - 1 do begin
if Pos('#', TL[i]) = 1 then
writeln(i, ' ', TL[i]);
end;
finally
TL.Free;
end;
readln;
end;
begin
TestHashTags;
end.

Related

Can't read a saved file in Pascal 1.9.2

PROGRAM archivosejercic1o;
TYPE
num = file of integer;
VAR
arch_num: num;
name: string[20];
x: integer;
BEGIN
writeln('Type the name of the file without extension:');
readln(name);
name:=name+'.txt';
Assign (arch_num,name);
Rewrite(arch_num);
writeln('Type in a number to add to the file:');
readln(x);
WHILE (x <> 0) DO BEGIN
write(arch_num,x);
writeln('Type in another number to add to the file:');
readln(x);
END;
close(arch_num);
END.
I have this code which creates a file with a series of numbers that the user provides, after compiling and executing the file appears in the Pascal directory but when I open it, I get something like this:
I'm just starting to interact with archives so I'm brand new at this, if you can help me I'd be thankful.
Binary vs. text
You are opening a file of Integer. This means that you write integers to the file, i.e. the file is in a binary format.
But you name it name + '.txt', which suggests to me you want to write text, not binary values, to the file.
Now if you display it as text, but it isn't text, you don't get what you want.
Binary means that you, for instance, write a 4 byte integer like 12345 (or hex $3039) as those 4 bytes, i.e. the bytes $39, $30, $00 and $00, in that order (or in reverse order, depending on the endianness of your computer).
Binary is compact, but seldom human readable. If you display such a binary file as text, you may get weird output, or perhaps even none at all (just try to open an .exe file in a text editor and see what you get). You probably want to write the numbers as text. Or you use something like a hex editor to view them (if you can read hex).
So open your files as type text:
PROGRAM archivosejercic1o;
TYPE
num = text;
VAR
arch_num: num;
name: string[20];
x: integer;
BEGIN
...
writeln(arch_num, x);
...
END.
More info on binary vs text: https://fileinfo.com/help/binary_vs_text_files. Note that that doesn't explain the different versions of Unicode text yet.

why am i getting 22 / 3 itprog~1.pas Fatal: Syntax error, ; expected but ELSE found

PROGRAM approvedapplicants(input,output);
uses crt;
var
applcntname,housingcomm,clarendon_court,providence_gardens,
sangre_grande_villas:string;
slry,spcslry:integer;
c_qual_sal,s_qual_sal,p_qual_sal,qualifying_salary:integer;
BEGIN
writeln('enter applicant name, salary, spouce salary');
readln(applcntname,slry,spcslry);
writeln('enter housing community');
readln(housingcomm);
BEGIN
qualifying_salary:=0;
IF(housingcomm=clarendon_court)
then
qualifying_salary:=$12500;
writeln('you have selected clarendon court!');
readln(c_qual_sal) ;
end if ;
else if(housingcomm=sangre_grande_villas)then
qualifying_salary:=$9500;
writeln('you have selected sangre grande villas!');
readln(s_qual_sal);
end if ;
else(housingcomm=providence_gardens)then;
qualifying_salary:=$7500;
writeln('you have selected providence gardens!');
readln(p_qual_sal);
end if;
END.
Ordinarily, on SO, we don't post answers to homework/coursework, but your code is so far wide of the mark that I think it's ok to make an exception in this case.
Try compiling and running this program, which I think does pretty much what I think you are intending, then I'll explain a few things about it:
program approvedapplicants(input,output);
uses crt;
var
ApplicantName,
HousingCommunity,
ClarendonCourt,
ProvidenceGardens,
SangreGrandVillas :string;
Salary,
SpouseSalary,
QualifyingSalary : Integer;
CQualSal,
PQualSal,
SQualSal : Integer;
slry,spcslry:integer;
begin
ClarendonCourt := 'Clarendon Court';
ProvidenceGardens := 'Providence Gardens';
SangreGrandVillas := 'Sangre Grand Villas';
QualifyingSalary := 0;
writeln('enter applicant name');
readln(ApplicantName);
writeln('enter salary');
readln(Salary);
writeln('enter spouse salary');
readln(SpouseSalary);
writeln('enter housing community');
readln(HousingCommunity);
if (HousingCommunity = ClarendonCourt) then begin
QualifyingSalary := $12500;
writeln('you have selected clarendon court!');
readln(CQualSal);
end
else
if(HousingCommunity = SangreGrandVillas)then begin
QualifyingSalary := $9500;
writeln('you have selected sangre grande villas!');
readln(SQualSal);
end
else
if HousingCommunity = ProvidenceGardens then begin
QualifyingSalary :=$7500;
writeln('you have selected providence gardens!');
readln(CQualSal);
end;
end.
Firstly, notice how much easier it is to read and follow its logic. This is mainly
because of
The use of a layout (including indented blocks) which reflects the logical
structure of the code.
The use of consistent, lower case for keywords like program, begin, end, etc.
Keywords are usually the least interesting contents of source code, and it is distracting
to have them SHOUTing at you.
The avoidance of arbitrarily dropping characters from variable names (like the "i"
and second "a" of "applicant". In the days of interpreted code running on slow machines there was
argubably some justification for this, but not any more imo. Likewise, the avoidance
of underscores in variable names - admittedly this is more of a personal preference, but
why have you used them everywhere except the applicant's name?
Secondly, you still have quite a bit of work to do.
Having 3 different variables for the salary (?) numbers you prompt the user
for, one for each of the 3 communities, is probably a bad idea unless you will
subsequently want to work with all 3 figures at the same time. Also, you haven't provided text prompts to tell the user what information to enter for the readln(c_qual_sal) etc statements. It wasn't obvious to me what you intend, so I have not tried to guess.
The way you echo the user's choice of community is just creating you a maintenance
headache (what if you want to add more communities later?). It would be better
to have a variable which you set to whichever of the community names matches
what the user has entered.
You have 3 statements to execute for each community, which are duplicated for
each community. The only one you actually need is the QualifyingSalary one -
the others can execute regardless of the inputted community.

Reversing encryption in delphi

It was not me who wrote this code, it was the previous programmer. However, I noticed he didn't provide a decryption algorithm, rendering the encryption useless.
How can I decrypt this?
function Encrypt(jstr: String): String;
var
I: Integer;
A: Real;
begin
if Length(jstr) = 0 Then begin
Result := '';
Exit;
end;
A := 0;
for I := 0 To Length(jstr) do
A := A + (Ord(jstr[I]) * Pos(jstr[I],jstr)) / 33;
Result := FormatFloat('0000000000.0000000000',A);
if Pos(',',Result) > 0 then begin
Insert('.',Result,Pos(',',Result));
Delete(Result,Pos(',',Result),1);
end;
end;
Thanks!
It looks like a one way hash and hence is not reversible. For example, is the string is very big the result is still a string representation of a float.
That function cannot be reversed. Since it takes input of arbitrary length and returns output of finite length, simple information theory tells you the futility of attempting to write a general inverse. Even for shorter input strings it seems to me that different input strings can result in the same encrypted string.
Even as a hash this function seems very brittle to me due to the bizarre use of floating point code. If I were you I would replace this function with something more fit for purpose.
Finally, I recommend that you undertake a review of all code produced by this developer. The low quality of this code and algorithm suggests to me that everything that this developer touched is liable to have defects.

How to read byte headers of untyped files and then use and display that data when they are file streams in Free Pascal and Lazarus

I am trying to learn Free Pascal using Lazarus and one of my pet projects involves reading the 64 byte headers of a particular set of untyped files that cannot be read and displayed using text or ASCII related procedures (so cannot be outputted directly to Memo boxes etc).
So far, I have devised the following code which does, I think, read in the 64 bytes of the header and I am using TStreams and a "Select Directory" dialog box to do this, based on advice received via the Lazarus IRC. My question though is how to actually USE the data that is read into the buffer from the header? For example, in the headers, there are sequences of 8 bytes, then 16 bytes, then 2 bytes and so on that I want to "work on" to generate other output that will eventually be converted to a string to go into my string grid.
Some of what I have so far is based on what I found here written by Mason Wheeler near the end (http://stackoverflow.com/questions/455790/fast-read-write-from-file-in-delphi) but it only shows how to read it in, not how to use it. I also read this (http://stackoverflow.com/questions/4309739/best-way-to-read-parse-a-untyped-binary-file-in-delphi) but again, it shows you how to READ the data too, but not subsequently USE the data. Any guidance wamrly received! So far, the code below just outputs single value integer numbers to the edit box, as opposed to, say, a range of 8 hexadecimal values.
PS - I am new to programming so please be gentle! Nothing too complex.
procedure TForm1.ProbeFile(FileIterator: TFileIterator);
type
TMyHeader = Array[1..64] of packed record
First8Bytes,
Next16Bytes,
Next2Bytes: byte;
end;
var
FI : TFileIterator; //File Iterator class
SG : TStringGrid;
NumRead : SmallInt;
FileToProbe: TStream;
header: TMyHeader;
begin
FI := TFileIterator.Create;
SG := TStringGrid.Create(self);
// Open the file and read the header
FileToProbe := TFileStream.Create(FileIterator.FileName, fmOpenRead);
try
FileToProbe.seek(0, soFromBeginning);
FileToProbe.ReadBuffer(header, SizeOf(header));
edit1.text := IntToStr(header[0].First8Bytes); // Just outputs '0' to the field? If I try '10' it ooutputs '29' and so on
finally
FileToProbe.Free;
end;
Please forgive me if I misunderstood your question.
As I understand it there is a header of 64 bytes. The first 8 bytes belong together, then the next 16 bytes and finally another 2 bytes.
To me it seems the declaration for this header should be:
TMyHeader = packed record
First8Bytes: array[0..7] of byte;
Next16Bytes: array [0..15] of byte;
Next2Bytes: array [0..1] of byte;
// add more if you like
end;
This recordtype has a size of 8+16+2 = 26 bytes.
Your code that reads the header looks ok to me, So I won't repeat that.
The next16bytes in your header can be retrieved, for example, like this:
edit1.text:= '';
// needs a declaration of a variable "i" as integer
for i:= 0 to 15 do
edit1.text:= edit1.text + IntToStr(header.next16bytes[i]) + '-';
Change the value of the first byte in the next2bytes part of your header as follows (again as an example):
header.next2bytes[0]:= 123;
Finally, you could write your changes back to the header of the file with help of the filetoprobe.writebuffer method.

Fast way to search lines of Tmemo

I have a TMemo on a form which allows users to enter a list of items. People can enter many items here. When they click Save the contents of the TMemo is checked and then added to the database.
I have a second list in a TStringList which I loop over and check to see if any of it's items are contained in the TMemo.
In a nut shell it looks like this
....
//slItems = TStringList
//mItems = TMemo
for i := slItems.Count -1 downto 0 do
begin
if mItems.Lines.IndexOf(slItems[i]) = -1 then
slItems[i].Delete;
end;
----
So stringlist looped, check to see if it exists in memo, if not delete from list.
However, with 200+ items this is starting to slow down a lot, and with 1000 it gets real bad.
Whats the fastest way to search a TMemo?
Read all of TMemo into a local TStringList and work from that. Every time you're accessing TMemo.Lines you're relying on Windows messaging to talk to the windows-provided multi line text box. Anything but efficient!
....
//slItems = TStringList
//mItems = TMemo
//L = TStringList
L.Text := mItems.Text; // edited per David's suggestion.
L.Sorted := True; // per Uwe Raabe's suggestion.
for i := slItems.Count -1 downto 0 do
begin
if L.IndexOf(slItems[i]) = -1 then
slItems[i].Delete;
end;
----

Resources