Fast way to search lines of Tmemo - performance

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;
----

Related

Reading a text file and constructing a matrix from it

I need to construct a matrix; a number of columns and rows are also in the first row of the matrix, I'll make an example so its more clearer.
4 3
1 2 3
5 6 7
9 10 8
1 11 13
Where m=4 (number of rows) and n=3 (number of columns)
This is an example of a text file. Is something like this even possible?
Program Feb;
const
max=100;
type
Matrix=array[1..max,1..max] of integer;
var datoteka:text;
m,n:integer;
counter:integer;
begin
assign(datoteka,'datoteka.txt');
reset(datoteka);
while not eoln(datoteka) do
begin
read(datoteka, m);
read(datoteka, n);
end;
repeat
read eoln(n)
until eof(datoteka)
write (m,n);
end.
My code isn't a big help, cause I don't know how to write it.
First, have a look at the code I wrote to do the task, and then look at my explanation below.
program Matrixtest;
uses
sysutils;
var
NoOfCols,
NoOfRows : Integer;
Source : TextFile;
Matrix : array of array of integer;
FileName : String;
Row,
Col : Integer; // for-loop iterators to access a single cell of the matrix
Value : Integer;
begin
// First, construct the name of the file defining the matrix
// This assumes that the file is in the same folder as this app
FileName := ExtractFilePath(ParamStr(0)) + 'MatrixDef.Txt';
writeln(FileName); // echo it back to the screen so we can see it
// Next, open the file
Assign(Source, FileName);
Reset(Source);
read(Source, NoOfRows, NoOfCols);
writeln('Cols: ', NoOfCols, 'Rows: ', NoOfRows);
SetLength(Matrix, NoOfCols, NoOfRows);
readln(source); // move to next line in file
// Next, read the array data
for Row := 1 to NoOfRows do begin
for Col := 1 to NoOfCols do begin
read(Source, Value);
Matrix[Col - 1, Row - 1] := Value;
end;
end;
// Display the array contents
for Row := 1 to NoOfRows do begin
for Col := 1 to NoOfCols do begin
writeln('Row: ', Row, ' contents', Matrix[Col - 1, Row - 1]);
end;
end;
Close(Source); // We're done with the file, so close it to release OS resources
readln; // this waits until you press a key, so you can read what's been displayed
end.
In your program, you can use a two-dimensional array to represent your matrix. Free Pascal supports multi-dimensional arrays; see https://wiki.lazarus.freepascal.org/Multidimensional_arrays for more information.
This is a complex task, so it helps to know how to do more basic things like reading an array of a size known at compile-time from a text file.
The wrinkle in this task is that you are supposed to read the dimensions (numbers of rows and columns) of the matrix at run-time from the file which contains the matrix's contents.
One inefficient way to do this would be to declare the matrix array with huge dimensions, larger than anything you would expect in practice, using the type of array declaration in the Wiki page linked above.
A better way is to use dynamic arrays, whose dimensions you can set at run-time. To use this, you need to know:
How to declare a dynamic array in Free Pascal
How to set the dimensions of the array at run-time, once you've picked them up from your matrix-definition file (hint: SetLength is the way to do this)
The fact that a Free Pascal dynamic array is zero-based
The easiest way of managing zero-based arrays is to write your code (in terms of Row and Column variables) as if the matrix were declared as array[1..NoOfRows, 1..NoOfColumns] and subtract one from the array indexes only when you actually access the array, as in:
Row := 3;
Column := 4;
Value := Matrix[Row - 1, Column - 1];

Find & Extract hashtags in text

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.

Ada thread to pass auto random generated data to a procedure

How would i go about creating a procedure that is a thread which continuously passes automatic random generated data within a specified range.
I currently would have to manually enter in each bit of data in the console using this procedure below. I want to creatre a procedure that when running is able to pass data to this procedure as if it was being typed into the console itself.
procedure Analyse_Data is
Data : Integer;
begin
DT_Put_Line("Data input by user");
loop
DT_Get(Data,"Data must be taken as a whole number");
exit when (Data >=0) and (Data <= Maximum_Data_Possible);
DT_Put("Please input a value between 0 and ");
DT_Put(Maximum_Data_Possible);
DT_Put_Line("");
end loop;
Status_System.Data_Measured := Data_Range(Data);
end Analyse_Data;
I havent included the specification files (.ads)
I am new to Ada and any help would be appreciated.
Use an instance of Discrete_Random to generate some number of random data values in the desired range:
subtype Valid_Range is Natural range 0 .. Maximum_Data_Possible;
package Some_Value is new Ada.Numerics.Discrete_Random(Valid_Range);
G : Some_Value.Generator;
…
procedure Generate is
N : Valid_Range;
begin
for I in 1 .. Count loop
N := Some_Value.Random(G);
Put(N);
end loop;
end;
Save the values to a file:
./generate > test_data.txt
Feed that file to your program using I/O redirection from the command line:
./analyse_data < test_data.txt
The exact details will depend on you actual program. See this related Q&A regarding empty lines in standard input.

Transpose a string in Pascal

I'm very new to Pascal and still learning much. I have to write a code that :
Takes input of a string
Split the string into two characters each (Snippet)
Use the Snippet to get an index from an array
Transpose the Snippet to a certain value
If Index + Transpose is larger than the length of the Array, return nothing
If not, append the transposed Snippet to a result string
Return the transposed string
I can only write 1 through 3, the rest is still a blur for me. Helps are appreciated.
(And I also want to improve it without many for loops. Any thoughts?)
program TransposeString;
var
melody : Array[1..24] of String[2] = ('c.', 'c#', 'd.', 'd#', 'e.', 'f.', 'f#', 'g.', 'g#', 'a.', 'a#', 'b.', 'C.', 'C#', 'D.', 'D#', 'E.', 'F.', 'F#', 'G.', 'G#', 'A.', 'A#', 'B.');
songstring, transposedstring : String;
transposevalue : byte;
function Transpose(song : String; transposevalue : byte): String;
var
songsnippet : String[2];
iter_song, iter_index, index : byte;
begin
for iter_song := 1 to length(song) do
begin
if iter_song mod 2 = 0 then continue;
songsnippet := song[iter_song] + song[iter_song + 1]; //Split the string into 2 characters each
for iter_index := 1 to 24 do
begin
if melody[iter_index] = songsnippet then
begin
index := iter_index; //Get Index
break;
end;
end;
//Check Transpose + Index
//Transpose Snippet
//Append Snippet to Result String
end;
end;
begin
readln(songstring);
readln(transposevalue);
transposedstring := transpose(songstring, transposevalue);
writeln(transposedstring);
end.
As a starter for you to work from, rather than just spoon-feeding an answer:
You have the index of the snippet (note) in index. Assuming the notes are in order you need to return the note from the array positions above it, so
result := result + melody[iter_index + transposevalue];
You need to check the length of the array before trying to read from it, otherwise it'll crash (step 5). This is just an if statement.
I wouldn't worry too much about for loops - 2 deep nesting isn't that bad. If you wanted to split it out a bit then GetTransposedNote(const note:string): string; could be split out as a new function.
Things you may want to think about are:
What if you can't find the note in the array?
Do you want to be case-sensitive
What if the input string has an odd number of characters?
You are most of the way there already, though.

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.

Resources