Imagine I have this code
public List<string> X
and I load the following items:
launch.txt
reset.txt
foldername
otherfoldername
I know I can find if an item is on that list by calling X.Contains("value")
but what if I pass "foldername/file.txt".
What's the easiest way to check if a string starts with any of the entries on the X list?
Ideally I want to catch all files that are inside "foldername/." and subdirectories too, so I thought to use the StartWith.
Is LINQ the right approach for this?
Use the Enumerable.Any extension method, which returns true if and only if there is some item in the sequence for which the given predicate returns true.
string search = "foldername/file.txt";
bool result = X.Any(s => search.StartsWith(s));
Of course, StartsWith might not actually be appropriate for your scenario. What if there were only a folder named foldername2 in X? You wouldn't want result to be true in that case, I suspect.
If you want to get the items in X that match the search, you can do the following.
string search = "foldername/file.txt";
IEnumerable<string> result = X.Where(s => search.StartsWith(s));
If you want to get the first item in X that matches the search, you can do the following.
string search = "foldername/file.txt";
string result = X.FirstOrDefault(s => search.StartsWith(s));
Use the Path class if you are fiddling around with paths:
List<string> X = new List<string>(){
"launch.txt","reset.txt","foldername","otherfoldername"
};
string search = "foldername/file.tx";
var searchInfo = new
{
FileNameWoe = Path.GetFileNameWithoutExtension(search),
FileName = Path.GetFileName(search),
Directory = Path.GetDirectoryName(search)
};
IEnumerable<String> matches = X.Select(x => new
{
str = x,
FileNameWoe = Path.GetFileNameWithoutExtension(x),
FileName = Path.GetFileName(x),
Directory = Path.GetDirectoryName(x)
}).Where(xInfo => searchInfo.FileName == xInfo.FileNameWoe
|| searchInfo.FileNameWoe == xInfo.FileName
|| searchInfo.Directory == xInfo.Directory
|| searchInfo.Directory == xInfo.FileNameWoe
|| searchInfo.FileNameWoe == xInfo.Directory)
.Select(xInfo => xInfo.str)
.ToList();
Finds: foldername because one of the filename's FileNameWithoutExtension equals the directory of the path you're searching.
Related
I have 2 lists
List 1
var hashTags = new List<HashTag>();
hashTags.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags.Add(new HashTag
{
Name = "#HashTag2",
Index = 2
});
hashTags.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
List 2
var hashTags2 = new List<HashTag>();
hashTags2.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags2.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags2.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
How do I check if all the elements in hashTags2 exist in hashTags? The index can be ignored and only the name matching is crucial. I can write a for loop to check element but I am looking for a LINQ solution.
Simple linq approach.
hashTags2.All(h=> hashTags.Any(h1 => h1.Name == h.Name))
Working Demo
As only equality of the names is to be taken into account, the problem can be solved by first mapping to the names and then checking containment as follows.
var hashTags2Names = hashTags2.Select( iItem => iItem.Name );
var hashTagsNames = hashTags.Select( iItem => iItem.Name );
var Result = hashTags2Names.Except( hashTagsNames ).Any();
So you want a boolean linq expression that returns true if the name of every element in hashTags2 exists in hashTags?
For this you want the function Enumerable.All, you want that every Hashtag in hashTags2 ...
bool result = hashTags2.All(hashTag => ...)
what do you want to check for every hashTag in hashTags2? That the name is a name in hashTags. So we need the names of hashTags:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
and to check if an item is in a sequence: Enumerable.Contains.
Put it all together:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
bool result = hashTags2.All(hashTag => names.Contains(hashTag.Name));
Of if you want one fairly unreadable expression:
bool result = hashTags2.All(hashTagX =>
hashTags.Select(hashTagY => hashTagY.Name)
.Contains(hashtagX)))
Because of delayed execution there is no difference between the first and the second method. The first one will be more readable.
With Linq to objects you will need at least one IEqualityComparar, to
tell linq how to compare objects and to determine when they are equal.
A simple comparer would be the following that uses the Name property to determine equality of your HashTag.
public class NameEquality : IEqualityComparer<HashTag>
{
public bool Equals(HashTag tag, HashTag tag2)
{
return tag.Name == tag2.Name;
}
public int GetHashCode(HashTag tag)
{
return tag.Name.GetHashCode();
}
}
With this Equality Comparer you can use the linq method Except(), to get all Elements from your list hashTag that are not part of hashTag2.
hashTags.Except(hashTags2, new NameEquality())
I prefer the join operator, however it is just a matter of taste, I guess:
var hashMatched = hashTags.Join(hashTags2,_o => _o.Name, _i => _i.Name, (_o,_i) => _o);
I'm facing this exception when I'm using String.Split with random strings.
List<string> linhas = new List<string>();
linhas.Add("123;abc");
linhas.Add("456;def");
linhas.Add("789;ghi");
linhas.Add("chocolate");
var novas = linhas.Where(l => l.ToString().Split(';')[1]=="def");
The last string "chocolate"doesn't contain a ";", so String.Split returns an array with a single string "chocolate". That's why you get the exception if you try to accesss the second.
You could use ElementAtOrDefault which returns null for strings instead:
var novas = linhas.Where(l => l.Split(';').ElementAtOrDefault(1) == "def");
A longer approach using an anonymous type:
var novas = linhas
.Select(l => new { Line = l, Split = l.Split(';') })
.Where(x => x.Split.Length >= 2 && x.Split[1] == "def")
.Select(x => x.Line);
I'm going to expand a little on Tim's answer and show a way to do a few extra things within your LINQ queries.
You can expand the logic within you Where clause to do some additional processes, which can make your code a bit more readable. This would be good for something small:
var novas = linhas.Where(l =>
{
var parts = l.Split(':');
return parts.Length > 1 ? parts[1] == "def" : false;
});
If you need multiple statements, you can wrap the body of your clause within curly braces, but then you need the return keyword.
Alternatively, if you have a lot of information that would make something inline like that unreadable, you can also use a separate method within your query.
public void FindTheStringImLookingFor()
{
var linhas = new List<string>();
linhas.Add("123;abc");
linhas.Add("456;def");
linhas.Add("789;ghi");
linhas.Add("chocolate");
var words = linhas.Where(GetTheStringIWant);
}
private bool GetTheStringIWant(string s)
{
var parts = s.Split(':');
// Do a lot of other operations that take a few lines.
return parts.Length > 1 ? parts[1] == "def" : false;
}
If I want to find the exact match or the next nearest for a string.
Using SQL, I can do :
SELECT TOP 1 *
FROM table
WHERE Code >= #searchcode
ORDER BY Code
How might I achieve this using LINQ and a List of the records.
I was expecting to be able to do something like:
var find = ListDS.Where(c => c.Code >= searchcode).First();
but you can't compare strings that way.
Note that Code is an alpha string, letters, numbers, symbols, whatever..
Nearest means if you have a list containing "England", "France", "Spain", and you search for "France" then you get "France". If you search for "Germany" you get "Spain".
Here is a simple code may help you
List<string> ls = new List<string>();
ls.Add("ddd");
ls.Add("adb");
var vv = from p in ls where p.StartsWith("a") select p;
select all element with starting string "a"
If Code is an int this might work:
var find = ListDS.Where(c => c.Code >= searchcode).OrderBy(c => c.Code).First();
otherwise you need to convert it to one:
int code = int.Parse(searchcode);
var find = ListDS.Where(c => Convert.ToInt32(c.Code) >= code).OrderBy(c => Convert.ToInt32(c.Code)).First();
Try this solution:
class Something
{
public string Code;
public Something(string code)
{
this.Code = code;
}
}
class Program
{
static void Main(string[] args)
{
List<Something> ListDS = new List<Something>();
ListDS.Add(new Something("test1"));
ListDS.Add(new Something("searchword1"));
ListDS.Add(new Something("test2"));
ListDS.Add(new Something("searchword2"));
string searchcode = "searchword";
var find = ListDS.First(x => x.Code.Contains(searchcode));
Console.WriteLine(find.Code);
Console.ReadKey();
}
}
I replaced your >= with .Contains. You can also add the action into First, no need for Where.
It will not find the "nearest", just the first word containg your search parameters.
You could compare string in C#, it will use alphabetically order:
var find = ListDS.Where(c => c.Code.CompareTo(searchcode) >= 0)
.OrderBy(c => c) // get closer one, need to order
.First();
See the CompareTo docs.
Note that with this method, "10" > "2".
I am reading a file that contains rows like
pathName; additionalString; maybeSomeNumbers
I read it using
var lines = File.ReadAllLines(fileListFile);
var fileListEntries = from line in lines
where !line.StartsWith("#")
select line.Split(';').ToArray();
This works well so far. However I would like to change the drive letter in the pathName. I could convert fileListEntries to an array and loop across elements [i][0], but is there a way that I could do this operation on the collection directly?
Use the LINQ extension method syntax in order to be able to use code blocks { ... } in the lambda expressions. If you do so, you have to include an explicit return-statement.
var fileListEntries = lines
.Where(l => !l.StartsWith("#"))
.Select(l => {
string[] columns = l.Split(';');
if (Path.IsPathRooted(column[0])) {
string root = Path.GetPathRoot(columns[0]);
columns[0] = Path.Combine(#"X:\", columns[0].Substring(root.Length));
}
return columns;
})
.ToArray();
I think you can do it inline with the LINQ.
File.ReadAllLines() returns a string array, so you should be able to perform Replace() on the line from the collection.
var replace = "The string to replace the drive letter";
var lines = File.ReadAllLines(fileListFile);
var fileListEntries = from line in lines
where !line.StartsWith("#")
select (line.Replace(line[0], replace).Split(';')).ToArray();
You could just call a method in your select that modifies the text in the manner that you would like.
static void Main(string[] args)
{
var fileListEntries = from line in lines
where !(line.StartsWith("#"))
select ( ModifyString(line));
}
private static string[] ModifyString(string line)
{
string[] elements = line.Split(';');
elements[0] = "modifiedString";
return elements;
}
lines.Where(l => !l.StartsWith("#").
Select(l => string.Concat(driveLetter, l.Substring(1))).
Select(l => l.Split(';');
I'm creating a tool that is supposed to concatenate docs that contain the same name.
example: C_BA_20000_1.pdf and C_BA_20000_2.pdf
These files should be grouped in one list.
That tool runs on a directory lets say
//directory of pdf files
DirectoryInfo dirInfo = new DirectoryInfo(#"C:\Users\derp\Desktop");
FileInfo[] fileInfos = dirInfo.GetFiles("*.pdf");
foreach (FileInfo info in fileInfos)
I want to create an ArrayList that contains filenames of the same name
ArrayList list = new ArrayList();
list.Add(info.FullName);
and then have a list that contains all the ArrayLists of similar docs.
List<ArrayList> bigList = new List<ArrayList>();
So my question, how can I group files that contains same name and put them in the same list.
EDIT:
Files have the same pattern in their names AB_CDEFG_i
where i is a number and can be from 1-n. Files with the same name should have only different number at the end.
AB_CDEFG_1
AB_CDEFG_2
HI_JKLM_1
Output should be:
List 1: AB_CDEFG_1 and AB_CDEFG_2
List 2: HI_JKLM_1
Create method which extracts 'same' part of file name. E.g.
public string GetRawName(string fileName)
{
int index = fileName.LastIndexOf("_");
return fileName.Substring(0, index);
}
And use this method for grouping:
var bigList = Directory.EnumerateFiles(#"C:\Users\derp\Desktop", "*.pdf")
.GroupBy(file => GetRawName(file))
.Select(g => g.ToList())
.ToList();
This will return List<List<string>> (without ArrayList).
UPDATE Here is regular expression, which will work with all kind of files, whether they have number at the end, or not
public string GetRawName(string file)
{
string name = Path.GetFileNameWithoutExtension(file);
return Regex.Replace(name, #"(_\d+)?$", "")
}
Grouping:
var bigList = Directory.EnumerateFiles(#"C:\Users\derp\Desktop", "*.pdf")
.GroupBy(GetRawName)
.Select(g => g.ToList())
.ToList();
It sounds like the difficulty is in deciding which files are the same.
static string KeyFromFileName(string file)
{
// Convert from "C_BA_20000_2" to "C_BA_20000"
return file.Substring(0, file.LastIndexOf("_"));
// Note: This assumes there is an _ in the filename.
}
Then you can use this LINQ to build a list of fileSets.
using System.Linq; // Near top of file
var files = Directory.GetFiles(#"C:\Users\derp\Desktop", "*.pdf")
var fileSets = files
.Select(file => file.FullName)
.GroupBy(KeyFromFileName)
.Select(g => new {g.Key, Files = g.ToList()}
.ToList();
Aside from the fact that your question doesnt identify what "same name" means. This is a typical solution.
fileInfos.GroupBy ( f => f.FullName )
.Select( grp => grp.ToList() ).ToList();
This will get you a list of lists... also won't throw an exception if a file doesn't contain the underscore, etc.
private string GetKey(FileInfo fi)
{
var index = fi.Name.LastIndexOf('_');
return index == -1 ? Path.GetFileNameWithoutExtension(fi.Name)
: fi.Name.Substring(0, index);
}
var bigList = fileInfos.GroupBy(GetKey)
.Select(x => x.ToList())
.ToList();