-1

According to the docs:

To perform a stable sort, you must implement a custom IComparer interface.

But according to this post.

What the documentation appears to be saying is that the only way you can get a stable sort with ArrayList.Sort is to use an IComparer that somehow 'knows' the indices of the items that are being compared (one would imagine accomplishing this by making it run an initial pass on the collection) and uses that information as a tie-breaker for otherwise equal elements.

So does anyone know how to properly implement an IComparer that somehow 'knows' the indices of the items that are being compared?

123iamking
  • 2,387
  • 4
  • 36
  • 56
  • 1
    Just out of curiousity, why not a generic `List` instead of an `ArrayList`? – vc 74 Apr 18 '20 at 06:17
  • _"does anyone know how to properly implement an IComparer..."_ -- yes. Many people probably do. Probably the easiest would be to create a new collection, of tuples, where one element is the object to be sorted and the other is the object's original index. Then use the index as the tie-breaker, just as described in the existing Stack Overflow answer. Have you tried _anything_? What _specifically_ do you need help with? Stack Overflow isn't a "gimme teh code" site. Please make some effort to write the comparer yourself; post a new question with a good [mcve] and specific question if you need to. – Peter Duniho Apr 18 '20 at 06:22
  • @vc74 I want to use List, but List only offer OrderBy(), OrderBy() create a new sorted List instead of sort directly, so I try to find a solution that sort directly. – 123iamking Apr 18 '20 at 09:24
  • @PeterDuniho I find it not necessary to post the code I tried in the question, because the code I tried is similar to the code of the link I already linked ( https://stackoverflow.com/q/5047566/4608491 & msdn docs) – 123iamking Apr 18 '20 at 09:28
  • @123iamking It's easy to use Linq's `OrderBy` and modify the original list (`list = list.OrderBy(...).ToList()`) which makes it a viable option too. – vc 74 Apr 18 '20 at 10:00
  • @vc74 : but `list.OrderBy(...).ToList()` return a new list instead of modify the `list`, `OrderBy(...)` allocated a bunch of memory, `ToList()` allocated a bunch of memory. – 123iamking Apr 18 '20 at 10:07
  • @vc74 : Also, I need to sort part of the list only & Sort has `Sort(Int32 index, Int32 count, IComparer)`. OrderBy has no such option. – 123iamking Apr 18 '20 at 10:21

1 Answers1

0

You could build something like this (borrowing code from this answer by Jon Skeet)

public sealed class IdentityEqualityComparer<T> : IEqualityComparer<T>
    where T : class
{
    public int GetHashCode(T value) => RuntimeHelpers.GetHashCode(value);

    public bool Equals(T left, T right) => left == right;
}

public class StableComparer : IComparer
{
    private readonly Dictionary<object, int> _itemsPosition = 
        new Dictionary<object, int>(new IdentityEqualityComparer<object>());

    private readonly IComparer _baseComparer;

    public StableComparer(IEnumerable collection, IComparer baseComparer)
    {
        _baseComparer = baseComparer;

        int index = 0;
        foreach (object item in collection)
        {
            _itemsPosition.Add(item, index);
            index++;
        }
    }

    public int Compare(object x, object y)
    {
        int baseResult = _baseComparer.Compare(x, y);
        if (baseResult != 0)
        {
            return baseResult;
        }

        int xIndex = _itemsPosition[x];
        int yIndex = _itemsPosition[y];

        return xIndex.CompareTo(yIndex);
    }
}

(I added the equality comparer part to make sure reference equality is used as suggested by @PeterDuniho).

This code can be used like this:

class Item
{
    public int Id { get; }
    public string Description { get; }

    public Item(int id, string description)
    {
        Id = id;
        Description = description;
    }
}

class ItemComparer : IComparer
{
    public int Compare(object x, object y) => ((Item)x).Id.CompareTo(((Item)y).Id);
}

and

class Program
{
    static void Main(string[] args)
    {
        var items = new ArrayList()
        {
            new Item(1, "Item 0 (1)"),
            new Item(3, "Item 1 (3)"),
            new Item(2, "Item 2 (2)"),
            new Item(3, "Item 3 (3)"),
            new Item(1, "Item 4 (1)"),
            new Item(4, "Item 5 (4)"),
            new Item(1, "Item 6 (1)")
        };

        Console.WriteLine("Not stable");
        SortAndDisplay((ArrayList)items.Clone(), new ItemComparer());

        Console.WriteLine("Stable");
        SortAndDisplay((ArrayList)items.Clone(), 
          new StableComparer(items, new ItemComparer()));
    }

    static void SortAndDisplay(ArrayList items, IComparer comparer)
    {
        items.Sort(comparer);

        foreach (var item in items)
        {
            Console.WriteLine(((Item)item).Description);
        }
    }
}

Note that there were changes on Sort in framework 4.5 which can change the ordering of 'equal' items. If I run this code in Framework 4, I get:

Not stable
Item 6 (1)
Item 4 (1)
Item 0 (1)
Item 2 (2)
Item 3 (3)
Item 1 (3)
Item 5 (4)
Stable
Item 0 (1)
Item 4 (1)
Item 6 (1)
Item 2 (2)
Item 1 (3)
Item 3 (3)
Item 5 (4)

I tried to post this code to .net Fiddle but it does not allow using framework 4. You could use Linq's OrderBy too which is stable.

vc 74
  • 37,131
  • 7
  • 73
  • 89
  • You should try testing your code on any input that actually has duplicate elements in it. – Peter Duniho Apr 18 '20 at 06:39
  • @PeterDuniho Would you mind giving more details on where there is an error? – vc 74 Apr 18 '20 at 07:24
  • Sorry, I should have been more specific. Because you've chosen to use a dictionary to maintain your map, your code will work only if the items being sorted do not themselves implement `IEquatable` and do not override `GetHashCode()` and `Equals()`. Many types, even those one would want a custom comparer for, do. (Also, seven elements is not nearly enough to demonstrate/prove stable vs not stable...that's few enough to get a seemingly stable result out of a not-stable sort, though that's not the issue here.) – Peter Duniho Apr 18 '20 at 07:41
  • OK thanks, I've added code to make sure identity equality occurs. – vc 74 Apr 18 '20 at 08:11
  • I have tested it. Interestingly, If the .Net Target framework is 4.0 (or higher), then "Not stable" and "Stable" is the same - both Stable. "Not stable" only really not stable if I set the Target framework to 3.5 or 3. – 123iamking Apr 18 '20 at 09:23
  • 1
    @123iamking Yes, I got the same results on 4.5. This is probably due to the change of algorithm from quicksort to introspective sort. Note that introspective sort is not stable either but seems to give stable results with this particular set. – vc 74 Apr 18 '20 at 09:54
  • 1
    @123iamking Last note, Linq's `OrderBy` is stable and is probably a better option. I hadn't used `ArrayList` for a while and the amount of casting in the above code is pain to the eye ;) – vc 74 Apr 18 '20 at 09:57