Sortowanie i usuwanie wpisow z ListBox

0

witam kolegów i koleżanki.

Po kilku latach przerwy w zabawie z Object Pascalem stwierdzeniem ze wrócę, i okazało się że wiele zapomniałem.
Chce do listboxa wczytać plik txt z danymi w formacie

0234.23,adrian
0234.24,adrian
0568.43,michal

chodzi dokładnie o usuwanie wpisów miedzy którymi jest niewielka różnica np 0.1 a pozostawienie reszty.
Czy ktoś mogły rzucić jakaś propozycje.

Z gory dziekuje,

0

Po pierwsze to napisz z jakiego IDE i kompilatora korzystasz – do Pascala nie istnieje tylko jeden.

siupol napisał(a):

chodzi dokładnie o usuwanie wpisów miedzy którymi jest niewielka różnica np 0.1 a pozostawienie reszty.

Doprecyzuj. Na jakiej zasadzie pozycje mają być usuwane (prędzej pomijane) – konkrety.

0

Dokładnie Lazarus. A program ze zbioru wpisow np o wartosci 11.7 ,11.9, 12, 12.7, 14, 15,15,1 zostawi tylko 12,14,15. Generalnie chodzi o punkty na mapie opisane przez wspolrzedne geograficzne. Chce wczytac plik txt a nastepnie usunac z niego punkty ktore sie powtarzaja, lub sa bardzo blisko siebie.

0

Najpierw należy się skupić na samym wczytywaniu i konwersji liczb na postać natywną. Nie wiem ile linii istnieje w tym pliku (bo nie podałeś), jednak jeśli wyglądają w ten sposób:

0234.23,adrian
0234.24,adrian
0568.43,michal

to należy sukcesywnie wczytywać linijki, oddzielać liczby od tekstowych sufiksów, konwertować np. do typu Double i wrzucać do jakiejś listy, aby po załadowaniu wszystkich, móc wykonać filtrowanie.

Jeśli dane w pliku są już posortowane (według liczb, np. rosnąco – jak w przykładzie) to filtrowanie można wykonać już podczas wczytywania, tak aby od razu odrzucać duplikaty i nie pakować ich do listy wynikowej.

0

przyklad linii

10.31045,48.38631,,,0,0
1.88016,47.89786,,,0,0
13.67024,52.50096,,,0,0
-2.54723,52.76172,,,0,0
12.07761,51.51636,,,0,0
2.44149,43.19501,,,0,0
7.02261,49.16961,,,0,0
14.46485,46.03653,,,0,0
3.20840,45.79668,,,0,0
21.23267,46.73073,,,0,0

a jesli chodzi o rozmiar to pliki txt maja po 10mb i 500.000 lini i to mozebyc klopot

0

W pierwszym poście podałeś zupełnie coś innego… :|

10.31045,48.38631,,,0,0

W każdej linii pliku masz liczby i puste pola. Każdą linię możesz możesz czytać z pliku po kolei, używając pseudoprocedury Readln. W ten sposób będziesz mógł obrabiać dowolnie duże pliki. Liczb nie jest aż tak dużo – można je wszystkie wrzucić do listy w pamięci (uprzednio konwertując np. na typ Double) i dopiero wtedy obrobić. Problem pojawi się podczas szukania duplikatów – może to trochę potrwać.

Najpierw należy sobie napisać procedurkę, która przekonwertuje ciąg znaków na tablicę natywnych liczb.

1

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.

0

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.

samo porównanie zrobił bym nieco inaczej i stworzył bym dwu parametryczną funkcję zwracającą odległość (metrykę) pomiędzy dwoma Itemami
np coś takiego:

function metrics(aItem1, aItem2: TFileItem): Double;
var
  lDistX, lDistY: Double;
begin
  lDistX := aItem1.CoordX - aItem2.CoordX;
  lDistY := aItem1.CoordY - aItem2.CoordY;
  result := sqrt(lDistX * lDistX + lDistY * lDistY);
end;
if metrics(Item1,Item2)< COMPARE_DELTA then 
  begin
    ////
  end
else
  begin
    ////
  end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1