4

Assuming I have the following list:

IList<string> list = new List<string>();
list.Add("Mouse");
list.Add("Dinner");
list.Add("House");
list.Add("Out");
list.Add("Phone");
list.Add("Hat");
list.Add("Ounce");

Using LINQ how would I select the words containing "ou" and sort the selection such that the words beginning with "ou" are listed at the start and then the words containing but not starting with "ou" are subsequently listed. The list I'm trying to create would be:

Ounce
Out
House
Mouse

I came up with the following but it is not working:

list.Where(x => x.Contains("ou"))
    .OrderBy(x => x.StartsWith("ou"))
    .Select(x => x);
Peadar Doyle
  • 1,064
  • 2
  • 10
  • 26
  • 1
    "it is not working": Can you be more precise? What happens? Where are you assigning the result? Or did you think that `OrderBy` mutates the list? – Mark Byers May 08 '12 at 14:22
  • @EVERYONE Why the hell are you all doing OrderByDescending? Descending is such a long word!!! Just add `!` before it all and it will be reversed! – SimpleVar May 08 '12 at 14:30
  • When I said not working I should have specified nothing was happening. It turns out this was due to case sensitivity which I should have copped but I was fixating on the StartsWith() method I have never used before. – Peadar Doyle May 08 '12 at 14:47
  • 1
    @YoryeNathan - Why should I order by the opposite of what im trying to do just because it means typing a few extra keys? That's nuts. – Jamiec May 08 '12 at 15:06
  • @Jamiec Ordering by the opposite is just like Opposite reordering. You just reorder the words of the opposite sentence and the meaning becomes opposite with a different order but the order remains. You get it? (hah). My point is - it isn't nuts - it's just a different view. – SimpleVar May 08 '12 at 15:11
  • @YoryeNathan - If you were writing SQL, and you wanted to order by a `BIT` field would you write `ORDER BY myBit DESC` or would you write `ORDER BY CASE WHEN myBit=1 THEN 0 ELSE 1 END`? Do you see my point? You dont purposly order by the inverse of what you want in SQL, why would you do it in LINQ? – Jamiec May 08 '12 at 15:13
  • Of course I would do `DESC`, because it is shorter and more readable. Just as `!`. I purposely order by what will be better at the given case. – SimpleVar May 08 '12 at 15:16
  • @YoryeNathan - OrderByDescending is so much more readable than !OrderBy and by all means, why should people avoid it if it's already implemented - are you scared of long words or what – Joanna Derks May 08 '12 at 15:27
  • @Joanna Yes, I'm Hippopotomonstrosesquipedaliophobic – SimpleVar May 08 '12 at 15:58

8 Answers8

5

You're getting a case-sensitive comparison, and also you need OrderByDescending(). A quick and dirty way to achieve the case-insensitivity is ToLowerInvariant():

var result = list.Where(x => x.ToLowerInvariant().Contains("ou"))
                    .OrderByDescending(x => x.ToLowerInvariant().StartsWith("ou"))
                    .Select(x => x);

Live example: http://rextester.com/GUR97180

This previous answer shows the correct way to do a case insensitive comparison (ie, dont use my example above, its bad)

Community
  • 1
  • 1
Jamiec
  • 133,658
  • 13
  • 134
  • 193
2

Your first mistake is not comparing strings in a case-insensitive way; "Out" and "Ounce" have capital Os and would not return "true" when you use Contains("ou"). The solution is to use ToLower() when checking letters.

list.Where(x => x.ToLower().Contains("ou"))
    .OrderByDescending(x => x.ToLower.StartsWith("ou")) //true is greater than false.
    .Select(x => x);
KeithS
  • 70,210
  • 21
  • 112
  • 164
1

Three problems:

  • You need to assign the result to something, otherwise it is simply discarded.
  • You need to use OrderByDescending because true sorts after false if you use OrderBy.
  • You need to use a case-insensitive compare.

Try this:

var needle = "ou";
var stringComparison = StringComparison.OrdinalIgnoreCase;

var query =
    from word in list
    let index = word.IndexOf(needle, stringComparison)
    where index != -1
    orderby index
    select word;
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
0
list = list.Where(x => x.ToLower().Contains("ou"))
           .OrderBy(x => !x.ToLower().StartsWith("ou")).ToList();

Or by using the methods of List (changing it from IList to List):

list.RemoveAll(x => !x.ToLower().Contains("ou"));
list.Sort((s1, s2) => -1 * 1.ToLower().StartsWith("ou")
                            .CompareTo(s2.ToLower().StartsWith("ou")));
SimpleVar
  • 14,044
  • 4
  • 38
  • 60
0

This will append an empty space to the beginning of words that start with "OU".

var result = list.Where(x => x.ToLowerInvariant().Contains("ou"))
                 .OrderBy(x => x.ToLowerInvariant()
                                .StartsWith("ou") ? " " + x : x.Trim());
Joe
  • 80,724
  • 18
  • 127
  • 145
0

I think this is what you're looking for:

list = list.Where(x => x.IndexOf("ou", StringComparison.OrdinalIgnoreCase) >= 0)
            .OrderByDescending(x => x.StartsWith("ou", StringComparison.OrdinalIgnoreCase))
            .ThenBy(x => x)
            .ToList();

Note that instead of converting the strings ToLower (or upper), I use a StringComparison enum (currently OrdinalIgnoreCase). This ensures that it works consistently as expected in any culture. Choose the right case-insensitive comparison depending on your circumstance.

If you prefer the LINQ query syntax that's:

list = (from x in list
        where x.IndexOf("ou", StringComparison.OrdinalIgnoreCase) >= 0
        orderby x.StartsWith("ou", StringComparison.OrdinalIgnoreCase) descending, x
        select x).ToList();
Tim S.
  • 55,448
  • 7
  • 96
  • 122
0

You can simply call the list.Sort method by passing in an instance of a custom comparer as follows:

public class MyCustomStringComparer: IComparer<string>  
{  
    public int Compare(Entity x, Entity y)  
    {  
        int result = 0;

        if (x.ToLower().StartsWith("ou") && y.ToLower().StartsWith("ou"))  
            result = x.Compare(y);  
        else if (x.ToLower().StartsWith("ou") && !y.ToLower().StartsWith("ou"))  
            result = -1;  
        else if (!x.ToLower().StartsWith("ou") && y.ToLower().StartsWith("ou"))  
            result = 1;  
        else  
            result = x.Compare(y);  

        return (result);  
    }  
}
Raheel Khan
  • 14,205
  • 13
  • 80
  • 168
0
var bla = "ou";

var list = new List<string>{
            "Mouse",
            "Dinner",
            "House",
            "Out",
            "Phone",
            "Hat",
            "Ounce"};


var groupa = list.GroupBy(x =>x.ToLower().Contains(bla));

groupa.First().ToList().OrderByDescending(x => x.ToLower().StartsWith(bla));
kayz1
  • 7,260
  • 3
  • 53
  • 56