An item with the same key has already been added C# - c#

I'm trying to browse all *.txt files from folder to get metadata inside.
void SearchAndPopulate(string path, string searchText)
{
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.txt");
Dictionary<String, dynamic> dictionary = new Dictionary<String, Object>();
int i = 0;
foreach (FileInfo file in files)
{
dictionary.Add(String.Format("name{0}", i.ToString()), i);
using (StreamReader sr = new StreamReader(file.FullName))
{
string content = sr.ReadToEnd().ToLower();
if (content.Contains(searchText.ToLower()))
{
dictionary["name"+i] = File
.ReadAllLines(file.FullName)
.Select(y => y.Split('='))
.Where(y => y.Length > 1)
.ToDictionary(y => y[0].Trim(), y => y[1]);
var temp = dictionary["name" + i];
listBox1.Text = temp["NUM_CLIENT"];
}
}
i++;
}
}
I get "An item with the same key has already been added" for dictionary variable.

This exception is thrown when you try to add a duplicate entry in a Dictionary using the same key. The key value in a Dictionary must be unique.
Possible causes
Look at the content of your file, you will find at least 2 lines where the left side of the = sign have the same string value.
You have multiple empty values on the left side of the = sign in your file. You can correct this in your Linq statement by ignoring those lines:
dictionary["name"+i] = File.ReadAllLines(file.FullName)
.Select(y => y.Split('='))
.Where(y => y.Length > 1 && !string.IsNullOrWhiteSpace(y[0]))
.ToDictionary(y => y[0].Trim(), y => y[1]);

Honestly I don't think you need any dictionaries in your code. Also you can reduce the number of times you read in each file.
void SearchAndPopulate(string path, string searchText)
{
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.txt");
foreach (FileInfo file in files)
{
var content = File.ReadAllLines(file.FullName);
if (content.Any(line => line.ToLower().Contains(searchText.ToLower())))
{
var numClient = content.Select(y => y.Split('='))
.Where(y => y.Length > 1 && y[0].Trim() == "NUM_CLIENT")
.Select(y => y[1])
.FirstOrDefault();
if(numClient != null)
listBox1.Text = numClient;
else
// Do something here if "NUM_CLIENT" wasn't in the file.
}
}
}

Related

Directory.EnumerateFiles - Get Files which start with certain number and greater than that same number?

I am trying to get list of files which starts with "6" and greater than this number. I have following list of files in a folder. In case of failure of certain file consider 6_q.sql I want to begin with that file and proceed ahead in ascending order.
1_q.sql
2_q.sql
6_q.sql
7_q.sql
8_q.sql
This is my current code, however I used StartsWith and it only takes 6_q.sql file not rest 7_q.sql and 8_q.sql in this case.
var files = Directory.EnumerateFiles(SQLScriptsFolderPath, "*.sql")
.Where(filename => Path.GetFileName(filename).StartsWith("6"))
.OrderBy(filename => filename);
May I know what can I use to take files starting with 6 and great then that in acending order?
Edit 1 - Filenames can have any characters after number like 11qq.sql
If file order is not important for you, you can just order files by filename us SkipWhile:
var files = Directory.EnumerateFiles(SQLScriptsFolderPath, "*.sql")
.OrderBy(filename => filename)
.SkipWhile(filename => Path.GetFileName(filename) != failedFileName)
.Skip(1);
If ordering by number is important (and number at start of filename is always present) then you will need to parse that number, convert it to int and order by it:
var reg = new Regex(#"^\d+");
var files = Directory.EnumerateFiles(SQLScriptsFolderPath, "*.sql")
.Select(file => (n: int.Parse(reg.Match(Path.GetFileName(file)).Value), file))
.Where(t => t.n > failedFileNumber)
.OrderBy(t => t.n)
.Select(t => t.file);
First, write a small function to convert a file name to a number that can be compared. One way to do it:
int FileNumber(string input)
{
string numericString = new string(input.Where( c => char.IsDigit(c) ).ToArray());
bool ok = int.TryParse(numericString, out int result);
if (ok) return result;
return default(int);
}
Once you have that it's pretty easy:
var files = Directory.EnumerateFiles(SQLScriptsFolderPath, "*.sql")
.Where(filename => FileNumber(filename) >= 6 )
.OrderBy(filename => filename);
If you're picky about the file order you may need something slightly more complex:
var files = Directory.EnumerateFiles(SQLScriptsFolderPath, "*.sql")
.Select( x => new { FileName = x, FileNumber = FileNumber(x) })
.Where( x => x.FileNumber >= 6 )
.OrderBy( x => x.FileNumber );
.Select( x => x.FileName )
Try this:
static void Main(string[] args)
{
var files = Directory.EnumerateFiles(#"yourpath", "*.sql")
.Where(filename => GetNumberPart(Path.GetFileName(filename)) >= 6)
.Select(Path.GetFileName);
foreach (var file in files)
{
Console.WriteLine(file);
}
Console.ReadKey();
}
public static int GetNumberPart(string value)
{
return Convert.ToInt32(Regex.Match(value, #"^\d+").Value);
}

Get Filepath, and move the files in an other Directory if they contain an ID in the name

Basically, I need to check if a file exist in 4 version which mean that a 11 digits code appear in the filename.
Once the check is done I need to move the file on another Server.
My problem is that I get the ID, and I do know when an ID appear 4 times, but I don't know how to get the files Path from the ID I got and then move the files.
Any kind of help would be super appreciated.
static void Main(string[] args)
{
string ExtractIDFromFileName(string filename)
{
return filename.Split('_').Last();
}
Dictionary<string, int> GetDictOfIDCounts()
{
List<string> allfiles = Directory.GetFiles("C:/Users/Desktop/Script/tiptop", "*.txt").Select(Path.GetFileNameWithoutExtension).ToList();
allfiles.AddRange(Directory.GetFiles("C:/Users/Desktop/Script/tiptop", "*.top").Select(Path.GetFileNameWithoutExtension).ToList());
Dictionary<string, int> dict = new Dictionary<string, int>();
foreach (var x in allfiles)
{
string fileID = ExtractIDFromFileName(x);
if (dict.ContainsKey(fileID))
{
dict[fileID]++;
}
else
{
dict.Add(fileID, 1);
}
}
return dict;
}
var result = GetDictOfIDCounts();
foreach (var item in result)
{
//Console.WriteLine("{0} > {1}", item.Key, item.Value);
if (item.Value == 4)
{
//When we know that those ID appear 4 times, I need to grab back the FilePath and then move the files in an other DIR.
Console.WriteLine("{0} > {1}", item.Key, item.Value);
}
}
Console.ReadLine();
}
You'll want to use File.Move: https://learn.microsoft.com/en-us/dotnet/api/system.io.file.move?view=netframework-4.7.2
It's pretty straightforward. Make the ID in your dictionary the full path of the source file, then File.Move(dict.Key, some-variable-holding-the-new-directory-and-file-name);
Since you're going to want to use the filepath, just switch over to using the instance version of Directory & File: DirectoryInfo & FileInfo:
replace
List<string> allfiles = Directory.GetFiles("C:/Users/Desktop/Script/tiptop", "*.txt").Select(Path.GetFileNameWithoutExtension).ToList();
with
DirectoryInfo di = new DirectoryInfo("C:/Users/Desktop/Script/tiptop");
var allFiles = di.GetFiles("*.txt");
Make the FileInfo the key of the dictionary. Then you can do things like
dict.Key.FullName
Try this to get files you want, it uses GroupBy on the ID and Count(). I haven't compiled it so there might be errors.
var files = Directory.GetFiles(#"C:\Users\Desktop\Script\tiptop", "*.*")
.Where(file => {
var ext = Path.GetExtension(file).ToLower();
return ext == ".txt" || ext == ".top";
})
.Select(file => new { Path = file, Id = file.Split('_').Last()})
.GroupBy(file => file.Id)
.Where(grp => grp.Count() >= 4)
.SelectMany(x => x)
.Select(x => x.Path);
Another solution would be to use a Dictionary<string, List<string>> instead of Dictionary<string, int>. When adding a new key to the Dictionary you would add new List<string> { x } so that you would keep the file names in the list of ids. Instead of checking for the item.Value == 4 in you if you could check the size of the list like item.Count == 4. Then you still have the file names so you may use them to move the files.
Hope it helps!

Difficulty using Orderby in Foreach loop C#

I have been trying to order my files by their substring at the end of their names which happens to end with a number that indicates their position relative to the rest of the files. (example: fs-1632_1.txt --> fs-1632_2.txt).
I am currently able to get the numbers and turn them into ints I just have problems getting the OrderBy Method to work correctly. I am mostly working off of this example of Orderby.
internal class Data
{
public string Name { get; set; }
public double Number { get; set; }
}
private void OrderByEx1(List<FileInfo> files)
{
int num = 0;
int index_num = 0;
string file_num = "";
string file_name = "";
foreach (FileInfo in files)
{
file_name = file.FullName;
file_name = Path.GetFileNameWithoutExtension(file_name);
index_num = file_name.LastIndexOf("_") + 1;
file_num = file_name.Substring(index_num);
num = Int32.Parse(file_num);
Data[] set = {new Data {Name = file_name, Number = num }};
}
IEnumerable<Data> query = set.OrderBy(data => data.Number);
foreach (Data file_s in query)
MessageBox.Show($"{file_s.Name} {file_s.Number}");
}
No need for the foreach-loop. You could use this safe LINQ approach:
files = files
.Select(f => new { File = f, Name = Path.GetFileNameWithoutExtension(f.Name) })
.Select(x => new
{
x.File,
x.Name,
Token = x.Name.Substring(x.Name.LastIndexOf("_", StringComparison.Ordinal) + 1)
})
.Select(x => new
{
x.File,
x.Name,
x.Token,
IsInt = int.TryParse(x.Token, out int number),
ParsedNumber = number
})
.OrderByDescending(x => x.IsInt)
.ThenBy(x => x.ParsedNumber)
.Select(x => x.File)
.ToList();
If there is no number or it can't be parsed to int the file will be listed at the bottom.
You are declaring a Data array named set, add a single element to it and then restart the loop forgetting what you have loaded in the previous loop. The order is executed only when you exit the loop, but at that point the set array contains a single element, the last one.
You need to add your Data structure to a list and then order that list
List<Data> dataFiles = new List<Data>();
foreach (FileInfo file in files)
{
file_name = file.FullName;
file_name = Path.GetFileNameWithoutExtension(file_name);
index_num = file_name.LastIndexOf("_") + 1;
file_num = file_name.Substring(index_num);
num = Int32.Parse(file_num);
dataFiles.Add(new Data {Name = file_name, Number = num });
}
// If you don't need the query var you can just order directly in the for loop
// IEnumerable<Data> query = dataFiles.OrderBy(data => data.Number);
foreach (Data file_s in dataFiles.OrderBy(data => data.Number))
{
MessageBox.Show(file_s.Name + " " + file_s.Number);
}

Extract common path from a collection of grouped file paths?

I have a question related to https://en.wikipedia.org/wiki/Longest_common_substring_problem, my source collection contains a list of file paths that doesn't always share a common path (outside of the C:\ drive sometimes) ex:
Source collection :
C:\Test\Root\Common\Data\a.txt
C:\Test\Root\Common\Data\Home\b.txt
C:\Test\Root\Common\Data\Home\Dev\c.txt
C:\Test2\Random\Data\a.txt
C:\Test2\Random\b.txt
C:\Test2\c.txt
D:\Data\a.txt
Output should be a collection :
C:\Test\Root\Common\Data\
C:\Test2\
D:\Data\
How to find the common path of each "group" of file paths ? I've found many solutions here but it is always with a collection of file paths sharing at least one common directory which is not the case here.
I am still not sure that I understand problem correctly...
I hope this will work.
public List<string> ExtractCommonPaths(List<string> paths)
{
var separatedImput = paths
.Select(path => path.Split(new [] {":\\", "\\" }, StringSplitOptions.RemoveEmptyEntries))
.Select(path => path.Take(path.Length - 1).ToList());
return separatedImput.GroupBy(path => path[0] + ":\\" + path[1])
.Select(g =>
{
var commonPath = g.Key;
var commpoPathLength = 2;
for (;;)
{
var exit = false;
var pathItem = string.Empty;
foreach (var path in g)
{
if (path.Count <= commpoPathLength)
{
exit = true;
break;
}
if (pathItem == string.Empty)
pathItem = path[commpoPathLength];
else
{
if (pathItem != path[commpoPathLength])
{
exit = true;
break;
}
}
}
if (exit)
break;
commonPath += "\\" + pathItem;
commpoPathLength++;
}
return commonPath;
})
.ToList();
}
I have something if you want to look for the directories inside some location.
To this method i have every of directories from some (C:\Files1) example location.
If you want to get only main directories from this list:
public List<DirectoryInfo> ExtractDirectoriesCommonPaths(List<DirectoryInfo> directories, string location)
{
var result = new List<DirectoryInfo>() { };
directories.ForEach(directory =>
{
var otherDirectories = directories.Where(d => d.FullName != directory.FullName);
var anyDirectoryWithThisSamePath = otherDirectories.Where(x => directory.FullName.Contains(x.FullName) && x.FullName.Length < directory.FullName.Length);
if (anyDirectoryWithThisSamePath.Any())
{
result.Add(anyDirectoryWithThisSamePath.FirstOrDefault());
}
});
return result.Where(x => x.FullName != location && x.FullName.Length > location.Length).Distinct().ToList();
}
Input:
C:\Files1\test_announcements
C:\Files1\Parent
C:\Files1\test_announcements\test_announcements_archive
C:\Files1\Parent\child
C:\Files1\Parent\child2
C:\Files1\Parent\child\subchild
C:\Files1\test_announcements
C:\Files1\Parent
Output:
C:\Files1\test_announcements
C:\Files1\Parent

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();
}
}

Categories