Is it possible to search a TextFile line by line to find a specific string.
ie. Hello
It searches line by line to find if any line has Hello in it. There will only be one string per line.
Is this possible?
If so how do I attempt this?
It's certainly easiest to load the entire file into memory. Provided that your file is small enough then you can do it like this:
found := false;
sl := TStringList.Create;
try
sl.LoadFromFile(fileName);
for line in sl do
if Pos('Hello', line)<>0 then
begin
found := true;
break;
end;
finally
sl.Free;
end;
I assume that when you say
if any line has Hello in it
that you are looking for lines that contain the search string rather than lines that equal the search string.
In a comment you ask:
Ok can I then ask if it would be possible to ask on how to
delete a string that is typed from an edit box out of a file? So
you enter a string into an edit box then it searches the file
for it and deletes that line?
That's an easy enough variation of the above:
procedure RemoveLinesContaining(const fileName, searchText: string);
var
sl: TStringList;
i: Integer;
begin
sl := TStringList.Create;
try
sl.LoadFromFile(fileName);
for i := sl.Count-1 downto 0 do
if Pos(searchText, sl[i])<>0 then
sl.Delete(i);
sl.SaveToFile(fileName);
finally
sl.Free;
end;
end;
This function deletes all lines that contain the search string. If you only want to delete the first such line, then break out of the for loop after the call to Delete.
The loop variable is descending to allow the loop to modify the list.
If you want to use a different test, say equality rather than contains, then simply modify the if statement.
An easy way is to use TStringList.LoadFromFile to load the file, then check IndexOf('hello') - if it's greater than -1, the string is in the file.
var
sl : TStringList;
ix : Integer;
begin
sl := TStringList.Create;
try
sl.LoadFromFile('test.txt');
ix := sl.IndexOf('Hello');
if ix > -1 then ShowMessage('Yup, the file contains a greeting.');
finally
sl.Free;
end;
end;
Related
I have to check each line of a file against another file.
If one line from the first file exists in the second file I have to delete it.
Right now i'm using 2 listboxes and the "for listbox1.items.count-1 downto do begin..."
My program works but I have to check this for huge files with over 1 milion lines.
Is there a faster approach to this method?
I want to load the files inside memory in order to be extremely fast!
Thanks
You can use TStringList for this. List for second file should be sorted for faster search. Try this:
var
l1, l2: TStringList;
i: integer;
begin
l1 := nil;
l2 := nil;
try
l1 := TStringList.Create;
l1.loadfromFile('file1');
l2 := TStringList.Create;
l2.LoadFromFile('file2');
l2.Sorted := True;
for i := l1.Count -1 downto 0 do
begin
if l2.IndexOf(l1[i]) <> -1 then
l1.Delete(i);
end;
l1.SaveToFile('file1');
finally
FreeEndNil(l1);
FreeEndNil(l2);
end
end
A quick solution (but not the fastest one) is to use two TStringList lists instead of list boxes.
var
a, b: TStringList;
i: Integer;
begin
a := TStringList.Create;
b := TStringList.Create;
try
a.LoadFromFile('C:\1.txt');
b.LoadFromFile('C:\2.txt');
b.Sorted := True;
for i := a.Count - 1 downto 0 do
begin
// Check if line of file 'a' are present in file 'b'
// and delete line if true
if b.IndexOf(a[i]) > -1 then
a.Delete(i);
end;
a.SaveToFile('C:\1.txt');
finally
b.Free;
a.Free;
end;
end;
Again, this is a slow and simple solution that loads whole files in RAM. It still will be much faster than using a ListBox. Sometimes simple is just enough for solving a one-time problem.
A faster method would be to create an index (eg. binary tree) of both files on hard disk and use this index to compare. That way you will not need to store the whole files on disk.
I am having problems with the following code:
var l :string;
var f:Textfile;
begin
assignfile(f,'c:\test\file.txt');
reset(f);
while not eof(f) do
readln(f,l);
closefile(f);
showmessage(l);
My problem is that the showmessage is returning nothing ... it's empty and the text file is not empty.
Why is this happening?
In addition to what has already been said:
Nobody uses lowlevel file routines these days. There are at least two much better approaches. The first involves streams, the second a TStringList:
var sl : TStringList;
begin
sl := TStringList.Create;
try
sl.LoadFromFile('c:\your\file.txt');
ShowMessage(sl.Text);
finally
sl.Free;
end;
end;
Disclaimer: code not tested
You read each new line in the same variable, thereby overwriting the last line with a new line. If the last read returns an empty string (or a string with just spaces or an enter), your message box will seem empty. And even if it were not empty, it would show only the last line, not the whole file.
Use l as a buffer. After each read, append it to another string:
var l: string;
var t: string;
var f: Textfile;
begin
t := '';
assignfile(f, 'c:\test\file.txt');
reset(f);
while not eof(f) do
begin
readln(f, l);
t := t + l;
end;
closefile(f);
showmessage(t);
For large files it is more efficient to use a String Builder instead of concatting everything to the same string, because t will be reallocated on each iteration.
Alternatively, use a TStringList or a TFileStream to read files. Using a TFileStream, you can read the whole file at once into a string. The TStringList has the advantage that it parses the file and makes each line an item in the stringlist.
But with those solutions, you are going to read the whole file into memory. If you can process it line by line, do the processing inside the loop.
The line
Readln(f, l)
puts the current line of f into the string l. Hence it will replace the previous line, so at the end, l will only contain the last line of the file. Maybe the last line is empty?
Most likely the last line of your file is empty.
You may want to try the following to see the difference:
while not eof(f) do
begin
readln(f,l);
showmessage(l);
end;
closefile(f);
In a TMemo field I have 3 lines:
line1
line2
line3
Is it posible to get all three lines as one string?
Example:
line1,line2,line3
You can use the Lines.CommaText property for this. Do the following:
CommaString := Memo1.Lines.CommaText;
Its also useful to use the DelimitedText property if you want the text to make use of another separator character. You can do that by using something like this:
Memo1.Lines.Delimiter := '-';
Memo1.Lines.StrictDelimiter := True;
DashString := Memo1.Lines.DelimitedText;
This works both ways. You can assign a value to the CommaText or DelimiterText to set the lines. This is actually a of TStringList so it will work with TListBox, TMemo, TComboBox, etc. Basically anything that uses a string list internally.
maybe something like this suits your needs
d:=memo1.lines.count;
for i:=1 to d do
memo1.lines[0]:=memo1.lines[0]+' '+memo1.lines[i];
for i:=1 to d do
memo1.lines.Delete(1);
here is a 3 line function that do it.
function getOneLineMemo(memo:Tmemo):String;
var
i:integer;
begin
result := '';
for i:=0 to memo1.lines.count do
result := result + memo.lines[0];
end;
Say i have a combobox with
apples
apples
pears
oranges
oranges
i would like to have it show
apples
pears
oranges
how can i do this?
for iter := combobox.Items.Count - 1 downto 0 do
begin
index := combobox.Items.IndexOf(combobox.Items[iter]);
if index < iter then
combobox.Items.Delete(iter);
end;
I suggest that you simply refill the combo box each time. That makes the logic simpler:
ComboBox.Items.BeginUpdate;
try
ComboBox.Clear;
for Str in Values do
begin
if ComboBox.Items.IndexOf (Str) = -1 then
ComboBox.Items.Add (Str);
end;
finally
ComboBox.Items.EndUpdate;
end;
Just to put methods against eachother: one keeps the order but is increasingly slow with larger number of items. The other stays relatively faster but doesn't keep order:
procedure SortStringlist;
var
i,index,itimer: integer;
sl : TStringlist;
const
numberofitems = 10000;
begin
sl := TStringlist.Create;
for i := 0 to numberofitems-1 do begin
sl.Add(IntToStr(random(2000)));
end;
Showmessage(IntToStr(sl.Count));
itimer := GetTickCount;
sl.Sort;
for I := sl.Count-1 downto 1 do begin
if sl[i]=sl[i-1] then sl.Delete(i);
end;
Showmessage(IntToStr(sl.Count)+' Time taken in ms: '+IntToStr(GetTickCount-itimer));
sl.free;
sl := TStringlist.Create;
for i := 0 to numberofitems-1 do begin
sl.Add(IntToStr(random(2000)));
end;
Showmessage(IntToStr(sl.Count));
itimer := GetTickCount;
for i := sl.Count - 1 downto 0 do
begin
index := sl.IndexOf(sl[i]);
if index < i then
sl.Delete(i);
end;
Showmessage(IntToStr(sl.Count)+' Time taken in ms: '+IntToStr(GetTickCount-itimer));
end;
If you don't care if the items get reordered (or they're sorted already), TStrings can do the work for you - it eliminates all of the looping, deletion, and other work. (Of course, it requires the creation/destruction of a temporary TStringList, so if that's an issue for you it won't work.)
var
SL: TStringList;
begin
ComboBox1.Items.BeginUpdate;
try
SL := TStringList.Create;
try
SL.Sorted := True; // Required for Duplicates to work
SL.Duplicates := dupIgnore;
SL.AddStrings(ComboBox1.Items);
ComboBox1.Items.Assign(SL);
finally
SL.Free;
end;
finally
ComboBox1.Items.EndUpdate;
end;
end;
To properly compare with Igor's answer (which includes no BeginUpdate/EndUpdate), remove those things:
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.Sorted := True; // Required for Duplicates to work
SL.Duplicates := dupIgnore;
SL.AddStrings(ComboBox1.Items);
ComboBox1.Items.Assign(SL);
finally
SL.Free;
end;
end;
You have to remove duplicates from the source data.
In most scenarios, a ComboBox is filled with data in run-time, which means, data is coming from some source. There are basically 2 scenarios here: a dataset from database and a collection of strings from any other source. In both cases you filter out duplicates before inserting anything into the ComboBox.
If source is a dataset from database, simply use the SQL DISTINCT keyword.
If source is any collection of strings, use a peace of code provided in the answer by #Smasher.
I faced this problem several times before, and i used all the previous approaches and I'm still using them, but do you know : i think the best approach , though not mentioned here, is to subclass TComboBox, creating a new method (say AddUnique ) that add the string to the combo ONLY if it does not exist previously , otherwise it will drop it.
This solution may cost some extra time in the beginning , but it will solve the problem once and for all.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to get the sort order in Delphi as in Windows Explorer?
I am trying to scan a directory, but I can't get it to Sort by File Name.
Example, say if I have these Filenames in a folder:
File1
File2
File3
File4
File5
File6
File7
File8
File9
File10
File11
File12
File13
File14
File15
File16
File17
File18
File19
File20
File21
File22
If I use something like this:
var
SL: TStringList;
SR: TSearchRec;
begin
SL := TStringList.Create;
try
if FindFirst(Path + '*.*', faAnyFile and not faDirectory and not faHidden, SR) = 0 then
repeat
SL.Add(Path + SR.Name)
until FindNext(SR) <> 0;
FindClose(SR);
// handle the filenames..
finally
SL.Free;
end;
end;
The result will be:
File10
File11
File12
File13
File14
File15
File16
File17
File18
File19
File2
File20
File21
File22
File3
File4
File5
File6
File7
File8
File9
It should be sorted by Filename (as I wrote in the first Filename list example).
I bet this is really simple but I cannot see it, what do I need to do to sort this?
Thanks.
You're starting with the assumption that there's some sort of inherent "order" for file names. There isn't. You appear to want the file names to be sorted alphabetically, with numerical portions of names sorted numerically. I'm not sure what you want to happen with punctuation and other characters.
The file-enumeration functions don't define any order that names will be returned in. They're returned in whatever order the underlying file system decides to provide them. There are two steps to getting a sorted list of file names. You're already doing the first one:
Collect the file names in a list for post-processing.
Arrange the names in the order you want. If plain "asciibetical" isn't what you want, then you can write a custom sorting function and pass it to TStringList.CustomSort.
For example, if you want them to be in the same order you see file names in Windows Explorer as of Windows XP, you can use the StrCmpLogicalW API function. Call that from your comparison function, like this:
function LogicalCompare(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := StrCmpLogicalW(PWideChar(List[Index1]), PWideChar(List[Index2]));
end;
SL.CustomSort(LogicalCompare);
If you have something earlier than Delphi 2007, you'll need to do something about converting your strings to wide characters, at least for the duration of the sorting phase.
FindFirst() and FindNext() merely enumerate the files on the file system as-is. The files can be returned in any order. You have to sort the TStringList yourself afterwards, eg:
function SortFilesByName(List: TStringList; Index1, Index2: Integer): Integer;
var
FileName1, FileName2: String;
FileNumber1, FileNumber2: Integer;
begin
// assuming the files are all named "Path\File###.xxx",
// where "###" is the number to sort on...
FileName1 := ChangeFileExt(ExtractFileName(List[Index1]), '');
FileName2 := ChangeFileExt(ExtractFileName(List[Index1]), '');
FileNumber1 := StrToInt(Copy(FileName1, 5, MaxInt));
FileNumber2 := StrToInt(Copy(FileName2, 5, MaxInt));
Result := (FileNumber2 - FileNumber1);
end;
var
SL: TStringList;
SR: TSearchRec;
begin
SL := TStringList.Create;
try
if FindFirst(Path + '*.*', faAnyFile and (not faDirectory) and (not faHidden), SR) = 0 then
try
repeat
SL.Add(Path + SR.Name)
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
SL.CustomSort(SortFilesByName);
// handle the filenames..
finally
SL.Free;
end;
end;
Any simple sort system (such as the one windows uses to return you files and delphi uses for sorting) will sort alphabetically and then by number, but unless you pad your numbers with zeros
1 comes before 2 so
11 comes before 2 (in the same way that aa comes before b)
you either need to pad you numbers with zeros such as
filename001
filename010
filename020
for use the answer provided by Remy Lebeau - TeamB above which will pull out the number at the end of you filename and sort by that.