program nf;
{$mode objfpc}
{$H-} //don't need ansistrings
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils;
{$R *.res}
{.$DEFINE DEBUG}
{.$DEFINE TEST}
type
TNumberFileOption = (nfoFirstValueIsNrOfLines, nfoSecondValueIsCount);
TNumberFileOptions = set of TNumberFileOption;
TSolution = (solLowest, solHighest);
TSolutions = set of TSolution;
{ TNumberFileStream }
TNumberFileStream = class
private
FOptions: TNumberFileOptions;
FList: TList;
FExpectedNrOfLines: Integer;
FRealNrOfLines: Integer;
FExpectedCount: Integer;
FSolutions: TSolutions;
procedure SetOptions(AValue: TNumberFileOptions);
procedure Scan(AStream: TStream);
procedure AddToList(L: PtrInt);
procedure CheckFileValidity;
function CountLineEndings(S: String): Integer;
function Count: Integer;
function FindLowest: PtrInt;
function FindHighest: PtrInt;
{$IFDEF DEBUG}
procedure DebugOutPut;
{$ENDIF}
protected
public
constructor Create;
destructor Destroy; override;
procedure ReadAssignmentFromFile(const Fn: String);
procedure DisplaySolution;
procedure SaveSolutionToFile(const Fn: String);
property Options: TNumberFileOptions read FOptions write SetOptions default [nfoFirstValueIsNrOfLines];
property RequiredSolutions: TSolutions read FSolutions write FSolutions default [solLowest];
end;
EInvalidFileFormat = class (EStreamError);
function ListSortUp(Item1, Item2: Pointer): Integer;
begin
Result := {%H-}{%H-}PtrInt(Item1) - {%H-}{%H-}PtrInt(Item2);
end;
function ListSortDown(Item1, Item2: Pointer): Integer;
begin
Result := {%H-}{%H-}PtrInt(Item2) - {%H-}{%H-}PtrInt(Item1);
end;
procedure TNumberFileStream.SetOptions(AValue: TNumberFileOptions);
begin
if FOptions = AValue then Exit;
FOptions := AValue;
end;
procedure TNumberFileStream.Scan(AStream: TStream);
var
C: Char;
S, LEString: String;
L: PtrInt;
const
WhiteSpace = [#0,#9,#10,#13,#32];
NewLineChars = [#10,#13];
begin
AStream.Position := 0;
if (AStream.Size <> 0) then
FRealNrOfLines := 1;
FList.Clear;
S:= '';
LEString := '';
while (AStream.Read(C{%H-}, 1) = 1) do
begin
if (C in NewLineChars) then
begin
LEString := LEString + C;
end;
if (C in WhiteSpace) then
begin
if (S <> '') then
begin
if not TryStrToInt(S, L) then
Raise EInvalidFileFormat.CreateFmt('Invalid file format: %s is not a number.',[S]);
S := '';
AddToList(L);
end;
end
else
begin
S := S + C;
end;
end;
FRealNrOfLines := CountLineEndings(LEString) + FRealNrOfLines;
if (C in NewLineChars) then Dec(FRealNrOfLines); //file ended with empty line
CheckFileValidity;
{$IFDEF DEBUG}
DebugOutPut;
{$ENDIF}
end;
procedure TNumberFileStream.AddToList(L: PtrInt);
begin
if (FList.Count = 0) and (nfoFirstValueIsNrOfLines in FOptions) and (FExpectedNrOfLines = -1) then
begin
if (L <= 0) then
Raise EInvalidFileFormat.CreateFmt('Invalid file format: %d is not a valid value for "expected number of lines".',
[L]);
FExpectedNrOfLines := L
end
else
begin
if (FList.Count = 0) and (nfoSecondValueIsCount in FOptions) and (FExpectedNrOfLines <> -1) and (FExpectedCount = -1) then
begin
if (L < 0) then
Raise EInvalidFileFormat.CreateFmt('Invalid file format: %d is not a valid value for "expected amount of numbers".',
[L]);
FExpectedCount := L;
end
else
FList.Add({%H-}Pointer(L));
end;
end;
procedure TNumberFileStream.CheckFileValidity;
begin
//nfoFirstValueIsNrOfLines, nfoSecondValueIsCount
if (Count = 0) then
Raise EInvalidFileFormat.Create('Invalid file format: number of values is zero.');
if FOptions = [] then Exit;
if (nfoFirstValueIsNrOfLines in FOptions) and (FExpectedNrOfLines <> FRealNrOfLines) then
Raise EInvalidFileFormat.CreateFmt('Invalid file format:'+LineEnding+
'Expected number of lines (%d) does not match real number of lines (%d)',
[FExpectedNrOfLines,FRealNrOfLines]);
if (nfoSecondValueIsCount in FOptions) and (FExpectedCount <> FList.Count) then
Raise EInvalidFileFormat.CreateFmt('Invalid file format:'+LineEnding+
'Expected number of values (%d) does not match real number of values (%d)',
[FExpectedCount,FList.Count]);
end;
function TNumberFileStream.CountLineEndings(S: String): Integer;
//Assumes S only contains LineEnding characters: #10 and/or #13
begin
S := AdjustLineBreaks(S);
Result := Length(S) div Length(LineEnding);
end;
function TNumberFileStream.Count: Integer;
begin
Result := FList.Count;
end;
function TNumberFileStream.FindLowest: PtrInt;
var
NewList: TList;
i: Integer;
begin
NewList := TList.Create;
try
for i := 0 to FList.Count - 1 do
NewList.Add(FList.Items[i]);
NewList.Sort(@ListSortUp);
Result := {%H-}PtrInt(NewList.Items[0]);
finally
NewList.Free;
end;
end;
function TNumberFileStream.FindHighest: PtrInt;
var
NewList: TList;
i: Integer;
begin
NewList := TList.Create;
try
for i := 0 to FList.Count - 1 do
NewList.Add(FList.Items[i]);
NewList.Sort(@ListSortDown);
Result := {%H-}PtrInt(NewList.Items[0]);
finally
NewList.Free;
end;
end;
{$IFDEF DEBUG}
procedure TNumberFileStream.DebugOutPut;
var
i: Integer;
begin
writeln('FExpectedNrOfLines = ',FExpectedNrOfLines);
writeln('FRealNrOfLines = ',FRealNrOfLines);
writeln('FExpectedCount = ',FExpectedCount);
writeln('Count = ',FList.Count);
for i := 0 to FList.Count - 1 do
begin
writeln(' * ',i:2,#32,{%H-}PtrInt(FList.Items[i]));
end;
writeln('Lowest = ',FindLowest);
writeln('Highest = ',FindHighest);
end;
{$ENDIF}
constructor TNumberFileStream.Create;
begin
FList := TList.Create;
FExpectedNrOfLines := -1;
FRealNrOfLines := 0;
FExpectedCount := -1;
FOptions := [nfoFirstValueIsNrOfLines];
FSolutions := [solLowest];
end;
destructor TNumberFileStream.Destroy;
begin
FList.Free;
inherited Destroy;
end;
procedure TNumberFileStream.ReadAssignmentFromFile(const Fn: String);
var
FS: TFileStream;
begin
FS := TFileStream.Create(Fn, fmOpenRead or fmShareDenyNone);
try
Scan(FS);
finally
FS.Free;
end;
end;
procedure TNumberFileStream.DisplaySolution;
var
i: Integer;
begin
if (FSolutions = []) then Exit;
writeln('Numbers found: ');
for i := 0 to FList.Count - 1 do
begin
write({%H-}PtrInt(FList.Items[i]), #32);
end;
writeln;
if (solLowest in FSolutions) then writeln('Lowest number : ',FindLowest);
if (solHighest in FSolutions) then writeln('Highest number: ',FindHighest);
end;
procedure TNumberFileStream.SaveSolutionToFile(const Fn: String);
var
SL: TStringList;
S: String;
i: Integer;
begin
if (FSolutions = []) then Exit;
SL := TStringList.Create;
try
SL.Add('Numbers Found: ');
S := '';
for i := 0 to FList.Count - 1 do
begin
S := S + IntToStr({%H-}PtrInt(FList.Items[i])) + #32;
end;
S := Trim(S);
SL.Add(S);
if (solLowest in FSolutions) then SL.Add(Format('Lowest number : %d',[FindLowest]));
if (solHighest in FSolutions) then SL.Add(Format('Highest number: %d',[FindHighest]));
SL.SaveToFile(Fn);
finally
SL.Free;
end;
end;
procedure SyntaxHelp;
begin
writeln('Syntax:');
writeln('nf --in=Filename1 [--out=Filename2] [--sol=Lowest|Highest|Both]');
end;
function ParseCommandLine(out InFn, OutFn: String; out Sol: TSolutions): Boolean;
{$IFNDEF TEST}
var
i: Integer;
S:String;
P: SizeInt;
Ext: RawByteString;
{$IFDEF DEBUG}
ASol: TSolution;
{$ENDIF}
{$ENDIF TEST}
begin
{$IFDEF TEST}
InFn := 'in.txt';
OutFn := 'out.txt';
Sol := [solLowest, solHighest];
Result := True;
{$ELSE}
Result := False;
InFn := '';
OutFn := '';
Sol := [solLowest];
if (ParamCount < 1) or (ParamCount > 3)
or (UpperCase(ParamStr(1)) = '-h')
or (UpperCase(ParamStr(1)) = '--help')
or (UpperCase(ParamStr(1)) = '-?')
or (UpperCase(ParamStr(1)) = '/?') then
begin
Exit;
end;
for i := 1 to ParamCount do
begin
S := ParamStr(i);
if (Pos('--IN=',UpperCase(S)) = 1) then
begin
S := Copy(S, 6, MaxInt);
P := Pos(#32, S);
if (P > 0) then System.Delete(S, P, MaxInt);
InFn := Trim(S);
end
else if (Pos('--OUT=',UpperCase(S)) = 1) then
begin
S := Copy(S, 7, MaxInt);
P := Pos(#32, S);
if (P > 0) then System.Delete(S, P, MaxInt);
OutFn := Trim(S);
end
else if (Pos('--SOL=',UpperCase(S)) = 1) then
begin
S := Copy(S, 7, MaxInt);
P := Pos(#32, S);
if (P > 0) then System.Delete(S, P, MaxInt);
S := Trim(S);
case UpperCase(S) of
'LOWEST': Sol := [solLowest];
'HIGHEST': Sol := [solHighest];
'BOTH': Sol := [solLowest, solHighest];
else
begin
writeln('Ilegal parameter: ',ParamStr(i));
Exit;
end;
end;
end
else
begin
writeln('Illegal parameter: ',ParamStr(i));
Exit;
end;
end;
if (InFn = '') then
begin
writeln('InFn is empty');
Exit;
end;
if (OutFn = '') then
begin
OutFn := InFn;
Ext := ExtractFileExt(OutFn);
OutFn := Copy(OutFn, 1, Length(OutFn) - Length(Ext));
OutFn := OutFn + '-solution' + Ext;
end;
Result := True;
{$IFDEF DEBUG}
writeln('InFn = ',InFn);
writeln('OutFn = ',OutFn);
writeln('Sol = ');
for ASol in Sol do writeln(#32, ASol);
{$ENDIF DEBUG}
{$ENDIF TEST}
end;
var
NFS: TNumberFileStream;
InFn, OutFn: String;
Solutions: TSolutions;
begin
if ParseCommandLine(InFn, OutFn, Solutions) then
begin
NFS := TNumberFileStream.Create;
try
try
NFS.Options := [nfoFirstValueIsNrOfLines, nfoSecondValueIsCount];
NFS.RequiredSolutions := Solutions;
NFS.ReadAssignmentFromFile(InFn);
NFS.DisplaySolution;
NFS.SaveSolutionToFile(OutFn);
except
on E: EStreamError do
begin
write('An unhandled exception has occurred of type ',E.ClassName);
writeln(', with message:');
writeln(E.Message);
end;
end;
finally
NFS.Free;
end;
end
else
SyntaxHelp;
end.