Reputation: 183
My situation is that I am given a List which represents a directory structure, in the following format:
"My Folder\Images"
"My Folder\Images\Gif"
"My Folder\Images\JPG"
"My Folder\Media"
"My Folder\Media\Mov"
"My Folder\Media\Mov\QT"
"My Folder\Media\MPG"
There is no restriction on how many levels this can be nested.
I need to build something which represents a treeview from this, in the format:
public class Folder
{
public string FolderName { get; set; }
public List<Folder> Folders{ get; set; } // a list of subfolders
}
I just can't get the recrusive function which builds this quite right. Any help from the Gurus would be greatly appreciated.
TIA
Edit: My full class definition is:
public class Folder
{
public string Name { get; set; }
public List<Folder> Folders { get; set; }
public Folder(List<string> input)
{
foreach (var folder in input)
{
var delimPos = folder.IndexOf("\\");
if (delimPos == -1)
{
Name = folder ;
}
else
{
Name = folder.Substring(0, delimPos);
var subFolders= input.Select(o => o.Substring(delimPos + 1)).ToList();
Folders= new List<Folder>();
foreach (var subFolder in subFolders)
{
Folders.Add(new Folder(new List<string>() { subFolder }));
}
}
}
}
}
Upvotes: 1
Views: 93
Reputation: 1799
Below is a non-recursive solution, it parses and prints the values as desired. Let me know if it helps. Demo
Logic
public static IEnumerable<Folder> Parse(IEnumerable<string> locations)
{
var folders = new List<Folder>();
foreach (var location in locations)
{
var parts = location.Split(new[]{Path.DirectorySeparatorChar}, StringSplitOptions.RemoveEmptyEntries);
Folder currentFolder = null;
foreach (var part in parts)
{
var parentFolders = currentFolder!=null ? currentFolder.Folders : folders;
currentFolder = parentFolders.Find(folder => folder.Name == part) ?? new Folder { Name = part };
if (!parentFolders.Any(folder => folder.Name.Equals(currentFolder.Name)))
{
parentFolders.Add(currentFolder);
}
}
}
return folders;
}
Full Program
//Rextester.Program.Main is the entry point for your code. Don't change it.
//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace Rextester
{
public class Folder
{
public string Name { get; set; }
public List<Folder> Folders { get; internal set; }
public Folder()
{
this.Folders = new List<Folder>();
}
public static IEnumerable<Folder> Parse(IEnumerable<string> locations)
{
var folders = new List<Folder>();
foreach (var location in locations)
{
var parts = location.Split(new[]{Path.DirectorySeparatorChar}, StringSplitOptions.RemoveEmptyEntries);
Folder currentFolder = null;
foreach (var part in parts)
{
var parentFolders = currentFolder!=null ? currentFolder.Folders : folders;
currentFolder = parentFolders.Find(folder => folder.Name == part) ?? new Folder { Name = part };
if (!parentFolders.Any(folder => folder.Name.Equals(currentFolder.Name)))
{
parentFolders.Add(currentFolder);
}
}
}
return folders;
}
public override string ToString()
{
if (this.Folders.Count == 0)
{
return this.Name;
}
else
{
var folders = this.Folders
.SelectMany(folder => folder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None))
.Select(f => this.Name + Path.DirectorySeparatorChar + f);
return string.Join(Environment.NewLine, folders);
}
}
}
public class Program
{
public static void Main(string[] args)
{
var locationList = new List<string>()
{
@"My Folder\Images",
@"My Folder\Media",
@"My Folder\Images\Gif",
@"My Folder\Images\JPG",
@"My Folder\Media\Mov",
@"My Folder\Media\Mov\QT",
@"My Folder\Media\MPG"
};
var folderLists = Folder.Parse(locationList);
Console.WriteLine(string.Join(Environment.NewLine, folderLists));
Console.ReadLine();
}
}
}
Output:
My Folder\Images\Gif
My Folder\Images\JPG
My Folder\Media\Mov\QT
My Folder\Media\MPG
Upvotes: 0
Reputation: 151588
You of course have the problem that after splitting and processing Images\Gif
, you're going to re-add another Images
folder when splitting and processing Images\JPG
. The first will have a Gif
subfolder, the second will have a JPG
subfolder.
You can fix this by grouping on the first part, and only processing the parts that follow:
public static List<Folder> ParseInputRecursive(string[] input)
{
var foldersInParts = input.Select(f => f.Split(new [] { '\\' }, StringSplitOptions.RemoveEmptyEntries).ToList()).ToList();
return ParseInputRecursive(foldersInParts);
}
public static List<Folder> ParseInputRecursive(List<List<string>> input)
{
var folders = new List<Folder>();
foreach (var folderPartsGroup in input.GroupBy(p => p[0]))
{
var folder = new Folder { Name = folderPartsGroup.Key };
// Remove parent name, skip parent itself
var subFolders = folderPartsGroup.Select(f => f.Skip(1).ToList()).Where(f => f.Count > 0).ToList();
folder.Folders = ParseInputRecursive(subFolders);
folders.Add(folder);
}
return folders;
}
Printing them to verify:
// Sort to make sure parents always come first
Array.Sort(input);
var rootFolders = ParseInputRecursive(input);
foreach (var folder in rootFolders)
{
PrintFoldersRecursive(folder);
}
public static void PrintFoldersRecursive(Folder folder, int depth = 0)
{
Console.WriteLine(new string('*', depth++) + folder.Name);
foreach (var subFolder in folder.Folders)
{
PrintFoldersRecursive(subFolder, depth);
}
}
Given this input:
var input = new string[]
{
@"F1\Images",
@"F1\Images\Gif",
@"F1\Images\JPG",
@"F1\Media",
@"F1\Media\Mov",
@"F2\Docs",
@"F2\Docs\Foo",
};
Gives this output:
F1
*Images
**Gif
**JPG
*Media
**Mov
F2
*Docs
**Foo
Upvotes: 2
Reputation: 525
For a simple node traversal by string parsing please refer the below example
private void button1_Click(object sender, EventArgs e)
{
var folders = ProcessNode("My Folder\\Media\\Mov\\QT", 0);
MessageBox.Show("finished");
}
private Folder ProcessNode(string input, int index)
{
var newIndex = input.IndexOf("\\", index + 1);
if (newIndex < 0) return new Folder() { FolderName = input.Substring(index, input.Length - index) };
var nodeName = input.Substring(index, newIndex - index);
var thisFolder = new Folder()
{
FolderName = nodeName,
Folders = new List<Folder>()
};
thisFolder.Folders.Add(ProcessNode(input, newIndex));
return thisFolder;
}
If you want to use the split function and list
private void button1_Click(object sender, EventArgs e)
{
var splitted = "My Folder\\Media\\Mov\\QT".Split('\\').ToList();
var foldersList = ProcessNode(splitted, 0);
MessageBox.Show("finished");
}
private Folder ProcessNode(List<string> input, int index)
{
if (index >= input.Count) return null;
var thisFolder = new Folder()
{
FolderName = input[index],
Folders = new List<Folder>()
};
thisFolder.Folders.Add(ProcessNode(input, index + 1));
return thisFolder;
}
Upvotes: 0