Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Алгоритм поиска всех уникальных слов в файле

01.01.2007
////////////////////////////////////////////////////////////////////////////////
//
//  ****************************************************************************
//  * Unit Name : Dictionary
//  * Purpose   : Набор классов для работы с индексированным списком поиска
//  * Author    : Александр Багель
//  * Version   : 1.00
//  ****************************************************************************
//
 
unit
Dictionary;
 
interface
 
uses
 
Windows, Classes, SysUtils{, FullTextGetter};
 
type
 
// Класс отвечающий за создание словаря уникальных слов
 
TDictionaryFounder = class
 
private
   
FDict: TList;
   
FDictMem: array of String;
   
FDictMemCount: Integer;
 
protected
   
function GetPos(const Value: String): Integer; virtual;
    procedure
Insert(Value: String; Position: Integer); virtual;
   
function Prepare(const Value: String): String; virtual;
 
public
   
constructor Create;
    destructor
Destroy; override;
    procedure
AddData(Value: String); //overload;
   
// procedure AddData(ObjText: IFullTextGetter); overload;
    procedure
SaveToStream(var AStream: TMemoryStream);
 
end;
 
 
// Класс осуществляющий поиск в словаре
 
// полученном от TDictionaryFounder
 
TDictionaryFinder = class
 
private
   
FDict: array of ShortString;
   
FDictLength: Cardinal;
 
protected
   
function GetPos(const Value: ShortString;
     
const SubStr: Boolean = False): Boolean; virtual;
 
public
    destructor
Destroy; override;
    procedure
LoadFromStream(const AStream: TMemoryStream);
   
function Find(const Value: String;
     
const SubStr: Boolean = False): Boolean;
 
end;
 
implementation
 
{ TDictionaryFounder }
 
//
//  Добавление информации для построения массива индексов
// =============================================================================
procedure
TDictionaryFounder.AddData(Value: String);
var
 
Tmp: String;
 
Position, I: Integer;
  S
: TStringList;
begin
 
Value := Prepare(Value);
  S
:= TStringList.Create;
 
try
    S
.Text := Value;
   
for I := 0 to S.Count - 1 do
   
begin
     
Tmp := S[I];
     
if Tmp = '' then Continue;
     
if FDict.Count = 0 then
       
Insert(Tmp, 0)
     
else
     
begin
       
Position := GetPos(Tmp);
       
if (Position >= 0) then
         
if FDict.Count > Position then
         
begin
           
if String(FDict.Items[Position]) <> Tmp then
             
Insert(Tmp, Position);
         
end
         
else
           
Insert(Tmp, Position);
     
end;
   
end;
 
finally
    S
.Free;
 
end;
end;
 
//
//  Добавление информации для построения массива индексов
//  Информация приходит из интерфейса
// =============================================================================
{procedure TDictionaryFounder.AddData(ObjText: IFullTextGetter);
var
  S
: String;
begin
 
if ObjText = nil then
   
raise Exception.Create('IFullTextGetter is empty.');
  S
:= ObjText.GetText;
 
AddData(S);
end;   }
 
constructor TDictionaryFounder.Create;
begin
 
FDict := TList.Create;
end;
 
destructor
TDictionaryFounder.Destroy;
begin
 
FDict.Free;
 
FDictMemCount := 0;
 
SetLength(FDictMem, FDictMemCount);
  inherited
;
end;
 
//
//  Возвращает номер позиции где находится слово, или должно находится...
//  Поиск методом половинного деления...
// =============================================================================
function TDictionaryFounder.GetPos(const Value: String): Integer;
var
 
FLeft, FRight, FCurrent: Cardinal;
begin
 
if FDict.Count = 0 then
 
begin
   
Result := 0;
   
Exit;
 
end;
 
FLeft := 0;
 
FRight := FDict.Count - 1;
 
FCurrent := (FRight + FLeft) div 2;
 
if String(FDict.Items[FLeft]) > Value then
 
begin
   
Result := 0;
   
Exit;
 
end;
 
if String(FDict.Items[FRight]) < Value then
 
begin
   
Result := FRight + 1;
   
Exit;
 
end;
  repeat
   
if String(FDict.Items[FCurrent]) = Value then
   
begin
     
Result := FCurrent;
     
Exit;
   
end;
   
if String(FDict.Items[FCurrent]) < Value then
     
FLeft := FCurrent
   
else
     
FRight := FCurrent;
   
FCurrent := (FRight + FLeft) div 2;
 
until FLeft = FCurrent;
 
if String(FDict.Items[FCurrent]) < Value then Inc(FCurrent);
 
Result := FCurrent;
end;
 
//
//  Добавление нового индекса в массив индексов
// =============================================================================
procedure
TDictionaryFounder.Insert(Value: String; Position: Integer);
begin
 
if FDictMemCount < FDict.Count + 1 then
 
begin
   
Inc(FDictMemCount, FDict.Count + 1);
   
SetLength(FDictMem, FDictMemCount);
 
end;
 
FDictMem[FDict.Count] := Value;
 
FDict.Insert(Position, @FDictMem[FDict.Count][1]);
end;
 
//
//  Сохранение массива индексов в поток
// =============================================================================
procedure
TDictionaryFounder.SaveToStream(var AStream: TMemoryStream);
var
  I
: Integer;
  S
: PChar;
 
TmpS: TStringList;
begin
 
if AStream = nil then Exit;
 
TmpS := TStringList.Create;
 
try
   
for I := 0 to FDict.Count - 1 do
   
begin
      S
:= FDict.Items[I];
     
TmpS.Add(S);
   
end;
   
AStream.Position := 0;
   
AStream.Size := Length(TmpS.Text);
   
AStream.Write(TmpS.Text[1], Length(TmpS.Text));
   
AStream.Position := 0;
 
finally
   
TmpS.Free;
 
end;
end;
 
//
//  Подготовка данных к обработке...
//  Удаляются все не буквенные символы, каждое слово начинется с новой строки...
// =============================================================================
function TDictionaryFounder.Prepare(const Value: String): String;
var
  I
: Integer;
 
Len: Cardinal;
  C
: PAnsiChar;
 
LastEnter: Boolean;
begin
 
SetLength(Result, Length(Value) * 2);
 
Len := 0;
 
LastEnter := False;
 
for I := 1 to Length(Value) do
 
begin
    C
:= CharLower(@Value[I]);
   
if C^ in ['a'..'z', 'а'..'я'] then
   
begin
     
Inc(Len);
     
Result[Len] := C^;
     
LastEnter := False;
   
end
   
else
     
if not LastEnter then
     
begin
       
Inc(Len);
       
Result[Len] := #13;
       
Inc(Len);
       
Result[Len] := #10;
       
LastEnter := True;
     
end;
 
end;
 
SetLength(Result, Len);
end;
 
{ TDictionaryFinder }
 
destructor
TDictionaryFinder.Destroy;
begin
 
FDictLength := 0;
 
SetLength(FDict, FDictLength);
  inherited
;
end;
 
//
//  Поиск введенных слов...
// =============================================================================
function TDictionaryFinder.Find(const Value: String;
 
const SubStr: Boolean = False): Boolean;
var
  S
: TStringList;
  I
: Integer;
begin
 
Result := False;
 
if Value = '' then Exit;
  S
:= TStringList.Create;
 
try
    S
.Text := StringReplace(Value, ' ', #13#10, [rfReplaceAll]);
    S
.Text := AnsiLowerCase(S.Text);
   
if S.Count = 0 then Exit;
   
for I := 0 to S.Count - 1 do
   
begin
     
Result := GetPos(S.Strings[I], SubStr);
     
if not Result then Exit;
   
end;
 
finally
    S
.Free;
 
end;
end;
 
//
//  Поиск каждого слова в массиве индексов
// =============================================================================
function TDictionaryFinder.GetPos(const Value: ShortString;
 
const SubStr: Boolean = False): Boolean;
var
 
FLeft, FRight, FCurrent, I: Cardinal;
begin
 
Result := False;
 
if SubStr then
 
begin
   
for I := 0 to FDictLength - 1 do
     
if Pos(Value, FDict[I]) > 0 then
     
begin
       
Result := True;
       
Exit;
     
end;
 
end
 
else
 
begin
   
if FDictLength = 0 then Exit;
   
FLeft := 0;
   
FRight := FDictLength - 1;
   
FCurrent := (FRight + FLeft) div 2;
   
if FDict[FLeft] > Value then Exit;
   
if FDict[FRight] < Value then Exit;
   
if FDict[FLeft] = Value then
   
begin
     
Result := True;
     
Exit;
   
end;
   
if FDict[FRight] = Value then
   
begin
     
Result := True;
     
Exit;
   
end;
    repeat
     
if FDict[FCurrent] = Value then
     
begin
       
Result := True;
       
Exit;
     
end;
     
if FDict[FCurrent] < Value then
       
FLeft := FCurrent
     
else
       
FRight := FCurrent;
     
FCurrent := (FRight + FLeft) div 2;
   
until FLeft = FCurrent;
 
end;
end;
 
//
//  Загрузка массива индексов из потока
// =============================================================================
procedure
TDictionaryFinder.LoadFromStream(const AStream: TMemoryStream);
var
  S
: TStringList;
  I
: Integer;
begin
  S
:= TStringList.Create;
 
try
   
AStream.Position := 0;
    S
.LoadFromStream(AStream);
   
FDictLength := S.Count;
   
if FDictLength = 0 then Exit;
   
SetLength(FDict, FDictLength);
   
for I := 0 to FDictLength - 1 do
     
FDict[I] := S.Strings[I];
 
finally
    S
.Free;
 
end;
end;
 
end.

пример использования:

unit Unit1;
 
interface
 
uses
 
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
 
Dialogs, StdCtrls, ComCtrls, Dictionary;
 
type
  TForm1
= class(TForm)
    Button1
: TButton;
    ProgressBar1
: TProgressBar;
    Button2
: TButton;
    Edit1
: TEdit;
    Label1
: TLabel;
    CheckBox1
: TCheckBox;
    procedure Button1Click
(Sender: TObject);
    procedure Button2Click
(Sender: TObject);
 
end;
 
var
  Form1
: TForm1;
 
implementation
 
uses
ComObj;
 
{$R *.dfm}
 
procedure TForm1
.Button1Click(Sender: TObject);
var
  SH
: TDictionaryFounder;
  S
: TStringList;
  M
: TMemoryStream;
  I
: Integer;
 
Start: Cardinal;
begin
  S
:= TStringList.Create;
 
try
    S
.LoadFromFile('c:\1.txt');
    ProgressBar1
.Position := 0;
    ProgressBar1
.Max := S.Count;
    SH
:= TDictionaryFounder.Create;
   
try
     
Start := GetTickCount;
     
for I := 0 to S.Count - 1 do
     
begin
        SH
.AddData(S.Strings[I]);
        ProgressBar1
.Position := I;
     
end;
     
ShowMessage('Время составления словаря: ' + IntToStr(GetTickCount - Start));
      M
:= TMemoryStream.Create;
     
try
        SH
.SaveToStream(M);
        M
.SaveToFile('c:\2.txt');
        ProgressBar1
.Position := 0;
        Button2
.Enabled := True;
     
finally
        M
.Free;
     
end;
   
finally
      SH
.Free;
   
end;
 
finally
    S
.Free;
 
end;
end;
 
 
procedure TForm1
.Button2Click(Sender: TObject);
var
  S
: TDictionaryFinder;
  M
: TMemoryStream;
begin
  S
:= TDictionaryFinder.Create;
 
try
    M
:= TMemoryStream.Create;
   
try
      M
.LoadFromFile('c:\2.txt');        
      S
.LoadFromStream(M);
     
if S.Find(Edit1.Text, CheckBox1.Checked) then
       
ShowMessage('Элемент найден')
     
else
       
ShowMessage('Элемент не найден');
   
finally
      M
.Free;
   
end;
 
finally
    S
.Free;
 
end;
end;
 
end.

Автор: Александр (Rouse_) Багель

Взято из https://forum.sources.ru