3

I've found that when I implement CompareTo(..) for one of my classes, the ordering is inconsistent between machines. When 2 objects come out equal, they don't always sort in the same order. I'd assume some single threaded iterative approach would be use to sort so I would have assumed a consistant ordering.

For Given the following class..

class Property : ICompareable<Property> 
{
    public int Value;
    public int Name;

    public int CompareTo(Property other)
    {
        if(this.Value > other.Value)
            return 1;
        if(this.Value < other.Value)
            return -1;
        return 0;
    }
}

And given the following objects

{ 
   List.add(new Property( name="apple", value = 1) );
   List.add(new Property( name="grape", value = 2) );
   List.add(new Property( name="banana", value = 1) );
}

When I execute

List.sort();

Then when stepping through the list using indexes, the order of banana and apple changes depending on what PC i am executing the code on. Why is this?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
bgura
  • 860
  • 15
  • 33
  • 4
    answer here http://stackoverflow.com/a/800026/841176 – Konstantin Nov 15 '13 at 20:13
  • 3
    Since you *only* compare off the integer (value) axis, there is *no guarantee* about the ordering along the other (name) axis. – user2864740 Nov 15 '13 at 20:13
  • Please put a Console.WriteLine(this.Value + "-" + other.Value) at the start of your CompareTo() method. – fejesjoco Nov 15 '13 at 20:13
  • Since it is `IComparable`, not `ISortYourselfNeatlyBetweenYourPeersWhenStoredInAContainer`. The fact that something is comparable does not imply anything about the traits of the surrounding container. Don't assume anything that's not set in concrete. – JensG Nov 15 '13 at 20:18
  • By the way, your method is equivalent to `return this.Value.CompareTo(other.Value);` – Tim S. Nov 15 '13 at 20:32
  • In this example you could return `Name.CompareTo(other.Name)` instead of `0` to sort alphabetically when two objects have the same `Value`. – JosephHirn Nov 15 '13 at 20:52
  • Thanks kostyan, other question you provided is pretty much the same issue I was seeing. This can be marked as a duplicate or removed if one of you SO gurus wants to do that. – bgura Nov 15 '13 at 21:12
  • Quick little tip, your `CompareTo` function could be simplified to `public int CompareTo(Property other) { return this.Value - other.Value; }`. ICompareable only cares about `> 0`, `< 0`, and `== 0` so you are not forced to return `-1`, `1` or `0`. It does this specifically to allow for these kinds of optimizations. – Scott Chamberlain Nov 17 '13 at 05:38

3 Answers3

7

List.Sort doesn't provide a stable sort, as per MSDN:

This implementation performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.

If you need a stable sort consider using OrderBy from LINQ, which is a stable sort.

Servy
  • 202,030
  • 26
  • 332
  • 449
3

Since the CompareTo function only compares off the integer (Value) axis, there is no guarantee about the ordering along the other (Name) axis.


Even an unstable sort (e.g. List.Sort) with the same implementation and the same input sequence should result in the same sorted output. Thus, given an incomplete compare function as per above, the results may differ across environments (PCs) because of the following reasons:

  1. The sort implementation uses a different or environment-sensitive algorithm, or;
  2. The initial order of the items differs across environments.

A stable sort (e.g. OrderBy in LINQ to Objects) will produce consistent output along different environments if and only if the input sequence is in the same order in each environment: a stable sort is free of issue #1 but still affected by #2.

(While the input order is guaranteed given the posted sample code, it might not be in a "real" situation, such as if the items initially came from an unordered source.)


I would likely update the compare function such that it defines a total ordering over all item attributes - then the results will be consistent for all input sequences over all environments and sorting methods.

An example of such an implementation:

public int CompareTo(Property other)
{
    var valueCmp = this.Value.CompareTo(other.Value);
    if (valueCmp == 0) {
        // Same Value, order by Name
        return this.Name.CompareTo(other.Name);
    } else {
        // Different Values
        return valueCmp;
    }
}
Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
1

Some sorting methods guarantee that items which compare equal will appear in the same sequence relative to each other after sorting as they did before sorting, but such a guarantee generally comes with a cost. Very few sorting algorithms can move an item directly from its original position to its final position in a single step (and those which do so are generally slow). Instead, sorting algorithms move items around a few times, and generally don't keep track of where items came from. In the Anthony Hoare's "quicksort" algorithm, a typical step will pick some value and move things around so that everything which compares less than that value will end up in array elements which precede all the items which are greater than that value. If one ignores array elements that precisely equal the "pivot" value, there are four categories of items:

  • Items which are less than the pivot value, and started in parts of the array that precede the pivot value's final location.
  • Items which are less than the pivot value, but started in parts of the array that follow the pivot value's final location.
  • Items which are greater than the pivot value, but started in parts of the array that precede the pivot value's final location.
  • Items which are greater than the pivot value, and started in parts of the array that follow the pivot value's final location.

When moving elements to separate those above and below the pivot value, items in the first and fourth categories (i.e. those which started and ended on the same side of the pivot value's final location) generally remain in their original order, but are intermixed with those which started on the other side, which get copied in reverse order. Because the values from each side get intermixed, there's no way the system can restore the sequence of items which crossed from one side of the pivot to the other unless it keeps track of which items those are. Such tracking would require extra time and memory, which Sort opts not to spend.

If two runs of Sort on a given list always perform the same sequence of steps, then the final ordering should match. The documentation for Sort, however, does not specify the exact sequence of steps it performs, and it would be possible that one machine has a version of .NET which does things slightly differently from the other. The essential thing to note is that if you need a "stable" sort [one in which the relative sequence of equal items after sorting is guaranteed to match the sequence before sorting], you will either need to use a sorting method which guarantees such behavior, or else add an additional field to your items which could be preloaded with their initial position in the array, and make your CompareTo function check that field when items are otherwise equal.

supercat
  • 77,689
  • 9
  • 166
  • 211