Exporting diectory structure to csv/xl file - c#

my requirement is to enumerate all directories and specific .tif files (that are at the end of the structure). Sample is
A (path selected from UI) <has>
B<has> and C<has>
D <has> E F G H I J
K L<has>
1.tif 2.tif
In the above directory, A has B and C. Named as clients. B has D,E,F (as dated), D has K and L (family).
So Ineed your help in retrieving the directory structure in txt or excel file as
B D
K 0
L 2 (since there are two tif files)
E
F
Similary for c and other directories.

Maybe this would do the trick (or give at least a good start point):
public void OutputStructureToFile(string outputFileName, string folder, string searchPattern)
{
using (var file = new StreamWriter(outputFileName))
{
file.Write(GetStructure(new DirectoryInfo(folder), searchPattern));
}
}
public string GetStructure(DirectoryInfo directoryInfo, string searchPattern)
{
return GetStructureRecursive(directoryInfo, searchPattern, 0);
}
private string GetStructureRecursive(DirectoryInfo directoryInfo, string searchPattern, int level)
{
var sb = new StringBuilder();
var indentation = level * 5;
sb.Append(new String(' ', indentation));
sb.AppendLine(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
{
sb.Append(GetStructureRecursive(directory, searchPattern, level+1));
}
var groupedByExtension = directoryInfo.GetFiles(searchPattern)
.GroupBy(file => file.Extension)
.Select(group => new { Group = group.Key, Count = group.Count() });
foreach (var entry in groupedByExtension)
{
sb.Append(new String(' ', indentation));
sb.AppendLine(String.Format(" {0,10} {1,3}", entry.Group, entry.Count));
}
return sb.ToString();
}
And if you need it for Excel as a .csv file you should instead use this recursive function
private string GetStructureRecursiveForCsv(DirectoryInfo directoryInfo, string searchPattern, int level)
{
var sb = new StringBuilder();
var indentation = level;
sb.Append(new String(';', indentation));
sb.AppendLine(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
{
sb.Append(GetStructureRecursiveForCsv(directory, searchPattern, level+1));
}
var groupedByExtension = directoryInfo.GetFiles(searchPattern)
.GroupBy(file => file.Extension)
.Select(group => new { Group = group.Key, Count = group.Count() });
foreach (var entry in groupedByExtension)
{
sb.Append(new String(';', indentation));
sb.AppendLine(String.Format(";{0};{1}", entry.Group, entry.Count));
}
return sb.ToString();
}

I am not sure that I understand what you want but here is something that to get you started
private static void ProcessFolder(string folder, string level, string separator, StreamWriter output)
{
var dirs = Directory.GetDirectories(folder);
foreach ( var d in dirs )
{
output.Write(level);
output.WriteLine(d);
ProcessFolder(d, level + separator, separator, output);
}
Console.WriteLine();
var files = Directory.GetFiles(folder);
foreach ( var f in files )
{
output.Write(level);
output.WriteLine(f);
}
}
You will have to customize it to filter TIFF or whatever you want. You can call this function like this and it will generate the file
using ( var output = new StreamWriter(#"C:\test.csv") )
{
ProcessFolder(#"c:\Program files", "", ";", output);
}
Double-click on the generated file and Excel will probably open :)

Related

How to Get Groups of Files from GetFiles()

I have to process files everyday. The files are named like so:
fg1a.mmddyyyy
fg1b.mmddyyyy
fg1c.mmddyyyy
fg2a.mmddyyyy
fg2b.mmddyyyy
fg2c.mmddyyyy
fg2d.mmddyyyy
If the entire file group is there for a particular date, I can process it. If it isn't there, I should not process it. I may have several partial file groups that run over several days. So when I have fg1a.12062017, fg1b.12062017 and fg1c.12062017, I can process that group (fg1) only.
Here is my code so far. It doesn't work because I can't figure out how to get only the full groups to add to the the processing file list.
fileList = Directory.GetFiles(#"c:\temp\");
string[] fileGroup1 = { "FG1A", "FG1B", "FG1C" }; // THIS IS A FULL GROUP
string[] fileGroup2 = { "FG2A", "FG2B", "FG2C", "FG2D" };
List<string> fileDates = new List<string>();
List<string> procFileList;
// get a list of file dates
foreach (string fn in fileList)
{
string dateString = fn.Substring(fn.IndexOf('.'), 9);
if (!fileDates.Contains(dateString))
{
fileDates.Add(dateString);
}
}
bool allFiles = true;
foreach (string fg in fileGroup1)
{
foreach (string fd in fileDates)
{
string finder = fg + fd;
bool foundIt = false;
foreach (string fn in fileList)
{
if (fn.ToUpper().Contains(finder))
{
foundIt = true;
}
}
if (!foundIt)
{
allFiles = false;
}
else
{
foreach (string fn in fileList)
{
procFileList.Add(fn);
}
}
}
}
foreach (string fg in fileGroup2)
{
foreach (string fd in fileDates)
{
string finder = fg + fd;
bool foundIt = false;
foreach (string fn in fileList)
{
if (fn.ToUpper().Contains(finder))
{
foundIt = true;
}
}
if (!foundIt)
{
allFiles = false;
}
else
{
foreach (string fn in fileList)
{
procFileList.Add(fn);
}
}
}
}
Any help or advice would be greatly appreciated.
Because it can sometimes get messy dealing with multiple lists, groupings, and parsing file names, I would start by creating a class that represents a FileGroupItem. This class would have a Parse method that takes in a file path, and then has properties that represent the group part and date part of the file name, as well as the full path to the file:
public class FileGroupItem
{
public string DatePart { get; set; }
public string GroupName { get; set; }
public string FilePath { get; set; }
public static FileGroupItem Parse(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath)) return null;
// Split the file name on the '.' character to get the group and date parts
var fileParts = Path.GetFileName(filePath).Split('.');
if (fileParts.Length != 2) return null;
return new FileGroupItem
{
GroupName = fileParts[0],
DatePart = fileParts[1],
FilePath = filePath
};
}
}
Then, in my main code, I would create a list of the file group definitions, and then populate a list of FileGroupItems from the directory we're scanning. After that, we can determine if any file group definition is complete by comparing it's items (in a case-insensitive way) to the actual FileGroupItems we found in the directory (after first grouping the FileGroupItems by it's DatePart). If the intersection of these two lists has the same number of items as the file group definition, then it's complete and we can process that group.
Maybe it will make more sense in code:
private static void Main()
{
var scanDirectory = #"f:\public\temp\";
var processedDirectory = #"f:\public\temp2\";
// The lists that define a complete group
var fileGroupDefinitions = new List<List<string>>
{
new List<string> {"FG1A", "FG1B", "FG1C"},
new List<string> {"FG2A", "FG2B", "FG2C", "FG2D"}
};
// Populate a list of FileGroupItems from the files
// in our directory, and group them on the DatePart
var fileGroups = Directory.EnumerateFiles(scanDirectory)
.Select(FileGroupItem.Parse)
.GroupBy(f => f.DatePart);
// Now go through each group and compare the items
// for that date with our file group definitions
foreach (var fileGroup in fileGroups)
{
foreach (var fileGroupDefinition in fileGroupDefinitions)
{
// Get the intersection of the group definition and this file group
var intersection = fileGroup
.Where(f => fileGroupDefinition.Contains(
f.GroupName, StringComparer.OrdinalIgnoreCase))
.ToList();
// If all the items in the definition are there, then process the files
if (intersection.Count == fileGroupDefinition.Count)
{
foreach (var fileGroupItem in intersection)
{
Console.WriteLine($"Processing file: {fileGroupItem.FilePath}");
// Move the file to the processed directory
File.Move(fileGroupItem.FilePath,
Path.Combine(processedDirectory,
Path.GetFileName(fileGroupItem.FilePath)));
}
}
}
}
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
I think you could simplify your algorithm so you just have file groups as a prefix and a number of files to expect, fg1 is 3 files for a given date
I think your code to find the distinct dates present is a good idea, though you should use a hash set rather than a list, if you occasionally expect a large number of dates.. ("Valentine's Day?" - Ed)
Then you just need to work on the other loop that does the checking. An algorithm like this
//make a new Dictionary<string,int> for the filegroup prefixes and their counts3
//eg myDict["fg1"] = 3; myDict["fg2"] = 4;
//list the files in the directory, into an array of fileinfo objects
//see the DirectoryInfo.GetFiles method
//foreach string d in the list of dates
//foreach string fgKey in myDict.Keys - the list of group prefixes
//use a bit of Linq to get all the fileinfos with a
//name starting with the group and ending with the date
var grplist = myfileinfos.Where(fi => fi.Name.StartsWith(fg) && fi.Name.EndsWith(d));
//if the grplist.Count == the filegroup count ( myDict[fgKey] )
//then send every file in grplist for processing
//remember that grplist is a collection of fileinfo objects,
//if your processing method takes a string filename, use fileinfo.Fullname
Putting your file groupings into one dictionary will make things a lot easier than having them as x separate arrays
I haven't written all the code for you, but I've comment sketched the algorithm, and I've put in some of the more awkward bits like the link, dictionary declaration and how to fill it.. have a go at fleshing it out with code, ask any questions in a comment on this post
First, create an array of the groups to make processing easier:
var fileGroups = new[] {
new[] { "FG1A", "FG1B", "FG1C" },
new[] { "FG2A", "FG2B", "FG2C", "FG2D" }
};
Then you can convert the array into a Dictionary to map each name back to its group:
var fileGroupMap = fileGroups.SelectMany(g => g.Select(f => new { key = f, group = g })).ToDictionary(g => g.key, g => g.group);
Then, preprocess the files you get from the directory:
var fileList = from fname in Directory.GetFiles(...)
select new {
fname,
fdate = Path.GetExtension(fname),
ffilename = Path.GetFileNameWithoutExtension(fname).ToUpper()
};
Now you can take your fileList and group by date and group, and then filter to just completed groups:
var profFileList = (from file in fileList
group file by new { file.fdate, fgroup = fileGroupMap[file.ffilename] } into fng
where fng.Key.fgroup.All(f => fng.Select(fn => fn.ffilename).Contains(f))
from fn in fng
select fn.fname).ToList();
Since you didn't preserve the groups, I flattened the groups at the end of the query into just a list of files to be processed. If you needed, you could keep them in groups and process the groups instead.
Note: If a file exists that belongs to no group, you will get an error from the lookup in fileGroupMap. If that is a possiblity you can filter the fileList to just known names as follows:
var fileList = from fname in GetFiles
let ffilename = Path.GetFileNameWithoutExtension(fname).ToUpper()
where fileGroupMap.Keys.Contains(ffilename)
select new {
fname,
fdate = Path.GetExtension(fname),
ffilename
};
Also note that having a name in multiple groups will cause an error in the creation of fileGroupMap. If that is a possibility, the queries would become more complex and have to be written differently.
Here is a simple class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] filenames = { "fg1a.12012017", "fg1b.12012017", "fg1c.12012017", "fg2a.12012017", "fg2b.12012017", "fg2c.12012017", "fg2d.12012017" };
new SplitFileName(filenames);
List<List<SplitFileName>> results = SplitFileName.GetGroups();
}
}
public class SplitFileName
{
public static List<SplitFileName> names = new List<SplitFileName>();
string filename { get; set; }
string prefix { get; set; }
string letter { get; set; }
DateTime date { get; set; }
public SplitFileName() { }
public SplitFileName(string[] splitNames)
{
foreach(string name in splitNames)
{
SplitFileName splitName = new SplitFileName();
names.Add(splitName);
splitName.filename = name;
string[] splitArray = name.Split(new char[] { '.' });
splitName.date = DateTime.ParseExact(splitArray[1],"MMddyyyy", System.Globalization.CultureInfo.InvariantCulture);
splitName.prefix = splitArray[0].Substring(0, splitArray[0].Length - 1);
splitName.letter = splitArray[0].Substring(splitArray[0].Length - 1,1);
}
}
public static List<List<SplitFileName>> GetGroups()
{
return names.OrderBy(x => x.letter).GroupBy(x => new { date = x.date, prefix = x.prefix })
.Where(x => string.Join(",",x.Select(y => y.letter)) == "a,b,c,d")
.Select(x => x.ToList())
.ToList();
}
}
}
With everyone's help, I solved it too. This is what I'm going with because it's the most maintainable for me but the solutions were so smart!!! Thanks everyone for your help.
private void CheckFiles()
{
var fileGroups = new[] {
new [] { "FG1A", "FG1B", "FG1C", "FG1D" },
new[] { "FG2A", "FG2B", "FG2C", "FG2D", "FG2E" } };
List<string> fileDates = new List<string>();
List<string> pfiles = new List<string>();
// get a list of file dates
foreach (string fn in fileList)
{
string dateString = fn.Substring(fn.IndexOf('.'), 9);
if (!fileDates.Contains(dateString))
{
fileDates.Add(dateString);
}
}
// check if a date has all the files
foreach (string fd in fileDates)
{
int fgCount = 0;
// for each file group
foreach (Array masterfg in fileGroups)
{
foreach (string fg in masterfg)
{
// see if all the files are there
bool foundIt = false;
string finder = fg + fd;
foreach (string fn in fileList)
{
if (fn.ToUpper().Contains(finder))
{
pfiles.Add(fn);
}
}
fgCount++;
}
if (fgCount == pfiles.Count())
{
foreach (string fn in pfiles)
{
procFileList.Add(fn);
}
pfiles.Clear();
}
else
{
pfiles.Clear();
}
}
}
return;
}

Updating node contents from another file in c#?

I'm trying to update node contents of a *.xml file from another file.
The folder structure is in the form Folder_structure
I want to change the first node label of the xml file which is inside the xml folder by the contents of the first node label of the xml file which is inside the meta folder.
Below is the code that I've tried
using System;
using System.IO;
using System.Xml.Linq;
using System.Linq;
using System.Windows.Forms;
namespace XML_Parse
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
void Button1Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) {
TextBox1.Text = folderBrowserDialog1.SelectedPath;
}
}
void Button3Click(object sender, EventArgs e)
{
string targetDirectory1 = TextBox1.Text;
string[] xmlDir = Directory.GetDirectories(targetDirectory1+#"\", "xml*", SearchOption.AllDirectories);
string[] xmlFilesArray1 = Directory.GetFiles(xmlDir[0], "*.xml", SearchOption.AllDirectories);
string[] xmlDir2 = Directory.GetDirectories(targetDirectory1+#"\", "meta*", SearchOption.AllDirectories);
string[] xmlFilesArray2 = Directory.GetFiles(xmlDir[0], "*.xml", SearchOption.AllDirectories);
foreach (string xmlFile in xmlFilesArray1)
{
var FileInfo1 = new FileInfo(xmlFile);
string FileLocation1 = FileInfo1.FullName;
string file_name = Path.GetFileName(FileLocation1);
foreach (var xmlFile2 in xmlFilesArray2)
{
if (xmlFile2.Contains(file_name))
{
string path = Path.GetFullPath(xmlFile2);
XDocument doc = XDocument.Load(path);
string name = doc.Root.Element("Employee").Element("label").Value;
XDocument doc2 = XDocument.Load(FileLocation1);
doc2.Root.Element("Employee").SetElementValue("label", name);
doc2.Save(FileLocation1);
}
}
}
MessageBox.Show("Process complete");
}
}
}
But I'm getting an error message System.IndexOutOfRangeException: Index was outside the bounds of the array.
NOTE: The folder path given by the user in TextBox1 contains multiple folders with the above mentioned folder structure and I want to perform this operation on all folder which has the above mentioned folder structure.
the first use of xmlDir[0] is where you are getting the exception. The previous line must be returning 0 directories matching your criteria.
Set a break point on the line that assigns xmlDir and launch in the debugger. When you step over the line you will see zero elements in xmlDir. You are trying to access the item at element index 0 (first element) but there are none, so you get an IndexOutOfRangeException.
Best practice is to check via xmlDir.Length > 0 or xmlDir.Any() before trying to access the first element.
Try this
//get all directories from basepath
string[] filesindirectory = Directory.GetDirectories(basePath);
//Loop through each parent directory and get each matching xml file from it
List<string[]> newList = filesindirectory.Select(folder => (from item in Directory.GetDirectories(folder, "meta", SearchOption.AllDirectories)
.Select(item => Directory.GetFiles(item, "*.xml"))
.ToList()
.SelectMany(x => x)
let sx = Directory.GetDirectories(folder, "xml", SearchOption.AllDirectories)
.Select(items => Directory.GetFiles(items, "*.xml"))
.ToList()
.SelectMany(s => s)
.Any(s => Path.GetFileName(s) == Path.GetFileName(item))
where sx
select item).ToArray()
.Concat((from xmlItem in Directory.GetDirectories(folder, "xml", SearchOption.AllDirectories)
.Select(item => Directory.GetFiles(item, "*.xml"))
.ToList()
.SelectMany(xs => xs)
let sx = Directory.GetDirectories(folder, "meta", SearchOption.AllDirectories)
.Select(items => Directory.GetFiles(items, "*.xml"))
.ToList()
.SelectMany(sc => sc)
.Any(sc => Path.GetFileName(sc) == Path.GetFileName(xmlItem))
where sx
select xmlItem).ToArray()))
.Select(xmlFiles => xmlFiles.ToArray()).ToList();
//loop through each element of the jagged array
foreach (string[] path in newList)
{
for (int j = 0; j < path.Length / 2; j++)
{
XDocument doc = Xdocument.Load(path[j]);
string name = doc.Root.Element("Emp").Element("lbl").Value;
XDocument doc2 = Xdocument.Load(path[(path.Length / 2) + j]);
doc2.Root.Element("Employee").SetElementValue("label", name);
doc2.Save(path[(path.Length / 2) + j]);
}
}
Credit to JapanDave

Walking through directory while controling depth - C#

I need to be able to get all files from a directory and sub directories, but I would like to give the user the option to choose the depth of sub-directories.
I.e., not just current directory or all directories, but he should be able to choose a depth of 1,2,3,4 directories etc.
I've seen many examples of walking through directory trees and none of them seemed to address this issue. And personally, I get confused with recursion... (which I currently use). I am not sure how I would track the depth during a recursive function.
Any help would be greatly appreciated.
Thanks,
David
Here is my current code (which I found here):
static void FullDirList(DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, string depth)
{
try
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
if (excludeFolders != "")
if (Regex.IsMatch(file.FullName, excludeFolders, RegexOptions.IgnoreCase)) continue;
myStream.WriteLine(file.FullName);
MasterFileCounter += 1;
if (maxSz > 0 && myStream.BaseStream.Length >= maxSz)
{
myStream.Close();
myStream = new StreamWriter(nextOutPutFile());
}
}
}
catch
{
// make this a spearate streamwriter to accept files that failed to be read.
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return; // We alredy got an error trying to access dir so dont try to access it again
}
MasterFolderCounter += 1;
foreach (DirectoryInfo d in dir.GetDirectories())
{
//folders.Add(d);
// if (MasterFolderCounter > maxFolders)
FullDirList(d, searchPattern, excludeFolders, maxSz, depth);
}
}
using a maxdepth varibale that could be decremented each recursive call and then you cannot just return once reached the desired depth.
static void FullDirList(DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, int maxDepth)
{
if(maxDepth == 0)
{
return;
}
try
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
if (excludeFolders != "")
if (Regex.IsMatch(file.FullName, excludeFolders, RegexOptions.IgnoreCase)) continue;
myStream.WriteLine(file.FullName);
MasterFileCounter += 1;
if (maxSz > 0 && myStream.BaseStream.Length >= maxSz)
{
myStream.Close();
myStream = new StreamWriter(nextOutPutFile());
}
}
}
catch
{
// make this a spearate streamwriter to accept files that failed to be read.
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return; // We alredy got an error trying to access dir so dont try to access it again
}
MasterFolderCounter += 1;
foreach (DirectoryInfo d in dir.GetDirectories())
{
//folders.Add(d);
// if (MasterFolderCounter > maxFolders)
FullDirList(d, searchPattern, excludeFolders, maxSz, depth - 1);
}
}
Let's start out by refactoring the code a little bit to make its work a little easier to understand.
So, the key exercise here is to recursively return all of the files that match the patterns required, but only to a certain depth. Let's get those files first.
public static IEnumerable<FileInfo> GetFullDirList(
DirectoryInfo dir, string searchPattern, int depth)
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
yield return file;
}
if (depth > 0)
{
foreach (DirectoryInfo d in dir.GetDirectories())
{
foreach (FileInfo f in GetFullDirList(d, searchPattern, depth - 1))
{
yield return f;
}
}
}
}
This is just simplified the job of recursing for your files.
But you'll notice that it didn't exclude files based on the excludeFolders parameter. Let's tackle that now. Let's start building FullDirList.
The first line would be
var results =
from fi in GetFullDirList(dir, searchPattern, depth)
where String.IsNullOrEmpty(excludeFolders)
|| !Regex.IsMatch(fi.FullName, excludeFolders, RegexOptions.IgnoreCase)
group fi.FullName by fi.Directory.FullName;
This goes and gets all of the files, restricts them against excludeFolders and then groups all the files by the folders they belong to. We do this so that we can do this next:
var directoriesFound = results.Count();
var filesFound = results.SelectMany(fi => fi).Count();
Now I noticed that you were counting MasterFileCounter & MasterFolderCounter.
You could easily write:
MasterFolderCounter+= results.Count();
MasterFileCounter += results.SelectMany(fi => fi).Count();
Now, to write out these files it appears you are trying to aggregate the file names into separate files, but keeping a maximum length (maxSz) of the file.
Here's how to do that:
var aggregateByLength =
results
.SelectMany(fi => fi)
.Aggregate(new [] { new StringBuilder() }.ToList(),
(sbs, s) =>
{
var nl = s + Environment.NewLine;
if (sbs.Last().Length + nl.Length > maxSz)
{
sbs.Add(new StringBuilder(nl));
}
else
{
sbs.Last().Append(nl);
}
return sbs;
});
Writing the files now becomes extremely simple:
foreach (var sb in aggregateByLength)
{
File.WriteAllText(nextOutPutFile(), sb.ToString());
}
So, the full thing becomes:
static void FullDirList(
DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, int depth)
{
var results =
from fi in GetFullDirList(dir, searchPattern, depth)
where String.IsNullOrEmpty(excludeFolders)
|| !Regex.IsMatch(fi.FullName, excludeFolders, RegexOptions.IgnoreCase)
group fi.FullName by fi.Directory.FullName;
var directoriesFound = results.Count();
var filesFound = results.SelectMany(fi => fi).Count();
var aggregateByLength =
results
.SelectMany(fi => fi)
.Aggregate(new [] { new StringBuilder() }.ToList(),
(sbs, s) =>
{
var nl = s + Environment.NewLine;
if (sbs.Last().Length + nl.Length > maxSz)
{
sbs.Add(new StringBuilder(nl));
}
else
{
sbs.Last().Append(nl);
}
return sbs;
});
foreach (var sb in aggregateByLength)
{
File.WriteAllText(nextOutPutFile(), sb.ToString());
}
}

Grouping a list by the results of a split

so I have some strings in a list
Folder1\File.png
Folder1\File2.png
File3.png
File4.png
and I would like to group these on a split('\\')[0]; for example
foreach (var group in files.GroupBy(x => //mysplit))
{
if (group.Count() > 1)
{
// this is a folder and its files are: group
}
else
{
//group is an individual file
}
}
but I'm not sure how to group the files by this split?
I would just group items that Contains() a backslash:
var lst1 = new string[] {"Folder1\\File.png", "Folder1\\File2.png" , "File3.png", "File4.png" };
var grps = lst1.GroupBy(x => x.Contains(#"\"));
foreach (var g in grps)
{
if (g.Key) // we have a path with directory
Console.WriteLine(String.Join("\r\n", g.ToList()));
else // we have an individual file
Console.WriteLine(String.Join("\r\n", g.ToList()));
}
So my solution was:
foreach (var groupedFiles in files.GroupBy(s => s.Split('\\')[0]))
{
if (Path.GetExtension(groupedFiles.Key) == string.Empty)
{
//this is a folder
var folder = groupedFiles.Key;
var folderFiles = groupedFiles.ToList();
}
else
{
//this is a file
var file = groupedFiles.First();
}
}

Group files in a directory based on their prefix

I have a folder with pictures:
Folder 1:
Files:
ABC-138923
ABC-3223
ABC-33489
ABC-3111
CBA-238923
CBA-1313
CBA-1313
DAC-38932
DAC-1111
DAC-13893
DAC-23232
DAC-9999
I want to go through this folder and count how many of each picture pre-fix I have.
For example, there are 4 pictures of pre-fix ABC and 3 pictures of pre-fix CBA above.
I'm having a hard time trying to figure out how to loop through this. Anyone can give me a hand?
Not a loop, but more clear and readable:
string[] fileNames = ...; //some initializing code
var prefixes = fileNames.GroupBy(x => x.Split('-')[0]).
Select(y => new {Prefix = y.Key, Count = y.Count()});
Upd:
To display the count for each prefix:
foreach (var prefix in prefixes)
{
Console.WriteLine("Prefix: {0}, Count: {1}", prefix.Prefix, prefix.Count);
}
Here it is with a 'foreach' loop:
var directoryPath = ".\Folder1\";
var prefixLength = 3;
var accumulator = new Dictionary<string, int>();
foreach (var file in System.IO.Directory.GetFiles(directoryPath)) {
var prefix = filefile.Replace(directoryPath, string.Empty).Substring(0, prefixLength);
if (!accumulator.ContainsKey(prefix))
{
accumulator.Add(prefix, 0);
}
accumulator[prefix]++;
}
foreach(var prefix in accumulator.Keys) {
Console.WriteLine("{0}: {1}", prefix, accumulator[prefix]);
}
in C#,
using System.IO;
using System.Collections.Generic;
...
DirectoryInfo dir = new DirectoryInfo("C:\\yourfolder");
FileInfo[] files = dir.GetFiles();
List<string> prefix = new List<string>();
List<int> count = new List<int>();
foreach (FileInfo file in files)
{
if (prefix.Count > 0)
{
Boolean AddNew = true;
for (int i = 0; i < prefix.Count; i++)
{
if (file.Name.Substring(0, 3) == prefix[i])
{
count[i]++;
AddNew = false;
}
}
if (AddNew)
{
prefix.Add(file.Name.Substring(0, 3));
count.Add(1);
}
}
else
{
prefix.Add(file.Name.Substring(0, 3));
count.Add(1);
}
}
...
The prefix string list is parallel to the count list, so to access you could loop through the array. I haven't tested or optimized it, but if you're heading down this route (c#) this could be a start.
The algorithm:
Create a dictionary:
Dictionary<string, int> D;
Loop through the directory using:
foreach (var file in System.IO.Directory.GetFiles(dir))
...
Complete the following 3 steps for each file:
Extract the prefix and see if a matching key exists in D. If TRUE, go to step 3.
Insert the prefix as a new key in D, with value 0
Increment the key's value by 1
To display results when the entire directory has been processed:
foreach (KeyValuePair<string, int> pair in D)
Console.WriteLine("{0} prefix has {1} files", pair.Key, pair.Value);

Categories