Quale sarebbe un buon approccio per generare un albero di cartelle?

7

Dire che ho una serie di stringhe, come questa:

var folders = new[]
{
    "Foo",
    "Bar",
    "Foo\Bar"
    "Foo\Bar\Baz"
};

E che ho un oggetto che rappresenta una cartella - qualcosa del genere:

class Folder
{
    private readonly string _name;
    private readonly IEnumerable<Folder> _folders;

    public Folder(string name, IEnumerable<Folder> folders)
    {
        _name = name; 
        _folders = folders;
    }

    public string Name { get { return _name; } }
    public IEnumerable<Folder> Folders { get { return _folders; } }
}

Quale sarebbe un buon modo per finire con una struttura di oggetti come questa?

- Folder {Name:Foo}
  - Folder {Name:Bar}
    - Folder {Name:Baz}
- Folder {Name:Bar}

Ci sto pensando in termini di divisione delle stringhe sul delimitatore e poi di raggruppamento ... e sto pensando che sia sbagliato, semplicemente non ho un approccio per arrivarci, non sta andando da nessuna parte. Ho la sensazione che devo coinvolgere la ricorsione in qualche modo , ma non vedo dove inserirmi, sono bloccato.

Il codice di esempio sopra è C #, ma non ho bisogno di codice effettivo, solo qualche pseudo-codice , o una linea di pensiero, un piccolo indizio.

... Spero che sia in-scope?

    
posta Mathieu Guindon 12.01.2016 - 23:53
fonte

3 risposte

7

Il seguente algoritmo dovrebbe fare quasi quello che hai chiesto.

paths : List<String>    ;; your list of paths
tree : Folder = NEW Folder("")
FOREACH path IN paths DO
    node : Folder = tree
    FOREACH component IN split(path, "/") DO
        next : Folder = node.getChild(component)
        IF next IS NULL THEN
            next := NEW Folder(component)
            node.addChild(next)
        FI
        node := next
    DONE
DONE

Sto dicendo quasi perché creerà una directory tree sotto un nodo radice comune etichettato con la stringa vuota (come / nei file system POSIX). Se non lo vuoi, ma hai una foresta di alberi di directory, dovresti scrivere una piccola struttura dati per contenere gli alberi della foresta. O vai avanti e usa la radice comune e dopo aver costruito l'albero, estrai i suoi nodi figli (gli alberi) in un elenco.

L'esempio di codice presuppone un metodo Folder::getChild che restituirà un riferimento a un figlio con quel nome se esiste o il puntatore NULL. Ovviamente Folder::addChild dovrebbe aggiungere un altro child Folder .

    
risposta data 13.01.2016 - 00:16
fonte
5

Supponiamo che tu disponga di un metodo aggiuntivo GetOrCreate(name) che restituisce una cartella per un determinato nome o che lo crea se non esiste alcuna cartella di questo tipo e che hai una cartella root che contiene l'intera gerarchia. Quindi, dato un elenco di stringhe path , possiamo facilmente implementare una soluzione iterativa:

Folder current = root;
for (name in path)
  current = current.GetOrCreate(name);

In alternativa puoi definire un metodo ricorsivo MakePath(path) :

public void MakePath(names) {
  return if path is empty;
  GetOrCreate(path.First).MakePath(path.Rest);
}

La domanda interessante è come potrebbe funzionare GetOrCreate . Il tuo problema è che stai usando un IEnumerable<Folder> , in cui vuoi veramente una struttura di dati associativi (mappa, dizionario, tabella hash, qualunque cosa venga chiamata nella tua lingua). Ciò consente un facile test di appartenenza e impone l'unicità - nessuna cartella con lo stesso genitore può condividere lo stesso nome.

La cartella radice sarebbe un altro problema nella tua attuale progettazione, dal momento che non ha alcun nome ovvio (beh, in Windows land sarebbe un po 'come le lettere delle unità, ma potrebbe essere inutile nella tua applicazione). Invece, il nome di child potrebbe essere una proprietà della cartella padre, non delle cartelle figlio. Il nome è implicito come chiave nella tabella hash piuttosto che come proprietà di un oggetto esplicito. Ciò evita la duplicazione dei dati, ma rende difficile rispondere a domande come "data questa cartella, qual è il suo nome?". Certo, è già difficile rispondere "data questa cartella, qual è il suo percorso completo?", A meno che ciascuna cartella non abbia un facile accesso al suo nome e al suo genitore.

    
risposta data 13.01.2016 - 00:15
fonte
3

Se osservi come il file system gestisce la cartella all'interno della loro struttura, c'è una differenza fondamentale che semplifica le cose da fare e che tiene traccia sia del genitore che delle cartelle (e dei file).

Nel tuo caso questo significa che dovresti tenere traccia del genitore di ogni dato Folder , e prendere una decisione riguardo al livello superiore per avere una cartella vuota vuota, o lasciare che la cartella al livello più alto abbia null come cartella principale, consentendo così alla cartella di essere allo stesso livello.

Questo significa che devi avere un costruttore che accetta fino a tre parametri:

  • name - Il nome della cartella corrente, dovrebbe richiedere sempre un valore
  • parent - Collega alla cartella genitore, preferibilmente un riferimento debole per evitare forti riferimenti circolari che possono gettare i garbage collector. Può essere impostato su null
  • folders - Elenco delle sottocartelle o null per non indicare ancora sottocartelle. Assicurarsi tuttavia che il costruttore crei un'appropriata istanza dell'elenco di cartelle. Può essere vuoto, ma dovrebbe essere pronto per aggiungere nuove cartelle.

Per quanto riguarda i metodi per questa classe, suggerirei almeno quanto segue:

  • GetOrCreate(folderName) - Cerca nell'elenco delle cartelle e, se non trovato, crea una nuova cartella con self / this come genitore. Restituisci questo% co_de appena creato
  • Opzionalmente Folder - Accetta un percorso completo, che si divide nei componenti della cartella. A partire dal livello corrente, fai un AddPath(path) , e cambia il livello attuale e ripeti per il prossimo componente della cartella.
  • GetOrCreate() - A partire dal livello corrente, aggiungi il livello corrente a tutti i livelli genitore per fornirti il percorso completo.

Se non implementa il GetFullPath() come metodo separato, devi comunque eseguire quanto segue per l'elenco di percorsi e questo potrebbe essere un metodo statico della tua classe AddPath() :

  • definisce il tuo livello superiore, ovvero Folder()
  • foreach tree = new Folder("") in path :
    • paths
    • foreach current = tree in split component
      • path

Spero che questo ti aiuti a iniziare.

    
risposta data 13.01.2016 - 01:13
fonte