Linq: Select data into multiple Lists - c#

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.

Related

More efficient way of using LINQ to compare two items?

I am updating records on a SharePoint list based on data from a SQL database. Lets say my table looks something like this:
VendorNumber
ItemNumber
Descrpition
1001
1
abc
1001
2
def
1002
1
ghi
1002
3
jkl
There can be multiple keys in each table. I am trying to make a generic solution that will work for multiple different table structures. In the above example, VendorNumber and ItemNumber would be considered keys.
I am able to retrieve the SharePoint lists as c# List<Microsoft.SharePoint.Client.ListItem>
I need to search through the List to determine which individual ListItem corresponds to the current SQL datarow I am on. Since both ListItem and DataRow allow bracket notation to specify column names, this is pretty easy to do using LINQ if you only have one key column. What I need is a way to do this if I have anywhere from 1 key to N keys. I have found this solution but realize it is very inefficient. Is there a more efficient way of doing this?
List<string> keyFieldNames = new List<string>() { "VendorNumber", "ItemNumber" };
List<ListItem> itemList = MyFunction_GetSharePointItemList();
DataRow row = MyFunction_GetOneRow();
//this is the part I would like to make more efficient:
foreach (string key in keyFieldNames)
{
//this filters the list with each successive pass.
itemList = itemList.FindAll(item => item[key].ToString().Trim() == row[key].ToString().Trim());
}
Edited to Add: Here is a link to the ListItem class documentation:
Microsoft.SharePoint.Client.ListItem
While ListItem is not a DataTable object, its structure is very similar. I have intentionally designed it so that both the ListItem and my DataRow object will have the same number of columns and the same column names. This was done to make comparing them easier.
A quick optimization tip first:
Create a Dictionary<string, string> to use instead of row
List<string> keyFieldNames = new List<string>() { "VendorNumber", "ItemNumber" };
DataRow row = MyFunction_GetOneRow();
var rowData = keyFieldNames.ToDictionary(name=>row[name].ToString().Trim());
foreach (string key in keyFieldNames)
{
itemList = itemList.FindAll(item => item[key].ToString().Trim() == rowData[key]);
}
This will avoid doing the ToString & Trim on the same records over & over. That's probably taking 1/3rd to 1/2 the time of the loop. (The comparison is fast compared to the string manipulation)
Beyond that, all I can think of is to use reflection to build a specific function, on the fly to handle the comparison. BUT, that would be a big effort, and I don't see it saving that much time. Basically, whatever you do, will still have to do the same basics: Lookup the values by key, and compare them. That's what's taking the majority of the time.
After I stopped looking for an answer, I stumbled across one. I have now realized that using a .Where is implemented using deferred execution. This means that even though the foreach loop iterates several times, the LINQ query executes all at once. This was the part I was struggling to wrap my head around.
My new sudo code:
List<string> keyFieldNames = new List<string>() { "VendorNumber", "ItemNumber" };
List<ListItem> itemList = MyFunction_GetSharePointItemList();
DataRow row = MyFunction_GetOneRow();
//this is the part I would like to make more efficient:
foreach (string key in keyFieldNames)
{
//this filters the list with each successive pass.
itemList = itemList.Where(item => item[key].ToString().Trim() == row[key].ToString().Trim());
}
I know that the .ToString().Trim() is still inefficient, I will address this at some point. But for now at least my mind can rest knowing that the LINQ executes all at once.

Need to get all combinations of several lists, taking sets amounts from each list

class Employee
{
int id;
Position position;
}
class Position
{
string name;
}
public List<List<Tuple<Position, Employee>>> getAllCombinations(Dictionary<Position, int> positionToCountMap, List<Employee> allEmployees)
{
}
positionToCountMap to would be something like this: {"Manager", 1}, {"Developer", "3"}, {"PM", "1"} (it's unknown how many keys there are)
I need to return a list of all possible combinations of allEmployees such that it satisfies the count requirements in the positionToCountMap. I need al combinations of 1 manager, 3 developers and 1 PM. My first step was to create a new dictionary of position to a list of employees with that position.
var positionToEmployeeMap = new Dictionary<Position, List<Employee>>()
//Loop through allEmployees adding each to this dictionary
Now the problem becomes that I have several lists, and I need to find all possible combinations taking the amount specified in positionToCountMap from each list.
Is this a good way to approach the problem? Even if it is, I can't wrap my head around how I would actually brute force this. I was originally trying to think of some recursive solution, but the size of the list may be large enough that recursion might not be a great choice. I'm stuck and could use some advice.
EDIT
I think I have a solution, though it's not great and i'd still love to get some advice.
var positionToEmployeeMap = new Dictionary<Position, List<Employee>>()
//Loop through allEmployees adding each to this dictionary
var relevantLists = new List<Employee>();
//for each key in positionToCountMap, find the list in positionToEmployeeMap and add it to relevantLists
var allCombos = new List<List<Employee>>();
//Loop through relevantLists. For each list, recursively generate all possible combinations of sublists of size N, where N is the number in positionToCountMap. Add a list of all the combinations to allCombos
//recursively loop through allCombos finding all possible combinations taking 1 element from each list
I'd use LINQ. You can do something similar to a SQL CROSS JOIN operation with the following:
var ListA = new List<object>();
var ListB = new List<object>();
var ListC = new List<object>();
var result = (from a in listA
from b in listB
from c in listC
select new { a, b, c }).ToList();
This will result in a list including all combinations of the values in ListA, ListB, and ListC.

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.

How can I check if a string in sql server contains at least one of the strings in a local list using linq-to-sql?

In my database field I have a Positions field, which contains a space separated list of position codes. I need to add criteria to my query that checks if any of the locally specified position codes match at least one of the position codes in the field.
For example, I have a local list that contains "RB" and "LB". I want a record that has a Positions value of OL LB to be found, as well as records with a position value of RB OT but not records with a position value of OT OL.
With AND clauses I can do this easily via
foreach (var str in localPositionList)
query = query.Where(x => x.Position.Contains(str);
However, I need this to be chained together as or clauses. If I wasn't dealing with Linq-to-sql (all normal collections) I could do this with
query = query.Where(x => x.Positions.Split(' ').Any(y => localPositionList.contains(y)));
However, this does not work with Linq-to-sql as an exception occurs due it not being able to translate split into SQL.
Is there any way to accomplish this?
I am trying to resist splitting this data out of this table and into other tables, as the sole purpose of this table is to give an optimized "cache" of data that requires the minimum amount of tables in order to get search results (eventually we will be moving this part to Solr, but that's not feasible at the moment due to the schedule).
I was able to get a test version working by using separate queries and running a Union on the result. My code is rough, since I was just hacking, but here it is...
List<string> db = new List<string>() {
"RB OL",
"OT LB",
"OT OL"
};
List<string> tests = new List<string> {
"RB", "LB", "OT"
};
IEnumerable<string> result = db.Where(d => d.Contains("RB"));
for (int i = 1; i < tests.Count(); i++) {
string val = tests[i];
result = result.Union(db.Where(d => d.Contains(val)));
}
result.ToList().ForEach(r => Console.WriteLine(r));
Console.ReadLine();

Linq question about grouping something that can change?

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]+", "");
}

Categories