『プログラミング作法』の中に出てくる、マルコフ連鎖 C バージョンを Delphi で書いてみた。ただし、標準入出力は使わずに、TStringList にテキストを読んでしまってから処理。結果も TStringList に返ってくるようにしてみた。
unit Markov_C;
interface
uses
Classes, SysUtils, Windows;
const
NPREF = 2;
NHASH = 4093;
MAXGEN = 10000;
NONWORD = #$0A;
type
PPrefix = ^TPrefix;
TPrefix = array[0..NPREF-1] of PChar;
PSuffix = ^TSuffix;
PState = ^TState;
TState = packed record
Pref: TPrefix;
Suf: PSuffix;
Next: PState;
end;
TSuffix = packed record
Word: PChar;
Next: PSuffix;
end;
var
StateTab: array[0..NHASH-1] of PState;
function Hash(S: PPrefix): Cardinal;
function Lookup(Prefix: PPrefix; Create: Boolean): PState;
procedure Build(Prefix: PPrefix; FileName: string);
procedure Add(Prefix: PPrefix; Suffix: PChar);
procedure AddSuffix(SP: PState; Suffix: PChar);
procedure Generate(nWords: Integer; SL: TStringList);
implementation
function Hash(S: PPrefix): Cardinal;
var
H: Cardinal;
P: PChar;
I: Integer;
const
MULTIPLIER = 31; // set 31 or 37
begin
H := 0;
for I := 0 to NPREF - 1 do
begin
P := S[I];
while Ord(P^) <> 0 do
begin
H := MULTIPLIER * H + Ord(P^);
Inc(P);
end;
end;
Result := H mod NHASH;
end;
function Lookup(Prefix: PPrefix; Create: Boolean): PState;
var
I, H: Integer;
SP: PState;
begin
H := Hash(Prefix);
SP := StateTab[H];
while SP <> nil do
begin
for I := 0 to NPREF - 1 do
if StrComp(Prefix[I], SP.Pref[I]) <> 0 then
Break;
if I = NPREF then
begin
Result := SP;
Exit;
end;
SP := SP.Next;
end;
if Create then
begin
GetMem(SP, Sizeof(TState));
for I := 0 to NPREF - 1 do
SP.Pref[I] := Prefix[I];
SP.Suf := nil;
SP.Next := StateTab[H];
StateTab[H] := SP;
end;
Result := SP;
end;
procedure Build(Prefix: PPrefix; FileName: string);
var
SL: TStringList;
I: Integer;
Buf: PChar;
begin
SL := TStringList.Create;
try
SL.LoadFromFile(FileName);
SL.Text := StringReplace(SL.Text, ' ', #$0D#$0A, [rfReplaceAll]);
for I := 0 to SL.Count - 1 do
if SL[I] <> '' then
begin
GetMem(Buf, Length(SL[I]) + 1);
StrCopy(Buf, PChar(SL[I]));
Add(Prefix, Buf);
end;
finally
SL.Free;
end;
end;
procedure Add(Prefix: PPrefix; Suffix: PChar);
var
SP: PState;
begin
SP := Lookup(Prefix, True);
AddSuffix(SP, Suffix);
System.Move(Prefix[1], Prefix[0], (NPREF-1)*SizeOf(Prefix[0]));
Prefix[NPREF-1] := Suffix;
end;
procedure AddSuffix(SP: PState; Suffix: PChar);
var
Suf: PSuffix;
begin
GetMem(Suf, SizeOf(TSuffix));
Suf.Word := Suffix;
Suf.Next := SP.Suf;
SP.Suf := Suf;
end;
procedure Generate(nWords: Integer; SL: TStringList);
var
SP: PState;
Suf: PSuffix;
Prefix: TPrefix;
W: PChar;
I, nMatch: Integer;
begin
for I := 0 to NPREF - 1 do
Prefix[I] := NONWORD;
Randomize;
W := nil; //コンパイラを黙らせる
for I := 0 to nWords - 1 do
begin
SP := Lookup(@Prefix, False);
nMatch := 0;
Suf := SP.Suf;
while Suf <> nil do
begin
Inc(nMatch);
if (Random($7FFF) mod nMatch) = 0 then
W := Suf.Word;
Suf := Suf.Next;
end;
if StrComp(W, NONWORD) = 0 then
Break;
SL.Add(W);
System.Move(Prefix[1], Prefix[0], (NPREF-1)*SizeOf(Prefix[0]));
Prefix[NPREF-1] := W;
end;
end;
end.
使うときはこんな感じ
procedure TForm1.btnBuildAndSaveClick(Sender: TObject);
var
Prefix: TPrefix;
I: Integer;
SL: TStringList;
FileName: string;
begin
if not OpenDialog1.Execute then Exit;
FileName := OpenDialog1.FileName;
for I := 0 to NPREF - 1 do
Prefix[I] := NONWORD;
Build(@Prefix, FileName);
Add(@Prefix, NONWORD);
SL := TStringList.Create;
try
Generate(MAXGEN, SL);
SL.Text := StringReplace(SL.Text, #$0D#$0A, ' ', [rfReplaceAll]);
SL.SaveToFile(ChangeFileExt(FileName, '_out.txt'));
finally
SL.Free;
end;
end;
なんか冗長な感じ・・・ツッコミ希望。次は Java, C++ バージョンを Delphi らしく書いてみたい。