Share via


Sorting one list based on values in another

Question

Tuesday, February 21, 2012 4:45 PM

Hi,

I have 2 lists of an object we'll call a "Thing". The first list contains all the "Things", the 2nd list is a subset that contains the selected "Things". A "Thing" has a unique name but no inherent value to order by. However, the list of selected "Things" needs to be ordered by the user. This order must be able to be edited and persisted. The current implementation does not take order into account. It uses 2 List<Thing> objects - one for all Thing's and another for selected Thing's. It grabs both lists (from different sources) and sets the "IsSelected" flag on the Thing's in the all Thing's list based on whether or not it exists in the selected Thing's list. What is the best way to modify this to take order into account? Can it still be done with List<Thing>'s or should I rip out those structures (a pain in the rear but I'll do it if I need to) and use a different structure? The one thing I can't change is that both lists come from different sources - hence the reason for 2 separate data structures.

Any suggestions would be appreciated. Any questions or clarifications needed let me know.

Thanks, Dennis

All replies (8)

Wednesday, February 22, 2012 4:54 PM âś…Answered

Hi Louis,

That might work if I were caching both lists - I could use that statement every time I needed to grab the selected ones in order. But I really only want to cache just the one list, keep an "IsSelected" flag for grabbing the ones I need, and sort the selected ones.

What I ended up doing was changing GetSelectedThings() to return a Dictionary<int, Thing> instead of a List. It was a pain, but not as bad as I thought it would be. That allowed me to collate the list and the dictionary using the following Linq statement:

var things = (from at in allThings
                              join st in selectedThings on at.Name equals st.Value.Name into items
                              from item in items.DefaultIfEmpty()
                              orderby item.Key
                              select at).ToList();

This gives me a list of all Things where the unselected Things are first - sorted in whatever order they came in from GetAllThings() - followed by the selected Things in the order that they arrived from GetSelectedThings(). It's not the prettiest solution in the world, but it seems to work. For that reason, I'll mark it as the answer. Thanks for everyone's help.

Dennis


Tuesday, February 21, 2012 5:00 PM

Can you be more exact what this "Thing" and "Thnings" object are? We can help you more.

I would say your "Thing" is a class, in which you can create a new property of a boolean type. So you set the selected one to True, and all others to false. This way you can find out which "Thing" is selected.

As said, for more of a help, tell us what class you you actually use and which are the properties of it.

Mitja


Tuesday, February 21, 2012 6:03 PM

Thanks for your reply Mitja. Yes, Thing is a class and there is an IsSelected property on that class:

public class Thing
{
    public string Name { get; set; }
    public bool IsSelected { get; set; }
.
.
.
}

They aren't actually auto-implemented properties and there are other properties, but this gives you the idea. The problem is, I need to collate the all Thing's list and selected Thing's list from two different sources. Most of the information for the other properties comes from the all Thing's source, but selection and now order (which is what's giving me difficulty) must come from the selected Thing's list.

Thanks, Dennis


Tuesday, February 21, 2012 6:08 PM

Sorry, but I dont really understand what you have (want). As far as i understand, you have 2 Lists, where T is the same class (List<T> = List<Thing>).

Would you like to gather them (two lists to one) and/or compare them?

Mitja


Tuesday, February 21, 2012 6:59 PM

As currently implemented, there are two List<Thing> lists collated into one. Basically, the current code does this:

public List<Thing> GetThings(string source)
{
    List<Thing> allThings = ThingRetriever.GetAllThings(source);
    List<Thing> selectedThings = ThingRetriever.GetSelectedThings();
    
    foreach (Thing t in allThings)
    {
        t.IsSelected = selectedThings.Any(st => st.Name == t.Name);
    }
    return allThings;
}

So essentially, you get returned a list of all Thing's - some of which are marked as IsSelected = true others marked IsSelected = false. However, it does not address the issue of order. The ones marked IsSelected = true (according to the new request) should be in the same order as the list retrieved from ThingRetriever.GetSelectedThings(). Whether those items end up all at the top or all at the bottom or scattered thrughout the list doesn't really matter much, as I would probably use a LINQ statement to pull out only the selected ones when I need them. The key is that, however they are interspersed with the unselected Thing's throughout the list, the selected Thing's have to be in the right order.

The two lists come from two different sources, that is why there are two lists (I cannot change the data sources). They are cached because it could potentially take a significant amount of time to read the lists from their sources so we only want to do that when necessary. They are merged into one list because it has always been easier to just deal with one list containing both the selected and unselected items and simply filter when we need to. Of course, that was before order mattered.

Sorry, I should have presented this more clearly originally.

Dennis


Tuesday, February 21, 2012 8:44 PM

I see you are using Linq (Any extension method). So just after the foreach lop you can do the orderBy on the list<T>, like:

selectedThings = selectedThings.OrderBy(o => o.Name); //Name is property of class

Mitja


Tuesday, February 21, 2012 9:30 PM

I did think about using the OrderBy clause but the problem is that there is no inherent order-related field. That is, the user needs to be free to order the list in any manner (not necessarily by name or any other property value). I have considered adding a property (e.g. SortOrder) to the current object but that would entail modifying code in the persistence layer. I'd rather not have to touch that if I can avoid it. I know that certain LINQ operations preserve list order, and of course using temp lists can allow you to control your list order. I get a properly ordered list from GetSelectedThings(). If I can use that to build a list in the order I want it, and feed it out to the receiver the consuming code should work. I'm just looking for a good way to do that.

Thanks, Dennis


Wednesday, February 22, 2012 12:55 PM

If I understood correctly, you want to retrieve from allThings all items with the same name as items in selectedThings, and in the same order.

Then what you need is to enumerate selectedThings and, for each item, retrieve the item with the same name from allThings:

selectedThings.Select(st => allThings.First(t => t.Name == st.Name))