views:

62

answers:

3

To take it down to brass tacks, I have a List containing 100 various records. A Car has a Year, Make and Model.

I need to be able to :

  1. order this according to Year, but I need to:

  2. have all Cars that are Ford Explorers appear as a "group" at the end of my List (also ordered according to Year)

I'm not sure if I'll have to create 2 separate List and then merge them... seems like there should be a better way to do this than having to create multiple objects.

Your help is appreciated and welcomed!

Many Thanks, paul

Example:



2001 Hummer H1
2002 Ford Focus
2008 BMW 325i
2008 Ford Escape
2009 BMW 328i
2009 Mitsubishi Galant
2003 Ford Explorer
2004 Ford Explorer
2008 Ford Explorer
2009 Ford Explorer


A: 
cars.OrderBy(c => 
        (c.Make == "Ford" && c.Model == "Explorer")
        ? c.Year + 10000
        : c.Year)
    .ToList()

thinking about my comment below, if it were LINQ to sql, I would probably write it with a union:

cars.Where(c => c.Make != "Ford" || c.Model != "Explorer")
    .OrderBy(c => c.Year)
    .Union(cars.Where(c => c.Make == "Ford" && c.Model == "Explorer")
               .OrderBy(c => c.Year))
Bill Barry
Fortunately LINQ provides nicer ways of ordering by a compound key :)
Jon Skeet
Yes, but that results in a 2 pass sort, whereas this is only doing it once. If it were LINQ to sql or some other form where the expression is evaluated before executing, than the prettier form would be better(much more likely to be converted when executed); straight up LINQ with POCOs, this solution will sort in one pass and so should get better as the dataset grows.
Bill Barry
@Bill: I don't believe it *does* require a two pass sort, actually. The sort is just performed using a comparison which takes both bits into account, as far as I'm aware. Certainly that's how the way my demo LINQ to Objects implementation works :) Even if this code *was* somewhat faster, I'd definitely stick to the more readable code until I'd actually shown that it had performance issues.
Jon Skeet
+2  A: 

This should do it:

var query = list.OrderBy(car => car.Make == "Ford" && car.Model == "Explorer")
                .ThenBy(car => car.Year);

(Obviously adjust the test for Ford Explorer-ness.)

Basically think of Ford Explorer-ness as a Boolean property, where false is ordered before true. You can express this as a query expression too, of course:

var query = from car in list
            orderby car.Make == "Ford" && car.Model == "Explorer", car.Year
            select car;

(I tend to prefer dot notation in this case.)

Jon Skeet
car does not have Type. only Make and Model... but we're close
paul
this new scenario would work as well. However, using the "let" syntax works best in my real-world application. Thanks for your help and healthy discussion!
paul
+2  A: 

If you truly only want the Ford Explorers to be grouped, you can do the following:

var groupedByFordExplorer = from c in cars
                    let isFordExplorer = c.Make == "Ford" && c.Model == "Explorer"
                    orderby isFordExplorer, c.Year
                    select c;

What the above query does is create an inline value , isFordExplorer, and assigns it a boolean value using the let keyword indicating whether the car is a Ford Explorer. That value can then be sorted by, along with the Year. The following is a working program that should demonstrate the concept:

class Program
{
    static void Main(string[] args)
    {
        var cars = new List<Car>
        {
            new Car { Year = 2009, Make = "Ford", Model = "Explorer" },
            new Car { Year = 2001, Make = "Hummer", Model = "H1" },             
            new Car { Year = 2002, Make = "Ford", Model = "Focus" },
            new Car { Year = 2008, Make = "BMW", Model = "325i" },
            new Car { Year = 2008, Make = "Ford", Model = "Explorer" },             
            new Car { Year = 2008, Make = "Ford", Model = "Escape" },               
            new Car { Year = 2009, Make = "Mitsubishi", Model = "Galant" },
            new Car { Year = 2004, Make = "Ford", Model = "Explorer" },
            new Car { Year = 2009, Make = "BMW", Model = "329i" },
            new Car { Year = 2003, Make = "Ford", Model = "Explorer" }              
        };

        var groupedByFordExplorer = from c in cars
                                    let isFordExplorer = c.Make == "Ford" && c.Model == "Explorer"
                                    orderby isFordExplorer, c.Year
                                    select c;

        foreach (var car in groupedByFordExplorer)
        {
            Console.WriteLine("{0} {1} {2}", car.Year, car.Make, car.Model);
        }

        Console.ReadLine();
    }
}

class Car
{
    public int Year { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
}
jrista
Note that although you may want to keep this "inline" variable for readability, it isn't actually necessary. Personally I'm not sure that it even adds to readability, but beauty is in the eye of the beholder. I still find the dot notation form the simplest :)
Jon Skeet
This worked like a charm. I often forget about the let command.... perfect for iterating over calculated data. Many thanks!
paul
@Jon Skeet: While I do respect your opinion, I think the power of the `let` term should not be underestimated. I personally like the dot notation for most simple things, however the LINQ query syntax offers a lot of power and control via `let` that the dot notation simply can't approach. I think this simple example is a good demonstration of this useful tool. I think it should also be noted that this is a ***tool*** for solving problems...how beautiful the code is doesn't mean nearly as much to me as how capable it is. However, I still prefer the readability of query syntax over dot syntax.
jrista
As I say, beauty (which I deem to be almost synonymous with readability) is in the eye of the beholder. In this case, the query expression has a lot of extra cruft whereas the dot notation expresses only what's interesting. For example, there's a "select" required in the query expression which isn't expressing anything you're really interested in, if you view it just as a sequence of cars from the start. I think `let` is useful when you're using the same expression more than once, or when it's complicated and giving a concept a name helps for clarity - but in this case I wouldn't use it.
Jon Skeet
@Jon Skeet: I have to disagree. I think the use of .OrderBy in your example is actually a little confusing, since it is not obvious that you are sorting by a boolean, rather than a property of the car object. Using `let` as I did makes it very clear that you are sorting by a boolean. I do agree that the select keyword is rather "useless", but it does not particularly reduce the clarity of the statement in any way. It may not be "pretty", but it is readable, understandable, and clear as to the intent.
jrista
@jrista: But I *am* sorting by a property of the car object, effectively - its "Ford Explorer-ness". It's just a compound property computed from more than one "real" property. As I say, it's subjective. I find my version more readable, understandable and clear as to the intent - and you find yours to be more so. That's fine. I'm in no way saying that `let` or query expressions are useless, either - I just wouldn't happen to use them here.
Jon Skeet