Search code examples
c#listlinqsortingcustom-sort

Sort only specific items in a list of items


Consider the following code:

// Apple
internal record class Apple(int Id);

// Orange
internal record class Orange(int Id);

// Input
// Note how Apple(1) is followed by Apple(3) and not by Apple(2)
List<object> list = new List<object>();
list.Add(new Apple(1));
list.Add(new Apple(3));
list.Add(new Orange(1));
list.Add(new Apple(2));
list.Add(new Orange(3));
list.Add(new Orange(2));    

I want to sort all Apples in the list, however indexes that are currently used by Oranges shall also be used by Oranges after sorting. Also, I don't want to sort Oranges at all.

My expected output is therefore a list like this:

List<object> expected = new List<object>();
list.Add(new Apple(1));
list.Add(new Apple(2)); // changed
list.Add(new Orange(1));
list.Add(new Apple(3)); // changed
list.Add(new Orange(3));
list.Add(new Orange(2)); // don't sort oranges, so this remains after Orange(3)

I tried it with

internal class MyComparer : IComparer<object>
{
    public int Compare(object? x, object? y)
    {
        if(x is Apple a1 && y is Apple a2)
        {
            Console.WriteLine($"Compare {a1.Id} and {a2.Id}");
            return a1.Id.CompareTo(a2.Id);
        }

        return 0; //don't compare apples and oranges or oranges and oranges
    }
}

// use comparer
var sorted = list.OrderBy(key => key, new MyComparer()).ToList();

but that does not change any order in the list, because it only compares an Apple directly following an Apple (console output is only "Compare 3 and 1").

Any elegant way to do such special sort? Note that this is a reduced example and I have reasons not to have Apples and Oranges in different lists.


Solution

  • A slightly more efficient version of @JohnWu's answer. We remove the Queue and just use the IEnumerator directly.

    static IEnumerable<object> SortByType<T, TKey>(this List<object> source, Func<T, TKey> keySelector, IComparer<TKey> comparer = null)
    {
        using var toSort = source.OfType<T>().OrderBy(keySelector, comparer).GetEnumerator();
    
        foreach (var item in source)
        {
            if (item is Apple)
            {
                toSort.MoveNext();
                yield return toSort.Current;
            }
            else
            {
                yield return item;
            }
        }
    }
    

    dotnetfiddle