r3plica
r3plica

Reputation: 13367

Order list of objects by a list of ids

So, I have a list of objects (let's say there are 20) and they have an id. Then I have another list (which is ordered correctly). I had this linq to sort the object list by the id list:

var outcomeIds = outcomeRequestModels
  .OrderByDescending(m => m.Score)
  .Select(m => m.Id)
  .ToList();

groupResponseModel.Outcomes = groupOutcomes
  .OrderBy(m => outcomeIds.IndexOf(m.Id))
  .ToList();

Now, this "would" work, but the problem is the outcomeIds only has a selection of ids in it. I would have thought that indexOf would return -1 for any id that was not found and it would be put under the matched ids. Instead they appear first in the list. How can I modify my code to get the matching ids at the top and the rest at the bottom. I can't do a reverse, because it would mean that the order of the matching ids would be in reverse too.

Upvotes: 1

Views: 1869

Answers (4)

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186688

Why not using mapping (say, id == 5 corresponds to 0, id = 123 to 1 etc.) with a help of dictionary? It will be efficient in case of long lists:

var order = outcomeRequestModels
  .OrderByDescending(m => m.Score)
  .Select((m, index) => new {
     id = m.id,
     index = index }) 
  .ToDictionary(item => item.id,      // id
                item => item.index);  // corresponding index

Now let's sort the 2nd list:

groupResponseModel.Outcomes = groupOutcomes
  .OrderBy(m => order.TryGetValue(m.Id, out var order) 
     ? order          // if we have corresponding index, use it   
     : int.MaxValue)  // otherwise, put the item at the bottom
  .ToList();    

Upvotes: 0

Tanmay
Tanmay

Reputation: 1165

var outcomeIds = outcomeRequestModels
  .OrderByDescending(m => m.Score)
  .Select(m => m.Id)
  .ToList();

groupResponseModel.Outcomes = groupOutcomes
  .OrderBy(m => outcomeIds.IndexOf(m.Id) != -1
     ? outcomeIds.IndexOf(m.Id) 
     : outcomeIds.Max())
  .ToList();

Upvotes: 2

Wai Ha Lee
Wai Ha Lee

Reputation: 8805

Sounds like you want to order by the result of IndexOf, but to have the -1 values go to the end instead of the start. In that case, you could just process the value of the IndexOf to, say, int.MaxValue so it'll go at the end.

I've tidied up your code a bit to make it more readable - only the OrderBy is different to your original code.

var outcomeIds = outcomeRequestModels
    .OrderByDescending(m => m.Score)
    .Select(m => m.Id)
    .ToList();

groupResponseModel.Outcomes = groupOutcomes
    .Select(m => Tuple.Create(m, outcomeIds.IndexOf(m.Id))
    .OrderBy(m => outcomeIds.IndexOf(m.Id) == -1 ? int.MaxValue : outcomeIds.IndexOf(m.Id))
    .ToList();

Or, if you don't want to call IndexOf multiple times, you could extract the conditional statement into a method:

var outcomeIds = outcomeRequestModels
    .OrderByDescending(m => m.Score)
    .Select(m => m.Id)
    .ToList();

groupResponseModel.Outcomes = groupOutcomes
    .Select(m => Tuple.Create(m, outcomeIds.IndexOf(m.Id))
    .OrderBy(m => orderByKeySelector(outcomeIds(m.Id)))
    .ToList();

where orderByKeySelector is

private static int orderByKeySelector<T>(List<T> source, T value)
{
    var indexOfValue = source.IndexOf(value);
    return indexOfValue == -1 ? int.MaxValue : indexOfValue;
}

Upvotes: 2

Mithgroth
Mithgroth

Reputation: 1212

I prefer keeping it simple:

var outcomeList;
var unorderedList;

//check all elements of the ordered list in order
foreach(var item in orderedList) 
{
    //if your unordered list has this item
    if(unorderedList.Any(item))
    {
        //add this item to the final list
        outcomeList.Add(item);
        //and remove it from unordered
        unorderedList.Remove(item);
    }
}

//at this point, you added all your matching entities in order, the rest is the remainder:

outcomeList.AddRange(unorderedList);

You can even turn this into an extension method for reusability.

Upvotes: 0

Related Questions