0

I need to sort a list of objects and an arbitrary sort property. If there are multiple objects in my list that have the same value for that sort property, then repeatedly sorting the same list will rearrange the members with the same value of sort property. To be clear, each time sort is run, a new list is generated and the order of members is arbitrary and not necessarily the same as it was the last time the list was sorted. Is there a way to avoid this?

Here is a code sample:

List<T> myList; // T is arbitrary and I doesn't implement any common interface
PropertyInfo sortPorperty = some property of T

var sortedList = myList.OrderBy(x => sortProperty.GetValue(x));

Multiple executions of this code will result in different order of the objects.

My initial idea is to also sort by the object itself

var sortedList = myList.OrderBy(x => sortProperty.GetValue(x)).ThenBy(x => x);

But as far as I can tell that will sort by hashcode and that is basically the memory location of an object so it would not be the same between runs. Is there anything else that would work?

Vanquished Wombat
  • 9,075
  • 5
  • 28
  • 67
Eugene
  • 73
  • 5
  • A quick and dirty solution is to associate to every item an increasing index and implement the comparison function in a way that in case of ties it compares the indexes. –  Aug 31 '18 at 16:40
  • You have to find a property that you can use to order. Why not by name? – João Martins Aug 31 '18 at 16:40
  • 3
    I think you are looking for what is called a [stable sort](https://stackoverflow.com/questions/1517793/what-is-stability-in-sorting-algorithms-and-why-is-it-important), right? – Wyck Aug 31 '18 at 16:44
  • @YvesDaoust That wouldn't work because the list isn't maintained between sorts. Each time a new list (with the same members) is generated and sorted – Eugene Aug 31 '18 at 16:45
  • `OrderBy` is a stable sort according to the docs. – Lee Aug 31 '18 at 16:46
  • @Wyck a stable sort will maintain the order of element relative to their original position, but there is no guarantee that their original position is the same each time the list is generated – Eugene Aug 31 '18 at 16:47
  • So you're just looking for some way to order items that are otherwise have identical sort criteria so that the properties that are not part of the sort criteria don't bounce around each time you sort it? You're looking for _some consistent order_ that considers all fields (even those that are not part of the sort criteria) but you just don't care what it is? Is that right? – Wyck Aug 31 '18 at 16:53
  • Are the items serializable? If so, serialize the item to a string, and then add the serialized version of the object as the final sort criteria. – Wyck Aug 31 '18 at 16:56
  • @wyck I think serializing to string would work, thanks. – Eugene Aug 31 '18 at 17:02
  • I believe this premise is incorrect: *"repeatedly sorting the same list will rearrange the members with the same value of sort property"*. Objects with the same value for the sort property will not be rearranged on repeated sorting, but rather will remain in relative order to their original positions in the list. – Rufus L Aug 31 '18 at 17:07
  • We can't tell you how to uniquely identify some unknown object. If the object already has a uniquely identifying value, we can't tell you what it is, if it doesn't, we can't tell you how it may or may not be appropriate to add one, without knowing anything about the object. – Servy Aug 31 '18 at 17:14
  • Can you please provide a small code sample that demonstrates the behavior you're describing? Because given a list of items, reassigning it over and over to the result of `OrderBy(x => sortProperty.GetValue(x))` should not rearrange the order of items with the same value for the sort property. – Rufus L Aug 31 '18 at 17:18
  • Also, what does this comment mean: *"T is arbitrary and I doesn't implement any common interface"*. `T` may be unknown at compile time, but will represent a specific type at runtime, and all the objects in the list will have `T` as a common interface. – Rufus L Aug 31 '18 at 17:22
  • @Eugene: this argument doesn't hold, generating the index has lower complexity than sorting. –  Aug 31 '18 at 18:13
  • OP, If you only know about this one property, how do you know the sorted order is different each time? I'm not saying it isn't. I'm asking how you *know*. – John Wu Aug 31 '18 at 18:31

1 Answers1

2

If the type is serializable then you can use the serialization of the object to serve as the final sort criteria.

Use BinaryFormatter to generate a unique string for the object (which I have called Idem in this example) and use that as the final .ThenBy sorting criteria.

In this example I transformed the binary formatted version of the object to a base64 string (that's performance overhead for sure, and there are other approaches to get the the binary versions to compare nicely, but I'm just illustrating the general approach.)

I think this example has everything you asked for. Arbitrary type with no interface, uses a property as the OrderBy criteria, and does not rely on the initial order of the items to produce the same order of the output on subsequent runs.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;

public static class Extensions
{
    // Returns a string unique(TM) to this object.
    public static string Idem<T>(this T toSerialize)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        var memoryStream = new MemoryStream();
        using (memoryStream)
        {
            formatter.Serialize(memoryStream, toSerialize);
            return Convert.ToBase64String(memoryStream.ToArray());
        }
    }
}

[Serializable()]
public class Person
{
    public Person(string name, string secret)
    {
        this.name = name;
        this.secret = secret;
    }

    private string secret; // some private info
    public string Nickname { get { return name; } } // some property
    public string name; // some public info

    public override string ToString() // a way to see the private info for this demo
    {
        return string.Format("{0} ({1})", name, secret);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // You can rearrange the items in this list and they will always come out in the same order.
        List<Person> myList = new List<Person>() {
            new Person("Bob", "alpha"),
            new Person("Bob", "bravo"),
            new Person("Alice", "delta"),
            new Person("Bob", "echo"),
            new Person("Bob", "golf"),
            new Person("Bob", "foxtrot"),
        };
        PropertyInfo sortProperty = typeof(Person).GetProperty("Nickname");

        Random random = new Random();
        for (int i = 0; i < 3; ++i)
        {
            var randomList = myList.OrderBy(x => random.Next());


            var sortedList = randomList.OrderBy(x => sortProperty.GetValue(x))
                .ThenBy(x => x.Idem()); // Here's the magic "Then By Idem" clause.

            Console.WriteLine(string.Join(Environment.NewLine, sortedList));
            Console.WriteLine();
        }
    }
}
Wyck
  • 10,311
  • 6
  • 39
  • 60