AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Algorithmus für Datei-Umbenennen?

Ein Thema von Benmik · begonnen am 13. Feb 2016 · letzter Beitrag vom 14. Feb 2016
Antwort Antwort
Benmik

Registriert seit: 11. Apr 2009
543 Beiträge
 
Delphi 11 Alexandria
 
#1

Algorithmus für Datei-Umbenennen?

  Alt 13. Feb 2016, 16:59
Ich stehe vor dem Problem, Dateien in einem Verzeichnis umzubenennen und dabei das Problem von Dateinamenkollisionen möglichst effizient zu lösen.

Es wird ein Grund-Dateiname erstellt und eine fortlaufende Zahl in frei konfigurierbarer Weise (Position, Stellen, Startzahl, Vornullen) eingefügt. Anzahl und Auswahl der umzubenennenden Dateien können völlig variieren. Die umbenannten Dateien bilden dann einen homogenen Block mit gleichem Grundnamen und fortlaufender Nummer. Also in etwa das, was die zahlreichen Datei-Umbenenner so machen, mit dem Unterschied, dass es sein kann, dass eine Datei innerhalb des Blocks auf eine neue Position soll. Dann sollen die Dateinamen innerhalb des Blocks entsprechend angepasst werden.

Ich habe nicht den Überblick über die ganze Programmvielfalt, aber die Umbenenner, die ich kenne, machen es sich einfach und stellen bei Kollisionen schlicht die Arbeit ein. Das geht besser.

Mein bisheriger Ansatz:

Alle Dateinamen sind in einer TObjectList<TDateiInfo>. TDateiInfo ist eine Klasse mit einer Reihe Feldern, von denen hier nur "Dateiname" und "DateinameNeu" interessant sind. Die ObjectList verfügt über die notwendigen Funktionen zum Sortieren, Vergleichen und BinarySearch.

Von der ObjectList werden zwei weitere Instanzen erzeugt. In die eine kommen alle Dateien außerhalb des Blocks, in die andere die umzubenennenden, wobei in "DateinameNeu" dann auch gleich der neue Dateiname gespeichert wird. Bei jedem neuen Dateinamen wird in beiden Listen gesucht, ob der entsprechende Name schon vorhanden ist. Ist dies in der "Außenliste" der Fall, wird abgebrochen, da dieser Zustand nicht reparierbar ist. Gibt es auch "intern" keine Duplikate, dann ist die Sache einfach, es wird einfach ruckizucki durchbenannt.

Natürlich geht es hier nur um den zweiten Fall. Klar ist, dass es Kombinationen gibt, wo die Vergabe eines temporären Dateinamens unumgänglich ist. Und genau das will ich durch "intelligentes" Umbenennen auf das unvermeidbare Maß (am besten natürlich Null) drücken.

Und das ist letztlich meine Frage: Gibt es für sowas einen schlauen Algorithmus?
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#2

AW: Algorithmus für Datei-Umbenennen?

  Alt 13. Feb 2016, 17:59
Du kannst es dir etwas einfacher machen.

Die TObjectList<T> kann auch auf Gleichheit prüfen. Dazu gibts du bei der Erzeugung der Liste Delphi-Referenz durchsuchenSystem.Generics.Collections.TObjectList.Create einen Delphi-Referenz durchsuchenSystem.Generics.Defaults.IComparer mit an.

In diesem Falle wirst du auf TDateiInfo.DateinameNeu vergleichen wollen.
Delphi-Quellcode:
list := TObjectList<TDateiInfo>.Create( TComparer<TDateiInfo>.Construct(
  function ( const L, R: TDateiInfo ): Integer
  begin
    Result := CompareStr( L.DateinameNeu, R.DateinameNeu );
  end ) );

// alle Dateien in die Liste eintragen

di := TDateiInfo.Create( 'SomeName.jpg' );

// prüfen, ob es diesen Eintrag schon gibt (wird über den Comparer ermittelt)
while list.Contains( di ) do
begin
  di.DateinameNeuAndersMachen();
end;

list.Add( di );

// Wenn fertig

for di in list do
  di.RenameTheFile();
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
543 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: Algorithmus für Datei-Umbenennen?

  Alt 13. Feb 2016, 19:01
Muss ich mir erstmal zu Gemüte führen, vielen Dank.
Allerdings bin ich schockiert:

KEIN INTERFACE ???
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#4

AW: Algorithmus für Datei-Umbenennen?

  Alt 13. Feb 2016, 21:48
IComparer<T>
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#5

AW: Algorithmus für Datei-Umbenennen?

  Alt 14. Feb 2016, 00:18
dass eine Datei innerhalb des Blocks auf eine neue Position soll.
Falls Du das mit der Position der Datei ernst meinst, verabschiede Dich davon. Du hast allerhöchstens eine Position innerhalb einer (Namens)Liste.

Gruß
K-h
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
Namenloser

Registriert seit: 7. Jun 2006
Ort: Karlsruhe
3.724 Beiträge
 
FreePascal / Lazarus
 
#6

AW: Algorithmus für Datei-Umbenennen?

  Alt 14. Feb 2016, 13:20
Und das ist letztlich meine Frage: Gibt es für sowas einen schlauen Algorithmus?
Du kannst das Problem als Graph darstellen: Die Knoten sind die Dateinamen (alte sowie neue), jede Umbenennung ist eine gerichtete Kante. Im Endeffekt läuft es dann darauf hinaus, Kreise in dem Graphen zu finden (einen schlauen Algorithmus dafür findest du auf Wikipedia ). Pro Kreis musst du einen temporären Dateinamen einfügen. Das ist die minimale Anzahl.

Geändert von Namenloser (14. Feb 2016 um 13:29 Uhr)
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
543 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Algorithmus für Datei-Umbenennen?

  Alt 14. Feb 2016, 21:40
Ich habe mir jetzt selber eine Lösung gebastelt. Der Schlauheitsgrad ist ungewiss, aber jedenfalls funktioniert sie. Bei ca. 1.400 Dateien (SSD, ältere CPU) benötigt sie zwischen 10 und 350 Millisekunden. Die besondere Herausforderung war, dass ich sie für ein Bildprogramm brauche, bei dem die Bilder auf einer Übersicht auf neue Positionen gezogen werden können, wobei die Dateinamen automatisch angepasst werden.

Der Algorithmus funktioniert so, dass die Dateien des Verzeichnisses zunächst getrennt werden nach denen, die umbenannt werden sollen, und den anderen (so vorhanden). Dabei wird für die umzubenennenden Dateien gleich der zukünftige Name ermittelt und gespeichert. Es wird geprüft, ob es Kollisionen gibt; bei solchen mit "äußeren" Dateien ist sogleich Schluss.

Danach wird in einer Schleife versucht umzubenennen. Erfolgreich umbenannte Dateien werden aus der Liste entfernt, die somit immer kleiner wird. Schluss ist hier, wenn entweder alle Dateien umbenannt werden konnten oder bei einem abgeschlossenen Durchlauf keine erfolgreichen Umbenennungen mehr möglich waren.

Danach wird mit temporären Umbenennungen gearbeitet bis zum hoffentlich guten Ende.

Der unten stehende Code ist herausoperiert und daher unvollständig und zum Teil simplifiziert; er dient nur zur Verdeutlichung des Algorithmus. Unter Umständen habe ich auch ein paar Anpassungen vergessen.

Die "Contains"-Lösung von Sir Rufo iteriert über alle Elemente der Liste; da ist eine BinarySearch sicher schneller. Ich habe auch eine Interface-Lösung von hier verwendet ("Replacing SHFileOperation With IFileOperation To Process Multiple File Operations In One Call"), aber mit dieser Lösung lief eine simple Umbenennung 50 - 300 Mal langsamer. Der Hinweis von Namenloser ist nichts für meine Preisklasse.

Delphi-Quellcode:
type

TSortArt = (soNachDatname,soNachUmbName);

TDateiInfo = class(TObject)
  Datname : string;
  Daterw : string;
  UmbName : string;
end;

TDateiListe = class(TObjectList<TDateiInfo>)
  private
    function VergleicheDatname(const L,R:TDateiInfo):Integer;
    function VergleicheUmbName(const L,R:TDateiInfo):Integer;
  protected
    function getItem(Index: Integer): TDateiInfo; virtual;
    procedure setItem(Index: Integer; Objekt: TDateiInfo); virtual;
  public
    procedure Sort(SortArt:TSortArt);
    function SucheDatName(const DatName:string;var P:Integer):Boolean;
    function SucheUmbName(const UmbName:string):Boolean;
  end;

const Verz = 'C:\temp\';

function BenenneBilderUm:Boolean;
var BInfo,UmbInfo:TDateiInfo;
    InDatUmbListe,InDatVerzListe:Boolean;
    DatUmbListe,DatVerzListe:TDateiListe;
const Zusatz = '$$$$';
//------------------------------------------------------------------------------------------------------------------------------------------
  procedure PrüfeDateinamenKollisionen;
  var DatNr,AnzUmb,p:integer; DatnameNeu:string;
  begin
    // Erstelle jeweils eine Liste der umzubenennenden Dateien und der im Zielverz vorhandenen (ggfs. übrigen) Dateien
    AnzUmb := 0;
    // Ordne alle Dateien der einen oder der anderen Liste zu
    For DatNr := 1 to Dateiliste.Count do begin
      BInfo := Dateiliste[DatNr - 1];
      If SollUmbenanntWerden then begin
        Inc(AnzUmb);
        DatnameNeu := ErmittleDatnameNeu(AnzUmb);
        BInfo.UmbName := DatnameNeu;
        DatUmbListe.Add(BInfo);
      end else begin
        BInfo.UmbName := '';
        DatVerzListe.Add(BInfo);
      end;
    end;
    DatUmbListe.Sort(soNachUmbName);
    // Doppelte Dateinamen durch Umbenennen innerhalb der umzubenennenden Dateien kann gehandhabt werden, bei den anderen nicht
    For DatNr := 1 to DatUmbListe.Count do begin
      If DatUmbListe.SucheDatName(Dateiliste[DatNr - 1].UmbName,p)
        then InDatUmbListe := True;
      InDatVerzListe := DatVerzListe.SucheDatName(Dateiliste[DatNr - 1].UmbName,p);
      If InDatVerzListe
        then break;
    end;
    If InDatVerzListe
      then Showmessage('Die Benennung der Dateien in der gewählten Form kollidiert mit vorhandenen Dateinamen im Zielverzeichnis.');
  end;
//------------------------------------------------------------------------------------------------------------------------------------------
  function BenenneDateiUm(DatnameAlt,DatnameNeu:string):Boolean; overload;
  begin
    Result := SameText(DatnameAlt,DatnameNeu) or (not FileExists(DatnameNeu) and RenameFile(DatnameAlt,DatnameNeu));
  end;
//------------------------------------------------------------------------------------------------------------------------------------------
  function BenenneDateiUm(Info:TDateiInfo;MitZusatz:Boolean = False):Boolean; overload;
  var DatnameAlt,DatnameNeu:string;
  begin
    DatnameAlt := Verz + Info.Datname + Info.Daterw;
    If MitZusatz
      then DatnameNeu := VUmbn.ZielVerz + Info.Datname + Zusatz + Info.Daterw
      else DatnameNeu := VUmbn.ZielVerz + Info.Umbname + Info.Daterw;
    Result := BenenneDateiUm(DatnameAlt,DatnameNeu);
    If Result and not MitZusatz then begin
      Info.DatName := Info.UmbName;
      Info.UmbName := '';
      DatUmbListe.Remove(Info);
    end;
  end;
//------------------------------------------------------------------------------------------------------------------------------------------
  function VergebeNeueDateinamen:Boolean;
  var DatNr,AnzUmb,p:integer;
  begin
    DatUmbListe.Sort(soNachDatName);
    Try
      // Erster Angriff von unten - alles umbenennen, was geht
      Repeat
        AnzUmb := DatUmbListe.Count;
        For DatNr := DatUmbListe.Count downto 1 do
          Result := BenenneDateiUm(DatUmbListe[DatNr - 1]);
      Until (DatUmbListe.Count = 0) or (AnzUmb = DatUmbListe.Count);
      // Im Idealfall ist hier schon Schluss - wenn nicht, liegen Dateikollisionen vor
      If DatUmbListe.Count > 0 then begin
        Repeat
          // Ausgangswert; "Repeat" wird solange wiederholt, wie sich hier etwas tut (DatUmbListe.Count sinkt)
          AnzUmb := DatUmbListe.Count;
          For DatNr := DatUmbListe.Count downto 1 do begin
            BInfo := DatUmbListe[DatNr - 1];
            // Handelt es sich um eine temporär umbenannte Kollisionsdatei?
            If EndsText(Zusatz,BInfo.Datname) then begin
              If DatUmbListe.SucheDatName(BInfo.UmbName,p) then begin
                UmbInfo := DatUmbListe[p];
                BenenneDateiUm(UmbInfo);
              end else begin
                BenenneDateiUm(BInfo);
              end;
            // der "störende" Gegenpart wird gesucht und durch Umbenennung aus dem Weg geräumt
            end else if DatUmbListe.SucheDatName(BInfo.UmbName,p) then begin
              UmbInfo := DatUmbListe[p];
              If BenenneDateiUm(UmbInfo,True) then begin
                UmbInfo.Datname := UmbInfo.Datname + Zusatz;
                // jetzt ist der Weg frei
                BenenneDateiUm(BInfo);
              end;
            end else begin
              BenenneDateiUm(BInfo);
            end;
          end;
        Until (DatUmbListe.Count = 0) or (AnzUmb = DatUmbListe.Count);
      end;
    Except
      Result := False;
      Fehlerbehandlung;
      exit;
    End;
    Result := (DatUmbListe.Count = 0);
    If not Result
      then Showmessage('Von ' + IntToStr(Dateiliste.Count) + ' Dateien konnten ' + IntToStr(DatUmbListe.Count) + ' nicht umbenannt werden.');
  end;
//------------------------------------------------------------------------------------------------------------------------------------------
begin
  PrüfeDateinamenKollisionen;
  VergebeNeueDateinamen;
end;

Geändert von Benmik (14. Feb 2016 um 23:13 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von BUG
BUG

Registriert seit: 4. Dez 2003
Ort: Cottbus
2.094 Beiträge
 
#8

AW: Algorithmus für Datei-Umbenennen?

  Alt 14. Feb 2016, 22:02
Der Schlauheitsgrad ist ungewiss, aber jedenfalls funktioniert sie.
Das ist das Wichtigste

Das ist die minimale Anzahl.
Der Graph, der da herauskommt hat interessante Eigenschaften: jeder Knoten kann nur einen Vorgänger haben (da sonst mehrere Dateien den gleichen Zielnamen hätten). Damit sollte jede ungerichteten Zusammenhangskomponent nur einen Kreis haben können.
In dem Fall könntest du mit einer Tiefensuche (finde Vorgänger, füge temporäre Umbenennung ein wenn du einen Kreis findest) vielleicht wirklich die optimale Lösung finden

Disclaimer: Graphentheorie und Bier ... das rat ich dir.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:27 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz