Complex LINQ query - c#

I know LINQ but my knowledge is pretty much only selects, where, orderby and all of the most common functions. Now I have a need to do something that I think is really difficult and
maybe not even possible to do just with LINQ. What I have is a list of people. That's east
to query but I need to create a text string from that list. The text string has to give a letter followed by the name of each person.
IList<person> Person
I need to be able to have a LINQ statement that checks through the Person list. I need
to be able to look for names that appear more than once. So far I have the following. It works okay but doesn't give everything needed:
Person[0] name="Fred" &
Person[1] name="Pete" &
Person[2] name="Tony" the var abc = "a) Fred. b) Pete. c) Tony
var a = "";
foreach (var person in _persons
.Select((data, value) => new { Data = data, Value = value })
{
a = a + (char)(details.Value + 64) + details.name
}
What I need is the additional functionality so that:
Person[0] name="John" then var abc = "a) John."
Person[1] name="John" &
Person[3] name="John" then var abc = "b) & d) John."
Person[1] name="John" &
Person[2] name="John" &
Person[3] name="John" then var abc = "b),c) & d) John."
In other words, get the names and put a character before them that shows what position the name is in the list. However if the name appears twice then instead of a)name1. b)name1 I need to get a),b) name.
It's something I can't really figure out how to do. I would appreciate any advice or pointers that anyone can give me.

Given:
var persons=new[] {"Fred", "John", "John", "Pete", "John"};
You can write:
char id = 'a';
foreach (var row in persons
.Select(w => new { id = id++, name = w })
.GroupBy(w => w.name)
.Select(w => w
.Select(ww => ww.id + ")")
.Aggregate((c, n) => c + "&" + n)
+ " " + w.Key))
{
Console.WriteLine(row);
}
And that gives you:
a) Fred
b)&c)&e) John
d) Pete

I think I understand it, but I deserve a medal if I do.
foreach (var group in _persons
.Select((data, value) => new { Data = data, Value = value }
.GroupBy (x => x.Data))
{
foreach (var item in group)
a = a + (char)(item.Value + 64) + ") ";
a = a + group.Key;
}

What you need is to first group by the names, and then convert them to strings.
var result = names
.Select((name, index) => new { Name = name, Prefix = (char)(index + 'a') + ")" })
.GroupBy(p => p.Name, p => p.Prefix)
.Select(g => string.Join(" & ", g) + " " + g.Key);
This example doesn't entirely do the formatting of the a), b) & c) thing (instead it gives a) & b) & c), but it should get you started.

You can use Count() to achieve this. It can take a function to determine whether something should be counted.

Related

c# how to sort nested collections

I am probably overthinking this all and I'm lost. I'm new to C# and don't know what is the best way to solve this.
string[] input = new string { "FR_Paris", "UK_London", "UK_Bristol" };
Desirable output in console is ordered by occurrence of cities in a country and cities are sorted alphabetically.
In this situation it is:
UK 2x Bristol, London
FR 1x Paris
I'm not going to lie, this is my homework. I know how to parse the input and I think for cities has to be used a collection which can be sorted but don't know which type. I'm kind of lost when it comes to nested collections.
Please give me at least a direction.
Thanks a lot!
Try This
// Init Array
string[] input = new string[] { "FR_Paris", "UK_London", "UK_Bristol" };
//Get All Codes
List<string> Codes = new List<string>();
foreach (var CityName in input)
{
var Name = CityName.Split('_')[0]; // Get Name After _ Paris
if (!Codes.Contains(Name))
Codes.Add(va);
}
// Print
foreach (var Code in Codes.OrderBy(x => x))
{
var AllNames = input.Where(x => x.StartsWith(Code + "_")).Select(x => x.Split('_')[1]);
Console.WriteLine(Code + " " + xd.Count() + "x " + string.Join(",", AllNames.OrderBy(x => x)));
}
You can try with linq
var input = new List<string> { "FR_Paris", "UK_London", "UK_Bristol" };
var result = input.Select(x => new { Country = x.Split("_")[0], City = x.Split("_")[1] })
.GroupBy(x => x.Country)
.Select(x => $"{x.Key} {x.Count()}x {String.Join(", ", x.OrderBy(x => x.City).Select(x => x.City))} ");
foreach (var item in result)
{
Console.WriteLine(item);
}
OUTPUT
FR 1x Paris
UK 2x Bristol, London
Using LINQ:
var grouped = input
.Select(x => x.Split('_')) // Split all strings into array[2]
.GroupBy(x => x[0]) // Group by country
.OrderByDecending(x => x.Count()); // Order by number of cities

Split the string and join all first elements then second element and so on in c#

I have a string like this -
var roleDetails = "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1,24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1,76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1,B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1,CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1,E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1";
Spliting it with "," returns this -
[0] "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1"
[1] "24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1"
[2] "6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1"
[3] "76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1"
[4] "B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1"
[5] "CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1"
[6] "D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1"
[7] "E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1"
All elements contains carat (^). so spliting each element further with ^ symbol will return four element.
But I want to join all first element then all second element and then third and so on and get the result like this -
[0]: 09A880C2-8732-408C-BA09-4AD6F0A65CE9, 24B11B23-1669-403F-A24D-74CE72DFD42A, 6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6, 76B3B165-0BB4-4E3E-B61F-0C0292342CE2, B3C0CE51-00EE-4A0A-B208-98653E21AE11, CBA225BC-680C-4627-A4F6-BED401682816, D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9, E0275936-FBBB-4775-97D3-9A7D19D3E1B4
[1]: Z:WB:SELECT_DOWNLOAD:0000,Z:WB:TRAINING_SUBSCRIBER:0000, Z:WB:LIC_MGR_HOME_REDL:0000,Account Admin, Z:WB:1BENTLEY_ISA_ADMIN:0000, ReadOnly, Z:WB:MY_SELECT_CD:0000, Z:WB:LICENSE_MANAGER
[2]: Product Delivery - Download, Training Subscriber, License Manager - Home use, Account Admin, Co-Administrator, ReadOnly, Product Delivery - DVD, License Manager
[3]: 1,1,1,1,1,1,1,1
What is the quickest and simplest way of achieving this?
EDIT
This is what I tried so far -
var rolearray = roleDetails.Split(',').Select(s => s.Split('^')).Select(a => new { RoleId = a[0], RoleNme = a[1], FriendlyName = a[2], IsUserInRole = a[3] });
but again this is not returning the way I need it. But I want to join all a[0]s , then all a[1] and so on
SOLUTION:
After comparing solutions and ran it 10 times in a loop to see the performance I found solution suggested by Jamiec is taking less time. So selecting this solution.
Pure LINQ solution:
roleDetails.Split(',')
.SelectMany(x => x.Split('^').Select((str, idx) => new {str, idx}))
.GroupBy(x => x.idx)
.Select(grp => string.Join(", ", grp.Select(x => x.str)))
The easiest way to do this, is to simply do:
var split = roleDetails.Split(',')
.Select(x => x.Split('^').ToArray())
.ToArray();
You would then access the elements like a multi dimensional jagged array
Console.WriteLine(split[0][0]);
// result: 09A880C2-8732-408C-BA09-4AD6F0A65CE9
Live example: http://rextester.com/NEUVOR15080
And if you then want all the elements grouped
Console.WriteLine(String.Join(",",split.Select(x => x[0])));
Console.WriteLine(String.Join(",",split.Select(x => x[1])));
Console.WriteLine(String.Join(",",split.Select(x => x[2])));
Console.WriteLine(String.Join(",",split.Select(x => x[3])));
Live example: http://rextester.com/BZXLG67151
Here you can user Aggregate and Zip extension method of Linq.
Aggregate: Performs a specified operation to each element in a collection, while carrying the result forward.
Zip: The Zip extension method acts upon two collections. It processes each element in two series together.
var roleDetails = "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1,24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1,76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1,B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1,CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1,E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1";
var rolearray = roleDetails.Split(',')
.Select(s => s.Split('^'))
.Aggregate((s1Array, s2Array) => s1Array.Zip(s2Array, (s1, s2) => s1 + "," + s2).ToArray());
string roleDetails = "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1,24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1,76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1,B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1,CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1,E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1";
var RawItems = roleDetails.Split(',').Select(x=> x.Split('^'));
var Items1 = RawItems.Select(x=> x.ElementAt(0));
var Items2 = RawItems.Select(x=> x.ElementAt(1));
var Items3 = RawItems.Select(x=> x.ElementAt(2));
var Items4 = RawItems.Select(x=> x.ElementAt(3));
If you don't like the LINQ solutions, here's a solution without:
var result = new string[4];
var i = 0;
foreach(var line in roleDetails.Split(','))
foreach(var piece in line.Split('^'))
result[i++ % 4] += (i <= 4 ? "" : ",") + piece;
Basically, you split on commas and carets, and foreach on each, using a counter that tells us which array element to concatenate in, and whether to use a comma separator or not.
If your initial string is much bigger than in this example, consider first creating an array of StringBuilders first as these are better performing with concatenations:
var stringBuilders = new StringBuilder[4];
var result = new string[4];
var i = 0;
for (var i = 0; i < 4; i++)
stringBuilders[i] = new StringBuilder();
foreach(var line in roleDetails.Split(','))
foreach(var piece in line.Split('^'))
stringBuilders[i++ % 4].Append((i <= 4 ? "" : ",") + piece);
foreach (var stringBuilder in stringBuilders)
result[i++ % 4] = stringBuilder.ToString();
One more LINQ solution. But not as clean as #Pavel's:
string a = "", b = "", c = "", d = "";
roleDetails.Split(',').ToList().ForEach(x =>
{
a += x.Split('^')[0] + ',';
b += x.Split('^')[1] + ',';
c += x.Split('^')[2] + ',';
d += x.Split('^')[3] + ',';
});
MessageBox.Show(a.Trim(','));
MessageBox.Show(b.Trim(','));
MessageBox.Show(c.Trim(','));
MessageBox.Show(d.Trim(','));
OUTPUT:
a = 09A880C2-8732-408C-BA09-4AD6F0A65CE9,24B11B23-1669-403F-A24D-74CE72DFD42A,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6,76B3B165-0BB4-4E3E-B61F-0C0292342CE2,B3C0CE51-00EE-4A0A-B208-98653E21AE11,CBA225BC-680C-4627-A4F6-BED401682816,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9,E0275936-FBBB-4775-97D3-9A7D19D3E1B4
b = Z:WB:SELECT_DOWNLOAD:0000,Z:WB:TRAINING_SUBSCRIBER:0000,Z:WB:LIC_MGR_HOME_REDL:0000,Account Admin,Z:WB:1BENTLEY_ISA_ADMIN:0000,ReadOnly,Z:WB:MY_SELECT_CD:0000,Z:WB:LICENSE_MANAGER:0000
c = Product Delivery - Download,Training Subscriber,License Manager - Home use,Account Admin,Co-Administrator,ReadOnly,Product Delivery - DVD,License Manager
d = 1,1,1,1,1,1,1,1
Fairly clean and fast...
var sets = new[]
{
new List<string>(),
new List<string>(),
new List<string>(),
new List<string>(),
};
foreach (var role in roleDetails.Split(','))
{
var details = role.Split('^');
sets[0].Add(details[0]);
sets[1].Add(details[1]);
sets[2].Add(details[2]);
sets[3].Add(details[3]);
}
var lines = sets.Select(set => string.Join(",", set)).ToArray();
... little nuts to understand and doesn't really save anything on performance ...
var ret = roleDetails.Split(',')
.Aggregate(seed: new { SBS = new[] { new StringBuilder(), new StringBuilder(),
new StringBuilder(), new StringBuilder(), },
Start = true },
func: (seed, role) =>
{
var details = role.Split('^');
if (seed.Start)
{
seed.SBS[0].Append(details[0]);
seed.SBS[1].Append(details[1]);
seed.SBS[2].Append(details[2]);
seed.SBS[3].Append(details[3]);
return new
{
seed.SBS,
Start = false,
};
}
else
{
seed.SBS[0].Append(',').Append(details[0]);
seed.SBS[1].Append(',').Append(details[1]);
seed.SBS[2].Append(',').Append(details[2]);
seed.SBS[3].Append(',').Append(details[3]);
return seed;
}
},
resultSelector: result => result.SBS.Select(sb => sb.ToString()).ToArray()
);
You can use Tuple here
var roles = roleDetails.Split(',')
.Select(x => x.Split('^'))
.Where(x=>x.Length==4)
.Select(x=>
new Tuple<string, string, string, string>(x[0], x[1], x[2], x[3]))
.ToList();
var item1 = string.Join(",", roles.Select(x=>x.Item1).ToArray());
var item2 = string.Join(",", roles.Select(x => x.Item2).ToArray());
var item3 = string.Join(",", roles.Select(x => x.Item3).ToArray());
var item4 = string.Join(",", roles.Select(x => x.Item4).ToArray());
Your attempt tries to do everything in a single line, which is making it much harder for you to understand what's happening.
You're already using all the tools you need (Select() and Split()). If you make your code more readable by separating everything into separate lines of code, then it becomes much easier to find your way:
//Your data string
string myDataString = "...";
//Your data string, separated into a list of rows (each row is a string)
var myDataRows = myDataString.Split(',');
//Your data string, separated into a list of rows (each row is a STRING ARRAY)
var myDataRowsAsStringArrays = myDataRows.Select(row => row.Split('^'))
And now, all you need to do is retrieve the correct data.
var firstColumnValues = myDataRowsAsStringArrays.Select(row => row[0]);
var secondColumnValues = myDataRowsAsStringArrays.Select(row => row[1]);
var thirdColumnValues = myDataRowsAsStringArrays.Select(row => row[2]);
var fourthColumnValues = myDataRowsAsStringArrays.Select(row => row[3]);
And if you so choose, you can join the values into a single comma separated string:
var firstColumnString = String.Join(", ", firstColumnValues);
var secondColumnString = String.Join(", ", secondColumnValues);
var thirdColumnString = String.Join(", ", thirdColumnValues);
var fourthColumnString = String.Join(", ", fourthColumnValues);

Best way to parse a string into Dictionary of terms

Input - string: "TAG1xxxTAG2yyyTAG3zzzTAG1tttTAG1bbb"
Expected result: pairs TAG1 = {xxx,,ttt,bbb}, TAG2 = {yyy}, TAG3 = {zzz}.
I did it using regexps, but I'm really confused by using Regex.Replace and not using return value. I want to improve this code, so how can it be realized?
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace TermsTest
{
class Program
{
static void Main(string[] args)
{
string[] tags = { "TAG1", "TAG2", "TAG3", "TAG4", "TAG5", "TAG6", "TAG7", "TAG8" };
string file = "TAG2jjfjfjndbfdjTAG1qqqqqqqTAG3uytygh fhdjdfTAG5hgjdhfghTAG6trgfmxc hdfhdTAG2jfksksdhjskTAG3kdjbjvbsjTAG2jskjdjdvjvbxjkvbjdTAG2jkxcndjcjbkjn";
string tag = "(" + string.Join("|", tags) + ")";
var dictionary = new Dictionary<string, List<string>>(tags.Length);
Regex.Replace(file, string.Format(#"({0})(.+?)(?={0}|$)", tag), match =>
{
string key = match.Groups[1].Value, value = match.Groups[3].Value;
if (dictionary.ContainsKey(key))
dictionary[key].Add(value);
else
dictionary[key] = new List<string> {value};
return "";
});
foreach (var pair in dictionary)
{
Console.Write(pair.Key + " =\t");
foreach (var entry in pair.Value)
{
Console.Write(entry + " ");
}
Console.WriteLine();
Console.WriteLine();
}
}
}
}
string input = "TAG1xxxTAG2yyyTAG3zzzTAG1tttTAG1bbb";
var lookup = Regex.Matches(input, #"(TAG\d)(.+?)(?=TAG|$)")
.Cast<Match>()
.ToLookup(m => m.Groups[1].Value, m => m.Groups[2].Value);
foreach (var kv in lookup)
{
Console.WriteLine(kv.Key + " => " + String.Join(", ", kv));
}
OUTPUT:
TAG1 => xxx, ttt, bbb
TAG2 => yyy
TAG3 => zzz
What are you trying to do is simply grouping of the values of the same tag, so it should be easier to use GroupBy method:
string input = "TAG1xxxTAG2yyyTAG3zzzTAG1tttTAG1bbb";
var list = Regex.Matches(input, #"(TAG\d+)(.+?)(?=TAG\d+|$)")
.Cast<Match>()
.GroupBy(m => m.Groups[1].Value,
(key, values) => string.Format("{0} = {{{1}}}",
key,
string.Join(", ",
values.Select(v => v.Groups[2]))));
var output = string.Join(", ", list);
This produces as a output string "TAG1 = {xxx, ttt, bbb}, TAG2 = {yyy}, TAG3 = {zzz}"
I'm not sure that I'm aware of all your assumptions and conventions in this problem; but this gave me similar result:
var tagColl = string.Join("|", tags);
var tagGroup = string.Format("(?<tag>{0})(?<val>[a-z]*)", tagColl);
var result = from x in Regex.Matches(file, tagGroup).Cast<Match>()
where x.Success
let pair = new { fst = x.Groups["tag"].Value, snd = x.Groups["val"].Value }
group pair by pair.fst into g
select g;
And a simple test would be:
Console.WriteLine(string.Join("\r\n", from g in result
let coll = string.Join(", ", from item in g select item.snd)
select string.Format("{0}: {{{1}}}", g.Key, coll)));
This is a perfect job for the .NET CaptureCollection object—a unique .NET feature that lets you reuse the same capture group multiple times.
Use this regex and use Matches to create a MatchCollection:
(?:TAG1(.*?(?=TAG|$)))?(?:TAG2(.*?(?=TAG|$)))?(?:TAG3(.*?(?=TAG|$)))?
Then inspect the captures:
Groups[1].Captures will contain all the TAG1
Groups[2].Captures will contain all the TAG2
Groups[3].Captures will contain all the TAG3
From there it's a short step to your final data structure.
To reduce the potential for backtracking, you can make the tokens atomic:
(?>(?:TAG1(.*?(?=TAG|$)))?)(?>(?:TAG2(.*?(?=TAG|$)))?)(?>(?:TAG3(.*?(?=TAG|$)))?)
For details about how this works, see Capture Groups that can be Quantified.

Optimizing a value based search algorithm with LINQ

I want to build a value based search algorithm. What this means is that once I'm given a list of words I would like to search for entries on the database using those words. However depending on what column/property those words match, I want to alter the value of results returned.
Here is a lazy algorithm that achieves that but is very slow.
//search only active entries
var query = (from a in db.Jobs where a.StatusId == 7 select a);
List<SearchResult> baseResult = new List<SearchResult>();
foreach (var item in search)
{
//if the company title is matched, results are worth 5 points
var companyMatches = (from a in query where a.Company.Name.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 5 });
//if the title is matched results are worth 3 points
var titleMatches = (from a in query where a.Title.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 3 });
//if text within the body is matched results are worth 2 points
var bodyMatches = (from a in query where a.FullDescription.ToLower().Contains(item.ToLower()) select new SearchResult() { ID = a.ID, Value = 2 });
//all results are then added
baseResult = baseResult.Concat(companyMatches.Concat(titleMatches).Concat(bodyMatches)).ToList();
}
// the value gained for each entry is then added and sorted by highest to lowest
List<SearchResult> result = baseResult.GroupBy(x => x.ID).Select(p => new SearchResult() { ID = p.First().ID, Value = p.Sum(i => i.Value) }).OrderByDescending(a => a.Value).ToList<SearchResult>();
//the query for the complete result set is built based on the sorted id value of result
query = (from id in result join jbs in db.Jobs on id.ID equals jbs.ID select jbs).AsQueryable();
I'm looking for ways to optimize this. I am new to LINQ query so I was hoping I could get some help. If there is away I can create the LINQ query that achieves all of this in one go instead of checking for company name and then title and the body text and bringing it all together and creating a sorted list and running it again against the database to get full listing it would be great.
It's best if I study the problem first. My previous answer was optimizing the wrong thing. The primary problem here is going over the results list multiple times. We can change that:
foreach (var a in query)
{
foreach (var item in search)
{
itemLower = item.ToLower();
int val = 0;
if (a.Company.Name.ToLower.Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 5});
if (a.Title.ToLower.Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 3});
if (a.FullDescription.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 2});
}
}
After that, you have your base result and you can continue with your processing.
That reduces it to a single query rather than three queries for each search item.
I wasn't sure if you wanted unique items in your baseResult, or if there was some reason you allowed duplicates and then used the sum of the values to order them. If you want unique items, you could make baseResult a Dictionary, with the ID as the key.
Edit after comment
You could reduce the number of items in the list by doing:
int val = 0;
if (a.Company.Name.ToLower.Contains(itemLower))
val += 5;
if (a.Title.ToLower.Contains(itemLower))
val += 3;
if (a.FullDescription.ToLower().Contains(itemLower))
val += 2;
if (val > 0)
{
baseResult.Add(new SearchResult { ID = a.ID, Value = val });
}
That won't eliminate duplicates altogether, though, because the company name could match one search term, and the title might match another search term. But it would reduce the list somewhat.
Thanks to Jim's answer and some tweeking on my side I managed to reduce the time it takes to complete the search by 80%
Here is the final solution:
//establish initial query
var queryBase = (from a in db.Jobs where a.StatusId == 7 select a);
//instead of running the search against all of the entities, I first take the ones that are possible candidates, this is done through checking if they have any of the search terms under any of their columns. This is the one and only query that will be run against the database
if (search.Count > 0)
{
nquery = nquery.Where(job => search.All(y => (job.Title.ToLower() + " " + job.FullDescription.ToLower() + " " + job.Company.Name.ToLower() + " " + job.NormalLocation.ToLower() + " " + job.MainCategory.Name.ToLower() + " " + job.JobType.Type.ToLower()).Contains(y))); // + " " + job.Location.ToLower() + " " + job.MainCategory.Name.ToLower() + " " + job.JobType.Type.ToLower().Contains(y)));
}
//run the query and grab a list of baseJobs
List<Job> baseJobs = nquery.ToList<Job>();
//A list of SearchResult object (these object act as a container for job ids and their search values
List<SearchResult> baseResult = new List<SearchResult>();
//from here on Jim's algorithm comes to play where it assigns points depending on where the search term is located and added to a list of id/value pair list
foreach (var a in baseJobs)
{
foreach (var item in search)
{
var itemLower = item.ToLower();
if (a.Company.Name.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 5 });
if (a.Title.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 3 });
if (a.FullDescription.ToLower().Contains(itemLower))
baseResult.Add(new SearchResult { ID = a.ID, Value = 2 });
}
}
List<SearchResult> result = baseResult.GroupBy(x => x.ID).Select(p => new SearchResult() { ID = p.First().ID, Value = p.Sum(i => i.Value) }).OrderByDescending(a => a.Value).ToList<SearchResult>();
//the data generated through the id/value pair list are then used to reorder the initial jobs.
var NewQuery = (from id in result join jbs in baseJobs on id.ID equals jbs.ID select jbs).AsQueryable();

How to Group the elements within group in Linq

I have an Employee Collection and i want to filter in such a way that first 2 columns only should be filtered and the third column values should be appended and the final result should be in a single row
The below is my code
List<Employe> Employeecollection = new List<Employe>();
Employeecollection.Add(new Employe("Employee1", "Dept1","Language1"));
Employeecollection.Add(new Employe("Employee2", "Dept2", "Language2"));
Employeecollection.Add(new Employe("Employee3", "Dept3", "Language3"));
Employeecollection.Add(new Employe("Employee3", "Dept3", "Language3"));
Employeecollection.Add(new Employe("Employee1", "Dept1", "Language2"));
foreach (Employe item in Employeecollection.GroupBy(x => new { fName = x.EmpName, lName = x.EmpDept, mName = x.KnownLanguages }).Select(g => g.First()))
{
Console.WriteLine(item.EmpName + " " + item.EmpDept + " " + item.KnownLanguages);
}
but i would like to display the results like below
Employee1 Dept1 Language1,Language2
Employee2 Dept2 Language2
Employee3 Dept3 Language3
Group employees by name and department, then select joined string of known languages for each employee:
from e in Employeecollection
group e by new { e.Name, e.EmpDept } into g
select new {
g.Key.Name,
g.Key.EmpDept,
Languages = String.Join(",", g.Select(x => x.KnownLanguages))
}
If you want results as single row, then do following projection instead:
select String.Format("{0} {1} {2}",
g.Key.Name, g.Key.EmpDept, String.Join(",", g.Select(x => x.KnownLanguages)))
BTW I think its a weird property name KnownLanguages for property which holds single language
You don't want to group on KnownLanguages. It shouldn't be included in your group selector. The group selector should select all of thing that you want to be the same for all items in a group.
You also need to change how you print your results. Get the common values for each of the items in a group through the Key, and the other values through iterating the group itself.
var query = Employeecollection.GroupBy(x => new
{
x.EmpName,
x.EmpDept
};
foreach (var group in query)
{
string languages = string.Join(", ",
group.Select(employee => employee.KnownLanguages)
.Distinct());
Console.WriteLine(group.Key.EmpName + " " + group.Key.EmpDept + " "
+ languages;
}

Categories