Reputation: 1275
How to order by a list so that the duplicate items appear in the reverse order of their insertion (consider that I am not going to insert any items in the middle by using insert method of list)
Take following example:
public class Program
{
public static void Main(string[] args)
{
List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
list.Add(new KeyValuePair<int, string>(1, "A"));
list.Add(new KeyValuePair<int, string>(3, "F"));
list.Add(new KeyValuePair<int, string>(4, "G"));
list.Add(new KeyValuePair<int, string>(2, "B"));
list.Add(new KeyValuePair<int, string>(2, "C"));
list.Add(new KeyValuePair<int, string>(3, "E"));
list.Add(new KeyValuePair<int, string>(3, "D"));
list=list.OrderBy(a=>a.Key).ToList();
foreach(var item in list)
{
Console.WriteLine(item);
}
}
}
Output:
[1, A]
[2, B]
[2, C]
[3, F]
[3, E]
[3, D]
[4, G]
I want the order of duplicates in reverse order of insertion, e. g. F, E, D should appear as D, E, F.
I though orderByDescending would do the trick but that would also maintain the order of insertion.
Upvotes: 0
Views: 113
Reputation: 1289
Try this:
list=list.OrderBy(a => a.Key).ThenBy(a => a.Value).ToList();
I believe this will give the results you are looking for. But not the logical execution you have explained.
In order to order them by insertion, you need to track that info some how. In your current code your order by knows nothing about when an item was added to the list, it only knows Key and Value.
You may do this with a custom object rather than a Key-Value Pair.
An object made up of a
List<kvp<int, kvp<int, string>>>
with it's own Add() method would work. Use the outer int as your insert order. Use your overwrite to Add() to grab the the current length of the list and increment your insert order before adding the new object, thus maintaining the correct sequence regardless of the index it is inserted at.
Upvotes: 1
Reputation: 1271
I haven't double-checked the syntax yet, but ...
list
.Zip(Enumerable.Range(0, list.Count), (item, insertOrder) => new { Item = item, InsertOrder = insertOrder })
.OrderBy(x => x.Item.Key)
.ThenBy(x => x.InsertOrder)
.Select(x => x.Item)
.ToList();
What this does is create an Enumerable.Range
to record the insertion order of each item, then it's a straightforward case of OrderBy
and ThenBy
to correctly order the items, then Select
to remove the InsertOrder
again.
As user7396598 quite correctly points out, the IEnumerable
order of the list is not necessarily the order they were inserted. However if you're using this in a known context where the items have been inserted without using specific indices, it should suffice. If the context is not known, you'll need to expand the data you're capturing to include the insert order, possibly by creating your own collection class that records the insertion order and doesn't permit index insertion.
Hope this helps
Upvotes: 1
Reputation: 3017
OrderBy is a stable sort, this means that Items cannot be differentiated remain in the same order. https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.orderby?view=netframework-4.7.2
This means that duplicate items will be remain in the order they were added so we can do this
list = list
.GroupBy(kvp => kvp.Key)
.OrderBy(kvp => kvp.Key)
.SelectMany(g => g.Reverse())
.ToList();
So we group by the keys, unique items will have 1 item in the group, duplicates will have more than 1 item. The items in the groups will be in the same order as the source Enumerable, See here
So we can order by the keys of the groups and for each group we select its items in reverse order.
Also this works too
list = list
.Select((x, i) => new { Item = x, Index = i })
.OrderBy(_ => _.Item.Key)
.ThenByDescending(_ => _.Index)
.Select(_ => _.Item)
.ToList();
This makes use of the Select Override that provides you the index of the item in the collection as well as the item
Upvotes: 4