LINQ query that combines grouping and sorting

I'm relatively new to LINQ and am currently working on a query that combines grouping and sorting. I am going to start with an example here. Basically, I have an arbitrary sequence of numbers represented as strings:

 List<string> sNumbers = new List<string> {"34521", "38450", "138477", "38451", "28384", "13841", "12345"} 

I need to find all sNumbers in this list that contain a search pattern (say "384") then return the filtered sequence so that sNumbers starting with a search pattern ("384") are first sorted and then the rest of sNumbers containing where Any search pattern. So it will be (pay attention also to alphabetical sorting in groups):

 {"38450", "38451", "13841", "28384", "138477"} 

Here's how I started:

 outputlist = (from n in sNumbers where n.Contains(searchPattern select n).ToList(); 

So now we have the whole number containing the search pattern. And here I am stuck. I know that at this moment I need to "group" the results in two sequences. One that starts with a search pattern and others that don't. Then apply secondary sorting in each group alphabetically. How to write a query that combines all this?

+5
source share
7 answers

This seems pretty straight forward if I don't understand something:

 List<string> outputlist = sNumbers .Where(n => n.Contains("384")) .OrderBy(n => int.Parse(n)) .OrderByDescending(n => n.StartsWith("384")) .ToList(); 

I get this:

outputlist

+2
source

I think you do not need grouping or splitting lists to get the desired result, so instead of the answer about combining and grouping, I will post what I would do to get the desired result:

 sNumbers.Where(x=>x.Contains(pattern)) .OrderByDescending(x => x.StartsWith(pattern)) // first criteria .ThenBy(x=>Convert.ToInt32(x)) //this do the trick instead of GroupBy .ToList(); 
+3
source

Here's an optimized version that requires only one LINQ statement:

 string match = "384"; List<string> sNumbers = new List<string> {"34521", "38450", "138477", "38451", "28384", "13841", "12345"}; // That all it is var result = (from x in sNumbers group x by new { Start = x.StartsWith(match), Contain = x.Contains(match)} into g where g.Key.Start || g.Key.Contain orderby !g.Key.Start select g.OrderBy(Convert.ToInt32)).SelectMany(x => x); result.ToList().ForEach(x => Console.Write(x + " ")); 

Steps:

1.) Group g based on StartsWith and Contains

2.) Just select the groups that contain the match.

3.) Order the reverse key StartsWith (so that StartsWith = true appears before StartsWith = false)

4.) Select a sorted list of items in both groups

5.) Make flatMap (SelectMany) on both lists to get one final list of results


Here's a non-optimized version:

 string match = "384"; List<string> sNumbers = new List<string> {"34521", "38450", "138477", "38451", "28384", "13841", "12345"}; var matching = from x in sNumbers where x.StartsWith(match) orderby Convert.ToInt32(x) select x; var nonMatching = from x in sNumbers where !x.StartsWith(match) && x.Contains(match) orderby Convert.ToInt32(x) select x; var result = matching.Concat(nonMatching); result.ToList().ForEach(x => Console.Write(x + " ")); 
+2
source
 var result = sNumbers .Where(e => e.StartsWith("384")) .OrderBy(e => Int32.Parse(e)) .Union(sNumbers .Where(e => e.Contains("384")) .OrderBy(e => Int32.Parse(e))); 
+2
source

Linq has an OrderBy method that allows you to give a custom class to determine how things should be sorted. See here: https://msdn.microsoft.com/en-us/library/bb549422(v=vs.100).aspx

Then you can write your IComparer class, which takes a value in the constructor, and then the Compare method, which prefers values ​​starting with that value.

Something like this might be:

 public class CompareStringsWithPreference : IComparer<string> { private _valueToPrefer; public CompareStringsWithPreference(string valueToPrefer) { _valueToPrefer = valueToPrefer; } public int Compare(string s1, string s2) { if ((s1.StartsWith(_valueToPrefer) && s2.StartsWith(_valueToPrefer)) || (!s1.StartsWith(_valueToPrefer) && !s2.StartsWith(_valueToPrefer))) return string.Compare(s1, s2, true); if (s1.StartsWith(_valueToPrefer)) return -1; if (s2.StartsWith(_valueToPrefer)) return 1; } } 

Then use it as follows:

 outputlist = (from n in sNumbers where n.Contains(searchPattern) select n).OrderBy(n, new CompareStringsWithPreference(searchPattern))ToList(); 
0
source

You can create a list with strings starting with the variable searchPattern and the other with searchPattern , but not starting with (to avoid repeating elements in both lists):

 string searchPattern = "384"; List<string> sNumbers = new List<string> { "34521", "38450", "138477", "38451", "28384", "13841", "12345" }; var list1 = sNumbers.Where(s => s.StartsWith(searchPattern)).OrderBy(s => s).ToList(); var list2 = sNumbers.Where(s => !s.StartsWith(searchPattern) && s.Contains(searchPattern)).OrderBy(s => s).ToList(); var outputList = new List<string>(); outputList.AddRange(list1); outputList.AddRange(list2); 
0
source

Sorry, guys, after reading the answers, I understand that I was mistaken in my question. The correct answer would be as follows: (sorting by "starts with" first, and then alphabetically (not numerically)

// output: {"38450", "38451", "13841", "138477", "28384"}

I was able to achieve this with the following query:

 string searchPattern = "384"; List<string> result = sNumbers .Where(n => n.Contains(searchpattern)) .OrderBy(s => !s.StartsWith(searchpattern)) .ThenBy(s => s) .ToList(); 

thanks

0
source

Source: https://habr.com/ru/post/1242921/


All Articles