No dobra – pokażę Ci jak załadować dane do pamięci.
Z tego co pisałeś, najważniejsze w tym pliku są współrzędne – te trzeba będzie porównywać. Dlatego też wystarczy, że program wczyta dwie pierwsze liczby i przekonwertuje je na postać natywną, a pozostałą część linii po prostu zapamięta (jako ciąg znaków, bez żadnej konwersji). Czyli dane dotyczące jednej linijki z pliku będą reprezentowane w pamięci jako dwie liczby typu Double oraz ciąg sufiksu jako String.
Dane te przyda się pogrupować, więc trzeba zadeklarować jakąś paczkę. Ja posłużę się prostą klasą:
type
TFileItem = class(TObject)
private
FCoordX: Double;
FCoordY: Double;
FSufix: String;
public
constructor Create(ACoordX, ACoordY: Double; const ASufix: String);
public
property CoordX: Double read FCoordX;
property CoordY: Double read FCoordY;
property Sufix: String read FSufix;
end;
Jej zadaniem jest jedynie przechowanie danych, które powinna otrzymać w parametrach konstruktora:
constructor TFileItem.Create(ACoordX, ACoordY: Double; const ASufix: String);
begin
FCoordX := ACoordX;
FCoordY := ACoordY;
FSufix := ASufix;
end;
Przyda się też mieć do nich dostęp (aby móc później zapisać dane do pliku wynikowego, już po obróbce), więc dodałem właściwości.
Dane dotyczące pojedynczej linii mamy już gdzie przechowywać – teraz trzeba przygotować typ danych, umożliwiający załadowanie zawartości całego pliku, a nie tylko jednej linii. Przykładowo, niech tym kontenerem będzie lista generyczna:
uses
FGL, SysUtils, Math;
type
TFileItems = class(specialize TFPGObjectList<TFileItem>)
private
function ReadCoord(var AFile: TextFile): Double;
function ReadSufix(var AFile: TextFile): String;
public
procedure LoadFromFile(const AFileName: String);
procedure SaveToFile(const AFileName: String);
end;
Pierwsza metoda – ReadCoord
– służyć będzie do wczytania współrzędnej, czytając do separatora. Tak pozyskaną liczbę w postaci ciągu znaków będzie konwertować i zwracać w rezultacie. Wywoływana będzie dwa razy dla każdej linii pliku. Druga metoda – ReadString
– służyć będzie do wczytania pozostałej części, czyli sufiksu. Przeznaczenia pozostałych dwóch metod raczej nie trzeba tłumaczyć.
function TFileItems.ReadCoord(var AFile: TextFile): Double;
var
LChar: Char;
LValue: String = '';
LDummy: Integer;
begin
repeat
Read(AFile, LChar);
if LChar <> ',' then
LValue += LChar;
until LChar = ',';
Val(LValue, Result, LDummy);
end;
Metoda ta czyta znaki z bieżącej linii dotąd, aż wczytanym znakiem będzie przecinek. Następnie za pomocą procedury Val konwertuje pozyskany ciąg znaków na liczbę typu Double i zwraca ją w rezultacie. I na tym koniec – po wykonaniu kodu metody, kursor w pliku ustawiony będzie na pierwszą cyfrę kolejnej współrzędnej (więc można ją wywołać znów) lub na pierwszy znak sufiksu (to już nie jest istotne).
function TFileItems.ReadSufix(var AFile: TextFile): String;
begin
ReadLn(AFile, Result);
end;
Ta metoda wczytuje pozostałą część linii i zwraca ją w rezultacie, przenosząc kursor w pliku na początek kolejnej linii.
procedure TFileItems.LoadFromFile(const AFileName: String);
var
LFile: TextFile;
var
LCoordX, LCoordY: Double;
LSufix: String;
begin
Self.Clear();
AssignFile(LFile, AFileName);
Reset(LFile);
try
while not EoF(LFile) do
begin
LCoordX := ReadCoord(LFile);
LCoordY := ReadCoord(LFile);
LSufix := ReadSufix(LFile);
Self.Add(TFileItem.Create(LCoordX, LCoordY, LSufix));
end;
finally
CloseFile(LFile);
end;
end;
Ta metoda służy do załadowania zawartości pliku do pamięci. Wczytuje dane linia po linii i dodaje obiekty do listy, wypełniając je danymi z pliku. Dla każdej linii:
- wczytuje pierwszą współrzędną (i ustawia kursor na pierwszą cyfrę drugiej współrzędnej),
- wczytuje drugą współrzędną (i ustawia kursor na początek ciągu sufiksu),
- wczytuje sufiks.
procedure TFileItems.SaveToFile(const AFileName: String);
var
LFile: TextFile;
LCoordX, LCoordY: String;
LItemIdx: Integer;
begin
AssignFile(LFile, AFileName);
ReWrite(LFile);
try
for LItemIdx := 0 to Self.Count - 1 do
begin
Str(Self.Items[LItemIdx].CoordX:0:5, LCoordX);
Str(Self.Items[LItemIdx].CoordY:0:5, LCoordY);
WriteLn(LFile, LCoordX, ',', LCoordY, ',', Self.Items[LItemIdx].Sufix);
end;
finally
CloseFile(LFile);
end;
end;
Metoda działająca na odwrót – tworzy plik docelowy i każdą pozycję z listy z pamięci zapisuje w osobnej linii w pliku. Konwersji współrzędnej z postaci natywnej do ciągu znaków dokonuje pseudoprocedura Str (użyłem tej, dlatego że ciągi wyjściowe będą identyczne z wejściowymi). Zaznaczyć trzeba, że po drugiej współrzędnej dodany jest ekstra przecinek – wszystko dlatego, że wczytanie drugiej współrzędnej pomijało separator tuż po niej.
To w sumie tyle – teraz można już zadeklarować listę, załadować zawartość pliku do pamięci oraz zapisać listę z powrotem do pliku:
var
LItems: TFileItems;
begin
LItems := TFileItems.Create();
try
LItems.LoadFromFile('input.txt');
LItems.SaveToFile('output.txt');
finally
LItems.Free();
end;
end.
Jeśli program zadziałał poprawnie to plik docelowy posiadać będzie dokładnie taką samą zawartość jak plik źródłowy. Jedyną różnica może polegać na tym, że współrzędne z mniejszą precyzją niż do pięciu cyfr po przecinku, będą zakończone zerami. Oczywiście w niczym to nie przeszkodzi – dane w dalszym ciągu będą poprawne.
Podstawa (a raczej większość) kodu już jest – Twoim zadaniem teraz jest napisanie metody odrzucającej duplikaty. Do tego celu sugeruję użyć funkcji CompareValue. Najpierw zdefiniuj sobie stałą z dopuszczalnym zakresem podobieństwa, np.:
const COMPARE_DELTA: Double = 0.1;
a następnie użyj do porównania koordynatów:
if (CompareValue(CoordX1, CoordX2, COMPARE_DELTA) = EqualsValue) and
(CompareValue(CoordY1, CoordY2, COMPARE_DELTA) = EqualsValue) then
Jeśli oba warunki zostaną spełnione to współrzędne są na tyle blisko siebie, aby jedną usunąć z listy.
W razie czego, cały kod oraz testowy plik z danymi wrzucam do załączników.