I have a list of strings which holds file paths.
List<string> allFilesWithPathList = new List<string>();
allFilesWithPathList.Add(#"G:\Test\A.sql");
allFilesWithPathList.Add(#"G:\Test\B.sql");
allFilesWithPathList.Add(#"G:\Test\C.sql");
return allFilesWithPathList;
I have another list which holds a subset of files – but it has only the file name; not the path.
List<string> excludeList = new List<string>();
excludeList.Add("B.sql");
Now I need to get files from allFilesWithPathList that is not present in excludeList. Currently I am doing the following, using EXCEPT, after creating another list with file names only.
List<string> allFileNamesOnlyList = new List<string>();
foreach (string fileNameWithPath in allFilesWithPathList)
{
//Remove path and get only file name
int pos = fileNameWithPath.LastIndexOf(#"\") + 1;
string value = fileNameWithPath.Substring(pos, fileNameWithPath.Length - pos);
allFileNamesOnlyList.Add(value);
}
//EXCEPT logic
List<string> eligibleListToProcess = allFileNamesOnlyList.Except(excludeList).ToList();
What is the best way in LINQ to get this logic working without introducing another list like the above?
Note: I am using .Net 4.5
Complete code
class Program
{
static void Main(string[] args)
{
List<string> allFilesWithPathList = GetAllFilesWithPath();
List<string> excludeList = new List<string>();
excludeList.Add("B.sql");
List<string> allFileNamesOnlyList = new List<string>();
foreach (string fileNameWithPath in allFilesWithPathList)
{
//Remove path and get only file name
int pos = fileNameWithPath.LastIndexOf(#"\") + 1;
string value = fileNameWithPath.Substring(pos, fileNameWithPath.Length - pos);
allFileNamesOnlyList.Add(value);
}
//EXCEPT logic
List<string> eligibleListToProcess = allFileNamesOnlyList.Except(excludeList).ToList();
//Print all eligible files
foreach (string s in eligibleListToProcess)
{
Console.WriteLine(s);
}
Console.ReadLine();
}
public static List<string> GetAllFilesWithPath()
{
List<string> allFilesWithPathList = new List<string>();
allFilesWithPathList.Add(#"G:\Test\A.sql");
allFilesWithPathList.Add(#"G:\Test\B.sql");
allFilesWithPathList.Add(#"G:\Test\C.sql");
return allFilesWithPathList;
}
}
allFilesWithPathList.Where(path => !allFileNamesOnlyList.Contains(Path.GetFileName(path));
There are two improvements here.
Path.GetFileName is much better than splitting the path yourself.
IEnumerable.Where in conjunction with ICollection.Contains to actually query the list in a succinct and easy to read way.
This should work
allFilesWithPathList.Where(x => !excludeList.Any(y => x.EndsWith(y)))
Related
I created a Yaml that looks like this:
Directories:
- ./Libraries:
- DLLList.yml
- ./Output:
- None
Now I deserialized that yaml into a list of Objects:
List<object> allDirectoriesList = new List<object>();
List<string> allFileNames = new List<string>();
using (var reader = new StringReader(File.ReadAllText("./FileConfig.yml")))
{
allDirectoriesList = deserializer.Deserialize<dynamic>(reader)["Directories"] as List<Object>;
}
foreach (var directory in allDirectoriesList)
{
var directoryAsDictionary = (Dictionary<object, object>)directory;
List<object> list = directoryAsDictionary.Select(kvp => kvp.Value).ToList();
IEnumerable<string> _fileList = list.Select(i => i.ToString());
List<string> fileList = _fileList.ToList<string>();
for (int i = 0; i < fileList.Count(); i++)
{
var x = (string)list[i];
}
}
directory is an object of type Dictionary where I converted it into a List in this part:
var directoryAsDictionary = (Dictionary<object, object>)directory;
List<object> list = directoryAsDictionary.Select(kvp => kvp.Value).ToList();
This list contains 1 object of type string, where the filename is stored. But I can't get these strings out of the objects. If I cast them, or convert them ToString(), I always get "System.Collections.Generic.List`1[System.Object]", but it has to be "DLLList.yml" in this case
Assuming you are using YamlDotNet:
List<object> allDirectoriesList = new List<object>();
using (var reader = new StringReader(File.ReadAllText("./FileConfig.yml")))
{
allDirectoriesList = new DeserializerBuilder().Build().Deserialize<dynamic>(reader)["Directories"] as List<object>;
}
foreach (var directory in allDirectoriesList)
{
var directoryAsDictionary = (Dictionary<object, object>)directory;
List<object> list = directoryAsDictionary.SelectMany(kvp => (List<object>)kvp.Value).ToList();
List<string> _fileList = list.Select(Convert.ToString).ToList();
foreach(var file in _fileList)
Console.WriteLine($"Item: {file} found in {Convert.ToString(directoryAsDictionary.Keys.First())}");
}
Basically you were trying to turn the dictionary value to a string, but it was a List. By using SelectMany, it can flatten all the lists into one and use that. There were a few redundant casts, which I've also removed. For future reference, try to make your structures as simple as possible and deserialise them into structs/classes - you'll find this a lot easier that way.
I trying to load 4 lines from text files:
email:pass
email1:pass1
email2:pass2
email3:pass3
I used string.split, however when I try to Add to the my List it doesn't load well.
Here what I tried:
List<string> AccountList = new List<string>();
Console.Write("File Location: ");
string FileLocation = Console.ReadLine();
string[] temp = File.ReadAllLines(FileLocation);
string[] tempNew = new string[1000];
int count = 0;
foreach(var s in temp)
{
AccountList.Add(s.Split(':').ToString());
count++;
}
I checked how it the strings look inside the lists and they were like this:
System.String[]
I want it to be like this:
AccountList[0] = email
AccountList[1] = pass
AccountList[2] = email1
AccountList[3] = pass1
String.Split yields a string array
foreach(var s in temp)
{
string[] parts = s.Split(':');
string email = parts[0];
string pass = parts[1];
...
}
To store these two pieces of information, create an account class:
public class Account
{
public string EMail { get; set; }
public string Password { get; set; }
}
Then declare your account list as List<Account>:
var accountList = new List<Account>();
foreach(var s in File.ReadLines(FileLocation))
{
string[] parts = s.Split(':');
var account = new Account { EMail = parts[0], Password = parts[1] };
accountList.Add(account);
}
Note that you don't need the temp variable. File.ReadLines reads the file as the loop progresses, so that the whole file needs not to be stored in memory. See: File.ReadLines Method (Microsoft Docs).
No need to count. You can get the count with
int count = accountList.Count;
This list will be easier to handle than a list interleaved with emails and passwords.
You can access an account by index
string email = accountList[i].EMail;
string pass = accountList[i].Password;
or
Account account = accountList[i];
Console.WriteLine($"Account = {account.EMail}, Pwd = {account.Password}");
From your expected result you can try this, string.Split will return a string array string[], which spite by your expect character.
then use the index to get string part.
foreach(var s in temp)
{
var arr = s.Split(':');
AccountList.Add(arr[0]);
AccountList.Add(arr[1]);
}
The problem is that Split returns a string array consisting of the parts of the string found between the split character(s), and you're treating it as a string.
Instead, your code can be simplified by taking the result of File.ReadAllLines (a string array) and using .SelectMany to select the resulting array from splitting each line on the : character (so you're selecting an array for each item in the array), and then calling ToList on the result (since you're storing it in a list).
For example:
Console.Write("Enter file location: ");
string fileLocation = Console.ReadLine();
// Ensure the file exists
while (!File.Exists(fileLocation))
{
Console.Write("File not found, please try again: ");
fileLocation = Console.ReadLine();
}
// Read all the lines, split on the ':' character, into a list
List<string> accountList = File.ReadAllLines(fileLocation)
.SelectMany(line => line.Split(':'))
.ToList();
I'm trying to read a text file and print out into a table.
I want the output to be this
But now I having different output
var column1 = new List<string>();
var column2 = new List<string>();
var column3 = new List<string>();
using (var rd = new StreamReader(#"C:\test.txt"))
{
while (!rd.EndOfStream)
{
var splits = rd.ReadLine().Split(';');
column1.Add(splits[0]);
column2.Add(splits[1]);
column3.Add(splits[2]);
}
}
Console.WriteLine("Date/Time \t Movie \t Seat");
foreach (var element in column1) Console.WriteLine(element);
foreach (var element in column2) Console.WriteLine(element);
foreach (var element in column3) Console.WriteLine(element);
You can use Linq to construct a convenient structure (e.g. List<String[]>) and then print out all the data wanted:
List<String[]> data = File
.ReadLines(#"C:\test.txt")
//.Skip(1) // <- uncomment this to skip caption if the csv has it
.Select(line => line.Split(';').Take(3).ToArray()) // 3 items only
.ToList();
// Table output (wanted one):
String report = String.Join(Environment.NewLine,
data.Select(items => String.Join("\t", items)));
Console.WriteLine(report);
// Column after column output (actual one)
Console.WriteLine(String.Join(Environment.NewLine, data.Select(item => item[0])));
Console.WriteLine(String.Join(Environment.NewLine, data.Select(item => item[1])));
Console.WriteLine(String.Join(Environment.NewLine, data.Select(item => item[2])));
EDIT: if you want to choose the movie, buy the ticket etc. elaborate the structure:
// Create a custom class where implement your logic
public class MovieRecord {
private Date m_Start;
private String m_Name;
private int m_Seats;
...
public MovieRecord(DateTime start, String name, int seats) {
...
m_Seats = seats;
...
}
...
public String ToString() {
return String.Join("\t", m_Start, m_Name, m_Seats);
}
public void Buy() {...}
...
}
And then convert to conventinal structure:
List<MovieRecord> data = File
.ReadLines(#"C:\test.txt")
//.Skip(1) // <- uncomment this to skip caption if the csv has it
.Select(line => {
String items[] = line.Split(';');
return new MovieRecord(
DateTime.ParseExact(items[0], "PutActualFormat", CultureInfo.InvariantCulture),
items[1],
int.Parse(items[2]));
}
.ToList();
And the table output will be
Console.Write(String.Join(Envrironment.NewLine, data));
Don't use Console.WriteLine if you want to add a "column". You should also use a single List<string[]> instead of multiple List<string>.
List<string[]> allLineFields = new List<string[]>();
using (var rd = new StreamReader(#"C:\test.txt"))
{
while (!rd.EndOfStream)
{
var splits = rd.ReadLine().Split(';');
allLineFields.Add(splits);
}
}
Console.WriteLine("Date/Time \t Movie \t Seat");
foreach(string[] line in allLineFields)
Console.WriteLine(String.Join("\t", line));
In general you should use a real csv parser if you want to parse a csv-file, not string methods or regex.
You could use the TextFieldParser which is the only one available in the framework directly:
var allLineFields = new List<string[]>();
using (var parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(#"C:\test.txt"))
{
parser.Delimiters = new string[] { ";" };
parser.HasFieldsEnclosedInQuotes = false; // very useful
string[] lineFields;
while ((lineFields = parser.ReadFields()) != null)
{
allLineFields.Add(lineFields);
}
}
You need to add a reference to the Microsoft.VisualBasic dll to your project.
There are other available: Parsing CSV files in C#, with header
You could attempt to solve this in a more Object-Orientated manner, which might make it a bit easier for you to work with:
You can declare a simple class to represent a movie seat:
class MovieSeat
{
public readonly string Date, Name, Number;
public MovieSeat(string source)
{
string[] data = source.Split(';');
Date = data[0];
Name = data[1];
Number = data[2];
}
}
And then you can read in and print out the data in a few lines of code:
// Read in the text file and create a new MovieSeat object for each line in the file.
// Iterate over all MovieSeat objets and print them to console.
foreach(var seat in File.ReadAllLines(#"C:\test.txt").Select(x => new MovieSeat(x)))
Console.WriteLine(string.Join("\t", seat.Date, seat.Name, seat.Number));
Background on this project. It started as a simple homework assignment that required me to store 5 zip codes and their corresponding cities. When a user puts a Zip code in a textbox, a corresponding city is returned, and likewise the opposite can be done. I wrote the code to return these values, but then I decided I wanted to store ALL zip codes and their corresponding Cities in an external .csv, and store those values in arrays and run the code off that because if its worth doing, its worth overdoing! To clarify, this is no longer for homework, just to learn more about using external files in C#.
In the following code, I have called to open the file successfully, now I just need help in figuring out how to pull the data that is stored in two separate columns (one for city, one for zip code) and store them in two arrays to be acted upon by the for loop. Here is the code I have now. You can see how I have previously stored the other values in arrays and pulled them out:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnConvert2City_Click(object sender, EventArgs e)
{
try
{
string dir = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string path = dir + #"\zip_code_database_edited.csv";
var open = new StreamReader(File.OpenRead(path));
int EnteredZipcode = Convert.ToInt32(txtZipcode.Text.Trim());
string result = "No Cities Found";
string[] Cities = new String[5] { "FLINTSTONE", "JAMAICA", "SCHENECTADY", "COTTONDALE", "CINCINNATI" };
int[] Zipcode = new int[5] { 30725, 11432, 12345, 35453, 45263 };
for (int i = 0; i <= Zipcode.Length - 1; i++)
{
if (Zipcode[i] == EnteredZipcode)
{
result = Cities[i];
break;
}
}
string DisplayState = result;
txtCity.Text = DisplayState;
}
catch (FormatException)
{
MessageBox.Show("Input must be numeric value.");
}
catch (OverflowException)
{
MessageBox.Show("Zipcode to long. Please Re-enter");
}
}
private void btnConvert2Zipcode_Click(object sender, EventArgs e)
{
string dir = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string path = dir + #"\zip_code_database_edited.csv";
var open = new StreamReader(File.OpenRead(path));
String EnteredCity = txtCity.Text.ToUpper();
string result = "No Zipcode Found";
string[] Cities = new String[5] { "FLINTSTONE", "JAMAICA", "SCHENECTADY", "COTTONDALE", "CINCINNATI" };
int[] Zipcode = new int[5] { 30725, 11432, 12345, 35453, 45263 };
for (int i = 0; i <= Cities.Length - 1; i++)
{
if (Cities[i] == EnteredCity)
{
result = Convert.ToString(Zipcode[i]);
break;
}
}
string DisplayZip = result;
txtZipcode.Text = DisplayZip;
}
}
The following data is a snippet of what the data in my excel .csv looks like:
zip,primary_city
44273,Seville
44274,Sharon Center
44275,Spencer
44276,Sterling
44278,Tallmadge
44280,Valley City
44281,Wadsworth
44282,Wadsworth
44285,Wayland
And so on for about 46,000 rows.
How can I pull the zip and the primary_city into two separate arrays (I'm guessing with some ".Split "," "line) that my for-loop can operate on?
Also, if there are better ways to go about this, please let me know (but be sure to leave an explanation as I want to understand where you are coming from).
Don't create two separate array.Create a separate class for city
class City
{
public string Name{get;set;}
public int ZipCode{get;set;}
}
Now to read the data from that csv file
List<City> cities=File.ReadAllLines(path)
.Select(x=>new City
{
ZipCode=int.Parse(x.Split(',')[0]),
Name=x.Split(',')[1]
}).ToList<City>();
Or you can do this
List<City> cities=new List<City>();
foreach(String s in File.ReadAllLines(path))
{
City temp=new City();
temp.ZipCode=int.Parse(s.Split(',')[0]);
temp.Name=s.Split(',')[1];
cities.Add(temp);
}
You can try this:
string dir = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string path = dir + #"\zip_code_database_edited.csv";
var open = new StreamReader(File.OpenRead(path));
var cities = new HashList<string>();
var zipCodes = new HashList<int>();
var zipAndCity = new string[2];
string line = string.Empty;
using (open)
{
while ((line = reader.ReadLine()) != null)
{
zipAndCity = line.Split(",");
zipCodes.Add(int.Parse(zipAndCity[0]));
cities.Add(zipAndCity[1]);
}
}
I am posting this answer having learned much more about C# since I posted this question. When reading a CSV, there are better options than String.Split().
The .NET Framework already has a built-in dedicated CSV parser called TextFieldParser.
It's located in the Microsoft.VisualBasic.FileIO namespace.
Not only are there many edge cases that String.Split() is not properly equipped to handle, but it's also much slower to use StreamReader.
I have bunch of directories at a certain paths in the following possible format:
C:\Program Files\Logic\DDC[ 0 ]
C:\Program Files\Logic\DDC[ 1]
C:\Program Files\Logic\DDC[2 ]
C:\Program Files\Logic\DDC[3]
I'd like to accomplish the following:
1)Enumurate all of numbered DDC directories and store their paths them in a List of String
I.E: List<String> ddcPaths -> should have:
ddcPaths[0] = "DDC[0]";
ddcPaths[1] = "DDC[1]";
ddcPaths[2] = "DDC[2]";
2)Enumurate all files directly under the DDC folder but nothing deeper than that
I.E: If DDC[0] has a.txt, b.txt and obj\c.txt, I should get
List<String> ddc_0 -> should have
ddc_0[0] = "a.txt";
ddc_0[1] = "b.txt";
I hope my explanation was clear enough but if something didn't make sense, please let me know.
If U have such data structure I suggest U should use one dictionary with directory name as key, and list of it's filenames as value. For example:
var ddcPaths = new Dictionary<string, List<string>>();
foreach (var directoryInfo in new DirectoryInfo(#"C:\Program Files\Logic\").GetDirectories())
{
if (directoryInfo.Name.Contains("DDC["))
{
ddcPaths.Add(directoryInfo.Name, new List<string>());
foreach (var fileInfo in directoryInfo.GetFiles())
{
ddcPaths[directoryInfo.Name].Add(fileInfo.FullName);
}
}
}
but you should notice than U cant get Dictionary value by int index, only by key, declared in this dictionary (folder name in our case). But if U don't want to do like this U can do the following:
var ddcPaths = new List<string>();
var filePaths = new List<List<string>>();
foreach (var directoryInfo in new DirectoryInfo(#"C:\Program Files\Logic\").GetDirectories())
{
if (directoryInfo.Name.Contains("DDC["))
{
ddcPaths.Add(directoryInfo.Name);
var tempList = new List<string>();
foreach (var fileInfo in directoryInfo.GetFiles())
{
tempList.Add(fileInfo.FullName);
}
filePaths.Add(tempList);
}
}
but in this case U use two different data structures to represent related data. I suggest it would be logically to use dictionary.
You can use DirectoryInfo.
DirectoryInfo dirInfo = new DirectoryInfo("yourFolderPath");
IEnumerable<DirectoryInfo> subDirs = dirInfo.EnumerateDirectories();
List<string> subDirsNames = new List<string>();
foreach (var subDir in subDirs)
{
subDirsNames.Add(subDir.Name.Trim());
IEnumerable<string> files = subDir.EnumerateFiles().Select(i => i.Name);
//do something with this list....
}