I have a List<string> containing file paths. How can I check if there is a particular file in my list e.g. file.txt? Preferably a method returning not a bool value but list element or element's index.
I've read the List<T> documentation but it only confused me as I'm a beginning programmer and my English isn't very good.
Use Where to get a list of values:
var found = fileList.
Where((f)=>System.IO.Path.GetFileName(f)
.Equals(SEARCH_VALUE,
StringComparison.InvariantCultureIgnoreCase));
or use FirstOrDefault for single element or null in case it's not found
If your list contains the full path (like c:\windows\system.ini") I would use System.IO.Path.GetFileName and also keep in mind to search case intenstive
var result = from f in files
where Path.GetFileName(f).Equals("file.txt",
StringComparison.InvariantCultureIgnoreCase)
select f;
bool found = result.Any();
The IndexOf method is what you need, if you want to find the path that exactly watches what you are looking for.
However, if you what to find paths in your list that end with a certain file name, you can do,
var matches = paths.Select((path, i) => new { Path = path, Index = i })
.Where(item => Path.GetFileName(item.Path).Equals(
"file.txt",
StringComparison.OrdinalIgnoreCase));
However, note that matches will be a sequence of 0 or more matches. So, you can do,
if (matches.Any())
{
// I found something.
foreach (var match in matches)
{
var matchIndex = match.Index;
var matchPath = match.Path;
}
}
else
{
// Oops, no matches.
}
or, if you only want the first.
var firstMatchPath = matches.First().Path;
would do.
If you just want the first value if there is one then you can do this.
var value = mylist.FirstOrDefault(x=>x.EndsWith("file.txt"));
or if you want to do something with each matching string.
foreach (string value in mylist.Where(x=>x.EndsWith("file.txt")) )
{
// Do whatever you intend with value.
}
or if you want a list of the indices of the values, then you could try this.
var indexValues = new List<int>();
foreach (string value in mylist.Where(x=>x.EndsWith("file.txt")) )
{
indexValues.Add(mylist.IndexOf(value));
}
Use LINQ (assuming you havethe paths as strings):
var found = from f in fileList where f.equals("file.txt") select f;
Considering that you have path and file name is located at the end of path:
//List of elements
List<string> foundElements = myInitialList.Where(s => s.EndsWith("file.txt")).ToList();
//List of Indexes (base on lift of elements here above)
List<int> indexList = new List<int>();
foundElements.ForEach(f => indexList.Add(myInitialList.IndexOf(f)));
It's not clear from your question, but it seems that the list will contain file paths, but what you are looking for is a filename.
The following code will give you the index of the first occurrence of a file called "file.txt" in the list of path names, or -1 if it isn't in the list.
Note how this uses Path.GetFileName(). This is so that it will match "c:\dir1\dir2\file.txt" and not "c:\dir1\dir2\wrongfile.txt".
int index = filenames.FindIndex(filename => Path.GetFileName(filename).Equals("file.txt", StringComparison.OrdinalIgnoreCase));
However, if you are searching for an entire path then you can do it like this:
int index = filenames.FindIndex(filename => filename.Equals("file.txt", StringComparison.OrdinalIgnoreCase));
Note how we do a comparison of the entire filename rather than using "EndsWith".
If the filenames are already all lowercase and you are comparing entire paths then you can do a simpler search:
int index = filenames.IndexOf("file.txt");
Or if they are all uppercase you'd have to do:
int index = filenames.IndexOf("FILE.TXT");
Related
I'm working on a program that is supposed to scan a specific directory looking for any directories within it that have specific names, and if it finds them, tell the user.
Currently, the way I am loading the names its searching for is like this:
static string path = Path.Combine(Directory.GetCurrentDirectory(), #"database.txt");
static string[] database = File.ReadAllLines(datapath);
I am using this as an array of names to look for when looking through a specific directory. I am doing so with a foreach method.
System.IO.DirectoryInfo di = new DirectoryInfo("C:\ExampleDirectory");
foreach (DirectoryInfo dir in di.GetDirectories())
{
}
Is there a way to see if any of the names in the file "database.txt" match any names of directories found within "C:\ExampleDirectory"?
The only way I can think of doing this is:
System.IO.DirectoryInfo di = new DirectoryInfo(versionspath);
foreach (DirectoryInfo dir in di.GetDirectories())
{
if(dir.Name == //Something...) {
Console.WriteLine("Match found!");
break;}
}
But this obviously won't work, and I cannot think of any other way to do this. Any help would be greatly appreciated!
Based on your other questions on stackoverflow, I presume your question is a homework or you are a passionate hobby programmer, am I right? So I'll try to explain the principle here continuing your almost complete solution.
You will need a nested loop here, a loop in a loop. In the outer loop you iterate through the directories. You already got this one. For each directory you need to loop through the names in database to see if any item in it matches the name of the directory:
System.IO.DirectoryInfo di = new DirectoryInfo(versionspath);
foreach (DirectoryInfo dir in di.GetDirectories())
{
foreach (string name in database)
{
if (dir.Name == name)
{
Console.WriteLine("Match found!");
break;
}
}
}
Depending on your goal, you might want to exit at the first matching directory. The sample code above doesn't. The single break; statement only exits the inner loop, not the outer one. So it continues to check the next directory. Try to figure it out yourself how to stop at the first match (by exiting the outer loop).
As usual, LINQ is the way to go. Whenever you have to find matches or not-matches between two lists and both lists containing different types, you'll have to use .Join() or .GroupJoin().
The .Join() comes into play, if you need to find a 1:1 relationship and the .GroupJoin() for any kind of 1-to relationship (1:0, 1:many or also 1:1).
So, if you need the directories that match your list, this sounds for a job to the .Join() operator:
public static void Main(string[] args)
{
// Where ever this comes normally from.
string[] database = new[] { "fOo", "bAr" };
string startDirectory = #"D:\baseFolder";
// A method that returns an IEnumerable<string>
// Using maybe a recursive approach to get all directories and/or files
var candidates = LoadCandidates(startDirectory);
var matches = database.Join(
candidates,
// Simply pick the database entry as is.
dbEntry => dbEntry,
// Only take the last portion of the given path.
fullPath => Path.GetFileName(fullPath),
// Return only the full path from the given matching pair.
(dbEntry, fullPath) => fullPath,
// Ignore case on comparison.
StringComparer.OrdinalIgnoreCase);
foreach (var match in matches)
{
// Shows "D:\baseFolder\foo"
Console.WriteLine(match);
}
Console.ReadKey();
}
private static IEnumerable<string> LoadCandidates(string baseFolder)
{
return new[] { #"D:\baseFolder\foo", #"D:\basefolder\baz" };
//return Directory.EnumerateDirectories(baseFolder, "*", SearchOption.AllDirectories);
}
You can use LINQ to do this
var allDirectoryNames = di.GetDirectories().Select(d => d.Name);
var matches = allDirectoryNames.Intersect(database);
if (matches.Any())
Console.WriteLine("Matches found!");
In the first line we get all the directory names, then we use the Intersect() method to see which ones are present in both allDirectoryNames and database
I am having a little trouble getting files into a string[]. Everything seems to be ok until I have a .docx and doc file and a .xlsx and a .xls file in my directory that I am searching. Can someone advise me on how to acheive this?
Please see my code that I have so far below:
Filter = ".DOC|.DOCX|.XLS|.XLSX|.PDF|.TXT|.TIF|.TIFF"
public string[] getFiles(string SourceFolder, string Filter)
{
// ArrayList will hold all file names
System.Collections.ArrayList alFiles = new System.Collections.ArrayList();
// Create an array of filter string
string[] MultipleFilters = Filter.Split('|');
// for each filter find mathing file names
foreach (string FileFilter in MultipleFilters)
{
// add found file names to array list
alFiles.AddRange(Directory.GetFiles(SourceFolder, FileFilter));
}
// returns string array of relevant file names
return (string[])alFiles.ToArray(typeof(string));
}
Thanks,
George
You can take advantage of LINQ's Distinct() (System.Linq).
Returns distinct elements from a sequence by using the default equality comparer to compare values.
Filter = ".DOC|.DOCX|.XLS|.XLSX|.PDF|.TXT|.TIF|.TIFF";
public string[] GetFiles(string SourceFolder, string Filter)
{
List<string> alFiles = new List<string>();
string[] MultipleFilters = Filter.Split('|');
foreach (string FileFilter in MultipleFilters)
{
alFiles.AddRange(Directory.GetFiles(SourceFolder, FileFilter));
}
return alFiles.Distinct().ToArray();
}
Notice that I am now creating a new List<string> instance (System.Collections.Generic), instead of your ArrayList
First off, the code as originally posted doesn't return any files, because none of the calls to Directory.GetFiles() include a wildcard in the filter.
Second, assuming that the original filter did include wildcards, there's a nasty little surprise in the MSDN Directory.GetFiles(string, string) documentation:
When you use the asterisk wildcard character in a searchPattern such
as "*.txt", the number of characters in the specified extension
affects the search as follows:
•If the specified extension is exactly three characters long, the
method returns files with extensions that begin with the specified
extension. For example, "*.xls" returns both "book.xls" and
"book.xlsx".
•In all other cases, the method returns files that exactly match the
specified extension. For example, "*.ai" returns "file.ai" but not
"file.aif".
(emphasis added)
Rather than trying to work around the "helpful" behavior of the Directory.GetFiles(string, string) overload, I'd use the Directory.GetFiles(string) overload to get all the files and then filter the results using LINQ:
public string[] getFiles(string SourceFolder, string Filter)
{
string[] MultipleFilters = Filter.Split('|');
var SelectedFiles = Directory.GetFiles(SourceFolder)
.Where(f => MultipleFilters.Contains(Path.GetExtension(f).ToUpper()))
.Select(f => f);
return SelectedFiles.ToArray();
}
If there are huge number of files in the folder then getting all files could cause memory problem.
In below code I am searching files based on wild card filter and then filtering them using LINQ :)
string Filter = ".DOC|.DOCX|.XLS|.XLSX|.PDF|.TXT|.TIF|.TIFF" //without "*"
public string[] getFiles(string SourceFolder, string Filter)
{
var filters = Filter.ToUpper().Split('|');
return filters.SelectMany(filter => System.IO.Directory.GetFiles(SourceFolder, "*"+filter)).Where(file=> filters.Contains(Path.GetExtension(file).ToUpper())).ToArray();
}
I have problem with updating a single item under List<string> that matches a different string using LINQ. Let's say that I have a list of names and I want to check if name "John" already exists in my list. If yes, then replace "John" with "Anna".
Here is what I do:
var sItem = myList.First(n=> n == "John"); //I am 100% sure that John exists, that\s why I use .First
sItem = "Anna";
This is how it should work, but when I check my List (myList) after the process, the original item is still there (I can still see John, instead of Anna). I also tried to implement INotifyChanged on the List, but still no result.
What am I doing wrong?
If you need to update, use FindIndex:
int index = myList.FindIndex(n => n == "John");
myList[index] = "Anna";
You are assigning the result of linq query to a string variable. That is not the element of list but a variable that is also referencing the element of that list. Changing the value of variable sItem will define a new string that will be referenced by the sItem and the item in the list remains unchanged.
You can use FindIndex to get the index of element in the array and use it to refer to list element.
int index = myList.FindIndex(n => n == "John");
myList[index] = "Anna";
Searches for an element that matches the conditions defined by the
specified predicate, and returns the zero-based index of the first
occurrence within the entire List.
Edit
When one string variable is assigned to other. They both would be referencing the same string but when you assign a different string to second variable for instance then they both referencing different strings. See the following example from answer of Eric Lippert.
a----------------------Hello
Then you say that "b = a", which means attach another piece of string to the same thing that a is attached to:
a----------------------Hello
/
b---------------------
Then you say "now attach b to Hi"
a----------------------Hello
b----------------------Hi
int index = strList.FindIndex(n => n == "John");
if (index != -1)
{
strList[index] = "Anna";
}
This will ensure that if "John" does not exist in the list, the program does not crash.
It should work for you
List<string> list = new List<string>();
list.Add("Gandarez");
list.Add("Carlos");
var search = list.FirstOrDefault(l => l == "Carlos");
if (search != null)
{
var index = list.IndexOf("Carlos");
list.RemoveAt(index);
list.Insert(index, "Test");
}
int sItem = myList.FindIndex(x => x == "John");
myList[sItem] = "Anna";
The problem you are seeing is that System.String, while actually a reference type, acts like a value type. So, when you assign a new value to sItem you are replacing it, not changing it.
If you were using a true reference type, what you tried could have worked:
List<Person> myList = ....;
var sItem = myList.First(p=> p.Name == "John");
sItem.Name = "Anna";
(Assigning -- sItem = new Person("Anna"); -- would still fail the same way,)
The goal is to sort through a text (i.e. a speech) and output a list of the distinct words in the speech to a textbox. I have read through a lot of tips on the boards and played around a lot but at this point am at that point where I am more confused then when I started. Here is my code
private void GenerateList(string[] wordlist)
{
List<string> wordList = new List<string>();
for (int i = 0; i < wordlist.Length; i++)
{
wordList.Add(wordlist[i]);
}
var uniqueStr = from item in wordList.Distinct().ToList()
orderby item
select item;
for (int i = 0; i < uniqueStr.Count(); i++ )
{
txtOutput.Text = uniqueStr.ElementAt(i) + "\n";
}
}
At this point I am getting a return of one word. For the text I am using (the gettysburg address) it is the word "year" and it is the only instance of that word in the text.
I am passing the function each individual word loaded into a string array that is then put into a list (which may be redundant?).
I hope this does what you need in a simple and efficient manner (using .Dump() from LINQPad)
void Main()
{
// can be any IEnumerable<string> including string[]
var words = new List<string>{"one", "two", "four", "three", "four", "a", "z"};
words.ToDistinctList().Dump();
// you would use txtOutput.Text = words.ToDistinctList()
}
static class StringHelpers
{
public static string ToDistinctList(this IEnumerable<string> words)
{
return string.Join("\n", new SortedSet<string>(words));
}
}
A few tips regarding your question:
There is no reason to turn the array into list, because LINQ extension methods are defined on IEnumerable<T>, which is implemented by both the array and the list
Make sure that all letters are in the same case - use ToLower, for instance
You are overwriting txtOutput.Text in every iteration. Instead of setting the new value, append new part to the existing value
Here is the simple piece of code which produces the output you wanted:
IEnumerable<string> distinct =
wordList
.Select(word => word.ToLower())
.Distinct()
.OrderBy(word => word);
txtOutput.Text = string.Join("\n", distinct.ToArray());
On a related note, here is a very simple LINQ expression which returns distinct words from a text, where the whole text is specified as one string:
public static IEnumerable<string> SplitIntoWords(this string text)
{
string pattern = #"\b[\p{L}]+\b";
return
Regex.Matches(text, pattern)
.Cast<Match>() // Extract matches
.Select(match => match.Value.ToLower()) // Change to same case
.Distinct(); // Remove duplicates
}
You can find more variations of regex pattern for the same problem here: Regex and LINQ Query to Split Text into Distinct Words
Here's how I'd simplify your code, as well as achieve what you want to achieve.
private void GenerateList(string[] wordlist)
{
List<string> wordList = wordlist.ToList(); // initialize the list passing in the array
var uniqueStr = from item in wordList.Distinct().ToList()
orderby item
select item;
txtOutput.Text = String.Join("\n", uniqueStr.ToArray());
}
You can use the fact that the StringBuilder class has a fluent interface along with LINQ to simplify this greatly.
First, you can create the StringBuilder and concatenate all of the words into the same instance like so:
// The builder.
var builder = new StringBuilder();
// A copy of the builder *reference*.
var builderCopy = builder;
// Get the distinct list, order by the string.
builder = wordList
// Get the distinct elements.
.Distinct()
// Order the words.
.OrderBy(w => w).
// Append the builder.
Select(w => builderCopy.AppendLine(word)).
// Get the last or default element, this will
// cycle through all of the elements.
LastOrDefault();
// If the builder is not null, then assign to the output, otherwise,
// assign null.
txtOutput.Text = builder == null ? null : builder.ToString();
Note, you don't have to actually materialize the list, as wordList is already a materialized list, it's an array (and as a side note, typed arrays in C# implement the IList<T> interface).
The AppendLine method (and most of the methods on StringBuilder) return the instance of the StringBuilder that the operation was performed on, which is why the LastOrDefault method call works; simply call the operation and return the result (each item returned will be the same reference).
The builderCopy variable is used to avoid access to a modified closure (it never hurts to be safe).
The null check at the end is for the case where wordList doesn't contain any elements. In this case, the call to LastOrDefault will return null.
I know, this is very simple for you guys.
Please consider the following code:
string[] str = { "dataReader", "dataTable", "gridView", "textBox", "bool" };
var s = from n in str
where n.StartsWith("data")
select n;
foreach (var x in s)
{
Console.WriteLine(x.ToString());
}
Console.ReadLine();
Supposedly, it will print:
dataReader
dataTable
right?
What if for example I don't know the data, and what the results of the query will be (but I'm sure it will return some results) and I just want to print the second item that will be produced by the query, what should my code be instead of using foreach?
Is there something like array-indexing here?
You're looking forEnumerable.ElementAt.
var secondMatch = str.Where(item => item.StartsWith("data")) //consider null-test
.ElementAt(1);
Console.WriteLine(secondMatch); //ToString() is redundant
SinceWherestreams its results, this will be efficient - enumeration of the source sequence will be discontinued after the second match (the one you're interested in) has been found.
If you find that the implicit guarantee you have that the source will contain two matches is not valid, you can use ElementAtOrDefault.
var secondMatch = str.Where(item => item.StartsWith("data"))
.ElementAtOrDefault(1);
if(secondMatch == null) // because default(string) == null
{
// There are no matches or just a single match..
}
else
{
// Second match found..
}
You could use array-indexing here as you say, but only after you load the results into... an array. This will of course mean that the entire source sequence has to be enumerated and the matches loaded into the array, so it's a bit of a waste if you are only interested in the second match.
var secondMatch = str.Where(item => item.StartsWith("data"))
.ToArray()[1]; //ElementAt will will work too
you got a few options:
s.Skip(1).First();
s.ElementAt(1);
The first is more suited for scenarios where you want X elements but after the y first elements. The second is more clear when you just need a single element on a specific location