Linq question about grouping something that can change? - c#

I have a list of multiple string and I need to do operation on them by the suffixe they have. The only thing that is not changing is the beginning of the string (They will be always ManifestXXX.txt, FileNameItems1XXX...). The string end's with a suffix is different everytime. Here is what I have so far (Linq Pad):
var filesName = new[] { "ManifestSUFFIX.txt",
"FileNameItems1SUFFIX.txt",
"FileNameItems2SUFFIX.txt",
"FileNameItems3SUFFIX.txt",
"FileNameItems4SUFFIX.txt",
"ManifestWOOT.txt",
"FileNameItems1WOOT.txt",
"FileNameItems2WOOT.txt",
"FileNameItems3WOOT.txt",
"FileNameItems4WOOT.txt",
}.AsQueryable();
var query =
from n in filesName
group n by n.EndsWith("SUFFIX.txt") into ere
select new{ere} ;
query.Dump();
The condition in the GROUP is not good. I am thinking to try to get all possible suffixe with a nested SELECT in the group but I can't find a way to do it.
How can I have 3 differents group, grouping by their suffixe with Linq? Is it possible?
*Jimmy answer is great but still doesn't work the way desired. Any fix?

group by the suffix rather than whether it matches any particular one.
...
group by GetSuffix(n) into ere
...
string GetSuffix(string n) {
return Regex.Replace(n,"^Manifest|^FileNameItems[0-9]+", "");
}

Related

Optional Random ordered nested grouping in linq

I am really in a position where I can't think of answer regarding optional grouping in linq.
Basically,I want to generate report which comes from a screen having filters.
These filters(mostly grouped) are optional and can be rearranged.It's something like
Filters: 1.Clients Projects Tasks Duration
or
2.Projects Clients Tasks Duration
or
3.Task Duration etc.
with all possible combinations.
Then data should look like
1.ClientA
ProjectA
TaskA
26hrs 45mins
TaskB
43hrs 23mins
ProjectB
TaskX......
2.ProjectA
ClientA
TaskA
26hrs 45mins...
3.TaskA
26hrs 45mins
TaskB
6hrs 35mins
I have data.But unable to write logic which is generalized.
I am thinking with some enum which will hold filters (viewmodel)selected like
enum.Client,enum.Project... and
if (clientGroupChecked) then
foreach(var clientGroup in list){
//group list by client here
if(projectGroupChecked) then
foreach(var projectGroup in clientGroup){
//group list by project here
}
}
I know,it's wrong.This way I have to put logic for all the combinations possible.
Couldn't think of anything else.I want it really to be generalized because it may have more filters added in future and I don't want to change entire logic just for extra filters(Of course,I want to add new filter group somewhere in the logic.But I want it to be more easy to maintain also.
Edited:#sschimmel :My point is grouping can be shuffled(for this I have buttons[selected -->green and unselected-->gray and these buttons are movable for grouping].So when writing linq logic,how can I know on what criteria I have to group in particular way? For ex: I have columns A B C D E F.In this, I can just choose to group by A or by A B or B A or ACB....etc. with all possible combinations.How to achieve this?I don't want if else check because we have many possibilities.If one more filter is added,it would have many more possibilities. That's why I am thinking for need of general approach to do this.
Edit 2:
Please find attachment and how I am trying below.
//for the following ,I need some way of writing properly passing right values
var reportGroupingCP = (from t in TaskEntries
group t by new { clientId,projectId } into g
select new
{
ClientId = g.Key.clientId,
ProjectId = g.Key.projectId,
Result = (Type)g //What could be T
}).ToList();
var reportGroupingCE = (from t in TaskEntries
group t by new { clientId,employeeId } into g
select new
{
ClientId = g.Key.clientId,
EmployeeId = g.Key.employeeId,
Result = (Type)g //What could be T
}).ToList();
//Can't use above if there is filter only for client.similarly for other cases/I don't want to write for each one.I need way to do this dynamically.May be by passing enum or adding ids to some class or something else
Filter 1
Filter 2
If I understood your question correctly, you wan't to do group your data dynamically on multiple properties.
The easiest solution would be to use Dynamic LINQ which lets you create queries from strings you can easily compose from user inputs.
// userSelections is created dynamically from what the user selected.
var userSelections = new[] { "clientId", "projectId" };
var propertiesToGroupBy = string.Join(", ", userSelections);
var groupedData = data.GroupBy("new(" + propertiesToGroupBy + ")");
It's not type safe nor checked during compile time, but fairly easy to use and solves your problem. It's also documented nicely.
I tried to come up with a solution that dynamically combines Expression<Func<TaskEntry, object>>s but got stuck on creating the Expression that ìnstantiates the anonymous type you would use inside the GroupBy(new { ... }). Because the number of selected properties to group by is not known during compile time, it's not possible to create the anonymous type.

string grouping algorithm c#

I need something like a grouping algorithm for strings in C#.
I've tried for days and before I go mad, I should maybe ask someone :)
(no adjazenctmatrix^^)
what do I have is data in an Dictonary
something like this:
key|value
"bla","AAA;BBB;CCC" // ';' is split sign
"whatever","BBB;DDD;EEE;FF"
"hmm", "ZZZ,YYY,XXX"
"foo", "CCC,JJJ,VVV"
....
value1 and value2 contains "BBB" so group it to new string : (in a new dictionary,key whatever...counter?)
"AAA;BBB;CCC;EEE;FF" (or without distinct to "AAA;BBB;CCC;BBB;DDD;EEE;FF")
value3 is his own group
value4 contains "CCC" so group it to the others
"AAA;BBB;CCC;EEE;FF;JJJ;VVV" (or without distinct to "AAA;BBB;CCC;BBB;DDD;EEE;FF;CCC;JJJ;VVV")
I need that string for SQL update
update item set group = bar
where group in ('','',... )
I do it with split and join, this part works :-P
thanks
So first organize the data. Have a map keys["bla"] = some_set("AAA", "BBB", "CCC"); and so on. Then build a reverse map that should look like reverse["BBB"] = ["bla", "whatever"] both maps should be about the same size as the original data.
Next you can do a DFS over the implicit graph (pseudocode):
merge = some_set()
DFS(string key) {
if (key in merge) return; // Been here already.
merge.insert(key);
for (string edge : keys[key])
for (string other_key : reverse[edge])
DFS(other_key)
}
So you can now call DSF("bla"). When it returns it should contain "bla", "whatever", ..." and any other keys that might be in the group and you can concatenate their strings from keys to get the result you wanted.
You can call DFS for every key to get all the group each key belongs (complexity O(N^2*set_op)). Or, better, keep track of what keys you already processed to avoid working on them again (complexity O(N*set_op)).
If you use hash based sets/maps your set_op is O(average string length). If you use tree based structures then set_op is O(logN). This shouldn't matter unless you have very long strings or lots of keys.

Linq with Regex

I have the matches of a regex pattern and I'm having some difficulties designing the Linq around it to produce the desired output.
The data is fixed lengths: 1231234512341234567
Lengths in this case are: 3, 5, 4, 7
The regex pattern used is: (.{3})(.{5})(.{4})(.{7})
This all works perfectly fine and the matched results of the pattern are as expected, however, the desired output is proving to be somewhat difficult. In fact, I'm not even certain what it would be called in SQL terms - except maybe a pivot query. The desired output is to take all the values from each of the groups at a given position and concatenate them so for example:
field1:value1;value2;value3;valueN;field2:value2;value3;valueN;
Using the below Linq expression, I was able to get field1-value1, field2-value2, etc...
var matches = Regex.Matches(data, re).Cast<Match>();
var xmlResults = from m in matches
from e in elements
select string.Format("<{0}>{1}</{0}>", e.Name, m.Groups[e.Ordinal].Value);
but I can't seem to figure out how to get all the values at position 1 from "Groups" using the element's Ordinal, then all the values at position 2 and so on.
The "elements" in this example is a collection of field names and ordinal positions (starting at 1). So, it would look like this:
public class Element
{
public string Name { get; set; }
public int Ordinal { get; set; }
}
var elements = new List<Element>{
new Element { Name="Field1", Ordinal=1 },
new Element { Name="Field2", Ordinal=2 }
};
I've reviewed a bunch of various Linq expressions and dug into some pivot type Linq expressions, but none of them get me close - they all use the join operator which I don't think is possible.
Does anyone have any idea how to make this Linq?
You should be able to do this by changing the query to select from elements only, and bring in the matches through string.Join, like this:
// Use ToList to avoid iterating matches multiple times
var matches = Regex.Matches(data, re).Cast<Match>().ToList();
// For each element, join all matches, and pull in the value for e.Ordinal
var xmlResults = elements.Select(e =>
string.Format(
"<{0}>{1}</{0}>"
, e.Name
, string.Join(";", matches.Select(m => m.Groups[e.Ordinal].Value))
);
Note: this is not the best way of formatting XML. You would be better off using one of .NET's libraries for making XML, such as LINQ2XML.

Linq query, select everything from one lists property that starts with a string in another list

Hello I'm new to linq and lambda
I have two lists
fl.LocalOpenFiles ...
List<string> f....
there is a property (string) for example taking index 0
fl.LocalOpenFiles[0].Path
i wanted to select all from the first list fl.LocalOpenFiles where fl.LocalOpenFiles.Path starts with a string from the List<string> f
I finally got this...
List<LocalOpenFile> lof = new List<LocalOpenFile>();
lof = fl.LocalOpenFiles.Join(
folders,
first => first.Path,
second => second,
(first, second) => first)
.ToList();
But its just selecting folders that meet the requirement first.Path == second and i couldnt find a way to get the data that i want which is something meeting this "braindump" requirement:
f[<any>] == fl.LocalOpenFiles[<any>].Path.Substring(0, f[<any>].Length)
Another Example...
List<string> f = new List<string>{ "abc", "def" };
List<LocalOpenFile> lof = new List<LocalOpenFile>{
new LocalOpenFile("abc"),
new LocalOpenFile("abcc"),
new LocalOpenFile("abdd"),
new LocalOpenFile("defxsldf"),)}
// Result should be
// abc
// abcc
// defxsldf
I hope i explained it in a understandable way :)
Thank you for your help
Do you mean something like this :
List<LocalOpenFile> result =
lof.Where(file => f.Any(prefix => file.Path.StartsWith(prefix)))
.ToList();
You can use a regular where instead of a join, which will give you more straight forward control over the selection criteria;
var result =
from file in lof
from prefix in f
where file.Path.StartsWith(prefix)
select file.Path; // ...or just file if you want the LocalOpenFile objects
Note that a file matching multiple prefixes may show up more than once. If that is a problem, you can just add a call to Distinct to eliminate duplicates.
EDIT:
If you - as it seems in this case - only want to know the matching path and not the prefix it matches (ie you only want data from one collection as in this case), I'd go for #har07's Any solution instead.

Linq: Select data into multiple Lists

How can I select the result of a query into multiple Lists? For example,
class Person
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
void test()
{
var query =
from i in Persons
select i;
// now i want to select two lists - list of first names, and list of last names
// approach 1 - run query twice?
List<string> f = query.Select( i=>i.FirstName).ToList();
List<string> l = query.Select( i=>i.LastName).ToList();
// approach 2 - turn it into a list first, and break it up
List<Person> p = query.ToList();
List<string> f = p.Select( i=>i.FirstName).ToList();
List<string> l = p.Select( i=>i.LastName).ToList();
}
Problem with approach 1 is I need to run the query twice.
Problem with approach 2 is I use twice the memory. When the data set is huge, it may become an issue.
Problem with approach 1 is I need to run the query twice. Problem with approach 2 is I use twice the memory. When the data set is huge, it may become an issue.
Either of these tradeoffs may be adequate, but it depends on the resulting dataset and use case.
If you want to avoid this tradeoff entirely, however, you can. The way around this is to not use Linq:
var firstNames = new List<string>();
var lastNames = new List<string>();
foreach(var person in query)
{
firstNames.Add(person.FirstName);
lastNames.Add(person.LastName);
}
This avoids two queries as well as the "copy" of the items, as you only enumerate the query results once, and don't store any extra information.
Problem with approach 2 is I use twice the memory.
Wrong. Measure it. The string instances are reused.

Categories